Skip to content

fix(encoder): 动态调整输出缓冲区大小,解决编码包超限时静默丢帧问题#478

Merged
deepin-bot[bot] merged 1 commit into
linuxdeepin:release/eaglefrom
Resurgamz:release/eagle
May 29, 2026
Merged

fix(encoder): 动态调整输出缓冲区大小,解决编码包超限时静默丢帧问题#478
deepin-bot[bot] merged 1 commit into
linuxdeepin:release/eaglefrom
Resurgamz:release/eagle

Conversation

@Resurgamz
Copy link
Copy Markdown

@Resurgamz Resurgamz commented May 29, 2026

当编码包大小超过输出缓冲区时,替换原有的静默丢弃行为为动态扩容(1.5倍 增长),最大分配上限为 max(width*height/2, 2MB),防止内存无限增长。

Log: 修复编码器输出缓冲区溢出时静默丢帧的问题
Bug: https://pms.uniontech.com/bug-view-345769.html

Summary by Sourcery

Bug Fixes:

  • Replace silent frame drops on output buffer overflow with dynamic buffer resizing and explicit dropping only when packets exceed a capped maximum size or memory allocation fails.

@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented May 29, 2026

Reviewer's Guide

Refactors video encoder output buffer handling to dynamically resize the buffer when encoded packet size exceeds the current capacity, with a capped growth strategy and explicit frame dropping and logging when limits or allocations fail, and reuses this logic for both software and VAAPI paths.

Sequence diagram for dynamic encoder output buffer resizing

sequenceDiagram
    participant Encoder as encoder_encode_video
    participant EncCtx as encoder_context_t
    participant VidCtx as encoder_video_context_t
    participant Libs as LoadLibsInstance

    Encoder->>Encoder: libav_get_encode
    Encoder->>VidCtx: set pts, flags, duration from pkt
    Encoder->>VidCtx: resize_outbuf_if_needed
    alt pkt size <= outbuf_size
        VidCtx->>VidCtx: memcpy(outbuf, pkt->data, pkt->size)
        VidCtx-->>Encoder: return 1
    else pkt size > outbuf_size
        VidCtx->>EncCtx: compute max_reasonable_size
        alt pkt size > max_reasonable_size
            VidCtx->>Libs: m_av_packet_unref(pkt)
            VidCtx->>VidCtx: set last_video_pts
            VidCtx-->>Encoder: return 0
            Encoder->>Encoder: continue (drop frame)
        else pkt size <= max_reasonable_size
            VidCtx->>VidCtx: realloc(outbuf, alloc_size)
            alt realloc success
                VidCtx->>VidCtx: update outbuf and outbuf_size
                VidCtx->>VidCtx: memcpy(outbuf, pkt->data, pkt->size)
                VidCtx-->>Encoder: return 1
            else realloc failed
                VidCtx->>Libs: m_av_packet_unref(pkt)
                VidCtx->>VidCtx: set last_video_pts
                VidCtx-->>Encoder: return 0
                Encoder->>Encoder: continue (drop frame)
            end
        end
    end
Loading

File-Level Changes

Change Details Files
Introduce shared helper to dynamically resize encoder output buffer with safety cap instead of silently dropping oversized packets.
  • Add resize_outbuf_if_needed() to encapsulate buffer size checking, max-size computation, dynamic realloc with 1.5x growth, and copying encoded data into the output buffer
  • Enforce an upper bound on buffer size using max(width*height/2, 2MB) and drop frames exceeding this limit with diagnostic logging
  • Handle realloc failures by logging, unref-ing the packet, updating last_video_pts, and signalling caller to skip the frame
libcam/libcam_encoder/encoder.c
Apply dynamic output buffer resizing logic to both standard and VAAPI video encode paths.
  • Replace inlined pkt->size vs outbuf_size checks and memcpy logic in encoder_encode_video with calls to resize_outbuf_if_needed(), continuing the encode loop when frames are dropped
  • Replace the same size-check and memcpy logic in encoder_encode_video_vaapi with resize_outbuf_if_needed(), ensuring consistent handling and logging across both code paths
libcam/libcam_encoder/encoder.c

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue, and left some high level feedback:

  • Consider using a wider unsigned type (e.g. size_t or int64_t) for max_reasonable_size and the pkt->size/alloc_size calculations to avoid potential overflow when video_width * video_height is large.
  • The hardcoded 2 * 1024 * 1024 limit would be clearer and easier to tune if it were extracted into a named constant that documents the rationale for the minimum buffer size.
  • You now unref the packet and update last_video_pts inside resize_outbuf_if_needed; please double-check that the surrounding encode loops don’t also unref or otherwise assume ownership of pkt on the dropped-frame path to avoid double-unref or inconsistent PTS handling.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Consider using a wider unsigned type (e.g. size_t or int64_t) for `max_reasonable_size` and the `pkt->size`/`alloc_size` calculations to avoid potential overflow when `video_width * video_height` is large.
