You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android MediaCodec编解码器缓冲区大小不匹配问题咨询

问题

在Android平台使用C++的MediaCodec库实现转码流水线,原本H265解码器运行在surface模式,移除配置时的surface参数改为buffer模式后,解码输出缓冲区已有数据,计划将其复制到H264编码器的输入缓冲区中。

目前遇到的问题是:解码器输出缓冲区大小(3133440字节)大于编码器输入缓冲区大小(3110400字节),且二者颜色格式均为21(YUV420)。对于1080p的YUV420视频,理论总缓冲区大小应为3110400字节(Y平面2073600字节、U/V平面各518400字节)。

编码器配置代码片段:

AMediaFormat* avc_format = AMediaFormat_new();
AMediaFormat_setString(avc_format, AMEDIAFORMAT_KEY_MIME, "video/avc");
AMediaFormat_setInt32(avc_format, AMEDIAFORMAT_KEY_WIDTH, 1920);
AMediaFormat_setInt32(avc_format, AMEDIAFORMAT_KEY_HEIGHT, 1080);
AMediaFormat_setInt32(avc_format, AMEDIAFORMAT_KEY_BIT_RATE, 17000000);
AMediaFormat_setInt32(avc_format, AMEDIAFORMAT_KEY_I_FRAME_INTERVAL, 1);
AMediaFormat_setFloat(avc_format, AMEDIAFORMAT_KEY_FRAME_RATE, 30.0);
AMediaFormat_setInt32(avc_format, AMEDIAFORMAT_KEY_COLOR_FORMAT, 21);
avc_encoder_ = AMediaCodec_createCodecByName("OMX.qcom.video.encoder.avc");
AMediaCodec_configure(
        avc_encoder_, avc_format, NULL, NULL, AMEDIACODEC_CONFIGURE_FLAG_ENCODE);

获取缓冲区的代码片段:

ssize_t codec_out_buf_idx = AMediaCodec_dequeueOutputBuffer(hevc_decoder_, &info, timeout);
ssize_t bufidx = AMediaCodec_dequeueInputBuffer(avc_encoder_, timeout);
if (bufidx >= 0) {
    uint8_t* buf = AMediaCodec_getInputBuffer(avc_encoder_, bufidx, &bufsize);
    size_t codec_out_bufsize = 0;
    uint8_t* codec_out_buf = AMediaCodec_getOutputBuffer(hevc_decoder_, codec_out_buf_idx, &codec_out_bufsize);
    if (codec_out_bufsize > bufsize) {
       return;
    }
 }

解码器输出格式变更回调显示参数为:宽1920、高1080、帧率30、颜色格式21、MIME类型video/raw。尝试过不同颜色格式但均确保与编码器匹配,请问缓冲区大小差异的原因是什么?是否有更优实现方式?


原因分析与解决方案

缓冲区大小差异的原因

  • 内存字节对齐优化:MediaCodec底层硬件编解码器(如你使用的高通OMX编码器)会对缓冲区做内存对齐处理,通常按16、32或64字节对齐。解码器输出缓冲区可能因硬件解码的内部要求,采用了更高的对齐标准,导致实际分配的缓冲区比理论值大——比如Y平面按32字节对齐后,总字节数向上取整,加上UV平面的对齐冗余,最终总大小超过理论值。
  • 颜色格式的隐性实现差异:虽然都标记为颜色格式21(OMX_COLOR_FormatYUV420Planar),不同硬件厂商的实现可能存在细微差别。部分解码器会在YUV平面末尾预留少量冗余字节用于硬件临时处理,这部分冗余不影响有效像素数据,但会增加缓冲区总大小。
  • 解码器输出缓冲区策略:buffer模式下,解码器可能会分配比实际需求更大的缓冲区,以兼容潜在的分辨率波动(即使你的视频是固定1080p,也可能存在这种预留逻辑)。

更优实现方式

  1. 只复制有效像素数据
    解码器输出的有效数据就是标准1080p YUV420的3110400字节,可直接忽略缓冲区中超出的部分,仅复制有效数据到编码器输入缓冲区。提交输入时需指定有效数据大小,而非整个缓冲区大小:

    if (bufidx >= 0 && codec_out_buf_idx >= 0) {
        uint8_t* enc_buf = AMediaCodec_getInputBuffer(avc_encoder_, bufidx, &bufsize);
        size_t dec_bufsize = 0;
        uint8_t* dec_buf = AMediaCodec_getOutputBuffer(hevc_decoder_, codec_out_buf_idx, &dec_bufsize);
        // 1080p YUV420的有效数据总大小
        const size_t valid_size = 1920 * 1080 * 3 / 2;
        if (bufsize >= valid_size) {
            memcpy(enc_buf, dec_buf, valid_size);
            // 提交时指定有效数据大小
            AMediaCodec_queueInputBuffer(avc_encoder_, bufidx, 0, valid_size, info.presentationTimeUs, 0);
        }
    }
    
  2. Surface直通转码(推荐)
    回到Surface模式的思路,直接将解码器的输出Surface作为编码器的输入Surface,完全跳过CPU内存复制步骤——这是Android MediaCodec转码的最优路径,硬件可直接在Surface间传递纹理数据,性能提升显著。
    实现逻辑:

    • 创建SurfaceTexture,基于它生成同一个Surface,同时作为解码器的输出Surface和编码器的输入Surface
    • 解码器配置时传入该Surface,编码器配置时也传入此Surface(需保留AMEDIACODEC_CONFIGURE_FLAG_ENCODE标志)
    • 通过SurfaceTexture::updateTexImage()同步帧数据,完成转码流程
  3. 强制同步缓冲区对齐参数
    从解码器输出格式中获取stride(行字节数)和slice_height参数,设置到编码器的格式中,强制编码器使用与解码器一致的缓冲区规格:

    // 从解码器输出格式中读取对齐参数
    int stride, slice_height;
    AMediaFormat_getInt32(decoder_output_format, AMEDIAFORMAT_KEY_STRIDE, &stride);
    AMediaFormat_getInt32(decoder_output_format, AMEDIAFORMAT_KEY_SLICE_HEIGHT, &slice_height);
    // 同步到编码器格式
    AMediaFormat_setInt32(avc_format, AMEDIAFORMAT_KEY_STRIDE, stride);
    AMediaFormat_setInt32(avc_format, AMEDIAFORMAT_KEY_SLICE_HEIGHT, slice_height);
    

    注意:不是所有硬件编码器都支持自定义这两个参数,需要测试兼容性。

内容的提问来源于stack exchange,提问作者Sumanth Murali

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 15:07:09