Android音频开发(七)音频编解码之MediaCodec编解码AAC下

在上一篇初识MediaCodec中,我们认识了MediaCodec,知道了MediaCodec的基本工作流程和开发注意事项,这一篇我将讲述如何利用MediaCodec编解码AAC。

1:MediaCodec实时采集音频并编码

我们将使用 AudioRecord 和 MediaCodec 实现这个功能,关于 AudioRecord 的使用后期我会单独讲述。

为了保证兼容性,推荐的配置是 44.1kHz、单通道、16 位精度。首先创建并配置 AudioRecord 和 MediaCodec。

    // 输入源 麦克风
    private val AUDIO_SOURCE = MediaRecorder.AudioSource.MIC

    // 采样率 44.1kHz,所有设备都支持
    private val SAMPLE_RATE = 44100

    // 通道 单声道,所有设备都支持
    private val CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO

    // 精度 16 位,所有设备都支持
    private val AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT

    // 通道数 单声道
    private val CHANNEL_COUNT = 1

    // 比特率
    private val BIT_RATE = 96000

    fun createAudio() {
    
    
        mBufferSizeInBytes = AudioRecord.getMinBufferSize(MediaRecorder.AudioEncoder.SAMPLE_RATE, MediaRecorder.AudioEncoder.CHANNEL_CONFIG, MediaRecorder.AudioEncoder.AUDIO_FORMAT)
        if (mBufferSizeInBytes <= 0) {
    
    
            throw RuntimeException("AudioRecord is not available, minBufferSize: $mBufferSizeInBytes")
        }
        Log.i(TAG, "createAudioRecord minBufferSize: $mBufferSizeInBytes")
        mAudioRecord = AudioRecord(MediaRecorder.AudioEncoder.AUDIO_SOURCE, MediaRecorder.AudioEncoder.SAMPLE_RATE, MediaRecorder.AudioEncoder.CHANNEL_CONFIG, MediaRecorder.AudioEncoder.AUDIO_FORMAT, mBufferSizeInBytes)
        val state: Int = mAudioRecord.getState()
        Log.i(TAG, "createAudio state: " + state + ", initialized: " + (state == AudioRecord.STATE_INITIALIZED))
    }

    @Throws(IOException::class)
    fun createMediaCodec() {
    
    
        val mediaCodecInfo: MediaCodecInfo = CodecUtils.selectCodec(MIMETYPE_AUDIO_AAC)
                ?: throw RuntimeException(MIMETYPE_AUDIO_AAC + " encoder is not available")
        Log.i(TAG, "createMediaCodec: mediaCodecInfo " + mediaCodecInfo.name)
        val format = MediaFormat.createAudioFormat(MIMETYPE_AUDIO_AAC, SAMPLE_RATE, CHANNEL_COUNT)
        format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC)
        format.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE)
        mMediaCodec = MediaCodec.createEncoderByType(MIMETYPE_AUDIO_AAC)
        mMediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)
    }

然后开始录音,得到原始音频数据,再编码为 AAC 格式。这个地方会阻塞调用的线程,而且编码比较耗时,一定要在主线程之外调用。

 @Throws(IOException::class)
    fun start(outFile: File) {
    
    
        Log.d(TAG, "start() called with: outFile = [$outFile]")
        mStopped = false
        val fos = FileOutputStream(outFile)
        mMediaCodec.start()
        mAudioRecord.startRecording()
        val buffer = ByteArray(mBufferSizeInBytes)
        val inputBuffers: Array<ByteBuffer> = mMediaCodec.getInputBuffers()
        val outputBuffers: Array<ByteBuffer> = mMediaCodec.getOutputBuffers()
        try {
    
    
            while (!mStopped) {
    
    
                val readSize: Int = mAudioRecord.read(buffer, 0, mBufferSizeInBytes)
                if (readSize > 0) {
    
    
                    val inputBufferIndex: Int = mMediaCodec.dequeueInputBuffer(-1)
                    if (inputBufferIndex >= 0) {
    
    
                        val inputBuffer: ByteBuffer = inputBuffers[inputBufferIndex]
                        inputBuffer.clear()
                        inputBuffer.put(buffer)
                        inputBuffer.limit(buffer.size)
                        mMediaCodec.queueInputBuffer(inputBufferIndex, 0, readSize, 0, 0)
                    }
                    val bufferInfo: MediaCodec.BufferInfo = MediaCodec.BufferInfo()
                    var outputBufferIndex: Int = mMediaCodec.dequeueOutputBuffer(bufferInfo, 0)
                    while (outputBufferIndex >= 0) {
    
    
                        val outputBuffer: ByteBuffer = outputBuffers[outputBufferIndex]
                        outputBuffer.position(bufferInfo.offset)
                        outputBuffer.limit(bufferInfo.offset + bufferInfo.size)
                        val chunkAudio = ByteArray(bufferInfo.size + 7) // 7 is ADTS size
                        addADTStoPacket(chunkAudio, chunkAudio.size)
                        outputBuffer.get(chunkAudio, 7, bufferInfo.size)
                        outputBuffer.position(bufferInfo.offset)
                        fos.write(chunkAudio)
                        mMediaCodec.releaseOutputBuffer(outputBufferIndex, false)
                        outputBufferIndex = mMediaCodec.dequeueOutputBuffer(bufferInfo, 0)
                    }
                } else {
    
    
                    Log.w(TAG, "read audio buffer error:$readSize")
                    break
                }
            }
        } finally {
    
    
            Log.i(TAG, "released")
            mAudioRecord.stop()
            mAudioRecord.release()
            mMediaCodec.stop()
            mMediaCodec.release()
            fos.close()
        }
    }

