在上一篇文章介绍了 MediaCodec AudioRecord 硬编 AAC 格式音频,这篇介绍如何用 MediaCodec 硬解码 AAC 文件,并使用 AudioTrack 播放,总体相对而言遇到的坑比硬编要少一些。
介绍
MediaCodec
MediaCodec 是 Android 用于音视频编解码的一套偏底层的 API,直接利用硬件加速进行编解码。
MediaCodec 跟上一篇文章一样的流程:
- dequeueInputBuffer:从 input 缓存区申请 buffer 编号 Index
- getInputBuffer:用编号 Index 取得输入的缓冲区,将需要编码的数据写入 buffer
- queueInputBuffer:将 buffer 入 MediaCodec 的队列,MediaCodec 会从 buffer 中取数据处理
- dequeueOutputBuffer:从 output 缓冲区申请 buffer 编号
- getOutputBuffer:用编号 Index 取得输出的缓冲区,buffer 中就是处理后的数据
- releaseOutputBuffer:将该 buffer 放回 output 缓冲区队列
AudioTrack
使用来播放 PCM 音频数据的类
具体可以参考 Android AudioRecord、AudioTrack 录制播放音频
MediaExtractor
视音频分离器,将一些格式的视频分离出视频轨道和音频轨道。
具体可以参考 MediaExtractor、MediaMuxer 分离和合成 mp4
使用 MediaExtractor 原因是什么?
因为从外部读取了 AAC 文件格式的音频,需要知道 ACC 的具体格式以及其他信息,并且后续还需要从文件一帧一帧的读取出来,放置到 MediaCodec 中进行解码,最后通过 AudioTrack 中播放。刚好 MediaExtractor 可以满足需求。
流程
- 初始化
// MediaExtractor的初始化
mExtractor = new MediaExtractor();
mExtractor.setDataSource(getSDPath() + "/acc_encode.mp4");
MediaFormat mFormat = null;
int samplerate = 0;
int changelConfig = 0;
int selectTrack = 0;
String mine = MediaFormat.MIMETYPE_AUDIO_AAC;
for (int i = 0; i < mExtractor.getTrackCount(); i++) {mFormat = mExtractor.getTrackFormat(i);mine = mFormat.getString(MediaFormat.KEY_MIME);if (mine.startsWith("audio/")) {selectTrack = i;samplerate = mFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE);// 采样率changelConfig = mFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT); // 声道数break;}
}
mExtractor.selectTrack(selectTrack);// AudioTrack的初始化
int minBufferSize = AudioTrack.getMinBufferSize(samplerate, changelConfig, AudioFormat.ENCODING_PCM_16BIT);
mPlayer = new AudioTrack(AudioManager.STREAM_MUSIC, samplerate, changelConfig, AudioFormat.ENCODING_PCM_16BIT, minBufferSize, AudioTrack.MODE_STREAM);
mPlayer.play();// MediaCodec的初始化:利用mExtractor查询的信息
mDecoder = MediaCodec.createDecoderByType(mine);
mDecoder.configure(mFormat, null, null, 0);
mDecoder.start();
2. 解码并播放
以下是读取一帧数据并处理的详细过程:
int inputIdex = mDecoder.dequeueInputBuffer(10000);
if (inputIdex < 0) {isFinish = true;
}
ByteBuffer inputBuffer = mDecoder.getInputBuffer(inputIdex);
inputBuffer.clear();
int samplesize = mExtractor.readSampleData(inputBuffer, 0); // 读取一帧数据
if (samplesize > 0) {mDecoder.queueInputBuffer(inputIdex, 0, samplesize, 0, 0);mExtractor.advance();//下一帧
} else {isFinish = true;
}
int outputIndex = mDecoder.dequeueOutputBuffer(decodeBufferInfo, 10000);
//申请输入的buffer, 等待时间,0:不等待,-1:一直而等待
ByteBuffer outputBuffer;
byte[] chunkPCM;
while (outputIndex >= 0) { // 可能不能一次性读完outputBuffer = mDecoder.getOutputBuffer(outputIndex);chunkPCM = new byte[decodeBufferInfo.size];outputBuffer.get(chunkPCM);outputBuffer.clear();//不清空下次会得到同样的数据mPlayer.write(chunkPCM, 0, decodeBufferInfo.size);// 将数据写入AudioTrack播放 mDecoder.releaseOutputBuffer(outputIndex, false);// 若不释放,MediaCodec用完所有的Buffer后 将不能向外输出数据outputIndex = mDecoder.dequeueOutputBuffer(decodeBufferInfo, 10000);
}
因此想要完整播放,只需要全部读完文件并一帧帧处理就可以了。
完整源码
public class AacTrackActivity extends AppCompatActivity {public static String[] MICROPHONE = {Manifest.permission.RECORD_AUDIO};public static String[] STORAGE = {Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE};private MyAudioTrack myAudioTrack;@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_aac_track);myAudioTrack = new MyAudioTrack();findViewById(R.id.acc_playing).setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {checkRecordPermission();new Thread(new Runnable() {@Overridepublic void run() {myAudioTrack.start();}}).start();}});}private class MyAudioTrack {private AudioTrack mPlayer;private MediaCodec mDecoder;private MediaExtractor mExtractor;public void start() {try {mExtractor = new MediaExtractor();mExtractor.setDataSource(getSDPath() + "/acc_encode.mp4");MediaFormat mFormat = null;int samplerate = 0;int changelConfig = 0;int selectTrack = 0;String mine = MediaFormat.MIMETYPE_AUDIO_AAC;for (int i = 0; i < mExtractor.getTrackCount(); i++) {mFormat = mExtractor.getTrackFormat(i);mine = mFormat.getString(MediaFormat.KEY_MIME);if (mine.startsWith("audio/")) {selectTrack = i;samplerate = mFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE);changelConfig = mFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT);break;}}mExtractor.selectTrack(selectTrack);int minBufferSize = AudioTrack.getMinBufferSize(samplerate, changelConfig, AudioFormat.ENCODING_PCM_16BIT);mPlayer = new AudioTrack(AudioManager.STREAM_MUSIC, samplerate, changelConfig, AudioFormat.ENCODING_PCM_16BIT, minBufferSize, AudioTrack.MODE_STREAM);mPlayer.play();mDecoder = MediaCodec.createDecoderByType(mine);mDecoder.configure(mFormat, null, null, 0);mDecoder.start();decodeAndPlay();} catch (IOException e) {e.printStackTrace();runOnUiThread(new Runnable() {@Overridepublic void run() {Toast.makeText(AacTrackActivity.this, "文件不存在",Toast.LENGTH_SHORT).show();}});}}private void decodeAndPlay() {boolean isFinish = false;MediaCodec.BufferInfo decodeBufferInfo = new MediaCodec.BufferInfo();while (!isFinish) {int inputIdex = mDecoder.dequeueInputBuffer(10000);if (inputIdex < 0) {isFinish = true;}ByteBuffer inputBuffer = mDecoder.getInputBuffer(inputIdex);inputBuffer.clear();int samplesize = mExtractor.readSampleData(inputBuffer, 0);if (samplesize > 0) {mDecoder.queueInputBuffer(inputIdex, 0, samplesize, 0, 0);mExtractor.advance();} else {isFinish = true;}int outputIndex = mDecoder.dequeueOutputBuffer(decodeBufferInfo, 10000);ByteBuffer outputBuffer;byte[] chunkPCM;while (outputIndex >= 0) {outputBuffer = mDecoder.getOutputBuffer(outputIndex);chunkPCM = new byte[decodeBufferInfo.size];outputBuffer.get(chunkPCM);outputBuffer.clear();mPlayer.write(chunkPCM, 0, decodeBufferInfo.size);mDecoder.releaseOutputBuffer(outputIndex, false);outputIndex = mDecoder.dequeueOutputBuffer(decodeBufferInfo, 10000);}}release();}private void release() {if (mDecoder != null) {mDecoder.stop();mDecoder.release();mDecoder = null;}if (mExtractor != null) {mExtractor.release();mExtractor = null;}if (mPlayer != null) {mPlayer.stop();mPlayer.release();mPlayer = null;}runOnUiThread(new Runnable() {@Overridepublic void run() {Toast.makeText(AacTrackActivity.this, "播放完成", Toast.LENGTH_SHORT).show();}});}}public String getSDPath() {// 判断是否挂载if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {return Environment.getExternalStorageDirectory().getAbsolutePath();}return Environment.getRootDirectory().getAbsolutePath();}private void checkRecordPermission() {if (ActivityCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {ActivityCompat.requestPermissions(this, MICROPHONE, 1);return;}if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {ActivityCompat.requestPermissions(this, STORAGE, 1);return;}}
}