MediaCodec
是 Android 提供的底层多媒体编解码 API,允许开发者使用系统硬件编解码器对音视频数据进行压缩/解压。
使用流程(图像/音频通用)
MediaCodec
提供了同步和异步两种使用模式,推荐使用异步模式(API 21 及以上)。无论是编码器(Encoder)还是解码器(Decoder),MediaCodec 的典型使用流程是:
Configure → Start → (queueInputBuffer / dequeueOutputBuffer) → Stop → Release
创建 MediaCodec 实例:
通过 MediaCodec.createByCodecName
或 MediaCodec.createEncoderByType
/ MediaCodec.createDecoderByType
创建编码器或解码器,指定 MIME 类型(如 "video/avc"
、"audio/mp4a-latm"
)。配置 MediaFormat
,设置参数如分辨率、码率、采样率等。
配置 MediaCodec:
调用 configure()
方法,传入 MediaFormat
、Surface(视频解码时可选)等参数,指定编码或解码模式。
启动 MediaCodec:
调用 start()
进入执行状态。
处理输入输出缓冲区:
输入缓冲区:通过 dequeueInputBuffer()
获取可用的输入缓冲区索引,将数据(如原始 PCM 或 YUV 数据)写入缓冲区,然后调用 queueInputBuffer()
提交。
输出缓冲区:通过 dequeueOutputBuffer()
获取处理后的数据,处理后调用 releaseOutputBuffer()
释放。
停止和释放:
调用 stop()
和 release()
停止并释放资源。
异步模式(API 21+):使用 setCallback()
设置回调,处理输入输出缓冲区事件,避免手动轮询。在 onInputBufferAvailable
中填充数据,在 onOutputBufferAvailable
中处理结果。
同步示例:
以解码 H.264 视频为例(原始数据为裸流 .h264
)
val codec = MediaCodec.createDecoderByType("video/avc")val format = MediaFormat.createVideoFormat("video/avc", width, height)format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 0)codec.configure(format, surface, null, 0)codec.start()val inputBuffers = codec.inputBuffersval outputBuffers = codec.outputBufferswhile (running) { val inIndex = codec.dequeueInputBuffer(10000) if (inIndex >= 0) { val inputBuffer = inputBuffers[inIndex] inputBuffer.clear() val sampleSize = readH264Frame(inputBuffer) if (sampleSize > 0) { codec.queueInputBuffer(inIndex, 0, sampleSize, presentationTimeUs, 0) } else { codec.queueInputBuffer(inIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM) break } } val bufferInfo = MediaCodec.BufferInfo() val outIndex = codec.dequeueOutputBuffer(bufferInfo, 10000) if (outIndex >= 0) { // 解码帧已就绪,在 Surface 上显示 codec.releaseOutputBuffer(outIndex, true) } else if (outIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { val newFormat = codec.outputFormat Log.d("MediaCodec", "Output format changed: $newFormat") }}codec.stop()codec.release()
异步示例:
使用 MediaCodec
异步模式进行视频解码的示例( H.264 视频解码到 Surface ):
import android.media.MediaCodec;import android.media.MediaFormat;import android.view.Surface;import java.nio.ByteBuffer;public class VideoDecoder { private MediaCodec mediaCodec; private static final String MIME_TYPE = "video/avc"; // H.264 private static final int TIMEOUT_US = 10000; // 10ms 超时 public void startDecoder(Surface surface, byte[] sps, byte[] pps) { try { // 创建解码器 mediaCodec = MediaCodec.createDecoderByType(MIME_TYPE); // 配置 MediaFormat MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, 1920, 1080); // 分辨率 format.setByteBuffer("csd-0", ByteBuffer.wrap(sps)); // SPS format.setByteBuffer("csd-1", ByteBuffer.wrap(pps)); // PPS // 设置异步回调 mediaCodec.setCallback(new MediaCodec.Callback() { @Override public void onInputBufferAvailable(MediaCodec codec, int index) { // 获取输入缓冲区 ByteBuffer inputBuffer = codec.getInputBuffer(index); // 从数据源获取数据(示例中假设有数据) byte[] data = getVideoData(); // 需实现 if (data != null) { inputBuffer.put(data); codec.queueInputBuffer(index, 0, data.length, getPresentationTimeUs(), 0); } else { // 结束标志 codec.queueInputBuffer(index, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM); } } @Override public void onOutputBufferAvailable(MediaCodec codec, int index, MediaCodec.BufferInfo info) { // 释放输出缓冲区到 Surface if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { codec.stop(); codec.release(); return; } codec.releaseOutputBuffer(index, true); // 渲染到 Surface } @Override public void onError(MediaCodec codec, MediaCodec.CodecException e) { e.printStackTrace(); } @Override public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) { // 输出格式变化 // 可处理新的 MediaFormat } }); // 配置并启动 mediaCodec.configure(format, surface, null, 0); mediaCodec.start(); } catch (Exception e) { e.printStackTrace(); } } // 模拟获取视频数据 private byte[] getVideoData() { // 替换为实际数据源(如文件、网络流) return null; } // 模拟获取时间戳 private long getPresentationTimeUs() { return System.nanoTime() / 1000; } // 停止解码 public void stop() { if (mediaCodec != null) { mediaCodec.stop(); mediaCodec.release(); mediaCodec = null; } }}
使用方法:
- 创建
Surface
(如通过 SurfaceView
获取)。提供 H.264 流的 SPS(Sequence Parameter Set,序列参数集) 和 PPS(Picture Parameter Set,图像参数集)数据。调用 startDecoder()
传入 Surface 和 SPS/PPS。数据源需实现 getVideoData()
,提供 H.264 帧数据。调用 stop()
释放资源。编码示例:
与解码类似,只是配置为 CONFIGURE_FLAG_ENCODE
:
format.setInteger(MediaFormat.KEY_BIT_RATE, 125000)format.setInteger(MediaFormat.KEY_FRAME_RATE, 15)format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible)format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 5)codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)
然后从 inputBuffer
填入 YUV,读取 outputBuffer
得到压缩后的 H264。
常见使用注意点
线程安全:
MediaCodec
本身非线程安全,所有操作应在同一线程或通过同步机制处理。异步模式中,回调运行在内部线程,需确保数据处理线程安全。
缓冲区管理:
输入缓冲区需确保数据格式正确(如 YUV 帧格式、PCM 采样率)。输出缓冲区可能返回 INFO_
状态(如 INFO_OUTPUT_FORMAT_CHANGED
),需处理这些状态。不要长时间持有缓冲区,避免阻塞。
硬件限制:
不同设备支持的编解码器和参数(如分辨率、帧率)不同,需通过 MediaCodecInfo
查询支持的功能。硬件解码器可能有缓冲区数量限制,需合理管理。
配置 MediaFormat:
编码器需设置合理的码率、帧率、I 帧间隔等,避免过高参数导致性能问题。解码器需匹配输入数据的格式(如 H.264 的 SPS/PPS)。
错误处理:
处理 MediaCodec.CodecException
,可能由硬件或配置错误引发。检查 isRecoverable()
或 isTransient()
,决定是否重试或重启。
异步 vs 同步:
异步模式更适合现代应用,避免阻塞主线程。同步模式适合简单场景,但需手动轮询,可能影响性能。
Surface 使用:
视频解码时,输出到 Surface
可提高效率,但需确保 Surface
有效。编码器不支持 Surface
输出,需手动处理输出数据。
性能优化:
使用硬件加速(优先选择硬件编解码器)。避免频繁创建/销毁 MediaCodec
实例,复用实例以降低开销。
输入数据必须是“帧”对齐的
如果你是从网络流(如 RTP/RTMP)中接收数据,确保每次 queueInputBuffer()
的数据是一帧(NALU),否则容易出现花屏或丢帧。
MediaCodec
必须按严格顺序调用
configure()
→ start()
→ queue/dequeue
→ stop()
→ release()
调用顺序错了会抛出 IllegalStateException
Surface 与 ByteBuffer 二选一
解码可以输出到 Surface(适合显示)或 ByteBuffer(适合处理图像)。若使用 Surface 解码,不可访问 outputBuffer
。
解码时可能遇到 format change
dequeueOutputBuffer()
返回 INFO_OUTPUT_FORMAT_CHANGED
时必须处理新的输出格式:
val newFormat = codec.outputFormat// 处理新的分辨率等信息
- 记得处理 EOS(End Of Stream)
queueInputBuffer(..., MediaCodec.BUFFER_FLAG_END_OF_STREAM)
当 EOS 输入后,dequeueOutputBuffer
最终返回的 bufferInfo.flags
也会包含 EOS,代表流结束。
- 使用
setCallback()
进行异步处理(可选)从 Android 5.0 开始,支持异步 API:
codec.setCallback(object : MediaCodec.Callback() { override fun onInputBufferAvailable(codec: MediaCodec, index: Int) { /*...*/ } override fun onOutputBufferAvailable(codec: MediaCodec, index: Int, info: BufferInfo) { /*...*/ } override fun onError(codec: MediaCodec, e: MediaCodec.CodecException) { /*...*/ } override fun onOutputFormatChanged(codec: MediaCodec, format: MediaFormat) { /*...*/ }})
源码实现
MediaCodec
的底层实现基于 Android 的多媒体框架,主要依赖 AOSP 的 stagefright
和硬件抽象层(HAL)。
架构概览:
MediaCodec
是 Java 层 API,通过 JNI 调用 C++ 层的 MediaCodec
(frameworks/av/media/libstagefright/MediaCodec.cpp
)。底层通过 OMX
(OpenMAX IL)与硬件编解码器交互,或使用软件编解码器(如 FFmpeg 或 Google 的软件编解码器)。
初始化与配置:
MediaCodec::CreateByType
或 CreateByName
调用 OMXClient
初始化硬件解码器,查询设备支持的编解码器列表(MediaCodecList
)。configure()
方法将 MediaFormat
转换为 OMX 参数,设置编码/解码参数(如分辨率、码率)。
缓冲区处理:
输入输出缓冲区通过 AMessage
和 ABuffer
在 C++ 层管理,映射到 Java 层的 ByteBuffer
。异步模式使用 Looper
和 Handler
机制分发回调事件。
硬件加速:
硬件解码通过 OMX
层与芯片厂商提供的驱动交互(如 Qualcomm、MediaTek 的硬件编解码器)。源码中,OMXCodec
(frameworks/av/media/libstagefright/OMXCodec.cpp
)负责与硬件通信,处理缓冲区和帧数据。
错误处理:
底层通过 status_t
返回错误码,映射到 Java 层的 MediaCodec.CodecException
。常见错误包括硬件资源不足、格式不支持等。
关键类与文件:
MediaCodec.java
:Java 层 API 入口。MediaCodec.cpp
:C++ 层实现,桥接 Java 和 OMX。OMXCodec.cpp
:硬件编解码器交互逻辑。MediaCodecList.cpp
:管理支持的编解码器信息。调用链(解码器):
Android MediaCodec
底层通过 JNI 调用了 C++ 层的 MediaCodec
,最终与硬件编解码器通信:
- Java 层类:
android.media.MediaCodec
Native 层类:android_media_MediaCodec.cpp
、MediaCodec.cpp
底层服务:MediaCodecService
→ OMX
→ HAL → 硬件MediaCodec.configure() → native_configure() → MediaCodec.cpp::configure() ↓CodecLooper 创建 Codec(如 OMXCodec、C2Codec) ↓SurfaceTexture 或 BufferQueue 创建输出目标
源码路径(Android AOSP):
frameworks/av/media/libstagefright/ MediaCodec.cpp MediaCodecBuffer.cpp ...frameworks/av/services/mediacodec/ MediaCodecService.cpp
补充说明
调试技巧:
使用 adb logcat
查看 MediaCodec
日志,定位错误。检查 MediaCodecInfo
确保设备支持目标 MIME 类型和参数。验证输入数据的完整性(如 H.264 的 NAL 单元)。
性能优化:
使用 Surface
输出视频解码结果,减少数据拷贝。批量处理缓冲区,降低调用频率。优先选择硬件编解码器(名称通常包含厂商标识,如 OMX.qcom
)。
常见问题:
黑屏:可能是 Surface
无效或输入数据格式错误。卡顿:检查帧率、码率是否过高,或缓冲区处理不及时。崩溃:检查是否正确释放资源,或硬件不支持指定参数。