AAC 是一种压缩格式,可以直接使用播放器播放。为了实现流式播放,也就是做到边下边播,我们采用 ADTS 格式。给每帧加上 7 个字节的头信息。加上头信息就是为了告诉解码器,这帧音频长度、采样率、通道是多少,每帧都携带头信息,解码器随时都可以解码播放。我们这里采用单通道、44.1KHz 采样率的头信息配置。

    private fun addADTStoPacket(packet: ByteArray, packetLen: Int) {
    
    
        val profile = 2 //AAC LC
        val freqIdx = 4 //44.1KHz
        val chanCfg = 1 //CPE
        // fill in ADTS data
        packet[0] = 0xFF.toByte()
        packet[1] = 0xF9.toByte()
        packet[2] = ((profile - 1 shl 6) + (freqIdx shl 2) + (chanCfg shr 2)).toByte()
        packet[3] = ((chanCfg and 3 shl 6) + (packetLen shr 11)).toByte()
        packet[4] = (packetLen and 0x7FF shr 3).toByte()
        packet[5] = ((packetLen and 7 shl 5) + 0x1F).toByte()
        packet[6] = 0xFC.toByte()
    }

2:AAC 解码

我们可以利用 MediaExtractor 和 MediaCodec 来提取编码后的音频数据,并解压成音频源数据。

 /**
     * AAC 格式解码成 PCM 数据
     * @param aacFile
     * @param pcmFile
     * @throws IOException
     */
    @Throws(IOException::class)
    fun decodeAacToPcm(aacFile: File, pcmFile: File?) {
    
    
        val extractor = MediaExtractor()
        extractor.setDataSource(aacFile.getAbsolutePath())
        var mediaFormat: MediaFormat? = null
        for (i in 0 until extractor.getTrackCount()) {
    
    
            val format: MediaFormat = extractor.getTrackFormat(i)
            val mime: String = format.getString(MediaFormat.KEY_MIME)
            if (mime.startsWith("audio/")) {
    
    
                extractor.selectTrack(i)
                mediaFormat = format
                break
            }
        }
        if (mediaFormat == null) {
    
    
            Log.e(TAG, "Invalid file with audio track.")
            extractor.release()
            return
        }
        val fosDecoder = FileOutputStream(pcmFile)
        val mediaMime: String = mediaFormat.getString(MediaFormat.KEY_MIME)
        Log.i(TAG, "decodeAacToPcm: mimeType: $mediaMime")
        val codec: MediaCodec = MediaCodec.createDecoderByType(mediaMime)
        codec.configure(mediaFormat, null, null, 0)
        codec.start()
        val codecInputBuffers: Array<ByteBuffer> = codec.getInputBuffers()
        var codecOutputBuffers: Array<ByteBuffer> = codec.getOutputBuffers()
        val kTimeOutUs: Long = 10000
        val info: MediaCodec.BufferInfo = MediaCodec.BufferInfo()
        var sawInputEOS = false
        var sawOutputEOS = false
        try {
    
    
            while (!sawOutputEOS) {
    
    
                if (!sawInputEOS) {
    
    
                    val inputBufIndex: Int = codec.dequeueInputBuffer(kTimeOutUs)
                    if (inputBufIndex >= 0) {
    
    
                        val dstBuf: ByteBuffer = codecInputBuffers[inputBufIndex]
                        val sampleSize: Int = extractor.readSampleData(dstBuf, 0)
                        if (sampleSize < 0) {
    
    
                            Log.i(TAG, "saw input EOS.")
                            sawInputEOS = true
                            codec.queueInputBuffer(inputBufIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM)
                        } else {
    
    
                            codec.queueInputBuffer(inputBufIndex, 0, sampleSize, extractor.getSampleTime(), 0)
                            extractor.advance()
                        }
                    }
                }
                val outputBufferIndex: Int = codec.dequeueOutputBuffer(info, kTimeOutUs)
                if (outputBufferIndex >= 0) {
    
    
                    // Simply ignore codec config buffers.
                    if (info.flags and MediaCodec.BUFFER_FLAG_CODEC_CONFIG !== 0) {
    
    
                        Log.i(TAG, "audio encoder: codec config buffer")
                        codec.releaseOutputBuffer(outputBufferIndex, false)
                        continue
                    }
                    if (info.size !== 0) {
    
    
                        val outBuf: ByteBuffer = codecOutputBuffers[outputBufferIndex]
                        outBuf.position(info.offset)
                        outBuf.limit(info.offset + info.size)
                        val data = ByteArray(info.size)
                        outBuf.get(data)
                        fosDecoder.write(data)
                    }
                    codec.releaseOutputBuffer(outputBufferIndex, false)
                    if (info.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM !== 0) {
    
    
                        Log.i(TAG, "saw output EOS.")
                        sawOutputEOS = true
                    }
                } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
    
    
                    codecOutputBuffers = codec.getOutputBuffers()
                    Log.i(TAG, "output buffers have changed.")
                } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
    
    
                    val oformat: MediaFormat = codec.getOutputFormat()
                    Log.i(TAG, "output format has changed to $oformat")
                }
            }
        } finally {
    
    
            Log.i(TAG, "decodeAacToPcm finish")
            codec.stop()
            codec.release()
            extractor.release()
            fosDecoder.close()
        }
    }

