MediaCodec完成AAC文件硬解,5.0异步,AudioTrack播放,MediaExtractor获取音频

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中可以看到
如有错误,欢迎评论提出

友情提示: 不同的工作最好不要在一个线程做,比如录音和编码,或者取录音数据和解码
!!!!!!!!!!!!!! 很有可能出问题,特别是播放,不分开容易没声音

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值