本文全面解析了 Android MediaCodec 多媒体编解码框架的核心原理与实践应用。文章详细阐述了 MediaCodec 的异步工作模式、编解码流程及性能优化策略,并重点扩展了多媒体文件解析能力。通过 MediaExtractor,开发者可精准获取 MP3/MP4 等文件的采样率、声道数、比特率、时长与轨道信息。文章提供了从 MP3 文件中解码提取原始 PCM 数据的完整示例代码,涵盖文件分析、解码器配置、实时数据回调及 PCM 波形处理等关键环节。此外,还总结了缓冲区管理、低延迟处理及资源释放的最佳实践,为开发高效稳定的音视频处理应用提供了全面指导。

博主博客

目录

概述

Android MediaCodec 是 Android 多媒体框架中的核心编解码组件,自 Android 4.1(API 16)引入,提供了对音频和视频进行硬件加速编解码的能力。

主要特性

  • 硬件加速:充分利用设备硬件编解码器
  • 低延迟:适用于实时音视频处理
  • 多种格式支持:H.264, H.265, VP8, VP9, AAC, MP3 等
  • 灵活的数据处理:支持 ByteBuffer 和 Surface 输入

核心架构

// MediaCodec 的基本组成
MediaCodec ────┬─── Codec (编码器/解码器)
               ├─── Input Buffer (输入缓冲区)
               ├─── Output Buffer (输出缓冲区)
               └─── Format (媒体格式配置)

缓冲区管理

MediaCodec 使用异步缓冲区管理机制:

  • 输入缓冲区:存放待处理的原始数据
  • 输出缓冲区:存放处理后的结果数据

工作模式

1. 同步模式(已废弃)

// API 21 之前的使用方式(不推荐)
dequeueInputBuffer(timeout) → 填充数据 → queueInputBuffer()
dequeueOutputBuffer(timeout) → 处理数据 → releaseOutputBuffer()

2. 异步模式(推荐)

// API 21+ 推荐使用方式
mediaCodec.setCallback(new MediaCodec.Callback() {
    @Override
    public void onInputBufferAvailable(MediaCodec codec, int index) {
        // 处理输入缓冲区
    }
    
    @Override
    public void onOutputBufferAvailable(MediaCodec codec, int index, 
                                        MediaCodec.BufferInfo info) {
        // 处理输出缓冲区
    }
});

媒体文件信息读取

使用 MediaExtractor 读取 MP3/MP4 文件信息

class MediaFileAnalyzer {
    
    /**
     * 获取媒体文件详细信息
     * @param filePath 媒体文件路径
     * @return 包含所有媒体信息的 Map
     */
    fun analyzeMediaFile(filePath: String): Map<String, Any> {
        val extractor = MediaExtractor()
        val result = mutableMapOf<String, Any>()
        
        try {
            // 设置数据源
            extractor.setDataSource(filePath)
            
            // 获取轨道数量
            val trackCount = extractor.trackCount
            result["trackCount"] = trackCount
            
            val trackInfoList = mutableListOf<Map<String, Any>>()
            
            // 遍历所有轨道
            for (i in 0 until trackCount) {
                val trackInfo = analyzeTrack(extractor, i)
                trackInfoList.add(trackInfo)
            }
            
            result["tracks"] = trackInfoList
            
            // 获取总时长(单位:微秒)
            val durationUs = extractor.getTrackFormat(0)
                .getLong(MediaFormat.KEY_DURATION)
            result["duration"] = durationUs / 1000 // 转换为毫秒
            
        } catch (e: Exception) {
            Log.e("MediaFileAnalyzer", "分析文件失败: ${e.message}")
        } finally {
            extractor.release()
        }
        
        return result
    }
    
