MediaCodec API,完成音频 AAC 硬编,5.0异步处理,AudioRecord录音
上一篇写了硬编,这一篇写下硬解,其实和硬编逻辑一样,代码还是Kotlin
取录音和编码都设置在子线程
采取的是边取录音边解码边播放
6.0注意动态权限问题
示例使用的5.0以上的API
编解码器的MediaFormat必要填写的信息
MediaExtractor获取的数据一般不需要自己填
1.配置MediaExtractor,获取文件音轨
``
/**
* 通过MediaExtractor获取原音频的MediaFormat和读取数据
*/
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
private fun getAudioConfigure() {
//配置MediaExtractor
audioMediaExtractor = MediaExtractor()
val path = "${filesDir.path}${File.separator}record.aac"
//audioMediaExtractor添加文件路径,最好不要填写拼接路径,比如
// audioMediaExtractor!!.setDataSource(“${filesDir.path}${File.separator}record.aac”)
//有几率出现初始化失败的错误
audioMediaExtractor!!.setDataSource(path)
val trackCount = audioMediaExtractor!!.trackCount
//for循环获取音频轨 这个文件也只有音频轨道
for (i in 0 until trackCount) {
val format = audioMediaExtractor!!.getTrackFormat(i)
if (format.getString(MediaFormat.KEY_MIME).startsWith("audio/", true)) {
//找到音频轨道,选择这个轨道
audioMediaExtractor!!.selectTrack(i)
//由于我用的是之前编码生成的AAC文件,加了头,所以要配置下
//表示这是AAC文件 而且有ADTS头部
format.setInteger(MediaFormat.KEY_IS_ADTS, 1)
//配置解码头文件说明信息 2字节表示 信息组成的格式
// AAC Profile 5bit
//采样率 4bit
//声道数 4bit
//其他 3bit
//详细表示参见另一个blog
//https://blog.csdn.net/lavender1626/article/details/80431902
//我的配置换算后是下面
val keyData = byteArrayOf((0x12).toByte(), (0x08).toByte())
val buffer = ByteBuffer.wrap(keyData)
//设置头部解码信息
format.setByteBuffer("csd-0", buffer)
if (adjustDecoderSupport(format)) {
initDecoder(format, audioMediaExtractor, audioTrack)
}
}
}
}``
写到这我想说个我发现的神奇的事情,那就是我不配置解码头部信息也可以获取到
解码的数据,而且还能播放
adjustDecoderSupport()函数代码
/**
* 5.0以上 判断是否支持该解码类型
*/
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
private fun adjustDecoderSupport(format: MediaFormat): Boolean {
val mediaList = MediaCodecList(MediaCodecList.ALL_CODECS)
return mediaList.findDecoderForFormat(format) != null
}
2.创建解码器
/**
* 初始化解码器
*/
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
private fun initDecoder(format: MediaFormat,
audioMediaExtractor: MediaExtractor?, audioTrack: AudioTrack?) {
val audioDecoder = MediaCodec.createDecoderByType(MediaFormat.MIMETYPE_AUDIO_AAC)
//添加解码回调
audioDecoder.setCallback(object : MediaCodec.Callback() {
override fun onOutputBufferAvailable(codec: MediaCodec, index: Int, info: MediaCodec.BufferInfo) {
//解码后数据
val outBuffer = codec.getOutputBuffer(index)
val byteArray = ByteArray(info.size)
//将数据加入到创建的LinkedBlockingDeque中,方便AudioTrack读取
//尝试在这里用audioTrack.write播放数据,结果没声音
outBuffer.get(byteArray)
audioList.offer(byteArray)
//不要忘记释放,重要!!!!
codec.releaseOutputBuffer(index, false)
}
override fun onInputBufferAvailable(codec: MediaCodec, index: Int) {
//获取到输入buffer,用于填充要解码的数据
val inputBuffer = codec.getInputBuffer(index)
//从音频轨道读出要解码的数据,填充到buffer中
val readResult = audioMediaExtractor!!.readSampleData(inputBuffer!!, 0)
if (readResult < 0) {
//读取完毕
codec.queueInputBuffer(index, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM)
} else {
val data = ByteArray(readResult)
inputBuffer.get(data)
//将buffer还给Codec,重要!!!!!!!
codec.queueInputBuffer(index, 0, readResult, audioMediaExtractor.sampleTime, 0)
//audioMediaExtractor移动到下一个样本
audioMediaExtractor.advance()
}
}
override fun onOutputFormatChanged(codec: MediaCodec, format: MediaFormat) {
}
override fun onError(codec: MediaCodec, e: MediaCodec.CodecException) {
}
})
//调用configure进入configured状态
audioDecoder.configure(format, null, null, 0)
//调用start进入Excuting状态
audioDecoder.start()
}
``
3.创建AudioTrack播放数据
thread {
val minSize = AudioRecord.getMinBufferSize(
AudioConfig.SAMPLE_RATE, AudioConfig.CHANNEL_CONFIG,
AudioConfig.AUDIO_FORMAT
)
audioTrack = AudioTrack(
AudioManager.STREAM_MUSIC,
AudioConfig.SAMPLE_RATE, AudioConfig.CHANNEL_OUT_CONFIG,
AudioConfig.AUDIO_FORMAT, minSize, AudioTrack.MODE_STREAM
)
audioTrack!!.play()
while (isPlaying) {
//audioList为创建的LinkedBlockingDeque对象
val decodeData = audioList.poll()
if (decodeData != null) {
audioTrack!!.write(decodeData, 0, decodeData.size)
}
}
}
完整代码地址
代码在MediaCodecDecodeAACActivity中可以看到
如有错误,欢迎评论提出
友情提示: 不同的工作最好不要在一个线程做,比如录音和编码,或者取录音数据和解码
!!!!!!!!!!!!!! 很有可能出问题,特别是播放,不分开容易没声音