好了,最后附上完整的代码:

package com.bnd.myaudioandvideo.utils

import android.media.MediaCodec
import android.media.MediaCodecInfo
import android.media.MediaExtractor
import android.media.MediaFormat
import android.util.Log
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.IOException

object AacPcmCodec {
    
    
    private const val TAG = "AacPcmCodec"
    private const val AUDIO_MIME = "audio/mp4a-latm"
    private const val AUDIO_BYTES_PER_SAMPLE = (44100 * 1 * 16 / 8).toLong()

    /**
     * AAC 格式解码成 PCM 数据
     * @param aacFile
     * @param pcmFile
     * @throws IOException
     */
    @Throws(IOException::class)
    fun decodeAacToPcm(aacFile: File, pcmFile: File) {
    
    
        val extractor = MediaExtractor()
        extractor.setDataSource(aacFile.absolutePath)
        var mediaFormat: MediaFormat? = null
        var i = 0
        val count = extractor.trackCount
        while (i < count) {
    
    
            val format = extractor.getTrackFormat(i)
            val mime = format.getString(MediaFormat.KEY_MIME)
            if (mime!!.startsWith("audio/")) {
    
    
                extractor.selectTrack(i)
                mediaFormat = format
                break
            }
            i++
        }
        if (mediaFormat == null) {
    
    
            Log.e(TAG, "Invalid file with audio track.")
            extractor.release()
            return
        }
        val mime = mediaFormat.getString(MediaFormat.KEY_MIME)
        Log.i(TAG, "decodeAacToPcm: mimeType: $mime")
        val codec = MediaCodec.createDecoderByType(mime!!)
        codec.configure(mediaFormat, null, null, 0)
        codec.start()
        val inputBuffers = codec.inputBuffers
        var outputBuffers = codec.outputBuffers
        val outBufferInfo = MediaCodec.BufferInfo()
        val timeoutUs: Long = 10000
        var sawInputEOS = false
        var sawOutputEOS = false
        var outputBytes: ByteArray? = null
        try {
    
    
            FileOutputStream(pcmFile).use {
    
     fosAudio ->
                while (!sawOutputEOS) {
    
    
                    if (!sawInputEOS) {
    
    
                        val inputBufIndex = codec.dequeueInputBuffer(timeoutUs)
                        if (inputBufIndex >= 0) {
    
    
                            val inputBuffer = inputBuffers[inputBufIndex]
                            val sampleSize = extractor.readSampleData(inputBuffer, 0)
                            if (sampleSize < 0) {
    
    
                                Log.i(TAG, "saw input EOS.")
                                sawInputEOS = true
                                codec.queueInputBuffer(inputBufIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM)
                            } else {
    
    
                                codec.queueInputBuffer(inputBufIndex, 0, sampleSize, extractor.sampleTime, 0)
                                extractor.advance()
                            }
                        }
                    }
                    val outputBufIndex = codec.dequeueOutputBuffer(outBufferInfo, timeoutUs)
                    if (outputBufIndex >= 0) {
    
    
                        // Simply ignore codec config buffers.
                        if (outBufferInfo.flags and MediaCodec.BUFFER_FLAG_CODEC_CONFIG != 0) {
    
    
                            Log.i(TAG, "audio encoder: codec config buffer")
                            codec.releaseOutputBuffer(outputBufIndex, false)
                            continue
                        }
                        if (outBufferInfo.size > 0) {
    
    
                            val outputBuffer = outputBuffers[outputBufIndex]
                            outputBuffer.position(outBufferInfo.offset)
                            outputBuffer.limit(outBufferInfo.offset + outBufferInfo.size)
                            if (outputBytes == null || outputBytes!!.size < outBufferInfo.size) {
    
    
                                outputBytes = ByteArray(outBufferInfo.size)
                            }
                            outputBuffer[outputBytes]
                            fosAudio.write(outputBytes)
                        }
                        codec.releaseOutputBuffer(outputBufIndex, false)
                        if (outBufferInfo.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM != 0) {
    
    
                            Log.i(TAG, "saw output EOS.")
                            sawOutputEOS = true
                        }
                    } else if (outputBufIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
    
    
                        outputBuffers = codec.outputBuffers
                        Log.i(TAG, "output buffers have changed.")
                    } else if (outputBufIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
    
    
                        Log.i(TAG, "output format has changed to " + codec.outputFormat)
                    }
                }
            }
        } finally {
    
    
            Log.i(TAG, "decodeAacToPcm finish " + pcmFile.absolutePath)
            codec.stop()
            codec.release()
            extractor.release()
        }
    }