    private fun analyzeTrack(extractor: MediaExtractor, trackIndex: Int): Map<String, Any> {
        val format = extractor.getTrackFormat(trackIndex)
        val trackInfo = mutableMapOf<String, Any>()
        
        // 获取 MIME 类型
        val mime = format.getString(MediaFormat.KEY_MIME) ?: "unknown"
        trackInfo["mimeType"] = mime
        trackInfo["trackIndex"] = trackIndex
        
        // 根据 MIME 类型解析不同信息
        when {
            mime.startsWith("audio/") -> {
                // 音频轨道信息
                trackInfo["type"] = "audio"
                trackInfo["sampleRate"] = format.getInteger(MediaFormat.KEY_SAMPLE_RATE)
                trackInfo["channelCount"] = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT)
                
                // 获取比特率(如果存在)
                if (format.containsKey(MediaFormat.KEY_BIT_RATE)) {
                    trackInfo["bitrate"] = format.getInteger(MediaFormat.KEY_BIT_RATE)
                }
                
                // 获取编码配置(AAC 特定)
                if (format.containsKey(MediaFormat.KEY_AAC_PROFILE)) {
                    trackInfo["aacProfile"] = format.getInteger(MediaFormat.KEY_AAC_PROFILE)
                }
            }
            
            mime.startsWith("video/") -> {
                // 视频轨道信息
                trackInfo["type"] = "video"
                trackInfo["width"] = format.getInteger(MediaFormat.KEY_WIDTH)
                trackInfo["height"] = format.getInteger(MediaFormat.KEY_HEIGHT)
                
                // 获取帧率
                if (format.containsKey(MediaFormat.KEY_FRAME_RATE)) {
                    trackInfo["frameRate"] = format.getInteger(MediaFormat.KEY_FRAME_RATE)
                }
                
                // 获取颜色格式
                if (format.containsKey(MediaFormat.KEY_COLOR_FORMAT)) {
                    trackInfo["colorFormat"] = format.getInteger(MediaFormat.KEY_COLOR_FORMAT)
                }
            }
        }
        
        // 获取时长
        if (format.containsKey(MediaFormat.KEY_DURATION)) {
            trackInfo["duration"] = format.getLong(MediaFormat.KEY_DURATION) / 1000
        }
        
        // 获取最大输入大小
        if (format.containsKey(MediaFormat.KEY_MAX_INPUT_SIZE)) {
            trackInfo["maxInputSize"] = format.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE)
        }
        
        return trackInfo
    }
    
    /**
     * 打印媒体文件详细信息
     */
    fun printMediaInfo(filePath: String) {
        val info = analyzeMediaFile(filePath)
        
        println("=== 媒体文件分析结果 ===")
        println("文件路径: $filePath")
        println("总时长: ${info["duration"]} ms")
        println("轨道数量: ${info["trackCount"]}")
        
        val tracks = info["tracks"] as List<Map<String, Any>>
        tracks.forEach { track ->
            println("\n--- 轨道 ${track["trackIndex"]} ---")
            println("类型: ${track["type"]}")
            println("MIME: ${track["mimeType"]}")
            
            when (track["type"]) {
                "audio" -> {
                    println("采样率: ${track["sampleRate"]} Hz")
                    println("声道数: ${track["channelCount"]}")
                    track["bitrate"]?.let { println("比特率: $it bps") }
                }
                "video" -> {
                    println("分辨率: ${track["width"]}x${track["height"]}")
                    track["frameRate"]?.let { println("帧率: $it fps") }
                }
            }
            
            track["duration"]?.let { println("轨道时长: $it ms") }
        }
    }
}

使用示例

// 读取 MP3/MP4 文件信息
val analyzer = MediaFileAnalyzer()

// 分析 MP3 文件
val mp3Info = analyzer.analyzeMediaFile("/sdcard/music/sample.mp3")
analyzer.printMediaInfo("/sdcard/music/sample.mp3")

// 分析 MP4 文件
val mp4Info = analyzer.analyzeMediaFile("/sdcard/videos/sample.mp4")
analyzer.printMediaInfo("/sdcard/videos/sample.mp4")

基本使用流程

MP3 解码获取 PCM 数据流程

graph TD
    A[创建 MediaExtractor] --> B[设置数据源]
    B --> C[选择音频轨道]
    C --> D[获取音频格式]
    D --> E[创建 MediaCodec 解码器]
    E --> F[配置解码器]
    F --> G[启动解码器]
    G --> H{循环处理}
    H --> I[读取编码数据]
    I --> J[解码为 PCM]
    J --> K[处理 PCM 数据]
    K --> H
    K --> L[到达文件末尾]
    L --> M[停止并释放资源]

代码示例

示例1:从 MP3 文件提取 PCM 数据

class MP3ToPCMExtractor {
    
