第1章 Surface与MediaCodec角色定位
在Android多媒体管道中,Surface与MediaCodec分别承担图形缓冲区管理与编解码运算的核心职能。理解两者的角色边界、协作方式及数据通路,是掌握高效音视频处理的基础。本章从生产者/消费者模型出发,解析Surface与MediaCodec各自的设计定位,并揭示两者之间完整的buffer流转路径。
1.1 Surface:图形生产者与消费者
Surface是一个图形缓冲区队列(BufferQueue)的消费者端接口,同时也是图形生产端的抽象目标。其核心能力包括:
- 生产者角色:应用通过Canvas、OpenGL ES、Vulkan等API向Surface提交图形帧,Surface将这些帧排入BufferQueue。
- 消费者角色:SurfaceFlinger或MediaCodec作为消费者从BufferQueue取出帧数据进行合成或编码。
- 缓冲区管理:维护一个可复用的图形缓冲区池,通过dequeueBuffer / queueBuffer机制避免频繁内存分配。
1.2 MediaCodec:编解码引擎
MediaCodec是Android提供的音视频编解码器接口,采用明确的状态机驱动:
- Stopped(停止):包含Uninitialized、Configured、Error子状态。
- Executing(执行):包含Flushed、Running、End-of-Stream子状态。
- 释放:通过release()释放底层资源。
MediaCodec支持两种数据输入/输出模式:
- ByteBuffer模式:通过getInputBuffer / getOutputBuffer操作压缩数据,适合文件处理。
- Surface模式:直接以Surface作为输入(编码时从摄像头/屏幕捕获)或输出(解码时直接渲染),绕过多余内存拷贝,提升性能。
1.3 数据通路原理
Surface与MediaCodec的数据通路本质是一个异步缓冲区管道。以解码显示场景为例:
- MediaCodec从输入ByteBuffer中读取压缩码流(如H.264),解码为原始帧。
- 解码后的帧通过Surface(MediaCodec创建的Surface)输出到BufferQueue。
- SurfaceFlinger从BufferQueue消费帧并合成显示。
在编码输入场景中,应用将帧绘到Surface上,Surface将帧入队到BufferQueue,MediaCodec作为消费者取出帧并编码为压缩流。
性能关键点:Surface模式下的数据通路避免了应用进程与媒体进程间的显存拷贝,是实时预览、录屏、视频通话等低延迟场景的首选。
1.4 角色对比与状态转换
| 组件 | 数据流向 | 缓冲区类型 | 核心接口 |
|---|---|---|---|
| Surface | 生产者 → Surface → 消费者 | GraphicBuffer(硬件内存) | dequeueBuffer/queueBuffer, lock/unlock |
| MediaCodec | ByteBuffer 或 Surface | ByteBuffer 或 GraphicBuffer | queueInputBuffer, dequeueOutputBuffer, start/stop |
| BufferQueue | 生产 → 排队 → 消费 | GraphicBuffer | connect, disconnect, allocateBuffers |
注意:当MediaCodec以Surface作为输出时,输出帧直接进入显示管线,应用不可直接读取像素数据;若需要读取帧(如缩略图、录屏),应使用ImageReader或配置Surface模式为“Surface with virtual display”。
1.5 数据通路流程图
1.6 MediaCodec配置示例(Surface输出模式)
以下代码展示如何配置一个解码器,使用Surface作为输出:
MediaCodec decoder = MediaCodec.createDecoderByType("video/avc");
MediaFormat format = MediaFormat.createVideoFormat("video/avc", width, height);
decoder.configure(format, surface, null, 0); // surface来自SurfaceView/TextureView
decoder.start();
// 循环输入码流、输出帧
int inputIndex = decoder.dequeueInputBuffer(10000);
if (inputIndex >= 0) {
ByteBuffer inputBuffer = decoder.getInputBuffer(inputIndex);
inputBuffer.put(data);
decoder.queueInputBuffer(inputIndex, 0, data.length, pts, 0);
}
MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
int outputIndex = decoder.dequeueOutputBuffer(info, 10000);
if (outputIndex >= 0) {
// 当输出模式为Surface时,releaseOutputBuffer直接渲染
decoder.releaseOutputBuffer(outputIndex, true); // true = 渲染
}
常见错误:在Surface输出模式下调用
getOutputBuffer(outputIndex)将返回null,解码后的像素数据不可直接读取。务必使用releaseOutputBuffer(index, true)渲染显示,或通过ImageReader间接获取。