    /**
     * PCM 数据编码为 AAC 格式
     * @param inPcmFile
     * @param outAacFile
     * @throws IOException
     */
    @Throws(IOException::class)
    fun encodePcmToAac(inPcmFile: File?, outAacFile: File) {
    
    
        val audioEncoder = createAudioEncoder()
        try {
    
    
            FileInputStream(inPcmFile).use {
    
     fisAudio ->
                FileOutputStream(outAacFile).use {
    
     fosAudio ->
                    audioEncoder.start()
                    var sawInputEOS = false
                    var sawOutputEOS = false
                    var presentationTimeUs: Long = 0
                    var inputBytes: ByteArray? = null
                    var sumReadInputSize = 0
                    var outputPresentationTimeUs: Long = 0
                    val timeoutUs = 10000
                    val outBufferInfo = MediaCodec.BufferInfo()
                    val inputBuffers = audioEncoder.inputBuffers
                    var outputBuffers = audioEncoder.outputBuffers
                    while (!sawOutputEOS) {
    
    
                        if (!sawInputEOS) {
    
    
                            val inputBufIndex = audioEncoder.dequeueInputBuffer(timeoutUs.toLong())
                            if (inputBufIndex >= 0) {
    
    
                                val inputBuffer = inputBuffers[inputBufIndex]
                                inputBuffer.clear()
                                val bufferSize = inputBuffer.remaining()
                                if (inputBytes == null) {
    
    
                                    inputBytes = ByteArray(bufferSize)
                                }
                                val readInputSize = fisAudio.read(inputBytes)
                                if (readInputSize < 0) {
    
    
                                    audioEncoder.queueInputBuffer(inputBufIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM)
                                    sawInputEOS = true
                                    Log.i(TAG, "saw input EOS.")
                                } else {
    
    
                                    inputBuffer.put(inputBytes, 0, readInputSize)
                                    sumReadInputSize += readInputSize
                                    audioEncoder.queueInputBuffer(inputBufIndex, 0, readInputSize, presentationTimeUs, 0)
                                    presentationTimeUs = (1000000 * (sumReadInputSize.toFloat() / AUDIO_BYTES_PER_SAMPLE)).toLong()
                                }
                            }
                        }
                        val outputBufIndex = audioEncoder.dequeueOutputBuffer(outBufferInfo, timeoutUs.toLong())
                        if (outputBufIndex >= 0) {
    
    
                            // Simply ignore codec config buffers.
                            if (outBufferInfo.flags and MediaCodec.BUFFER_FLAG_CODEC_CONFIG != 0) {
    
    
                                Log.i(TAG, "audio encoder: codec config buffer")
                                audioEncoder.releaseOutputBuffer(outputBufIndex, false)
                                continue
                            }
                            if (outBufferInfo.size > 0) {
    
    
                                val outBuffer = outputBuffers[outputBufIndex]
                                outBuffer.position(outBufferInfo.offset)
                                outBuffer.limit(outBufferInfo.offset + outBufferInfo.size)
                                if (outputPresentationTimeUs <= outBufferInfo.presentationTimeUs) {
    
    
                                    outputPresentationTimeUs = outBufferInfo.presentationTimeUs
                                    val outBufSize = outBufferInfo.size
                                    val outPacketSize = outBufSize + 7
                                    outBuffer.position(outBufferInfo.offset)
                                    outBuffer.limit(outBufferInfo.offset + outBufSize)
                                    val outData = ByteArray(outPacketSize)
                                    addADTStoPacket(outData, outPacketSize)
                                    outBuffer[outData, 7, outBufSize]
                                    fosAudio.write(outData, 0, outData.size)
                                }
                            }
                            audioEncoder.releaseOutputBuffer(outputBufIndex, false)
                            if (outBufferInfo.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM != 0) {
    
    
                                sawOutputEOS = true
                                Log.i(TAG, "saw output EOS.")
                            }
                        } else if (outputBufIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
    
    
                            outputBuffers = audioEncoder.outputBuffers
                            Log.i(TAG, "output buffers have changed.")
                        } else if (outputBufIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
    
    
                            Log.i(TAG, "output format has changed to " + audioEncoder.outputFormat)
                        }
                    }
                }
            }
        } finally {
    
    
            Log.i(TAG, "encodePcmToAac: finish " + outAacFile.absolutePath)
            audioEncoder.release()
        }
    }

    @Throws(IOException::class)
    private fun createAudioEncoder(): MediaCodec {
    
    
        val codec = MediaCodec.createEncoderByType(AUDIO_MIME)
        val format = MediaFormat()
        format.setString(MediaFormat.KEY_MIME, AUDIO_MIME)
        format.setInteger(MediaFormat.KEY_BIT_RATE, 64000)
        format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1)
        format.setInteger(MediaFormat.KEY_SAMPLE_RATE, 44100)
        format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC)
        codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)
        return codec
    }

    /**
     * Add ADTS header at the beginning of each and every AAC packet.
     * This is needed as MediaCodec encoder generates a packet of raw
     * Sample rate and channel count are variables.
     */
    private fun addADTStoPacket(packet: ByteArray, packetLen: Int) {
    
    
        val profile = 2 //AAC LC
        //39=MediaCodecInfo.CodecProfileLevel.AACObjectELD;
        val freqIdx = 4 //44.1KHz
        val chanCfg = 1 //CPE
        // fill in ADTS data
        packet[0] = 0xFF.toByte()
        packet[1] = 0xF9.toByte()
        packet[2] = ((profile - 1 shl 6) + (freqIdx shl 2) + (chanCfg shr 2)).toByte()
        packet[3] = ((chanCfg and 3 shl 6) + (packetLen shr 11)).toByte()
        packet[4] = (packetLen and 0x7FF shr 3).toByte()
        packet[5] = ((packetLen and 7 shl 5) + 0x1F).toByte()
        packet[6] = 0xFC.toByte()
    }
}

3:总结

音频开发的知识点还是很多的,学习音频开发需要大家有足够的耐心,一步一个脚印的积累,只有这样才能把音频开发学好。下面推荐几个比较好的博主,希望对大家有所帮助。

  1. csdn博主:《雷神雷霄骅》
  2. 51CTO博客:《Jhuster的专栏》

猜你喜欢

转载自blog.csdn.net/ljx1400052550/article/details/114579786