    companion object {
        private const val TIMEOUT_US = 10000L
        private const val BUFFER_SIZE = 64 * 1024 // 64KB 缓冲区
    }
    
    /**
     * 提取 MP3 文件的 PCM 数据
     * @param inputPath MP3 文件路径
     * @param outputPath PCM 输出文件路径(可选)
     * @param onPCMData 回调处理 PCM 数据
     */
    fun extractPCMFromMP3(
        inputPath: String,
        outputPath: String? = null,
        onPCMData: ((ByteArray, MediaCodec.BufferInfo) -> Unit)? = null
    ) {
        var extractor: MediaExtractor? = null
        var decoder: MediaCodec? = null
        var outputFile: FileOutputStream? = null
        
        try {
            // 1. 创建 MediaExtractor 并设置数据源
            extractor = MediaExtractor()
            extractor.setDataSource(inputPath)
            
            // 2. 选择音频轨道
            val audioTrackIndex = selectAudioTrack(extractor)
            if (audioTrackIndex == -1) {
                throw IllegalStateException("未找到音频轨道")
            }
            
            extractor.selectTrack(audioTrackIndex)
            val format = extractor.getTrackFormat(audioTrackIndex)
            
            // 3. 打印音频信息
            printAudioInfo(format)
            
            // 4. 创建解码器
            val mime = format.getString(MediaFormat.KEY_MIME) ?: ""
            decoder = MediaCodec.createDecoderByType(mime)
            
            // 5. 配置解码器(不指定 Surface,用于获取 PCM)
            decoder.configure(format, null, null, 0)
            
            // 6. 启动解码器
            decoder.start()
            
            // 7. 如果需要,创建输出文件
            if (!outputPath.isNullOrEmpty()) {
                outputFile = FileOutputStream(outputPath)
            }
            
            // 8. 开始解码循环
            decodeLoop(extractor, decoder, outputFile, onPCMData)
            
        } catch (e: Exception) {
            Log.e("MP3ToPCMExtractor", "提取 PCM 失败: ${e.message}")
            e.printStackTrace()
        } finally {
            // 9. 清理资源
            decoder?.stop()
            decoder?.release()
            extractor?.release()
            outputFile?.close()
        }
    }
    
    /**
     * 选择音频轨道
     */
    private fun selectAudioTrack(extractor: MediaExtractor): Int {
        for (i in 0 until extractor.trackCount) {
            val format = extractor.getTrackFormat(i)
            val mime = format.getString(MediaFormat.KEY_MIME) ?: ""
            if (mime.startsWith("audio/")) {
                return i
            }
        }
        return -1
    }
    
    /**
     * 打印音频信息
     */
    private fun printAudioInfo(format: MediaFormat) {
        println("=== 音频信息 ===")
        println("MIME 类型: ${format.getString(MediaFormat.KEY_MIME)}")
        println("采样率: ${format.getInteger(MediaFormat.KEY_SAMPLE_RATE)} Hz")
        println("声道数: ${format.getInteger(MediaFormat.KEY_CHANNEL_COUNT)}")
        
        if (format.containsKey(MediaFormat.KEY_DURATION)) {
            val durationMs = format.getLong(MediaFormat.KEY_DURATION) / 1000
            println("时长: ${durationMs} ms")
        }
        
        if (format.containsKey(MediaFormat.KEY_BIT_RATE)) {
            println("比特率: ${format.getInteger(MediaFormat.KEY_BIT_RATE)} bps")
        }
        
        if (format.containsKey(MediaFormat.KEY_MAX_INPUT_SIZE)) {
            println("最大输入大小: ${format.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE)} bytes")
        }
    }
    