- The hardcoded `2 * 1024 * 1024` limit would be clearer and easier to tune if it were extracted into a named constant that documents the rationale for the minimum buffer size.
- You now unref the packet and update `last_video_pts` inside `resize_outbuf_if_needed`; please double-check that the surrounding encode loops don’t also unref or otherwise assume ownership of `pkt` on the dropped-frame path to avoid double-unref or inconsistent PTS handling.

## Individual Comments

### Comment 1
<location path="libcam/libcam_encoder/encoder.c" line_range="1593-1602" />
<code_context>
+ * Returns: 1 if data was successfully copied to outbuf,
+ *          0 if frame was dropped (caller must continue to next iteration).
+ */
+static int resize_outbuf_if_needed(encoder_video_context_t *enc_video_ctx,
+                                    encoder_context_t *encoder_ctx,
+                                    AVPacket *pkt)
+{
+    if (pkt->size <= enc_video_ctx->outbuf_size) {
+        memcpy(enc_video_ctx->outbuf, pkt->data, pkt->size);
+        return 1;
+    }
+
+    /* Buffer limit: width * height / 2, with 2MB minimum protection */
+    int max_reasonable_size = encoder_ctx->video_width * encoder_ctx->video_height / 2;
+    if (max_reasonable_size < 2 * 1024 * 1024)
+        max_reasonable_size = 2 * 1024 * 1024;
</code_context>
<issue_to_address>
**🚨 issue (security):** Avoid potential integer overflow in max_reasonable_size computation and size handling.

`encoder_ctx->video_width * encoder_ctx->video_height / 2` and `alloc_size` are both `int`. For large resolutions this multiplication can overflow before the division, so `max_reasonable_size` (and later `alloc_size`) may be derived from a corrupted value. Please compute this in a wider type (e.g. `int64_t` or `size_t`), clamp to a sane maximum, then cast down if needed.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +1593 to +1602
static int resize_outbuf_if_needed(encoder_video_context_t *enc_video_ctx,
encoder_context_t *encoder_ctx,
AVPacket *pkt)
{
if (pkt->size <= enc_video_ctx->outbuf_size) {
memcpy(enc_video_ctx->outbuf, pkt->data, pkt->size);
return 1;
}

/* Buffer limit: width * height / 2, with 2MB minimum protection */
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 issue (security): Avoid potential integer overflow in max_reasonable_size computation and size handling.

encoder_ctx->video_width * encoder_ctx->video_height / 2 and alloc_size are both int. For large resolutions this multiplication can overflow before the division, so max_reasonable_size (and later alloc_size) may be derived from a corrupted value. Please compute this in a wider type (e.g. int64_t or size_t), clamp to a sane maximum, then cast down if needed.

  当编码包大小超过输出缓冲区时,替换原有的静默丢弃行为为动态扩容(1.5倍                                增长),最大分配上限为 max(width*height/2, 2MB),防止内存无限增长。

  Log: 修复编码器输出缓冲区溢出时静默丢帧的问题
  Bug: https://pms.uniontech.com/bug-view-345769.html
@deepin-ci-robot
Copy link
Copy Markdown

deepin pr auto review

你好!我是CodeGeeX。我已经仔细审查了你提供的Git Diff。这次修改的主要目的是将原来因缓冲区不足而丢弃数据包的逻辑,优化为动态调整缓冲区大小,从而减少帧丢失并提升编码器的鲁棒性。整体思路非常清晰,但在语法逻辑、代码质量、性能和安全性方面,我发现了几个需要改进的关键点。

以下是详细的审查意见和改进建议:

1. 语法与逻辑

  • 整型溢出风险:
    encoder_ctx->video_width * encoder_ctx->video_height 这一步运算可能会产生整型溢出。在C语言中,如果 video_widthvideo_heightint 类型(通常为32位),对于4K及以上的分辨率(如4096*3072 = 12,582,912),乘积仍在 int 范围内;但如果宽度或高度更大,或者在特定平台上 int 为16位,则必然溢出导致 max_reasonable_size 变成负数或极小值,从而触发错误的丢帧逻辑。
    • 建议: 显式转换为更宽的类型进行计算,如 (int64_t)encoder_ctx->video_width * encoder_ctx->video_height
  • pkt->sizeoutbuf_size 类型不匹配:
    FFmpeg中的 pkt->sizeint 类型,而通常缓冲区大小 outbuf_size 应该是 size_t 或至少是 unsigned int。如果 outbuf_size 是无符号类型,pkt->size <= enc_video_ctx->outbuf_size 的比较会发生隐式转换(有符号转为无符号),虽然你在前面挡住了 < 0 的情况,但统一使用 size_t 或确保两者类型一致会更安全。
  • 丢帧时未重置实际数据大小:
    resize_outbuf_if_needed 返回0(丢帧)时,执行了 continue。但此时 enc_video_ctx 中的状态(如 outbuf_size 等)可能已经处于不一致的状态。更重要的是,调用者如何知道这一帧被丢弃了?如果外部依赖 enc_video_ctx 中的某些长度字段来判断数据有效性,可能会读到上一帧的残留数据。
    • 建议: 确保丢帧时,调用者能正确处理上下文状态,或者在丢帧时将上下文的数据长度标记为0。

2. 代码质量

  • 魔数:
    2 * 1024 * 1024 是一个魔数。
    • 建议: 将其定义为具名常量,提高可读性。例如:#define MIN_REASONABLE_OUTBUF_SIZE (2 * 1024 * 1024)
  • 重复代码:
    encoder_encode_videoencoder_encode_video_vaapi 中的修改完全相同。虽然目前只是调用同一个函数,但后续如果 resize_outbuf_if_needed 的调用逻辑变动,容易漏改。
    • 建议: 这属于现有架构问题,当前可以接受,但需注意保持同步。

3. 代码性能

  • 1.5倍扩容策略被上限截断:
    代码中 int alloc_size = pkt->size + (pkt->size >> 1);,随后 if (alloc_size > max_reasonable_size) alloc_size = max_reasonable_size;。如果 pkt->size 非常接近 max_reasonable_size,1.5倍扩容后会被截断为 max_reasonable_size。这导致分配的新大小仅比当前 pkt->size 大一点点,下次稍微大一点的包又会触发 realloc,失去了1.5倍扩容减少频繁分配的意义。
    • 建议: 如果 pkt->size 已经接近上限,可以接受只分配 pkt->size 的精确大小,或者允许稍微超出上限(如果业务允许),否则去掉1.5倍扩容直接分配 pkt->size 也是一种策略。但最合理的做法是:如果触达上限,仅分配 pkt->size 大小以节省内存;未触达时才1.5倍扩容。
  • realloc 的性能开销:
    在视频编码的实时路径中调用 realloc 可能会引起内存页缺失或系统调用开销。
    • 建议: 当前逻辑已通过1.5倍扩容来缓解,这是标准做法,可以接受。但需确保不要在极端情况下频繁触发。

4. 代码安全

  • realloc 失败导致内存泄漏(经典C语言坑):
    代码中:uint8_t *new_buf = realloc(enc_video_ctx->outbuf, alloc_size);
    如果 realloc 失败,返回 NULL,此时原有的 enc_video_ctx->outbuf 仍然有效且占用内存。但代码直接 return 0;,丢失了对原内存的引用,导致内存泄漏。并且,后续如果再次编码,enc_video_ctx->outbuf 仍指向已被泄漏的旧地址,继续使用可能导致UAF(Use-After-Free)或双重释放。
    • 建议: 必须在 realloc 失败时保留旧指针,且不要更新 enc_video_ctx->outbuf
  • memcpy 越界风险:
    如果 realloc 成功,代码执行 memcpy(enc_video_ctx->outbuf, pkt->data, pkt->size);。此时 enc_video_ctx->outbuf_size 被更新为 alloc_size。由于前面的逻辑 alloc_size 可能被截断为 max_reasonable_size,如果 pkt->size > max_reasonable_size 的判断因为某种边界条件(如整型溢出计算错误)被绕过,那么 pkt->size 可能会大于 alloc_size,导致 memcpy 越界堆溢出。
    • 建议:memcpy 前,增加断言或显式检查 pkt->size <= enc_video_ctx->outbuf_size

改进后的代码

综合以上意见,我为你重构了 resize_outbuf_if_needed 函数:

/* Minimum reasonable buffer size: 2MB */
#define MIN_REASONABLE_OUTBUF_SIZE (2 * 1024 * 1024)

/*
 * Ensure output buffer is large enough for the encoded packet.
 * Dynamically resizes the buffer if needed (1.5x reserved space, capped at
 * max(width*height/2, MIN_REASONABLE_OUTBUF_SIZE)). Drops the frame if it 
 * exceeds the reasonable upper limit or if realloc fails.
 *
 * Returns: 1 if data was successfully copied to outbuf,
 *          0 if frame was dropped (caller must continue to next iteration).
 */
static int resize_outbuf_if_needed(encoder_video_context_t *enc_video_ctx,
                                   encoder_context_t *encoder_ctx,
                                   AVPacket *pkt)
{
    /* Guard against invalid negative pkt->size to prevent memcpy overflow */
    if (pkt->size < 0) {
        fprintf(stderr, "ENCODER: invalid packet size %d, dropping frame\n", pkt->size);
        return 0;
    }

    /* Cast to int64_t to prevent integer overflow during multiplication */
    int64_t calc_size = (int64_t)encoder_ctx->video_width * encoder_ctx->video_height / 2;
    int max_reasonable_size = (calc_size < MIN_REASONABLE_OUTBUF_SIZE) ? 
                              MIN_REASONABLE_OUTBUF_SIZE : (int)calc_size;

    /* If packet size exceeds the absolute reasonable limit, drop it */
    if (pkt->size > max_reasonable_size) {
        fprintf(stderr, "ENCODER: packet size %d exceeds reasonable limit (%d), dropping frame\n",
                pkt->size, max_reasonable_size);
        return 0;
    }

    /* If current buffer is large enough, just copy */
    if (pkt->size <= enc_video_ctx->outbuf_size) {
        memcpy(enc_video_ctx->outbuf, pkt->data, pkt->size);
        return 1;
    }

    /* 
     * Need to resize. Calculate new size with 1.5x reserved space.
     * If the required size with reservation exceeds the limit, 
     * fallback to exact size to avoid truncating the reservation which 
     * leads to frequent reallocations.
     */
    int alloc_size = pkt->size + (pkt->size >> 1);
    if (alloc_size > max_reasonable_size) {
        alloc_size = pkt->size; /* Fallback to exact fit if reservation exceeds limit */
    }

    fprintf(stderr, "ENCODER: resizing output buffer (%i -> %i, reserved %i)\n",
            enc_video_ctx->outbuf_size, pkt->size, alloc_size);
            
    uint8_t *new_buf = realloc(enc_video_ctx->outbuf, alloc_size);
    if (new_buf) {
        enc_video_ctx->outbuf = new_buf;
        enc_video_ctx->outbuf_size = alloc_size;
        
        /* Safety check before memcpy to prevent heap overflow due to logic errors */
        if (pkt->size > enc_video_ctx->outbuf_size) {
             fprintf(stderr, "ENCODER: fatal logic error, pkt->size > outbuf_size\n");
             return 0; /* Should never happen, but defensive programming */
        }
        memcpy(enc_video_ctx->outbuf, pkt->data, pkt->size);
        return 1;
    }

    /* 
     * CRITICAL: If realloc fails, the original buffer is still valid.
     * We MUST NOT overwrite enc_video_ctx->outbuf with NULL, otherwise 
     * we leak the old buffer and cause a segfault on next access.
     * We just drop the current frame and keep the old buffer for future smaller frames.
     */
    fprintf(stderr, "ENCODER: failed to allocate %d bytes, dropping frame (old buffer retained)\n", alloc_size);
    return 0;
}

调用处的修改建议

encoder_encode_videoencoder_encode_video_vaapi 中,丢帧时除了 continue,建议确保 enc_video_ctx 中表征当前帧数据长度的字段被清零,以防调用者误读旧数据(具体字段名需根据你的结构体定义调整,假设为 encoded_size):

        if (!resize_outbuf_if_needed(enc_video_ctx, encoder_ctx, pkt)) {
            last_video_pts = enc_video_ctx->pts;
            getLoadLibsInstance()->m_av_packet_unref(pkt);
            // 建议增加:清空当前帧的长度记录,防止外部读取到脏数据
            // enc_video_ctx->encoded_size = 0; 
            continue;
        }

希望这些审查意见和代码改进能帮助你提升编码器的稳定性和安全性!如果有任何疑问,欢迎继续讨论。

@deepin-ci-robot
Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: max-lvs, Resurgamz

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@Resurgamz
Copy link
Copy Markdown
Author

/merge

@deepin-bot deepin-bot Bot merged commit 4cb38b1 into linuxdeepin:release/eagle May 29, 2026
22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants