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中可以看到 如有错误,欢迎评论提出
友情提示: 不同的工作最好不要在一个线程做,比如录音和编码,或者取录音数据和解码 !!!!!!!!!!!!!! 很有可能出问题,特别是播放,不分开容易没声音