    /**
     * 解码循环
     */
    private fun decodeLoop(
        extractor: MediaExtractor,
        decoder: MediaCodec,
        outputFile: FileOutputStream?,
        onPCMData: ((ByteArray, MediaCodec.BufferInfo) -> Unit)?
    ) {
        val bufferInfo = MediaCodec.BufferInfo()
        var inputEOS = false
        var outputEOS = false
        
        while (!outputEOS) {
            // 处理输入
            if (!inputEOS) {
                val inputBufferIndex = decoder.dequeueInputBuffer(TIMEOUT_US)
                if (inputBufferIndex >= 0) {
                    val inputBuffer = decoder.getInputBuffer(inputBufferIndex)
                    val sampleSize = extractor.readSampleData(inputBuffer!!, 0)
                    
                    if (sampleSize < 0) {
                        // 文件结束
                        decoder.queueInputBuffer(
                            inputBufferIndex,
                            0, 0, 0,
                            MediaCodec.BUFFER_FLAG_END_OF_STREAM
                        )
                        inputEOS = true
                    } else {
                        // 读取数据
                        val presentationTimeUs = extractor.sampleTime
                        decoder.queueInputBuffer(
                            inputBufferIndex,
                            0, sampleSize,
                            presentationTimeUs, 0
                        )
                        extractor.advance()
                    }
                }
            }
            
            // 处理输出
            val outputBufferIndex = decoder.dequeueOutputBuffer(bufferInfo, TIMEOUT_US)
            when (outputBufferIndex) {
                MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED -> {
                    Log.d("MP3ToPCMExtractor", "输出缓冲区改变")
                }
                
                MediaCodec.INFO_OUTPUT_FORMAT_CHANGED -> {
                    val newFormat = decoder.outputFormat
                    Log.d("MP3ToPCMExtractor", "输出格式改变: $newFormat")
                }
                
                MediaCodec.INFO_TRY_AGAIN_LATER -> {
                    // 没有可用的输出缓冲区
                }
                
                else -> {
                    if (outputBufferIndex >= 0) {
                        // 获取 PCM 数据
                        val outputBuffer = decoder.getOutputBuffer(outputBufferIndex)
                        outputBuffer?.let { buffer ->
                            // 确保 bufferInfo.size 有效
                            if (bufferInfo.size > 0) {
                                // 创建 PCM 数据数组
                                val pcmData = ByteArray(bufferInfo.size)
                                buffer.position(bufferInfo.offset)
                                buffer.limit(bufferInfo.offset + bufferInfo.size)
                                buffer.get(pcmData, 0, bufferInfo.size)
                                
                                // 回调处理 PCM 数据
                                onPCMData?.invoke(pcmData, bufferInfo)
                                
                                // 写入文件(如果需要)
                                outputFile?.write(pcmData)
                                
                                // 输出调试信息(可选)
                                if ((bufferInfo.flags and MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) {
                                    Log.v("MP3ToPCMExtractor", 
                                        "解码帧: size=${bufferInfo.size}, " +
                                        "time=${bufferInfo.presentationTimeUs}us")
                                }
                            }
                        }
                        
                        // 释放输出缓冲区
                        decoder.releaseOutputBuffer(outputBufferIndex, false)
                        
                        // 检查是否结束
                        if ((bufferInfo.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
                            outputEOS = true
                            Log.d("MP3ToPCMExtractor", "解码完成")
                        }
                    }
                }
            }
        }
    }
    
    /**
     * 实时处理 PCM 数据的回调版本
     */
    fun extractPCMWithCallback(
        inputPath: String,
        onAudioInfo: (MediaFormat) -> Unit,
        onPCMDataAvailable: (ByteArray, Long) -> Unit,
        onCompletion: () -> Unit
    ) {
        // 使用异步回调模式
        val extractor = MediaExtractor()
        
        try {
            extractor.setDataSource(inputPath)
            val audioTrackIndex = selectAudioTrack(extractor)
            extractor.selectTrack(audioTrackIndex)
            
            val format = extractor.getTrackFormat(audioTrackIndex)
            onAudioInfo(format)
            
            val mime = format.getString(MediaFormat.KEY_MIME) ?: ""
            val decoder = MediaCodec.createDecoderByType(mime)
            
            decoder.setCallback(object : MediaCodec.Callback() {
                override fun onInputBufferAvailable(codec: MediaCodec, index: Int) {
                    val inputBuffer = codec.getInputBuffer(index)
                    val sampleSize = extractor.readSampleData(inputBuffer!!, 0)
                    
                    if (sampleSize >= 0) {
                        val presentationTimeUs = extractor.sampleTime
                        codec.queueInputBuffer(
                            index, 0, sampleSize,
                            presentationTimeUs, 0
                        )
                        extractor.advance()
                    } else {
                        codec.queueInputBuffer(
                            index, 0, 0, 0,
                            MediaCodec.BUFFER_FLAG_END_OF_STREAM
                        )
                    }
                }
                
                override fun onOutputBufferAvailable(
                    codec: MediaCodec,
                    index: Int,
                    info: MediaCodec.BufferInfo
                ) {
                    if (info.size > 0) {
                        val outputBuffer = codec.getOutputBuffer(index)
                        outputBuffer?.let { buffer ->
                            val pcmData = ByteArray(info.size)
                            buffer.position(info.offset)
                            buffer.limit(info.offset + info.size)
                            buffer.get(pcmData, 0, info.size)
                            
                            // 回调 PCM 数据
                            onPCMDataAvailable(pcmData, info.presentationTimeUs)
                        }
                    }
                    
                    codec.releaseOutputBuffer(index, false)
                    
                    if ((info.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
                        extractor.release()
                        decoder.stop()
                        decoder.release()
                        onCompletion()
                    }
                }
                
                override fun onError(codec: MediaCodec, e: MediaCodec.CodecException) {
                    Log.e("MP3ToPCMExtractor", "解码错误: ${e.message}")
                }
                
                override fun onOutputFormatChanged(
                    codec: MediaCodec,
                    format: MediaFormat
                ) {
                    Log.d("MP3ToPCMExtractor", "输出格式改变: $format")
                }
            })
            
            decoder.configure(format, null, null, 0)
            decoder.start()
            
        } catch (e: Exception) {
            Log.e("MP3ToPCMExtractor", "处理失败: ${e.message}")
            extractor.release()
        }
    }
}

示例2:使用示例和数据处理

class AudioProcessor {
    
    /**
     * 示例:处理 MP3 文件的完整流程
     */
    fun processMP3File() {
        val inputPath = "/sdcard/music/test.mp3"
        val outputPath = "/sdcard/music/output.pcm"
        
        val extractor = MP3ToPCMExtractor()
        
        // 方式1:提取 PCM 到文件
        extractor.extractPCMFromMP3(inputPath, outputPath) { pcmData, bufferInfo ->
            // 实时处理 PCM 数据
            val sampleRate = 44100 // 从 MediaFormat 获取的实际值
            val channelCount = 2   // 从 MediaFormat 获取的实际值
            processPCMData(pcmData, sampleRate, channelCount, bufferInfo.presentationTimeUs)
        }
        
        // 方式2:使用回调方式处理
        extractor.extractPCMWithCallback(
            inputPath = inputPath,
            onAudioInfo = { format ->
                val sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE)
                val channelCount = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT)
                val duration = format.getLong(MediaFormat.KEY_DURATION) / 1000
                
                println("音频信息:")
                println("  采样率: $sampleRate Hz")
                println("  声道数: $channelCount")
                println("  时长: $duration ms")
            },
            onPCMDataAvailable = { pcmData, timestamp ->
                // 实时处理 PCM 数据
                visualizeAudioWaveform(pcmData)
                calculateVolumeLevel(pcmData)
            },
            onCompletion = {
                println("PCM 数据提取完成")
            }
        )
    }
    
    /**
     * 处理 PCM 数据的实用方法
     */
    private fun processPCMData(
        pcmData: ByteArray,
        sampleRate: Int,
        channelCount: Int,
        timestampUs: Long
    ) {
        // 1. 将字节数组转换为短整型数组(16位 PCM)
        val samples = ShortArray(pcmData.size / 2)
        ByteBuffer.wrap(pcmData).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().get(samples)
        
        // 2. 分离声道(如果是立体声)
        if (channelCount == 2) {
            val leftChannel = mutableListOf<Short>()
            val rightChannel = mutableListOf<Short>()
            
            for (i in samples.indices step 2) {
                leftChannel.add(samples[i])
                rightChannel.add(samples[i + 1])
            }
            
            // 处理左右声道数据...
        }
        
        // 3. 计算音频特征
        val volume = calculateRMS(samples)
        val frequency = estimateFrequency(samples, sampleRate)
        
        // 4. 记录或显示信息
        Log.d("AudioProcessor", 
            "时间: ${timestampUs / 1000}ms, " +
            "音量: $volume, " +
            "估计频率: $frequency Hz")
    }
    
    /**
     * 计算 RMS(均方根)音量
     */
    private fun calculateRMS(samples: ShortArray): Double {
        var sum = 0.0
        for (sample in samples) {
            val value = sample.toDouble() / Short.MAX_VALUE
            sum += value * value
        }
        return sqrt(sum / samples.size)
    }
    
    /**
     * 估计频率(零交叉法,简单实现)
     */
    private fun estimateFrequency(samples: ShortArray, sampleRate: Int): Double {
        var zeroCrossings = 0
        for (i in 1 until samples.size) {
            if (samples[i] >= 0 && samples[i - 1] < 0 ||
                samples[i] < 0 && samples[i - 1] >= 0) {
                zeroCrossings++
            }
        }
        
        val duration = samples.size.toDouble() / sampleRate
        return zeroCrossings / (2.0 * duration)
    }
    
    /**
     * 可视化音频波形
     */
    private fun visualizeAudioWaveform(pcmData: ByteArray) {
        // 简单的波形可视化(控制台输出)
        val samples = ShortArray(pcmData.size / 2)
        ByteBuffer.wrap(pcmData).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().get(samples)
        
        // 取前 50 个采样点显示
        val displayLength = min(50, samples.size)
        val waveform = StringBuilder()
        
        for (i in 0 until displayLength) {
            val normalized = (samples[i].toDouble() / Short.MAX_VALUE * 20).toInt()
            waveform.append(" ".repeat(20 + normalized)).append("*\n")
        }
        
        // 清除控制台并显示波形(实际应用中应该用 UI 组件)
        // println("\u001B[2J\u001B[H") // 清屏
        // println(waveform.toString())
    }
    
    /**
     * 计算音量级别
     */
    private fun calculateVolumeLevel(pcmData: ByteArray): Float {
        val samples = ShortArray(pcmData.size / 2)
        ByteBuffer.wrap(pcmData).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().get(samples)
        
        var sum = 0L
        for (sample in samples) {
            sum += Math.abs(sample.toLong())
        }
        
        val average = sum.toFloat() / samples.size
        val normalized = average / Short.MAX_VALUE
        
        // 转换为分贝(简化计算)
        val db = 20 * log10(normalized.toDouble())
        return db.toFloat()
    }
}

// 使用示例
fun main() {
    val processor = AudioProcessor()
    processor.processMP3File()
    
    // 读取媒体文件信息
    val analyzer = MediaFileAnalyzer()
    analyzer.printMediaInfo("/sdcard/music/sample.mp3")
    
    // 输出示例:
    // === 媒体文件分析结果 ===
    // 文件路径: /sdcard/music/sample.mp3
    // 总时长: 240000 ms
    // 轨道数量: 1
    // 
    // --- 轨道 0 ---
    // 类型: audio
    // MIME: audio/mpeg
    // 采样率: 44100 Hz
    // 声道数: 2
    // 比特率: 128000 bps
    // 轨道时长: 240000 ms
}

示例3:获取媒体文件元数据

class MediaMetadataExtractor {
    
    /**
     * 获取媒体文件的完整元数据
     */
    fun extractMetadata(filePath: String): MediaMetadata {
        val metadata = MediaMetadata()
        val extractor = MediaExtractor()
        
        try {
            extractor.setDataSource(filePath)
            
            // 获取轨道信息
            for (i in 0 until extractor.trackCount) {
                val format = extractor.getTrackFormat(i)
                val mime = format.getString(MediaFormat.KEY_MIME) ?: ""
                
                when {
                    mime.startsWith("audio/") -> {
                        metadata.audioTrack = AudioTrackInfo(
                            sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE),
                            channelCount = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT),
                            bitrate = if (format.containsKey(MediaFormat.KEY_BIT_RATE)) {
                                format.getInteger(MediaFormat.KEY_BIT_RATE)
                            } else null,
                            durationUs = format.getLong(MediaFormat.KEY_DURATION),
                            mimeType = mime
                        )
                    }
                    
                    mime.startsWith("video/") -> {
                        metadata.videoTrack = VideoTrackInfo(
                            width = format.getInteger(MediaFormat.KEY_WIDTH),
                            height = format.getInteger(MediaFormat.KEY_HEIGHT),
                            frameRate = if (format.containsKey(MediaFormat.KEY_FRAME_RATE)) {
                                format.getInteger(MediaFormat.KEY_FRAME_RATE)
                            } else null,
                            bitrate = if (format.containsKey(MediaFormat.KEY_BIT_RATE)) {
                                format.getInteger(MediaFormat.KEY_BIT_RATE)
                            } else null,
                            durationUs = format.getLong(MediaFormat.KEY_DURATION),
                            mimeType = mime
                        )
                    }
                }
            }
            
        } catch (e: Exception) {
            Log.e("MediaMetadataExtractor", "提取元数据失败", e)
        } finally {
            extractor.release()
        }
        
        return metadata
    }
    
    data class MediaMetadata(
        var audioTrack: AudioTrackInfo? = null,
        var videoTrack: VideoTrackInfo? = null,
        var fileSize: Long = 0,
        var filePath: String = ""
    )
    
    data class AudioTrackInfo(
        val sampleRate: Int,
        val channelCount: Int,
        val bitrate: Int?,
        val durationUs: Long,
        val mimeType: String
    ) {
        val durationMs: Long get() = durationUs / 1000
        val isStereo: Boolean get() = channelCount >= 2
    }
    
    data class VideoTrackInfo(
        val width: Int,
        val height: Int,
        val frameRate: Int?,
        val bitrate: Int?,
        val durationUs: Long,
        val mimeType: String
    ) {
        val durationMs: Long get() = durationUs / 1000
        val resolution: String get() = "${width}x${height}"
        val aspectRatio: Float get() = width.toFloat() / height
    }
}

高级特性

1. Surface 输入/输出

// 使用 Surface 作为输入(相机预览)
val surface = mediaCodec.createInputSurface()

// 使用 Surface 作为输出(渲染到视图)
mediaCodec.configure(format, surface, null, 0)

2. 动态配置

// 动态调整比特率
val params = Bundle().apply {
    putInt(MediaCodec.PARAMETER_KEY_VIDEO_BITRATE, newBitrate)
}
mediaCodec.setParameters(params)

