02-05-04 MediaCodec编解码

1. 概述

MediaCodec是Android提供的底层多媒体编解码API,支持硬件加速,可处理音频和视频的编码(压缩)与解码(解压缩)。相比MediaPlayer/MediaRecorder的高级API,MediaCodec提供了对编解码过程的精细控制,适合视频剪辑、直播推流、实时通信等需要自定义处理的场景。本文将深入探讨MediaCodec的工作原理、使用方法、性能优化及常见问题。

1.1 MediaCodec架构

┌─────────────────────────────────────────────────────────┐
│               MediaCodec Architecture                    │
├─────────────────────────────────────────────────────────┤
│                                                           │
│  ┌──────────────────────────────────────────────────┐   │
│  │         Application Layer (Java/Kotlin)           │   │
│  │  MediaCodec | MediaFormat | MediaExtractor       │   │
│  └──────────────────────────────────────────────────┘   │
│                         ↓ JNI                            │
│  ┌──────────────────────────────────────────────────┐   │
│  │         Native Layer (C++)                        │   │
│  │  AMediaCodec | AMediaFormat                       │   │
│  └──────────────────────────────────────────────────┘   │
│                         ↓                                │
│  ┌──────────────────────────────────────────────────┐   │
│  │         Media Framework                           │   │
│  │  stagefright | OMX | Codec2                       │   │
│  └──────────────────────────────────────────────────┘   │
│                         ↓                                │
│  ┌──────────────────────────────────────────────────┐   │
│  │         Codec Implementation                      │   │
│  │  Hardware Codec (GPU/DSP) | Software Codec       │   │
│  └──────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────┘

1.2 编解码器类型

类型 说明 优点 缺点
硬件编解码器 使用GPU/DSP 低功耗、高性能 设备兼容性差
软件编解码器 使用CPU 兼容性好 功耗高、性能低

常见编解码器名称

格式 硬件编码器 软件编码器 硬件解码器 软件解码器
H.264 OMX.qcom.video.encoder.avc c2.android.avc.encoder OMX.qcom.video.decoder.avc c2.android.avc.decoder
H.265 OMX.qcom.video.encoder.hevc c2.android.hevc.encoder OMX.qcom.video.decoder.hevc c2.android.hevc.decoder
AAC OMX.google.aac.encoder c2.android.aac.encoder OMX.google.aac.decoder c2.android.aac.decoder

2. MediaCodec工作流程

2.1 编解码生命周期

Stopped
   ↓ configure()
Configured
   ↓ start()
Executing ←─────────────┐
   │                    │
   ├→ Input Buffers    │
   │     ↓              │
   │  queueInputBuffer  │
   │     ↓              │
   │  Encode/Decode     │
   │     ↓              │
   │  dequeueOutputBuffer
   │     ↓              │
   ├→ Output Buffers   │
   │     ↓              │
   │  releaseOutputBuffer
   │     │              │
   └─────┘              │
   ↓ stop()             │
Released ←──────────────┘
   ↓ release()
End

2.2 基础使用流程

// MediaCodecBasics.kt - MediaCodec基础使用
class MediaCodecBasics {

    fun encodeH264Example() {
        // 1. 创建编码器
        val codec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC)

