Android MediaCodec实战从YUV生成到MP4封装的完整编码指南在移动应用开发中视频处理一直是技术难点之一。Android平台提供的MediaCodec API为开发者打开了高效视频编码的大门但陡峭的学习曲线让许多初学者望而却步。本文将带你从零开始完整实现一个YUV数据生成、编码到MP4封装的实战项目涵盖从内存操作到文件落地的每个技术细节。1. 环境准备与基础概念1.1 核心组件解析Android视频编码流程涉及三个关键组件MediaCodec负责实际的视频编码工作支持H.264/AVC、H.265/HEVC等主流编码格式MediaFormat定义编码参数配置包括分辨率、帧率、比特率等MediaMuxer将编码后的视频流封装为容器格式如MP4// 基本组件初始化示例 val mimeType MediaFormat.MIMETYPE_VIDEO_AVC val width 1280 val height 720 val frameRate 301.2 YUV格式选择YUV是视频编码的原始数据格式常见的有以下几种格式类型内存排列兼容性适用场景YUV420PlanarYUV三平面广泛软件处理YUV420SemiPlanarYUV双平面较好硬件加速YUV420Flexible自适应最佳通用场景关键选择建议优先使用COLOR_FormatYUV420Flexible确保兼容性确认设备支持的颜色格式val codecInfo encoder.codecInfo val capabilities codecInfo.getCapabilitiesForType(mimeType) val supportedFormats capabilities.colorFormats2. YUV数据生成实战2.1 构建测试图像人工生成YUV数据是测试编码流程的理想方式。以下是生成渐变色的典型实现fun generateColorGradientFrame( frameIndex: Int, width: Int, height: Int, yuvData: ByteArray ) { val halfWidth width / 2 val uvSize width * height / 4 // Y分量亮度 for (y in 0 until height) { for (x in 0 until width) { val yValue ((x frameIndex) % width) * 255 / width yuvData[y * width x] yValue.toByte() } } // UV分量色度 val uvOffset width * height for (i in 0 until uvSize) { yuvData[uvOffset i] 128 // U分量 yuvData[uvOffset uvSize i] 128 // V分量 } }2.2 性能优化技巧预分配内存避免在每帧编码时重复分配ByteArray并行生成使用工作线程提前生成后续帧数据格式转换如需从其他格式转换考虑使用RenderScript优化注意YUV数据的大小计算为 width × height × 1.5420格式错误的缓冲区大小会导致编码失败3. 编码器配置详解3.1 关键参数设置完整的编码器配置需要以下核心参数val format MediaFormat.createVideoFormat(mimeType, width, height).apply { setInteger(MediaFormat.KEY_COLOR_FORMAT, colorFormat) setInteger(MediaFormat.KEY_BIT_RATE, bitrate) // 如4000000表示4Mbps setInteger(MediaFormat.KEY_FRAME_RATE, frameRate) setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1) // 关键帧间隔秒 setInteger(MediaFormat.KEY_PROFILE, MediaCodecInfo.CodecProfileLevel.AVCProfileMain) setInteger(MediaFormat.KEY_LEVEL, MediaCodecInfo.CodecProfileLevel.AVCLevel31) }3.2 异步模式实现异步处理能显著提高编码效率核心回调实现如下encoder.setCallback(object : MediaCodec.Callback() { private var muxerStarted false private var videoTrackIndex -1 override fun onInputBufferAvailable(codec: MediaCodec, index: Int) { // 输入缓冲区处理逻辑 } override fun onOutputBufferAvailable( codec: MediaCodec, index: Int, info: MediaCodec.BufferInfo ) { // 输出缓冲区处理逻辑 } override fun onOutputFormatChanged(codec: MediaCodec, format: MediaFormat) { videoTrackIndex muxer.addTrack(format) muxer.start() muxerStarted true } override fun onError(codec: MediaCodec, e: MediaCodec.CodecException) { // 错误处理 } })4. 完整工作流程实现4.1 编码时序控制精确的时间戳管理对视频质量至关重要fun computePresentationTime(frameIndex: Int): Long { return (frameIndex * 1_000_000 / frameRate).toLong() } // 在输入回调中使用 val pts computePresentationTime(frameIndex) codec.queueInputBuffer(index, 0, data.size, pts, flags)4.2 异常处理机制健壮的编码器需要处理以下异常情况缓冲区不足适当增加输入队列长度格式不兼容动态调整编码参数设备过热监控温度并降低编码复杂度val thermalManager context.getSystemService(Context.THERMAL_SERVICE) as ThermalManager thermalManager.addListener { temperature - if (temperature.status ThermalManager.TEMPERATURE_CRITICAL) { // 降低编码比特率或分辨率 val newFormat createReducedFormat() encoder.reconfigure(newFormat) } }5. 高级优化技巧5.1 动态参数调整实时调整编码参数以适应不同场景fun adjustBitrate(newBitrate: Int) { val params Bundle().apply { putInt(MediaCodec.PARAMETER_KEY_VIDEO_BITRATE, newBitrate) } encoder.setParameters(params) }5.2 多线程编码利用多线程提升编码吞吐量生产者线程负责YUV数据生成编码线程处理MediaCodec回调写入线程管理MediaMuxer输出关键点确保线程间同步特别是MediaMuxer的操作必须在同一线程6. 实战调试技巧6.1 常见问题排查以下是典型问题及解决方案问题现象可能原因解决方案绿色条纹YUV格式不匹配确认COLOR_FORMAT设置视频卡顿时间戳错误检查presentationTime计算文件损坏过早关闭muxer等待BUFFER_FLAG_END_OF_STREAM6.2 性能分析工具推荐工具链Android Profiler监控CPU/内存使用MediaCodec日志adb shell setprop log.tag.MediaCodec VERBOSEFrame分析使用FFmpeg检查输出文件ffmpeg -i output.mp4 -vf selecteq(pict_type,I) -vsync vfr keyframes-%03d.png在实际项目中我发现正确处理编码器的drain和flush操作是保证视频完整性的关键。特别是在处理中断恢复场景时需要仔细管理编码器的状态转换。一个实用的技巧是在每次关键操作后检查编码器状态这可以避免许多难以追踪的边界条件问题。