3. 低延迟模式

// 配置低延迟编码
format.setInteger(MediaFormat.KEY_LATENCY, 1)

最佳实践

✅ 建议做法

  1. 总是检查设备能力

    fun isCodecSupported(mimeType: String): Boolean {
        val codecList = MediaCodecList(MediaCodecList.REGULAR_CODECS)
        return codecList.findDecoderForFormat(format) != null
    }
    
  2. 正确处理生命周期

    override fun onPause() {
        mediaCodec.stop()
    }
    
    override fun onResume() {
        mediaCodec.start()
    }
    
    override fun onDestroy() {
        mediaCodec.release()
    }
    
  3. 使用合适的缓冲区大小

    // 根据 PCM 参数计算缓冲区大小
    fun calculatePCMBufferSize(sampleRate: Int, channelCount: Int, durationMs: Int): Int {
        val bytesPerSample = 2 // 16-bit PCM
        return sampleRate * channelCount * bytesPerSample * durationMs / 1000
    }
    

PCM 数据处理的最佳实践

  1. 缓冲管理

    class PCMSampleBuffer {
        private val buffer = ByteArrayOutputStream()
        private val lock = Object()
        
        fun write(data: ByteArray) {
            synchronized(lock) {
                buffer.write(data)
            }
        }
        
        fun read(size: Int): ByteArray? {
            synchronized(lock) {
                return if (buffer.size() >= size) {
                    val result = buffer.toByteArray().copyOfRange(0, size)
                    val remaining = ByteArray(buffer.size() - size)
                    System.arraycopy(buffer.toByteArray(), size, remaining, 0, remaining.size)
                    buffer.reset()
                    buffer.write(remaining)
                    result
                } else {
                    null
                }
            }
        }
    }
    
  2. 实时处理优化

    class RealTimeAudioProcessor {
        private val executor = Executors.newSingleThreadExecutor()
        
        fun processInBackground(pcmData: ByteArray, callback: (ByteArray) -> Unit) {
            executor.submit {
                // 在后台线程处理音频数据
                val processed = applyEffects(pcmData)
                callback(processed)
            }
        }
        
        private fun applyEffects(data: ByteArray): ByteArray {
            // 应用均衡器、混响等效果
            return data // 简化实现
        }
    }
    

常见问题

Q1: 编解码器初始化失败