        // 2. 配置参数
        val format = MediaFormat().apply {
            setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_VIDEO_AVC)
            setInteger(MediaFormat.KEY_WIDTH, 1280)
            setInteger(MediaFormat.KEY_HEIGHT, 720)
            setInteger(MediaFormat.KEY_BIT_RATE, 2_000_000)
            setInteger(MediaFormat.KEY_FRAME_RATE, 30)
            setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1)
            setInteger(
                MediaFormat.KEY_COLOR_FORMAT,
                MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible
            )
        }

        codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)

        // 3. 启动编码器
        codec.start()

        // 4. 编码循环
        var inputEOS = false
        var outputEOS = false

        while (!outputEOS) {
            // 4.1 喂入输入数据
            if (!inputEOS) {
                val inputBufferId = codec.dequeueInputBuffer(10_000)
                if (inputBufferId >= 0) {
                    val inputBuffer = codec.getInputBuffer(inputBufferId)
                    inputBuffer?.let {
                        // 填充YUV数据
                        val yuvData = generateYUVFrame()  // 生成一帧YUV数据
                        it.put(yuvData)

                        codec.queueInputBuffer(
                            inputBufferId,
                            0,
                            yuvData.size,
                            computePresentationTimeUs(),  // 计算时间戳
                            0
                        )
                    }
                }
            }

            // 4.2 取出输出数据
            val bufferInfo = MediaCodec.BufferInfo()
            val outputBufferId = codec.dequeueOutputBuffer(bufferInfo, 10_000)

            when (outputBufferId) {
                MediaCodec.INFO_OUTPUT_FORMAT_CHANGED -> {
                    val outputFormat = codec.outputFormat
                    Log.d(TAG, "输出格式变化: $outputFormat")
                }
                MediaCodec.INFO_TRY_AGAIN_LATER -> {
                    // 暂时没有输出,稍后重试
                }
                else -> {
                    if (outputBufferId >= 0) {
                        val outputBuffer = codec.getOutputBuffer(outputBufferId)
                        outputBuffer?.let {
                            // 处理编码后的H.264数据
                            val h264Data = ByteArray(bufferInfo.size)
                            it.get(h264Data)

                            // 写入文件或发送到网络
                            saveToFile(h264Data)

                            // 释放输出buffer
                            codec.releaseOutputBuffer(outputBufferId, false)
                        }

                        // 检查EOS
                        if ((bufferInfo.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
                            outputEOS = true
                        }
                    }
                }
            }
        }

        // 5. 停止并释放
        codec.stop()
        codec.release()
    }

    private fun generateYUVFrame(): ByteArray {
        // 生成YUV数据(示例)
        return ByteArray(1280 * 720 * 3 / 2)
    }

    private fun computePresentationTimeUs(): Long {
        // 计算当前帧的时间戳
        return System.nanoTime() / 1000
    }

    private fun saveToFile(data: ByteArray) {
        // 保存到文件
    }

    companion object {
        private const val TAG = "MediaCodecBasics"
    }
}

3. 视频编码

3.1 H.264视频编码器

