当前位置: 首页 > news >正文

MediaCodec 编解码基础:Buffer 队列、状态机与零拷贝的艺术

引言:你以为简单,其实暗流涌动

MediaCodec是 Android 视频开发绕不开的核心类。初学者往往觉得它"挺简单的"——创建一个、配置一下、喂数据、取输出——然后在第一个IllegalStateException面前愣住,接着花一下午研究为什么 Buffer 一直取不到,最后发现自己把releaseOutputBuffer写在了错误的地方……

MediaCodec设计于 Android 4.1,是 Android 第一个直接暴露硬件加速编解码能力的公开 API。它的设计并不复杂,但有几个核心概念必须真正理解,否则代码跑得通但性能一塌糊涂,或者在特定设备上莫名崩溃。

本文把MediaCodec从状态机讲到 Buffer 队列,从同步模式讲到异步模式,再到 Surface 零拷贝的精髓,最后用两个实战案例(视频转码 + 实时编码)把所有知识串起来。


一、架构定位:MediaCodec 在哪一层?

App │ java: android.media.MediaCodec │ JNI (android_media_MediaCodec.cpp) │ ├── ACodec(OMX 通道,旧路径,Android 10 前主流) │ └── OMXNodeInstance → libstagefright_omx.so │ └── CCodec(Codec 2.0 通道,Android 10+ 主流) └── C2Component → Vendor Codec2 HAL └── 硬件编解码器(高通 MSM / MTK Vcodec / Google Tensor)

MediaCodec是一个门面(Facade),底层实际由两条路径支撑:旧 OMX(ACodec)新 Codec2(CCodec)。Android 10 开始,Codec2 成为主推路径,但对应用层完全透明——你写的MediaCodec代码不需要感知底层走哪条路。

关键知识点:硬件编解码器(HW)优先于软件(SW)MediaCodec.createEncoderByType("video/avc")默认返回硬件 H.264 编码器;如果你非要软件,用createByCodecName("OMX.google.h264.encoder"),但通常不该这么做。


二、状态机:MediaCodec 的生命周期

MediaCodec 有一套严格的状态机,在错误状态调用方法必然抛IllegalStateException。把它记住,能省掉大量调试时间。

2.1 状态迁移的关键约束

当前状态允许的操作
Initializedconfigure()
Configuredstart()setInputSurface()(编码器)
ExecutingdequeueInputBuffer()queueInputBuffer()dequeueOutputBuffer()releaseOutputBuffer()flush()stop()
Stoppedreset()(回到 Initialized)、release()
Anyrelease()(任意状态均可释放)

flush()vsreset()

  • flush():清空所有 Buffer,留在 Executing 状态,适合 seek 后重新开始喂数据
  • reset():回到 Initialized,需要重新 configure/start,适合格式变更

三、MediaFormat:把参数配对配全

MediaFormatMediaCodec.configure()的核心参数,配错了轻则编码质量差,重则直接报错。

3.1 视频编码参数

MediaFormatformat=MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC,// "video/avc",即 H.2641920,1080// 宽 × 高);// 必填参数format.setInteger(MediaFormat.KEY_BIT_RATE,8_000_000);// 码率:8 Mbpsformat.setInteger(MediaFormat.KEY_FRAME_RATE,30);// 帧率:30fpsformat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL,1);// I帧间隔:每秒一个关键帧// 直播建议 0.5,录像建议 1~2// 颜色格式(Surface 输入时不需要手动设)format.setInteger(MediaFormat.KEY_COLOR_FORMAT,MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);// Surface 输入// 或 COLOR_FormatYUV420Flexible(ByteBuffer 输入时用)// 可选但重要的参数format.setInteger(MediaFormat.KEY_PROFILE,// 编码 ProfileMediaCodecInfo.CodecProfileLevel.AVCProfileHigh);// High Profileformat.setInteger(MediaFormat.KEY_LEVEL,MediaCodecInfo.CodecProfileLevel.AVCLevel41);// Level 4.1(1080p@30fps)// 码率控制模式(Android 8+)format.setInteger(MediaFormat.KEY_BITRATE_MODE,MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR);// 可变码率// BITRATE_MODE_CBR:恒定码率(直播/实时通话)// BITRATE_MODE_CQ:恒定质量(不控码率,看画质)

3.2 视频解码参数

// 解码时 MediaFormat 来自 MediaExtractor,通常不需要手动构造// 但如果手动构建,至少需要:MediaFormatformat=MediaFormat.createVideoFormat("video/avc",width,height);// CSD(Codec-Specific Data):SPS/PPS,H.264 解码器必须有format.setByteBuffer("csd-0",ByteBuffer.wrap(sps));// Sequence Parameter Setformat.setByteBuffer("csd-1",ByteBuffer.wrap(pps));// Picture Parameter Set// H.265/HEVCformat.setByteBuffer("csd-0",ByteBuffer.wrap(vpsSpsPps));// VPS+SPS+PPS 合并// VP9 / AV1 无需 CSD,直接解码

3.3 常见参数错误

