Android 多媒体解码流程分析(十四):从码流输入到帧输出的完整链路拆解
·
以下是Android多媒体解码从码流输入到帧输出的完整链路拆解(以视频解码为例):
1. 码流输入与解封装
- 输入源:媒体文件(如MP4)或网络流
- 关键组件:
MediaExtractorMediaExtractor extractor = new MediaExtractor(); extractor.setDataSource(filePath); // 绑定数据源 int trackIndex = selectVideoTrack(extractor); // 选择视频轨道 extractor.selectTrack(trackIndex); - 过程:
- 分离音视频轨道
- 提取编码后的压缩帧(H.264/H.265等)
- 输出裸流数据(如NAL单元)
2. 解码器初始化
- 核心组件:
MediaCodecMediaCodec codec = MediaCodec.createDecoderByType("video/avc"); // 创建H.264解码器 MediaFormat format = extractor.getTrackFormat(trackIndex); codec.configure(format, surface, null, 0); // 绑定输出Surface codec.start(); // 启动解码器 - 关键参数:
format:包含分辨率、帧率、CSD(Codec Specific Data)等surface:用于直接渲染的Surface(零拷贝)
3. 输入缓冲区提交
// 异步模式(推荐)
codec.setCallback(new MediaCodec.Callback() {
@Override
public void onInputBufferAvailable(MediaCodec mc, int inputBufferId) {
ByteBuffer buffer = codec.getInputBuffer(inputBufferId);
int sampleSize = extractor.readSampleData(buffer, 0); // 读取压缩数据
if (sampleSize > 0) {
long pts = extractor.getSampleTime();
codec.queueInputBuffer(inputBufferId, 0, sampleSize, pts, 0);
extractor.advance(); // 移动到下一帧
}
}
});
- 关键点:
- 缓冲区从
MediaCodec池中获取 - 时间戳(PTS)必须正确传递
- 结束信号需传递
BUFFER_FLAG_END_OF_STREAM
- 缓冲区从
4. 解码与输出缓冲区处理
@Override
public void onOutputBufferAvailable(MediaCodec mc, int outputBufferId, BufferInfo info) {
if (info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG != 0) {
// 忽略编解码器配置信息
codec.releaseOutputBuffer(outputBufferId, false);
return;
}
// 渲染到Surface(硬件加速)
codec.releaseOutputBuffer(outputBufferId, true);
// 或获取YUV数据(CPU处理)
ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId);
processYUVData(outputBuffer, info); // 自定义处理
}
- 输出类型:
- Surface输出:直接渲染(效率高)
- YUV Buffer:需手动处理/转换
- BufferInfo关键字段:
presentationTimeUs:显示时间戳flags:帧类型(关键帧/结束帧)
5. 渲染显示
- SurfaceView/TextureView:
// 在SurfaceHolder.Callback中接收帧 surfaceHolder.addCallback(new SurfaceHolder.Callback() { @Override public void surfaceCreated(SurfaceHolder holder) { codec.configure(format, holder.getSurface(), null, 0); } }); - OpenGL ES渲染:
- 通过
SurfaceTexture获取帧数据 - 使用
GL_TEXTURE_EXTERNAL_OES纹理
- 通过
完整流程图
graph LR
A[媒体文件/网络流] --> B(MediaExtractor)
B --> C{分离轨道}
C --> D[压缩视频帧]
D --> E(MediaCodec)
E --> F[输入缓冲区]
F --> G[解码核心]
G --> H[输出缓冲区]
H --> I{输出方式}
I --> J[Surface直接渲染]
I --> K[YUV数据回调]
J --> L[SurfaceView/TextureView]
K --> M[CPU后处理]
关键优化点
- 异步模式:避免阻塞UI线程
- 零拷贝渲染:通过
Surface直接传递GPU纹理 - 缓冲区复用:减少内存分配开销
- 硬解码优先:使用
MediaCodecList检测设备支持MediaCodecList list = new MediaCodecList(MediaCodecList.REGULAR_CODECS); String codecName = list.findDecoderForFormat(format);
注意:音频解码流程类似,但输出为PCM数据,需通过
AudioTrack播放。完整链路需处理同步、丢帧、DTS/PTS校正等复杂逻辑。
更多推荐


所有评论(0)