以下是Android多媒体解码从码流输入到帧输出的完整链路拆解(以视频解码为例):


1. 码流输入与解封装

  • 输入源:媒体文件(如MP4)或网络流
  • 关键组件MediaExtractor
    MediaExtractor extractor = new MediaExtractor();
    extractor.setDataSource(filePath); // 绑定数据源
    int trackIndex = selectVideoTrack(extractor); // 选择视频轨道
    extractor.selectTrack(trackIndex);
    

  • 过程
    1. 分离音视频轨道
    2. 提取编码后的压缩帧(H.264/H.265等)
    3. 输出裸流数据(如NAL单元)

2. 解码器初始化

  • 核心组件MediaCodec
    MediaCodec 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后处理]


关键优化点

  1. 异步模式:避免阻塞UI线程
  2. 零拷贝渲染:通过Surface直接传递GPU纹理
  3. 缓冲区复用:减少内存分配开销
  4. 硬解码优先:使用MediaCodecList检测设备支持
    MediaCodecList list = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
    String codecName = list.findDecoderForFormat(format); 
    

注意:音频解码流程类似,但输出为PCM数据,需通过AudioTrack播放。完整链路需处理同步、丢帧、DTS/PTS校正等复杂逻辑。

Logo

中国智能体开发者社区,聚焦智能体与大模型开发,提供前沿资讯、实用工具链、开源项目及行业案例。通过技术沙龙、开发者大赛等活动,促进经验交流与协作,助力开发者快速构建创新智能应用。

更多推荐