// H264Encoder.kt - H.264视频编码器封装
class H264Encoder(
    private val width: Int,
    private val height: Int,
    private val bitrate: Int = 2_000_000,
    private val frameRate: Int = 30,
    private val iFrameInterval: Int = 1
) {
    private var mediaCodec: MediaCodec? = null
    private var isEncoding = false
    private var frameIndex = 0L

    interface EncoderCallback {
        fun onEncodedFrame(data: ByteArray, isKeyFrame: Boolean, presentationTimeUs: Long)
        fun onError(error: String)
    }

    private var callback: EncoderCallback? = null

    fun prepare() {
        try {
            // 1. 创建编码器
            mediaCodec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC)

            // 2. 配置参数
            val format = MediaFormat().apply {
                setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_VIDEO_AVC)
                setInteger(MediaFormat.KEY_WIDTH, width)
                setInteger(MediaFormat.KEY_HEIGHT, height)
                setInteger(MediaFormat.KEY_BIT_RATE, bitrate)
                setInteger(MediaFormat.KEY_FRAME_RATE, frameRate)
                setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, iFrameInterval)

                // 色彩格式
                setInteger(
                    MediaFormat.KEY_COLOR_FORMAT,
                    MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible
                )

                // Profile设置(Baseline/Main/High)
                setInteger(
                    MediaFormat.KEY_PROFILE,
                    MediaCodecInfo.CodecProfileLevel.AVCProfileHigh
                )

                // 码率控制模式
                setInteger(
                    MediaFormat.KEY_BITRATE_MODE,
                    MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR  // 可变码率
                )
            }

            mediaCodec?.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)
            mediaCodec?.start()

            isEncoding = true
            frameIndex = 0

            Log.d(TAG, "H.264编码器初始化完成:${width}x${height} @ ${bitrate}bps")

        } catch (e: Exception) {
            Log.e(TAG, "编码器初始化失败: ${e.message}")
            callback?.onError("编码器初始化失败: ${e.message}")
        }
    }

    fun encodeFrame(yuvData: ByteArray) {
        if (!isEncoding) return

        try {
            // 1. 获取输入buffer
            val inputBufferId = mediaCodec?.dequeueInputBuffer(10_000) ?: return

            if (inputBufferId >= 0) {
                val inputBuffer = mediaCodec?.getInputBuffer(inputBufferId)
                inputBuffer?.let {
                    it.clear()
                    it.put(yuvData)

                    // 2. 提交输入buffer
                    val presentationTimeUs = computePresentationTimeUs(frameIndex)
                    mediaCodec?.queueInputBuffer(
                        inputBufferId,
                        0,
                        yuvData.size,
                        presentationTimeUs,
                        0
                    )

                    frameIndex++
                }
            }

            // 3. 获取输出buffer
            drainEncoder(false)

        } catch (e: Exception) {
            Log.e(TAG, "编码帧失败: ${e.message}")
            callback?.onError("编码帧失败: ${e.message}")
        }
    }

    private fun drainEncoder(endOfStream: Boolean) {
        val bufferInfo = MediaCodec.BufferInfo()

        while (true) {
            val outputBufferId = mediaCodec?.dequeueOutputBuffer(bufferInfo, 0) ?: break

            when (outputBufferId) {
                MediaCodec.INFO_OUTPUT_FORMAT_CHANGED -> {
                    val outputFormat = mediaCodec?.outputFormat
                    Log.d(TAG, "输出格式变化: $outputFormat")

                    // 提取SPS/PPS
                    outputFormat?.let { extractSPSandPPS(it) }
                }
                MediaCodec.INFO_TRY_AGAIN_LATER -> {
                    if (!endOfStream) break
                }
                else -> {
                    if (outputBufferId >= 0) {
                        val outputBuffer = mediaCodec?.getOutputBuffer(outputBufferId)
                        outputBuffer?.let {
                            // 检查是否为关键帧
                            val isKeyFrame = (bufferInfo.flags and MediaCodec.BUFFER_FLAG_KEY_FRAME) != 0

                            // 提取H.264数据
                            val h264Data = ByteArray(bufferInfo.size)
                            it.position(bufferInfo.offset)
                            it.limit(bufferInfo.offset + bufferInfo.size)
                            it.get(h264Data)

                            // 回调编码数据
                            callback?.onEncodedFrame(h264Data, isKeyFrame, bufferInfo.presentationTimeUs)

                            // 释放buffer
                            mediaCodec?.releaseOutputBuffer(outputBufferId, false)
                        }

                        // 检查结束标志
                        if ((bufferInfo.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
                            break
                        }
                    }
                }
            }
        }
    }

    private fun extractSPSandPPS(format: MediaFormat) {
        // 提取SPS(Sequence Parameter Set)
        val sps = format.getByteBuffer("csd-0")
        sps?.let {
            val spsData = ByteArray(it.remaining())
            it.get(spsData)
            Log.d(TAG, "SPS: ${spsData.toHexString()}")
        }

        // 提取PPS(Picture Parameter Set)
        val pps = format.getByteBuffer("csd-1")
        pps?.let {
            val ppsData = ByteArray(it.remaining())
            it.get(ppsData)
            Log.d(TAG, "PPS: ${ppsData.toHexString()}")
        }
    }

    fun stop() {
        isEncoding = false

        try {
            // 发送EOS信号
            val inputBufferId = mediaCodec?.dequeueInputBuffer(10_000) ?: -1
            if (inputBufferId >= 0) {
                mediaCodec?.queueInputBuffer(
                    inputBufferId,
                    0,
                    0,
                    0,
                    MediaCodec.BUFFER_FLAG_END_OF_STREAM
                )
            }

            // 排空编码器
            drainEncoder(true)

            // 停止并释放
            mediaCodec?.stop()
            mediaCodec?.release()
            mediaCodec = null

            Log.d(TAG, "H.264编码器已停止")

        } catch (e: Exception) {
            Log.e(TAG, "停止编码器失败: ${e.message}")
        }
    }

    private fun computePresentationTimeUs(frameIndex: Long): Long {
        return frameIndex * 1_000_000L / frameRate
    }

    fun setCallback(callback: EncoderCallback) {
        this.callback = callback
    }

    companion object {
        private const val TAG = "H264Encoder"
    }
}

// ByteArray扩展函数:转十六进制字符串
fun ByteArray.toHexString(): String {
    return joinToString("") { "%02x".format(it) }
}

3.2 动态码率调整

// DynamicBitrateAdjuster.kt - 动态码率调整器
class DynamicBitrateAdjuster(private val mediaCodec: MediaCodec) {

    /**
     * 调整编码器码率(Android Kitkat+)
     */
    fun adjustBitrate(newBitrate: Int) {
        try {
            val bundle = Bundle().apply {
                putInt(MediaCodec.PARAMETER_KEY_VIDEO_BITRATE, newBitrate)
            }
            mediaCodec.setParameters(bundle)
            Log.d(TAG, "码率已调整为: $newBitrate bps")

        } catch (e: Exception) {
            Log.e(TAG, "调整码率失败: ${e.message}")
        }
    }

    /**
     * 请求关键帧(Android Kitkat+)
     */
    fun requestKeyFrame() {
        try {
            val bundle = Bundle().apply {
                putInt(MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME, 0)
            }
            mediaCodec.setParameters(bundle)
            Log.d(TAG, "已请求关键帧")

        } catch (e: Exception) {
            Log.e(TAG, "请求关键帧失败: ${e.message}")
        }
    }

    /**
     * 根据网络状况自动调整码率
     */
    fun autoAdjustBitrate(networkSpeedKbps: Int, currentBitrate: Int): Int {
        val targetBitrate = when {
            networkSpeedKbps > 5000 -> 3_000_000  // 高速网络:3 Mbps
            networkSpeedKbps > 2000 -> 1_500_000  // 中速网络:1.5 Mbps
            networkSpeedKbps > 1000 -> 800_000    // 低速网络:800 kbps
            else -> 400_000                        // 极低速:400 kbps
        }

        // 平滑过渡:每次最多调整20%
        val delta = (targetBitrate - currentBitrate) * 0.2
        val newBitrate = (currentBitrate + delta).toInt()

        adjustBitrate(newBitrate)

        return newBitrate
    }

    companion object {
        private const val TAG = "DynamicBitrateAdjuster"
    }
}

4. 视频解码

4.1 H.264视频解码器

// H264Decoder.kt - H.264视频解码器封装
class H264Decoder(
    private val width: Int,
    private val height: Int,
    private val surface: Surface? = null  // 可选:用于渲染
) {
    private var mediaCodec: MediaCodec? = null
    private var isDecoding = false

    interface DecoderCallback {
        fun onDecodedFrame(data: ByteArray, width: Int, height: Int, presentationTimeUs: Long)
        fun onError(error: String)
    }

    private var callback: DecoderCallback? = null

    fun prepare(sps: ByteArray, pps: ByteArray) {
        try {
            // 1. 创建解码器
            mediaCodec = MediaCodec.createDecoderByType(MediaFormat.MIMETYPE_VIDEO_AVC)

            // 2. 配置参数
            val format = MediaFormat().apply {
                setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_VIDEO_AVC)
                setInteger(MediaFormat.KEY_WIDTH, width)
                setInteger(MediaFormat.KEY_HEIGHT, height)

                // 设置SPS/PPS
                setByteBuffer("csd-0", ByteBuffer.wrap(sps))
                setByteBuffer("csd-1", ByteBuffer.wrap(pps))
            }

            // 如果提供了Surface,直接渲染到Surface
            mediaCodec?.configure(format, surface, null, 0)
            mediaCodec?.start()

            isDecoding = true

            Log.d(TAG, "H.264解码器初始化完成:${width}x${height}")

        } catch (e: Exception) {
            Log.e(TAG, "解码器初始化失败: ${e.message}")
            callback?.onError("解码器初始化失败: ${e.message}")
        }
    }

    fun decodeFrame(h264Data: ByteArray, presentationTimeUs: Long, isKeyFrame: Boolean) {
        if (!isDecoding) return

        try {
            // 1. 获取输入buffer
            val inputBufferId = mediaCodec?.dequeueInputBuffer(10_000) ?: return

            if (inputBufferId >= 0) {
                val inputBuffer = mediaCodec?.getInputBuffer(inputBufferId)
                inputBuffer?.let {
                    it.clear()
                    it.put(h264Data)

                    // 2. 提交输入buffer
                    val flags = if (isKeyFrame) MediaCodec.BUFFER_FLAG_KEY_FRAME else 0
                    mediaCodec?.queueInputBuffer(
                        inputBufferId,
                        0,
                        h264Data.size,
                        presentationTimeUs,
                        flags
                    )
                }
            }

            // 3. 获取输出buffer
            drainDecoder()

        } catch (e: Exception) {
            Log.e(TAG, "解码帧失败: ${e.message}")
            callback?.onError("解码帧失败: ${e.message}")
        }
    }

    private fun drainDecoder() {
        val bufferInfo = MediaCodec.BufferInfo()

        while (true) {
            val outputBufferId = mediaCodec?.dequeueOutputBuffer(bufferInfo, 0) ?: break

            when (outputBufferId) {
                MediaCodec.INFO_OUTPUT_FORMAT_CHANGED -> {
                    val outputFormat = mediaCodec?.outputFormat
                    Log.d(TAG, "输出格式变化: $outputFormat")
                }
                MediaCodec.INFO_TRY_AGAIN_LATER -> {
                    break
                }
                else -> {
                    if (outputBufferId >= 0) {
                        if (surface == null) {
                            // 没有Surface,手动获取YUV数据
                            val outputBuffer = mediaCodec?.getOutputBuffer(outputBufferId)
                            outputBuffer?.let {
                                val yuvData = ByteArray(bufferInfo.size)
                                it.position(bufferInfo.offset)
                                it.limit(bufferInfo.offset + bufferInfo.size)
                                it.get(yuvData)

                                callback?.onDecodedFrame(
                                    yuvData,
                                    width,
                                    height,
                                    bufferInfo.presentationTimeUs
                                )
                            }

                            // 释放buffer
                            mediaCodec?.releaseOutputBuffer(outputBufferId, false)
                        } else {
                            // 有Surface,直接渲染
                            mediaCodec?.releaseOutputBuffer(outputBufferId, true)
                        }
                    }
                }
            }
        }
    }

    fun stop() {
        isDecoding = false

        try {
            mediaCodec?.stop()
            mediaCodec?.release()
            mediaCodec = null

            Log.d(TAG, "H.264解码器已停止")

        } catch (e: Exception) {
            Log.e(TAG, "停止解码器失败: ${e.message}")
        }
    }

    fun setCallback(callback: DecoderCallback) {
        this.callback = callback
    }

    companion object {
        private const val TAG = "H264Decoder"
    }
}

4.2 视频渲染到Surface

// VideoRenderer.kt - 视频渲染器
class VideoRenderer(private val surfaceView: SurfaceView) {
    private lateinit var decoder: H264Decoder

    init {
        surfaceView.holder.addCallback(object : SurfaceHolder.Callback {
            override fun surfaceCreated(holder: SurfaceHolder) {
                // Surface创建后初始化解码器
                val surface = holder.surface
                decoder = H264Decoder(1280, 720, surface)

                // 准备解码器(需要SPS/PPS)
                val sps = getSPS()  // 获取SPS数据
                val pps = getPPS()  // 获取PPS数据
                decoder.prepare(sps, pps)
            }

            override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
                // Surface尺寸变化
            }

            override fun surfaceDestroyed(holder: SurfaceHolder) {
                // Surface销毁时停止解码器
                decoder.stop()
            }
        })
    }

    fun renderFrame(h264Data: ByteArray, pts: Long, isKeyFrame: Boolean) {
        decoder.decodeFrame(h264Data, pts, isKeyFrame)
    }

    private fun getSPS(): ByteArray {
        // 返回SPS数据
        return byteArrayOf()
    }

    private fun getPPS(): ByteArray {
        // 返回PPS数据
        return byteArrayOf()
    }
}

5. 音频编解码

5.1 AAC音频编码器

