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

177 阅读3分钟

[MediaCodec API,完成音频 AAC 硬编,5.0异步处理,AudioRecord录音] (juejin.cn/post/709455…)

上一篇写了硬编,这一篇写下硬解,其实和硬编逻辑一样,代码还是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中可以看到 如有错误,欢迎评论提出

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