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

Android MediaCodec 编解码

文章目录

  • 概述
  • 一、核心概念与工作原理
    • 1.编码器 (Encoder)
    • 2.解码器 (Decoder)
    • 3.MediaCodec 工作模型
    • 4. MediaCodec 生命周期
    • 5. 基本工作流程
  • 二、关键组件与 参数
    • 1.组件
    • 2.参数解析
  • 三、常见应用场景
  • 四、使用中的关键注意事项与难点

概述

Android MediaCodec 是 Android 系统提供的底层 API,用于访问设备的硬件(或软件)编解码器,实现高效、低功耗的音视频编码和解码。它是构建高性能多媒体应用(如视频播放器、视频录制、直播推流、视频编辑等)的核心组件。

一、核心概念与工作原理

MediaCodec 采用异步的生产者 - 消费者模型,通过输入和输出缓冲区队列处理数据。

1.编码器 (Encoder)

生产者:应用(提供原始数据,如 YUV 格式的视频帧或 PCM 格式的音频采样)。
消费者:MediaCodec 编码器(接收原始数据,输出压缩后的数据,如 H.264 视频流或 AAC 音频流)。

2.解码器 (Decoder)

生产者:应用(提供压缩数据)。
消费者:MediaCodec 解码器(接收压缩数据,输出原始数据)。

3.MediaCodec 工作模型

MediaCodec 采用双缓冲区队列(输入/输出)实现异步数据处理,其架构可分为以下三层:

客户端(Client)

  • 输入端:填充待编解码的原始数据(如 YUV 视频帧、PCM 音频)到输入缓冲区队列。
  • 输出端:从输出缓冲区队列读取编解码后的数据(如 H.264 流、AAC 音频)并进行渲染或播放。

编解码器(Codec)

  • 硬件加速层:优先调用设备专属编解码器(如高通 DSP、ARM Mali),显著降低 CPU 负载。
  • 处理逻辑:从输入队列取出数据,执行编码/解码后,将结果存入输出队列,并回收缓冲区供复用。

缓冲区队列(Buffer Queue)

  • 输入队列:存储待处理的原始数据缓冲区(ByteBuffer 数组)。
  • 输出队列:存储处理后的数据缓冲区,供客户端消费。

在这里插入图片描述

4. MediaCodec 生命周期

生命周期执行顺序和各各声明周期详解如下:

在这里插入图片描述

  1. Uninitialized(未初始化)
    创建方式:调用 MediaCodec.createEncoderByType() 或 createDecoderByType() 后进入。
    允许操作:
    configure(…)
    release() → 跳转到 Released
    禁止操作:调用 start(), dequeueInputBuffer(), getInputBuffer() 等会抛出 IllegalStateException
    ⚠️ 此时还未配置参数,不能进行任何数据处理。

  2. Configured(已配置)
    进入方式:在 Uninitialized 状态下调用 configure(mediaFormat, surface, crypto, flags)。
    允许操作:
    start() → 跳转到 Executing
    release() → 跳转到 Released
    禁止操作:调用 dequeueInputBuffer() 等数据操作会抛异常。
    📌 注意:如果使用 Surface 输入/输出(如相机或播放器),Surface 必须在 configure 时传入,之后不能更改。

  3. Executing(执行中) ← 核心工作状态
    进入方式:在 Configured 状态下调用 start()。
    子状态:
    Flushed(刚启动或调用 flush() 后)
    Running(正常处理数据中)
    End-of-Stream(收到 EOS 信号,正在清空缓冲区)
    ➤ Executing - Flushed
    刚调用 start() 或 flush() 后进入。
    输入/输出缓冲区队列为空。
    第一次调用 dequeueInputBuffer() 会返回有效索引。
    ➤ Executing - Running
    正常编解码状态。
    可以反复调用:
    dequeueInputBuffer() + queueInputBuffer() → 提交数据
    dequeueOutputBuffer() + releaseOutputBuffer() → 获取并释放结果
    ➤ Executing - End-of-Stream
    当你调用 queueInputBuffer(…, …, BUFFER_FLAG_END_OF_STREAM) 后进入。
    编解码器会继续输出剩余数据,直到 dequeueOutputBuffer() 返回带有 BUFFER_FLAG_END_OF_STREAM 的 buffer。
    此时仍需继续处理输出缓冲区,直到收到 EOS。
    ✅ 在 Executing 状态下可以调用:

flush() → 回到 Flushed 子状态(清空所有缓冲区,用于 seek 或重新开始)
stop() → 跳转到 Uninitialized
release() → 跳转到 Released
4. Released(已释放)
进入方式:在任何状态下调用 release()。
特点:
所有资源被释放,包括底层硬件编解码器实例。
对象不可再使用,任何方法调用都会抛出 IllegalStateException。
GC 会回收 Java 对象,但 native 资源必须手动 release() 才能释放。
✅ 最佳实践:在 Activity/Fragment 销毁、Surface 被销毁、或编码完成时,必须调用 release()

5. 基本工作流程

  • 创建 (Create):通过 MediaCodec.createEncoderByType() 或MediaCodec.createDecoderByType() 创建实例。
  • 配置 (Configure):使用 MediaFormat 对象设置编解码参数(如分辨率、码率、帧率、颜色格式、MIME类型等),然后调用 configure() 方法。
  • 启动 (Start):调用 start() 方法,使编解码器进入运行状态,此时可开始访问输入和输出缓冲区。 处理数据 (Process):
import android.media.MediaCodec;
import android.media.MediaFormat;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;public class H264Encoder {private static final String MIME_TYPE = "video/avc"; // H.264 MIME 类型private MediaCodec mEncoder;private FileOutputStream mOutputStream; // 用于写入 H.264 文件public void configure(int width, int height, int bitrate, int frameRate, int iframeInterval) {try {// 1. 创建编码器mEncoder = MediaCodec.createEncoderByType(MIME_TYPE);// 2. 创建 MediaFormat 并设置参数MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, width, height);// 关键参数设置format.setInteger(MediaFormat.KEY_COLOR_FORMAT,MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible); // 推荐使用 Flexibleformat.setInteger(MediaFormat.KEY_BIT_RATE, bitrate); // 码率,单位 bpsformat.setInteger(MediaFormat.KEY_FRAME_RATE, frameRate); // 帧率format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, iframeInterval); // I帧间隔,单位秒format.setInteger(MediaFormat.KEY_BITRATE_MODE, MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CBR); // 码率模式,可选 CBR/VBR/CQ// 3. 配置编码器(指定为编码器,传入 null 表示不关联 Surface)mEncoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);// 4. 启动编码器mEncoder.start();// 5. 打开输出文件mOutputStream = new FileOutputStream("output.h264");} catch (IOException e) {e.printStackTrace();}}
}
  • 输入:调用 dequeueInputBuffer() 获取可用输入缓冲区索引,通过 getInputBuffer() 获取缓冲区,写入数据后调用 queueInputBuffer() 提交给编解码器。
  • 输出:调用 dequeueOutputBuffer() 获取包含处理后数据的输出缓冲区索引,通过getOutputBuffer()获取数据,处理完毕后调用 releaseOutputBuffer() 释放缓冲区。
