MediaCodec AudioTrack 硬解AAC格式音频(二)详解手机开发

在上一篇文章介绍了 MediaCodec AudioRecord 硬编 AAC 格式音频,这篇介绍如何用 MediaCodec 硬解码 AAC 文件,并使用 AudioTrack 播放,总体相对而言遇到的坑比硬编要少一些。

介绍

MediaCodec
MediaCodec 是 Android 用于音视频编解码的一套偏底层的 API,直接利用硬件加速进行编解码。

MediaCodec 跟上一篇文章一样的流程:

  1. dequeueInputBuffer:从 input 缓存区申请 buffer 编号 Index
  2. getInputBuffer:用编号 Index 取得输入的缓冲区,将需要编码的数据写入 buffer
  3. queueInputBuffer:将 buffer 入 MediaCodec 的队列,MediaCodec 会从 buffer 中取数据处理
  4. dequeueOutputBuffer:从 output 缓冲区申请 buffer 编号
  5. getOutputBuffer:用编号 Index 取得输出的缓冲区,buffer 中就是处理后的数据
  6. releaseOutputBuffer:将该 buffer 放回 output 缓冲区队列

AudioTrack
使用来播放 PCM 音频数据的类
具体可以参考 Android AudioRecord、AudioTrack 录制播放音频

MediaExtractor
视音频分离器,将一些格式的视频分离出视频轨道和音频轨道。
具体可以参考 MediaExtractor、MediaMuxer 分离和合成 mp4

使用 MediaExtractor 原因是什么?
因为从外部读取了 AAC 文件格式的音频,需要知道 ACC 的具体格式以及其他信息,并且后续还需要从文件一帧一帧的读取出来,放置到 MediaCodec 中进行解码,最后通过 AudioTrack 中播放。刚好 MediaExtractor 可以满足需求。

流程

  1. 初始化
// 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; 
@Override 
protected 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() { 
@Override 
public void onClick(View view) { 
checkRecordPermission(); 
new Thread(new Runnable() { 
@Override 
public 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() { 
@Override 
public 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() { 
@Override 
public 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; 
} 
} 
}

原创文章,作者:奋斗,如若转载,请注明出处:https://blog.ytso.com/6280.html

(0)
上一篇 2021年7月17日
下一篇 2021年7月17日

相关推荐

发表回复

登录后才能评论