// AACEncoder.kt - AAC音频编码器
class AACEncoder(
    private val sampleRate: Int = 44100,
    private val channelCount: Int = 2,
    private val bitrate: Int = 128_000
) {
    private var mediaCodec: MediaCodec? = null
    private var isEncoding = false

    interface EncoderCallback {
        fun onEncodedFrame(data: ByteArray, presentationTimeUs: Long)
        fun onError(error: String)
    }

    private var callback: EncoderCallback? = null

    fun prepare() {
        try {
            // 1. 创建编码器
            mediaCodec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_AAC)

            // 2. 配置参数
            val format = MediaFormat().apply {
                setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_AUDIO_AAC)
                setInteger(MediaFormat.KEY_SAMPLE_RATE, sampleRate)
                setInteger(MediaFormat.KEY_CHANNEL_COUNT, channelCount)
                setInteger(MediaFormat.KEY_BIT_RATE, bitrate)
                setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC)
                setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 16384)
            }

            mediaCodec?.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)
            mediaCodec?.start()

            isEncoding = true

            Log.d(TAG, "AAC编码器初始化完成:${sampleRate}Hz, ${channelCount}ch, ${bitrate}bps")

        } catch (e: Exception) {
            Log.e(TAG, "编码器初始化失败: ${e.message}")
            callback?.onError("编码器初始化失败: ${e.message}")
        }
    }

    fun encodePCM(pcmData: ByteArray, presentationTimeUs: Long) {
        if (!isEncoding) return

        try {
            // 1. 获取输入buffer
            val inputBufferId = mediaCodec?.dequeueInputBuffer(10_000) ?: return

            if (inputBufferId >= 0) {
                val inputBuffer = mediaCodec?.getInputBuffer(inputBufferId)
                inputBuffer?.let {
                    it.clear()
                    it.put(pcmData)

                    // 2. 提交输入buffer
                    mediaCodec?.queueInputBuffer(
                        inputBufferId,
                        0,
                        pcmData.size,
                        presentationTimeUs,
                        0
                    )
                }
            }

            // 3. 获取输出buffer
            drainEncoder()

        } catch (e: Exception) {
            Log.e(TAG, "编码PCM失败: ${e.message}")
            callback?.onError("编码PCM失败: ${e.message}")
        }
    }

    private fun drainEncoder() {
        val bufferInfo = MediaCodec.BufferInfo()

        while (true) {
            val outputBufferId = mediaCodec?.dequeueOutputBuffer(bufferInfo, 0) ?: break

            when (outputBufferId) {
                MediaCodec.INFO_OUTPUT_FORMAT_CHANGED -> {
                    val outputFormat = mediaCodec?.outputFormat
                    Log.d(TAG, "输出格式变化: $outputFormat")
                }
                MediaCodec.INFO_TRY_AGAIN_LATER -> {
                    break
                }
                else -> {
                    if (outputBufferId >= 0) {
                        val outputBuffer = mediaCodec?.getOutputBuffer(outputBufferId)
                        outputBuffer?.let {
                            // 提取AAC数据
                            val aacData = ByteArray(bufferInfo.size)
                            it.position(bufferInfo.offset)
                            it.limit(bufferInfo.offset + bufferInfo.size)
                            it.get(aacData)

                            // 回调编码数据
                            callback?.onEncodedFrame(aacData, bufferInfo.presentationTimeUs)

                            // 释放buffer
                            mediaCodec?.releaseOutputBuffer(outputBufferId, false)
                        }
                    }
                }
            }
        }
    }

    fun stop() {
        isEncoding = false

        try {
            mediaCodec?.stop()
            mediaCodec?.release()
            mediaCodec = null

            Log.d(TAG, "AAC编码器已停止")

        } catch (e: Exception) {
            Log.e(TAG, "停止编码器失败: ${e.message}")
        }
    }

    fun setCallback(callback: EncoderCallback) {
        this.callback = callback
    }

    companion object {
        private const val TAG = "AACEncoder"
    }
}

6. 性能优化

6.1 硬件编解码器选择

// CodecSelector.kt - 编解码器选择器
object CodecSelector {

    /**
     * 列出所有支持的编解码器
     */
    fun listAllCodecs(): List<CodecInfo> {
        val codecList = MediaCodecList(MediaCodecList.ALL_CODECS)
        val codecInfos = mutableListOf<CodecInfo>()

        for (codecInfo in codecList.codecInfos) {
            val types = codecInfo.supportedTypes
            val isEncoder = codecInfo.isEncoder

            for (type in types) {
                codecInfos.add(
                    CodecInfo(
                        name = codecInfo.name,
                        type = type,
                        isEncoder = isEncoder,
                        isHardwareAccelerated = isHardwareAccelerated(codecInfo)
                    )
                )
            }
        }

        return codecInfos
    }

