前書き
AudioTrackクラスは、Javaアプリケーションの単一のオーディオリソースを管理および再生します。これにより、PCMオーディオバッファをオーディオレシーバーにストリーミングして再生できます。
AudioTrackには2つのモードがあります
モード | 説明 | 行動範囲 |
---|---|---|
静的モード(MODE_STATIC) | このモードでは、再生する前に、書き込み呼び出しを介してすべてのデータをAudioTrackの内部バッファーに渡すだけで済みます。 | メモリに適した短いサウンドを処理し、可能な限り最小の遅延で再生する場合は、静的モードを選択する必要があります |
ストリームモード(MODE_STREAM) | オーディオデータをストリームの形式でAudioTrack内のバッファに継続的に書き込みます | オーディオデータの特性(高いサンプリングレート、サンプルあたりのビット数など)により、メモリに収まらないなどの理由があります。 |
使用する
1.ファイルを読み取る権限を追加します
<uses-permissionandroid:name="android.permission.READ_EXTERNAL_STORAGE" />
2.システムファイルを使用するため、耗时操作
AsyncTaskが必要です
3.最も重要なことは、public int write
(@ NonNull byte [] audioData、int offsetInBytes、int sizeInBytes)メソッド
です。最初のパラメーターは、再生されるデータを保持する配列です。
2番目のパラメーターは、オーディオデータのバイト単位のオフセットとそれに書き込まれるデータです(明確ではありません。誰かが説明してくれるといいのですが、0を書き込むことをお勧めします)
3番目のパラメーターは、書き込まれたオーディオデータのバイト数です。
4.具体的な説明については、以下のコードを参照してください
package com.audioandvideo.two.Activity;
import androidx.appcompat.app.AppCompatActivity;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import com.audioandvideo.R;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
/**
* MODE_STATIC:这种模式下,在play之前只需要把所有数据通过一次write调用传递到AudioTrack中的内部缓冲区
* MODE_STREAM:以流的形式持续把音频数据写到AudioTrack内部的Buffer中
*/
public class AudioTrackActivity extends AppCompatActivity implements View.OnClickListener{
private Button btnStatic;
private Button btnStream;
private AudioTrack audioTrack;
private byte[] audioData;
// 采样率:音频的采样频率,每秒钟能够采样的次数,采样率越高,音质越高
// 44100是目前的标准,但是某些设备仍然支持22050,16000,11025
// 采样频率一般共分为22.05KHz、44.1KHz、48KHz三个等级
private final int AUDIO_SAMPLE_RATE = 44100;
// 声道设置:android支持双声道立体声和单声道。MONO单声道,STEREO立体声
private final int AUDIO_CHANNEL = AudioFormat.CHANNEL_OUT_STEREO;
// 编码制式和采样大小:采集来的数据当然使用PCM编码
// (脉冲代码调制编码,即PCM编码。PCM通过抽样、量化、编码三个步骤将连续变化的模拟信号转换为数字编码。)
// android支持的采样大小16bit 或者8bit。当然采样大小越大,那么信息量越多,音质也越高,现在主流的采样
// 大小都是16bit,在低质量的语音传输的时候8bit 足够了。
private final int AUDIO_ENCODING = AudioFormat.ENCODING_PCM_16BIT;
//要播放的pcm文件的储存位置及文件名
private final String pcmFileName = Environment.getExternalStorageDirectory() + "/Download/record.pcm";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_audio_track);
btnStatic = findViewById(R.id.btn_static);
btnStream = findViewById(R.id.btn_stream);
btnStatic.setOnClickListener(this);
btnStream.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.btn_static:
new AsyncStatic().execute();
break;
case R.id.btn_stream:
new AsyncStream().execute();
break;
}
}
/**
*MODE_STATIC:这种模式下,在play之前只需要把所有数据通过一次write调用传递到AudioTrack中的内部缓冲区
*/
private class AsyncStatic extends AsyncTask{
/**
* 后台执行,比较耗时的操作都可以放在这里。注意这里不能直接操作UI。
* 此方法在后台线程执行,完成任务的主要工作,通常需要较长的时间。
* @param objects
* @return
*/
@Override
protected Object doInBackground(Object[] objects) {
try {
InputStream in = new FileInputStream(new File(pcmFileName));
try {
//字节数组输出流在内存中创建一个字节数组缓冲区,所有发送到输出流的数据保存在该字节数组缓冲区中。
ByteArrayOutputStream out = new ByteArrayOutputStream();
for (int b; (b = in.read()) != -1; ) {
out.write(b);
}
//创建一个新分配的字节数组。数组的大小和当前输出流的大小,内容是当前输出流的拷贝。
audioData = out.toByteArray();
} finally {
//最后关闭文件
in.close();
}
} catch (IOException e) {
}
return null;
}
/**
* 相当于Handler 处理UI的方式,在这里面可以使用在doInBackground
* 得到的结果处理操作UI。 此方法在主线程执行,任务执行的结果作为此方法的参数返回
* @param o
*/
@Override
protected void onPostExecute(Object o) {
audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, AUDIO_SAMPLE_RATE,
AUDIO_CHANNEL, AUDIO_ENCODING, audioData.length,AudioTrack.MODE_STATIC);
audioTrack.write(audioData, 0, audioData.length); //将音频数据写入音频接收器以便播放
audioTrack.play();//开始播放
}
}
/**
* MODE_STREAM:以流的形式持续把音频数据写到AudioTrack内部的Buffer中
*/
private class AsyncStream extends AsyncTask{
@Override
protected Object doInBackground(Object[] objects) {
final int minBufferSize = AudioTrack.getMinBufferSize(AUDIO_SAMPLE_RATE, AUDIO_CHANNEL, AUDIO_ENCODING);
audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, AUDIO_SAMPLE_RATE,
AUDIO_CHANNEL, AUDIO_ENCODING, minBufferSize, AudioTrack.MODE_STREAM);//最后一个参数为audioTrack的模式
audioTrack.play();
File file = new File(pcmFileName);
//从文件系统中的某个文件中获得输入字节
FileInputStream fileInputStream = null;
try {
fileInputStream = new FileInputStream(file);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
byte[] tempBuffer = new byte[minBufferSize];
while (true) {
try {
if (!(fileInputStream.available() > 0)) {
stop(); //停止播放
break;
}
// fileInputStream.read(tempBuffer)返回读入缓冲区的总字节数;如果因为已经到达流末尾而不再有数据可用,则返回 -1。
int readCount = fileInputStream.read(tempBuffer);
Log.d("TAG", "doInBackground:"+String.valueOf(readCount));
if (readCount == AudioTrack.ERROR_INVALID_OPERATION ||
readCount == AudioTrack.ERROR_BAD_VALUE) {
continue;
}
if (readCount != 0 && readCount != -1) {
//将音频数据写入音频接收器以便播放
audioTrack.write(tempBuffer, 0, readCount);
}
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
}
//停止播放
private void stop(){
if(audioTrack.getState() != AudioTrack.STATE_UNINITIALIZED){
audioTrack.stop();
audioTrack.release();
}
}
}
テスト
インターフェイスにはテスト用の2つのシンプルなボタンしかなく、両方を再生できます。