// ❌ 错误:Surface 输入时还设置了 COLOR_FormatYUV420Flexible// 这两种模式互斥,用 Surface 就别设颜色格式format.setInteger(KEY_COLOR_FORMAT,COLOR_FormatYUV420Flexible);// 多余且可能报错// ❌ 错误:I 帧间隔设为 0format.setInteger(KEY_I_FRAME_INTERVAL,0);// 0 在某些厂商 ROM 上表示"无限间隔"(全 P 帧),播放器无法 seek// 如果真的需要每帧都是关键帧(如截屏录制),应该每帧手动触发关键帧:Bundleparams=newBundle();params.putInt(MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME,0);encoder.setParameters(params);// ❌ 错误:码率单位误用(填成 Kbps 而非 bps)format.setInteger(KEY_BIT_RATE,8000);// 8000 bps = 极低质量!format.setInteger(KEY_BIT_RATE,8_000_000);// ✅ 8 Mbps

四、同步模式:最直接的用法

同步模式是最经典的MediaCodec用法——通过dequeueInputBuffer/dequeueOutputBuffer主动轮询:

4.1 完整的同步编码流程

MediaCodecencoder=MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC);MediaFormatformat=buildVideoFormat();// 见上节encoder.configure(format,null,null,MediaCodec.CONFIGURE_FLAG_ENCODE);encoder.start();MediaCodec.BufferInfobufferInfo=newMediaCodec.BufferInfo();booleaninputDone=false;booleanoutputDone=false;while(!outputDone){// ─── 喂输入 ───if(!inputDone){// 超时 10ms,-1 表示无限等待,0 表示立即返回intinputIndex=encoder.dequeueInputBuffer(10_000);if(inputIndex>=0){ByteBufferinputBuffer=encoder.getInputBuffer(inputIndex);inputBuffer.clear();// 填入一帧 YUV 数据intsampleSize=fillYuvFrame(inputBuffer);if(sampleSize<0){// 没有更多数据了:发送 EOSencoder.queueInputBuffer(inputIndex,0,0,0,MediaCodec.BUFFER_FLAG_END_OF_STREAM);inputDone=true;}else{// 提交数据,presentationTimeUs 是显示时间戳(微秒)longpts=computePresentationTimeUs(frameIndex++);encoder.queueInputBuffer(inputIndex,0,sampleSize,pts,0);}}}// ─── 取输出 ───intoutputIndex=encoder.dequeueOutputBuffer(bufferInfo,10_000);switch(outputIndex)
http://www.jsqmd.com/news/624478/

相关文章:

  • Cosmos-Reason1-7B实际效果:对机器人抓取动作进行接触力与稳定性预判
  • 如何高效使用智能激活工具:KMS_VL_ALL_AIO完整实践指南
  • YOLOv10新手必看:镜像内Markdown文档,帮你秒懂所有操作
  • 惠普暗影精灵硬件控制新选择:OmenSuperHub技术解析与实践指南
  • Open GApps包怎么选?从Platform到Variant,一次讲清安卓11/12 GMS安装包下载门道
  • Windows 10/11 鼠标指针美化终极指南:免费获取macOS风格指针
  • 如何用Python脚本实现京东茅台自动化抢购:jd_maotai实战指南
  • mPLUG-Owl3-2B图文交互工具入门必看:上传→提问→解析三步闭环
  • Tableau 中实现优雅曲线:平滑折线图的进阶技巧
  • 千问3.5-2B图文理解实战:从原始图输入到结构化JSON输出的完整数据管道设计
  • 2026洛阳江浙菜宴请选型指南:满足3个硬指标 - 精选优质企业推荐榜
  • CUDA P2P技术在多GPU内存高效传输中的应用与优化
  • SIMULINK仿真结果美化与出版级图表导出全攻略
  • MyoWare肌电传感器嵌入式驱动库技术解析
  • 等离子处理机品牌怎么选?国产 vs 进口对比
  • 2026年4月汽车增压器源头厂家怎么选择,北汽2.0增压器/豪沃540国六增压器/帕金斯增压器,汽车增压器批发推荐分析 - 品牌推荐师
  • 从引物选择到功能预测:基于 QIIME2 的 16S rRNA 测序全流程实战与深度解析
  • 从崩溃地址到问题源码:手把手教你用map文件逆向分析嵌入式程序死机原因
  • Spring_couplet_generation 面试必备:AI模型部署与优化相关Java八股文梳理
  • 储能电池主动均衡实战:手把手教你用STM32G4搭建5A均衡系统(含完整物料清单)
  • 25mm 1:2.4 1.2‘‘
  • TransTeX实战:如何利用大语言模型为你的LaTeX论文实现一键精准翻译【开发者视角】
  • 外卖试吃、霸王餐活动API接口怎么对接?
  • Python 再次出发
  • 高性能PCB逆向工程工具:OpenBoardView企业级电路板分析架构解析
  • Harness、LLM、Token、Agent、MCP…AI圈最烧脑的8个概念,一文彻底讲透
  • Sunshine开源游戏串流平台:打造你的私人云端游戏服务器终极指南
  • Notepad--:基于Scintilla的跨平台代码编辑器架构深度解析
  • 2026电销困局破局:AI机器人如何拯救深夜加班的销售团队 - 真知灼见33
  • 微软发布的《生成式人工智能初学者.NET 第二版》课程纫