    /**
     * 选择最佳编码器(优先硬件编码器)
     */
    fun selectBestEncoder(mimeType: String): String? {
        val codecList = MediaCodecList(MediaCodecList.REGULAR_CODECS)

        // 1. 优先选择硬件编码器
        for (codecInfo in codecList.codecInfos) {
            if (codecInfo.isEncoder && isHardwareAccelerated(codecInfo)) {
                for (type in codecInfo.supportedTypes) {
                    if (type.equals(mimeType, ignoreCase = true)) {
                        Log.d(TAG, "选择硬件编码器: ${codecInfo.name}")
                        return codecInfo.name
                    }
                }
            }
        }

        // 2. 退化到软件编码器
        for (codecInfo in codecList.codecInfos) {
            if (codecInfo.isEncoder) {
                for (type in codecInfo.supportedTypes) {
                    if (type.equals(mimeType, ignoreCase = true)) {
                        Log.d(TAG, "选择软件编码器: ${codecInfo.name}")
                        return codecInfo.name
                    }
                }
            }
        }

        return null
    }

    /**
     * 判断是否为硬件编解码器
     */
    private fun isHardwareAccelerated(codecInfo: MediaCodecInfo): Boolean {
        // Android Q+: 使用官方API
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            return codecInfo.isHardwareAccelerated
        }

        // 旧版本:根据名称判断
        val name = codecInfo.name.toLowerCase(Locale.ROOT)
        return !name.startsWith("omx.google.") && !name.startsWith("c2.android.")
    }

    /**
     * 检查编解码器能力
     */
    fun checkCodecCapabilities(mimeType: String, width: Int, height: Int): CodecCapabilityInfo {
        val codecList = MediaCodecList(MediaCodecList.REGULAR_CODECS)

        for (codecInfo in codecList.codecInfos) {
            if (codecInfo.isEncoder) {
                for (type in codecInfo.supportedTypes) {
                    if (type.equals(mimeType, ignoreCase = true)) {
                        val capabilities = codecInfo.getCapabilitiesForType(type)
                        val videoCapabilities = capabilities.videoCapabilities

                        return CodecCapabilityInfo(
                            codecName = codecInfo.name,
                            supportedWidthRange = videoCapabilities.supportedWidths,
                            supportedHeightRange = videoCapabilities.supportedHeights,
                            supportedFrameRateRange = videoCapabilities.supportedFrameRates,
                            supportedBitrateRange = videoCapabilities.bitrateRange,
                            supportsResolution = videoCapabilities.isSizeSupported(width, height)
                        )
                    }
                }
            }
        }

        return CodecCapabilityInfo(codecName = "Unknown")
    }

    data class CodecInfo(
        val name: String,
        val type: String,
        val isEncoder: Boolean,
        val isHardwareAccelerated: Boolean
    )

    data class CodecCapabilityInfo(
        val codecName: String,
        val supportedWidthRange: Range<Int>? = null,
        val supportedHeightRange: Range<Int>? = null,
        val supportedFrameRateRange: Range<Double>? = null,
        val supportedBitrateRange: Range<Int>? = null,
        val supportsResolution: Boolean = false
    )

    private const val TAG = "CodecSelector"
}

6.2 异步模式

// AsyncMediaCodec.kt - 异步模式MediaCodec
class AsyncMediaCodec {

    fun createAsyncEncoder(): MediaCodec {
        val codec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC)