public void encodeFrame(byte[] yuvData, long presentationTimeUs) {try {// --- 处理输入 ---// 1. 获取输入缓冲区的索引int inputBufferIndex = mEncoder.dequeueInputBuffer(10000); // 超时 10msif (inputBufferIndex >= 0) {ByteBuffer inputBuffer = mEncoder.getInputBuffer(inputBufferIndex);inputBuffer.clear();// 2. 将 YUV 数据复制到输入缓冲区// 注意:这里假设 yuvData 的格式与 MediaFormat 中设置的 COLOR_FORMAT 一致!// 如果格式不匹配,需要先进行转换(例如 NV21 -> I420 或 NV12)inputBuffer.put(yuvData);// 3. 提交输入缓冲区给编码器// presentationTimeUs 是此帧的时间戳,单位微秒 (us)// 如果是最后一帧,需要加上 BUFFER_FLAG_END_OF_STREAM 标志mEncoder.queueInputBuffer(inputBufferIndex, 0, yuvData.length, presentationTimeUs, 0);}// --- 处理输出 ---MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();int outputBufferIndex;while ((outputBufferIndex = mEncoder.dequeueOutputBuffer(bufferInfo, 10000)) >= 0) {if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {// 这是编解码器配置信息 (SPS/PPS),通常需要保存并在文件开头或每个 I 帧前写入// 对于 H.264 文件,通常将 SPS/PPS 写在文件最开头ByteBuffer outputBuffer = mEncoder.getOutputBuffer(outputBufferIndex);byte[] spsPps = new byte[bufferInfo.size];outputBuffer.get(spsPps);// 将 spsPps 写入文件mOutputStream.write(spsPps);// 释放输出缓冲区mEncoder.releaseOutputBuffer(outputBufferIndex, false);continue;}if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {// 编码结束break;}// 获取包含编码后 H.264 数据的输出缓冲区ByteBuffer outputBuffer = mEncoder.getOutputBuffer(outputBufferIndex);byte[] encodedData = new byte[bufferInfo.size];outputBuffer.get(encodedData);// 将编码后的 H.264 NAL 单元写入文件// 通常需要在每个 NAL 单元前添加起始码 0x00000001mOutputStream.write(new byte[]{0, 0, 0, 1});mOutputStream.write(encodedData);// 释放输出缓冲区mEncoder.releaseOutputBuffer(outputBufferIndex, false);}} catch (IOException e) {e.printStackTrace();}
}
  • 结束 (Stop & Release):处理完所有数据后,发送结束信号(在 queueInputBuffer() 时设置
    BUFFER_FLAG_END_OF_STREAM),等待并处理完所有输出缓冲区,最后调用 stop() 和 release() 释放资源。
public void stopAndRelease() {try {// 1. 发送结束信号int inputBufferIndex = mEncoder.dequeueInputBuffer(-1); // 阻塞等待if (inputBufferIndex >= 0) {mEncoder.queueInputBuffer(inputBufferIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);}// 2. 处理所有剩余的输出数据MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();int outputBufferIndex;do {outputBufferIndex = mEncoder.dequeueOutputBuffer(bufferInfo, 10000);if (outputBufferIndex >= 0) {if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {break; // 确认结束}ByteBuffer outputBuffer = mEncoder.getOutputBuffer(outputBufferIndex);byte[] encodedData = new byte[bufferInfo.size];outputBuffer.get(encodedData);mOutputStream.write(new byte[]{0, 0, 0, 1});mOutputStream.write(encodedData);mEncoder.releaseOutputBuffer(outputBufferIndex, false);}} while (outputBufferIndex >= 0 || outputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER);// 3. 关闭文件流if (mOutputStream != null) {mOutputStream.close();}// 4. 停止并释放编码器资源mEncoder.stop();mEncoder.release();mEncoder = null;} catch (IOException e) {e.printStackTrace();}
}

二、关键组件与 参数

1.组件

  • MediaCodec:核心类,负责编解码操作。
  • MediaForma:用于描述媒体数据的格式。在配置编解码器时,必须提供正确的 MediaFormat。
  • MediaCodec.BufferInfo:伴随输出缓冲区返回,包含数据大小、时间戳、偏移量和标志位(如 BUFFER_FLAG_CODEC_CONFIG,BUFFER_FLAG_END_OF_STREAM)。
  • MediaCodecList:用于查询设备支持的编解码器及其能力。从 Android 10 (API 29) 开始,可以查询编解码器是否为硬件加速 (isHardwareAccelerated()) 以及支持的性能点 (getSupportedPerformancePoints())。

2.参数解析

视频编码关键参数 (Encoder)
这些参数在创建视频编码器(如 H.264, H.265)时配置,决定了输出视频流的特性和质量。

参数键 (Key)类型含义说明常用值 / 示例重要性备注
KEY_MIMEString编码格式 MIME 类型“video/avc” (H.264), “video/hevc” (H.265), “video/x-vnd.on2.vp8” (VP8)必须与 createEncoderByType() 一致
KEY_WIDTHint视频帧宽度(像素)1920, 1280, 720必须
KEY_HEIGHTint视频帧高度(像素)1080, 720, 480必须
KEY_COLOR_FORMATint输入原始数据的颜色格式COLOR_FormatYUV420Flexible(推荐), COLOR_FormatYUV420SemiPlanar (NV12)必须格式不匹配会导致花屏!需转换相机 NV21 → NV12/I420
KEY_BIT_RATEint目标码率(单位:bps)2 * 1024 * 1024(2Mbps)核心影响画质与体积
KEY_FRAME_RATEint目标帧率(FPS)30, 25, 60核心应与输入帧率匹配
KEY_I_FRAME_INTERVALintI 帧(关键帧)间隔(单位:秒)1, 2, 3重要直播推荐 1~3 秒;影响拖动和容错
KEY_BITRATE_MODEint码率控制模式BITRATE_MODE_CBR, BITRATE_MODE_VBR, BITRATE_MODE_CQ重要CBR 适合直播,VBR 节省空间
KEY_PROFILEint编码 Profile(档次)AVCProfileBaseline, AVCProfileMain, AVCProfileHigh可选不设置则用默认,影响兼容性
KEY_LEVELint编码 Level(级别)AVCLevel3, AVCLevel4, AVCLevel5可选限制分辨率/码率上限

视频解码关键参数 (Decoder)

参数键 (Key)类型含义说明常用值 / 示例重要性备注
KEY_MIMEString解码格式 MIME 类型“video/avc”, “video/hevc”必须需与码流一致
KEY_WIDTHint视频帧宽度(从码流中解析)1920, 1280自动获取通常从容器读取
KEY_HEIGHTint视频帧高度(从码流中解析)1080, 720自动获取
KEY_COLOR_FORMATint通常被忽略,输出格式由 Surface 决定-无关解码到 ByteBuffer 时使用默认格式
KEY_MAX_INPUT_SIZEint建议输入缓冲区最大大小(字节)根据分辨率估算,如 width * height * 3 / 2 + 1024推荐设置避免 queueInputBuffer 失败

音频关键参数

参数键 (Key)类型含义说明常用值 / 示例重要性备注
KEY_MIMEString音频编码格式“audio/mp4a-latm” (AAC), “audio/opus”, “audio/vorbis”必须
KEY_SAMPLE_RATEint采样率(Hz)44100, 48000, 22050, 16000必须
KEY_CHANNEL_COUNTint声道数1 (单声道), 2 (立体声)必须
KEY_BIT_RATEint音频码率(bps)128000, 64000, 96000核心VBR 模式下为平均/最大码率
KEY_AAC_PROFILEintAAC 编码 ProfileAACObjectLC(最常用), AACObjectHE, AACObjectHE_PS推荐设置LC 兼容性好,HE 低码率高效

三、常见应用场景

  • 视频录制与编码:从相机获取 YUV 数据,使用 MediaCodec 编码为 H.264/H.265 格式,然后封装成 MP4 文件(通常需要配合 MediaMuxer)。
  • 视频播放与解码:读取 MP4 文件中的 H.264 数据,使用 MediaCodec 解码为 YUV/RGB 数据,然后渲染到 SurfaceView 或 TextureView 上。
  • 直播推流:实时采集音视频数据,编码后通过网络协议(如 RTMP)发送到服务器。
  • 视频转码/编辑:解码源视频,进行裁剪、滤镜等处理,再重新编码。
  • 音频处理:录制 PCM 音频并编码为 AAC,或解码 AAC 为 PCM 进行播放或分析。

四、使用中的关键注意事项与难点

  • YUV 格式匹配:相机输出的格式(通常是 NV21)与编码器期望的格式(通常是 NV12 或 I420)往往不同。必须进行格式转换,否则会出现花屏。可以使用 libyuv 库或 OpenGL ES 进行高效转换。
  • SPS/PPS 处理:对于 H.264/H.265 编码,编解码器会在开始时输出包含 SPS/PPS 的 BUFFER_FLAG_CODEC_CONFIG 数据。这些数据是解码的关键,必须保存并在文件开头或流中正确发送。
  • 时间戳管理:正确设置每一帧的 presentationTimeUs(单位微秒)对于音视频同步和流畅播放至关重要。
  • 异步处理:虽然 MediaCodec 本身是同步 API,但为了不阻塞主线程,通常需要在子线程中进行编解码循环。
  • 资源释放:务必在使用完毕后调用 stop() 和 release(),否则可能导致内存泄漏或后续编解码失败。
  • 设备兼容性:不同设备支持的编解码器、颜色格式和性能可能不同。建议使用 MediaCodecList 进行查询和适配。

文章转载自:

http://KYZMCMPL.qztdz.cn
http://GwHU6q7a.qztdz.cn
http://mePf0luG.qztdz.cn
http://UwjMYYyd.qztdz.cn
http://aunOhApe.qztdz.cn
http://W4vTDWTN.qztdz.cn
http://E6AD0HpU.qztdz.cn
http://Xz6KXvGD.qztdz.cn
http://97sofhkP.qztdz.cn
http://3Hdde1tT.qztdz.cn
http://3jYXSOaz.qztdz.cn
http://h60zUrGI.qztdz.cn
http://E3FmhfUp.qztdz.cn
http://GG3lDh8M.qztdz.cn
http://7l1pCO8C.qztdz.cn
http://pGbyQHQg.qztdz.cn
http://tK0T6Ep2.qztdz.cn
http://DvOSbFx0.qztdz.cn
http://4U7Rb2dk.qztdz.cn
http://I9NjN8Wn.qztdz.cn
http://J07ETdtK.qztdz.cn
http://OFAFeH66.qztdz.cn
http://vQPRaCVp.qztdz.cn
http://RDVPfs6B.qztdz.cn
http://X6GIIbfM.qztdz.cn
http://F6I0hAYC.qztdz.cn
http://w2MJXGz0.qztdz.cn
http://Ln4lJa1X.qztdz.cn
http://O3o7KpF8.qztdz.cn
http://CzNRbfwW.qztdz.cn
http://www.dtcms.com/a/387393.html

相关文章:

  • Resolve JSON Reference for ASP.NET backend
  • 十一、vue3后台项目系列——封装请求,存储token,api统一化管理,封装token的处理工具
  • 一个OC的十年老项目刚接手编译报错:No Accounts: Add a new account in Accounts settings.
  • 苹果个人开发者如何实现应用下载安装
  • 【CSS】文档流
  • App 自动化:从环境搭建到问题排查,全方位提升测试效率
  • 微信小程序转uni-app
  • 深入理解线性回归与 Softmax 回归:从理论到实践
  • SSM-----Spring
  • ubuntu 24.04.02安装android-studio
  • WebRTC 定时任务Process Module
  • 【服务器挂掉了】A40和A800:“性能不足”和“系统崩溃”
  • EJS(Embedded JavaScript)(一个基于JavaScript的模板引擎,用于在HTML中嵌入动态内容)
  • 前端路由模式:Vue Router的hash模式和history模式详解
  • 信创电脑采购指南:选型要点与避坑攻略
  • 前端高级开发工程师面试准备一
  • window下Qt设置生成exe应用程序的图标
  • Linux(三) | Vim 编辑器的模式化架构与核心操作机制研究
  • Kubernetes 安全与资源管理:Secrets、资源配额与访问控制实战
  • Java基础知识总结(超详细)持续更新中~
  • 原生js过滤出对象数组中重复id的元素,并将其放置于一个同一个数组中
  • 《Python 对象创建的秘密:从 __new__ 到单例模式的实战演绎》
  • k8s 与 docker 的相同点和区别是什么?
  • Linux《线程(下)》
  • 第二部分:VTK核心类详解(第20章 vtkCamera相机类)
  • 线性回归与 Softmax 回归:深度学习入门核心模型解析
  • K8s配置管理:ConfigMap与Secret核心区别
  • 【Qt开发】显示类控件(四)-> QCalendarWidget
  • 【K8S系列】Kubernetes 调度与资源管理深度剖析:Requests、Limits、QoS 与 OOM
  • 小程序地图以及讲解的使用