可能原因

  • 不支持的格式
  • 资源不足
  • 错误的参数配置

解决方案

try {
    mediaCodec = MediaCodec.createDecoderByType(mimeType)
} catch (e: IOException) {
    // 尝试软件解码器
    mediaCodec = MediaCodec.createByCodecName("OMX.google.h264.decoder")
}

Q2: PCM 数据获取问题

常见问题

  1. 采样率不正确

    // 验证采样率
    val format = extractor.getTrackFormat(audioTrackIndex)
    val sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE)
    require(sampleRate in listOf(8000, 16000, 44100, 48000)) {
        "不支持的采样率: $sampleRate"
    }
    
  2. 声道数错误

    val channelCount = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT)
    when (channelCount) {
        1 -> println("单声道")
        2 -> println("立体声")
        else -> println("多声道: $channelCount")
    }
    

Q3: 内存泄漏

预防措施

class SafeMediaCodecWrapper {
    private val mediaCodec: MediaCodec
    
    init {
        mediaCodec = // 初始化
    }
    
    fun release() {
        try {
            mediaCodec.stop()
        } catch (e: Exception) {
            // 记录日志但不抛出
        }
        try {
            mediaCodec.release()
        } catch (e: Exception) {
            // 记录日志
        }
    }
}

性能优化建议

1. PCM 数据处理的优化

class OptimizedPCMProcessor {
    
    // 使用直接缓冲区减少拷贝
    private val directBuffer: ByteBuffer = ByteBuffer.allocateDirect(1024 * 1024)
    
    fun processPCMEfficiently(pcmData: ByteArray): ByteArray {
        // 1. 批量处理,减少函数调用开销
        val batchSize = 1024
        val result = ByteArrayOutputStream()
        
        for (i in pcmData.indices step batchSize) {
            val end = min(i + batchSize, pcmData.size)
            val batch = pcmData.copyOfRange(i, end)
            
            // 2. 使用直接缓冲区
            directBuffer.clear()
            directBuffer.put(batch)
            directBuffer.flip()
            
            // 3. 处理数据
            processBatch(directBuffer)
            
            // 4. 收集结果
            val processed = ByteArray(directBuffer.remaining())
            directBuffer.get(processed)
            result.write(processed)
        }
        
        return result.toByteArray()
    }
    
    private fun processBatch(buffer: ByteBuffer) {
        // 高效批处理逻辑
    }
}

2. 内存优化

// 重用缓冲区
object PCMBufferPool {
    private val pool = mutableListOf<ByteArray>()
    
    fun getBuffer(size: Int): ByteArray {
        return pool.find { it.size == size } ?: ByteArray(size).also {
            pool.add(it)
        }
    }
    
    fun returnBuffer(buffer: ByteArray) {
        // 可以清理缓冲区内容
        buffer.fill(0)
    }
}

总结

本文详细介绍了 Android MediaCodec 的核心功能,特别增加了:

关键增强内容:

  1. 媒体文件信息读取

    • 使用 MediaExtractor 获取 MP3/MP4 的采样率、声道数、时长
    • 支持音频和视频轨道的全面信息提取
  2. PCM 数据提取

    • 完整的 MP3 解码到 PCM 的示例
    • 实时 PCM 数据处理和回调机制
    • PCM 数据的实用处理方法(音量计算、波形可视化等)
  3. 实践指导

    • 如何正确配置解码器获取原始 PCM
    • PCM 数据的缓冲管理和处理优化
    • 常见问题的解决方案

核心要点:

  • MediaExtractor 是读取媒体文件信息的关键工具
  • PCM 数据获取需要正确配置解码器,不指定 Surface 输出
  • 实时处理应该使用异步模式和后台线程
  • 性能优化包括缓冲区重用、批量处理和直接缓冲区使用

通过本文的示例代码和最佳实践,开发者可以:

  1. ✅ 读取各种媒体文件的详细信息
  2. ✅ 从 MP3/MP4 中提取原始 PCM 数据
  3. ✅ 实时处理和分析音频数据
  4. ✅ 构建高效的音频处理应用

参考资料

注意:本文基于 Android API 21+,某些功能在旧版本可能不可用。在实际开发中请始终检查 API 级别兼容性,并正确处理权限和文件访问问题。