        // 设置异步回调
        codec.setCallback(object : MediaCodec.Callback() {
            override fun onInputBufferAvailable(codec: MediaCodec, index: Int) {
                // 输入buffer可用
                val inputBuffer = codec.getInputBuffer(index)
                inputBuffer?.let {
                    // 填充数据
                    val yuvData = generateYUVFrame()
                    it.put(yuvData)

                    codec.queueInputBuffer(index, 0, yuvData.size, computePTS(), 0)
                }
            }

            override fun onOutputBufferAvailable(
                codec: MediaCodec,
                index: Int,
                info: MediaCodec.BufferInfo
            ) {
                // 输出buffer可用
                val outputBuffer = codec.getOutputBuffer(index)
                outputBuffer?.let {
                    // 处理编码数据
                    val h264Data = ByteArray(info.size)
                    it.get(h264Data)

                    processEncodedFrame(h264Data)

                    codec.releaseOutputBuffer(index, false)
                }
            }

            override fun onError(codec: MediaCodec, e: MediaCodec.CodecException) {
                Log.e(TAG, "编解码错误: ${e.message}")
            }

            override fun onOutputFormatChanged(codec: MediaCodec, format: MediaFormat) {
                Log.d(TAG, "输出格式变化: $format")
            }
        })

        // 配置并启动
        val format = createVideoFormat()
        codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)
        codec.start()

        return codec
    }

    private fun generateYUVFrame(): ByteArray {
        return ByteArray(1280 * 720 * 3 / 2)
    }

    private fun computePTS(): Long {
        return System.nanoTime() / 1000
    }

    private fun processEncodedFrame(data: ByteArray) {
        // 处理编码后的数据
    }

    private fun createVideoFormat(): MediaFormat {
        return MediaFormat().apply {
            setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_VIDEO_AVC)
            setInteger(MediaFormat.KEY_WIDTH, 1280)
            setInteger(MediaFormat.KEY_HEIGHT, 720)
            setInteger(MediaFormat.KEY_BIT_RATE, 2_000_000)
            setInteger(MediaFormat.KEY_FRAME_RATE, 30)
            setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1)
            setInteger(
                MediaFormat.KEY_COLOR_FORMAT,
                MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible
            )
        }
    }

    companion object {
        private const val TAG = "AsyncMediaCodec"
    }
}

7. 案例研究

案例1:屏幕录制

// ScreenRecorder.kt - 屏幕录制器
class ScreenRecorder(
    private val context: Context,
    private val width: Int,
    private val height: Int
) {
    private lateinit var mediaProjection: MediaProjection
    private lateinit var virtualDisplay: VirtualDisplay
    private lateinit var encoder: H264Encoder

    fun start(resultCode: Int, data: Intent, outputFile: File) {
        // 1. 创建MediaProjection
        val mediaProjectionManager =
            context.getSystemService(Context.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
        mediaProjection = mediaProjectionManager.getMediaProjection(resultCode, data)

        // 2. 创建编码器
        encoder = H264Encoder(width, height)
        encoder.prepare()

        // 3. 创建VirtualDisplay
        virtualDisplay = mediaProjection.createVirtualDisplay(
            "ScreenCapture",
            width,
            height,
            context.resources.displayMetrics.densityDpi,
            DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
            encoder.getInputSurface(),  // 直接编码到Surface
            null,
            null
        )

        // 4. 保存编码数据到文件
        encoder.setCallback(object : H264Encoder.EncoderCallback {
            override fun onEncodedFrame(data: ByteArray, isKeyFrame: Boolean, presentationTimeUs: Long) {
                // 写入文件
                outputFile.appendBytes(data)
            }

            override fun onError(error: String) {
                Log.e(TAG, "编码错误: $error")
            }
        })
    }

    fun stop() {
        virtualDisplay.release()
        mediaProjection.stop()
        encoder.stop()
    }

    companion object {
        private const val TAG = "ScreenRecorder"
    }
}

总结

MediaCodec是Android音视频编解码的核心API,本文深入探讨了:

  1. MediaCodec架构:生命周期、工作流程
  2. 视频编码:H.264编码器、动态码率调整
  3. 视频解码:H.264解码器、Surface渲染
  4. 音频编解码:AAC编码器
  5. 性能优化:硬件编解码器选择、异步模式
  6. 案例研究:屏幕录制实现

关键要点

  • 硬件编解码器优先选择,性能更好
  • 异步模式降低延迟,提高吞吐量
  • 动态码率适应网络状况
  • Surface渲染减少内存拷贝

下一篇预告:《Camera2相机开发》- 深入讲解Camera2 API、预览、拍照、录像、手动对焦曝光。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