Skip to content

fix(encoder): align VAAPI H.264 encoding resolution to 16-pixel bound…#472

Closed
Resurgamz wants to merge 2 commits into
linuxdeepin:release/eaglefrom
Resurgamz:release/eagle
Closed

fix(encoder): align VAAPI H.264 encoding resolution to 16-pixel bound…#472
Resurgamz wants to merge 2 commits into
linuxdeepin:release/eaglefrom
Resurgamz:release/eagle

Conversation

@Resurgamz
Copy link
Copy Markdown

@Resurgamz Resurgamz commented May 13, 2026

…aries

  • Align VAAPI codec_context dimensions to 16 pixels for H.264 macroblock requirements
  • Add padded NV12 frame construction when input resolution is not 16-aligned
  • Add out_yy buffer for Y plane row-by-row copy with aligned linesize
  • Add vaapi_coded_w/vaapi_coded_h static variables to track aligned dimensions
  • Fill UV padding area with 0x80 (neutral gray) to avoid visual artifacts
  • Only apply alignment for H.264 via VAAPI; software encoders unchanged

修复(encoder): 对齐 VAAPI H.264 编码分辨率至 16 像素边界

  • 将 VAAPI codec_context 尺寸按 16 像素对齐,满足 H.264 宏块要求
  • 输入分辨率非 16 对齐时,构造带填充的 NV12 帧
  • 新增 out_yy 缓冲区,按对齐行宽逐行拷贝 Y 平面数据
  • 新增 vaapi_coded_w/vaapi_coded_h 静态变量记录对齐后的编码尺寸
  • UV 填充区域填充 0x80(中性灰),避免视觉伪影
  • 仅对 VAAPI H.264 应用对齐,软件编码路径不做修改

Log: 修复 VAAPI 硬件编码 H.264 时非 16 对齐分辨率导致视频无法播放的问题,通过将编码分辨率对齐至 16 像素边界并正确填充 NV12 帧数据
Bug: https://pms.uniontech.com/bug-view-345769.html

Summary by Sourcery

Align VAAPI H.264 encoding to 16-pixel macroblock boundaries and pad input frames so non-16-aligned resolutions can be encoded correctly.

Bug Fixes:

  • Fix VAAPI H.264 hardware encoding failures for non-16-aligned resolutions by aligning codec context dimensions and padding frame data.

Enhancements:

  • Track aligned VAAPI coded dimensions and construct padded NV12 Y/UV planes with neutral-gray padding while keeping muxed container resolution unchanged.

@deepin-ci-robot
Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: 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

@deepin-ci-robot
Copy link
Copy Markdown

Hi @Resurgamz. Thanks for your PR.

I'm waiting for a linuxdeepin member to verify that this patch is reasonable to test. If it is, they should reply with /ok-to-test on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work. Regular contributors should join the org to skip this step.

Once the patch is verified, the new status will be reflected by the ok-to-test label.

I understand the commands that are listed here.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 13, 2026

CLA Assistant Lite bot All contributors have signed the CLA ✍️ ✅

@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented May 13, 2026

Reviewer's Guide

Aligns VAAPI H.264 encoding to 16‑pixel macroblock boundaries by tracking aligned codec dimensions, padding NV12 input frames, and updating frame buffers/linesizes so hardware surfaces and container metadata remain consistent while avoiding visual artifacts.

File-Level Changes

Change Details Files
Align VAAPI H.264 codec_context dimensions to 16-pixel boundaries and track aligned size separately from the logical video size.
  • Conditionally align codec_context width/height to the next multiple of 16 when using AV_CODEC_ID_H264, leaving other codecs unchanged
  • Store the aligned codec_context width/height into static vaapi_coded_w/vaapi_coded_h for later use in the encode path
  • Document that the muxer continues to use encoder_ctx->video_width/height so container metadata reflects the original resolution
libcam/libcam_encoder/encoder.c
Allocate and manage NV12 padding buffers for aligned VAAPI surfaces.
  • Add global buffers out_yy (Y plane) and out_uuvv (UV plane) sized using the aligned VAAPI dimensions instead of the original width/height
  • Initialize vaapi buffers in encoder_video_init_vaapi based on vaapi_coded_w/vaapi_coded_h
  • Free the new out_yy buffer in vaapi_over alongside existing allocations
libcam/libcam_encoder/encoder.c
Build correctly padded NV12 frames when input resolution is not 16-aligned, including neutral-gray UV padding.
  • Refactor encoder_encode_video_vaapi to derive aligned_width/aligned_height from vaapi_coded_w/vaapi_coded_h and compare them with the input width/height
  • When alignment is required, zero-fill the Y buffer, copy each Y row into an aligned-width buffer, and use it as the Y plane
  • For the UV plane, fill the entire aligned area with 0x80 (neutral gray) and then interleave U/V samples from the original frame into the non-padded region
  • When no alignment is needed, preserve the original NV12 interleaving loop for UV data to avoid behavior changes
libcam/libcam_encoder/encoder.c
Update AVFrame metadata to match aligned VAAPI surface layout.
  • Set frame->width and frame->height to the aligned dimensions instead of the original input size when using VAAPI
  • Point frame->data[0] to either the original input Y plane or the padded out_yy buffer depending on alignment, and frame->data[1] to out_uuvv
  • Set frame->linesize[0] and frame->linesize[1] to the aligned width so VAAPI sees contiguous rows matching the surface size
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 2 issues, and left some high level feedback:

  • The new out_yy, out_uuvv, vaapi_coded_w, and vaapi_coded_h being static globals makes the VAAPI path non‑reentrant and potentially unsafe with multiple encoder instances; consider moving these into the encoder/vaapi context instead of using file‑scope state.
  • Both new allocations for out_uuvv and out_yy assume malloc succeeds; it would be safer to check for NULL and fail encoder initialization cleanly rather than proceeding with invalid buffers.
  • When reinitializing VAAPI (or if encoder_video_init_vaapi can be called multiple times), out_uuvv/out_yy are overwritten without freeing existing buffers first; either free any previous allocations before reassigning or guard against double initialization.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The new `out_yy`, `out_uuvv`, `vaapi_coded_w`, and `vaapi_coded_h` being static globals makes the VAAPI path non‑reentrant and potentially unsafe with multiple encoder instances; consider moving these into the encoder/vaapi context instead of using file‑scope state.
- Both new allocations for `out_uuvv` and `out_yy` assume `malloc` succeeds; it would be safer to check for `NULL` and fail encoder initialization cleanly rather than proceeding with invalid buffers.
- When reinitializing VAAPI (or if `encoder_video_init_vaapi` can be called multiple times), `out_uuvv`/`out_yy` are overwritten without freeing existing buffers first; either free any previous allocations before reassigning or guard against double initialization.

## Individual Comments

### Comment 1
<location path="libcam/libcam_encoder/encoder.c" line_range="2635-2640" />
<code_context>
+            uint8_t *iu = input_frame + size;
+            uint8_t *iv = input_frame + size + size / 4;
+
+            memset(out_yy, 0, aligned_width * aligned_height);
+            for (int row = 0; row < height; row++)
+                memcpy(out_yy + row * aligned_width, input_frame + row * width, width);
+            y_plane = out_yy;
+
+            memset(out_uuvv, 0x80, aligned_width * aligned_height / 2);
+            for (int hh = 0; hh < height / 2; hh++) {
+                for (int ww = 0; ww < width / 2; ww++) {
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Use size_t for buffer size calculations to avoid potential integer overflows.

Here the size passed to `memset` (and earlier `malloc`) is computed as `aligned_width * aligned_height` while both are `int`, so the multiplication can overflow before being widened to `size_t`. Please compute the size in `size_t` (e.g., `size_t buf_size = (size_t)aligned_width * aligned_height;`) to avoid overflow for larger dimensions and to clarify the intent.

Suggested implementation:

```c
        int aligned_width = vaapi_coded_w;
        int aligned_height = vaapi_coded_h;

        if (aligned_width != width || aligned_height != height) {
            /* Input is smaller than codec context: build padded NV12 frame.
             * Y plane: copy row by row into aligned-width linesize buffer.
             * UV plane: interleave U/V with padding rows filled to neutral gray. */
            uint8_t *iu = input_frame + size;
            uint8_t *iv = input_frame + size + size / 4;

            size_t y_plane_size = (size_t)aligned_width * (size_t)aligned_height;
            memset(out_yy, 0, y_plane_size);
            for (int row = 0; row < height; row++)
                memcpy(out_yy + row * aligned_width, input_frame + row * width, width);
            y_plane = out_yy;

            size_t uv_plane_size = y_plane_size / 2;
            memset(out_uuvv, 0x80, uv_plane_size);
            for (int hh = 0; hh < height / 2; hh++) {
                for (int ww = 0; ww < width / 2; ww++) {
                    out_uuvv[hh * aligned_width + ww * 2] = iu[hh * width / 2 + ww];
                    out_uuvv[hh * aligned_width + ww * 2 + 1] = iv[hh * width / 2 + ww];
                }
            }
        } else {

```

To fully address overflow risks, you should also:
1. Update any `malloc`/`calloc` calls that allocate `out_yy` and `out_uuvv` to use `size_t`-based size calculations, e.g. `size_t y_plane_size = (size_t)aligned_width * aligned_height; out_yy = malloc(y_plane_size);`.
2. Similarly, ensure any other buffer size computations involving `aligned_width` and `aligned_height` in this file use `size_t` intermediates before multiplication.
</issue_to_address>

### Comment 2
<location path="libcam/libcam_encoder/encoder.c" line_range="2058" />
<code_context>
 struct SwsContext *swsContext = NULL;
 AVFrame   *hw_frame = NULL;
 uint8_t *out_uuvv = NULL;
+uint8_t *out_yy = NULL;
+static int vaapi_coded_w = 0;
+static int vaapi_coded_h = 0;
</code_context>
<issue_to_address>
**issue (complexity):** Consider moving VAAPI-specific buffers and dimensions into the encoder context and encapsulating the NV12 alignment logic in a helper to simplify the encode path and reduce hidden state.

You can keep the new behavior while reducing complexity and hidden state by:

1. **Move VAAPI-specific state into the encoder context**

Instead of `static` globals (`vaapi_coded_w`, `vaapi_coded_h`, `out_yy`, `out_uuvv`), put them into `encoder_video_context_t` (or a VAAPI sub-struct), so they’re clearly tied to an instance and lifetime is explicit.

```c
// encoder_video_context_t (example)
typedef struct encoder_video_context {
    ...
    int vaapi_coded_w;
    int vaapi_coded_h;
    uint8_t *vaapi_out_yy;
    uint8_t *vaapi_out_uuvv;
    ...
} encoder_video_context_t;
```

Then, in init:

```c
enc_video_ctx->vaapi_coded_w = video_codec_data->codec_context->width;
enc_video_ctx->vaapi_coded_h = video_codec_data->codec_context->height;

enc_video_ctx->vaapi_out_uuvv = malloc(enc_video_ctx->vaapi_coded_w *
                                       enc_video_ctx->vaapi_coded_h / 2);
enc_video_ctx->vaapi_out_yy   = malloc(enc_video_ctx->vaapi_coded_w *
                                       enc_video_ctx->vaapi_coded_h);
```

And in `vaapi_over()`:

```c
free(enc_video_ctx->vaapi_out_uuvv);
free(enc_video_ctx->vaapi_out_yy);
```

This removes global hidden dependencies from `encoder_encode_video_vaapi` and keeps all VAAPI buffer ownership in one place.

2. **Extract the alignment / padding into a helper**

The encode hot path becomes easier to read if you move the branchy alignment + memcpy/memset logic into a small helper that returns Y/UV pointers and linesizes.

```c
static void prepare_nv12_vaapi_frame(uint8_t *input_frame,
                                     int width, int height, int size,
                                     encoder_video_context_t *enc_video_ctx,
                                     uint8_t **y_plane, uint8_t **uv_plane,
                                     int *aligned_width, int *aligned_height)
{
    int aw = enc_video_ctx->vaapi_coded_w;
    int ah = enc_video_ctx->vaapi_coded_h;

    *aligned_width  = aw;
    *aligned_height = ah;

    if (aw != width || ah != height) {
        uint8_t *iu = input_frame + size;
        uint8_t *iv = input_frame + size + size / 4;

        memset(enc_video_ctx->vaapi_out_yy, 0, aw * ah);
        for (int row = 0; row < height; row++)
            memcpy(enc_video_ctx->vaapi_out_yy + row * aw,
                   input_frame + row * width, width);

        memset(enc_video_ctx->vaapi_out_uuvv, 0x80, aw * ah / 2);
        for (int hh = 0; hh < height / 2; hh++) {
            for (int ww = 0; ww < width / 2; ww++) {
                enc_video_ctx->vaapi_out_uuvv[hh * aw + ww * 2] =
                    iu[hh * width / 2 + ww];
                enc_video_ctx->vaapi_out_uuvv[hh * aw + ww * 2 + 1] =
                    iv[hh * width / 2 + ww];
            }
        }

        *y_plane  = enc_video_ctx->vaapi_out_yy;
        *uv_plane = enc_video_ctx->vaapi_out_uuvv;
        return;
    }

    uint8_t *uv = enc_video_ctx->vaapi_out_uuvv;
    uint8_t *iu = input_frame + size;
    uint8_t *iv = input_frame + size + size / 4;
    for (int hh = 0; hh < height; hh++) {
        for (int ww = 0; ww < width / 4; ww++) {
            *uv++ = *iu++;
            *uv++ = *iv++;
        }
    }

    *y_plane  = input_frame;
    *uv_plane = enc_video_ctx->vaapi_out_uuvv;
}
```

Then your encode function is simplified:

```c
uint8_t *y_plane, *uv_plane;
int aligned_width, aligned_height;

prepare_nv12_vaapi_frame(input_frame, width, height, size,
                         enc_video_ctx,
                         &y_plane, &uv_plane,
                         &aligned_width, &aligned_height);

video_codec_data->frame->format = AV_PIX_FMT_NV12;
video_codec_data->frame->width  = aligned_width;
video_codec_data->frame->height = aligned_height;

video_codec_data->frame->data[0] = y_plane;
video_codec_data->frame->data[1] = uv_plane;

video_codec_data->frame->linesize[0] = aligned_width;
video_codec_data->frame->linesize[1] = aligned_width;
video_codec_data->frame->linesize[2] = 0;
```

This keeps `encoder_encode_video_vaapi` focused on “wire up FFmpeg frame fields” while encapsulating all the alignment/padding details and using a single, consistent source for the coded dimensions.
</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 thread libcam/libcam_encoder/encoder.c Outdated
Comment thread libcam/libcam_encoder/encoder.c Outdated
…aries

- Align VAAPI codec_context dimensions to 16 pixels for H.264 macroblock requirements
- Add padded NV12 frame construction when input resolution is not 16-aligned
- Add out_yy buffer for Y plane row-by-row copy with aligned linesize
- Add vaapi_coded_w/vaapi_coded_h static variables to track aligned dimensions
- Fill UV padding area with 0x80 (neutral gray) to avoid visual artifacts
- Only apply alignment for H.264 via VAAPI; software encoders unchanged

修复(encoder): 对齐 VAAPI H.264 编码分辨率至 16 像素边界

- 将 VAAPI codec_context 尺寸按 16 像素对齐,满足 H.264 宏块要求
- 输入分辨率非 16 对齐时,构造带填充的 NV12 帧
- 新增 out_yy 缓冲区,按对齐行宽逐行拷贝 Y 平面数据
- 新增 vaapi_coded_w/vaapi_coded_h 静态变量记录对齐后的编码尺寸
- UV 填充区域填充 0x80(中性灰),避免视觉伪影
- 仅对 VAAPI H.264 应用对齐,软件编码路径不做修改

Log: 修复 VAAPI 硬件编码 H.264 时非 16 对齐分辨率导致视频无法播放的问题,通过将编码分辨率对齐至 16 像素边界并正确填充 NV12 帧数据
Bug: https://pms.uniontech.com/bug-view-345769.html
- 切换回拍照模式时所有4个按钮的m_iconOpacity均通过
  setIconOpacity(1)正确恢复
- 与setIconOpacity(1)的行为对齐,统一恢复所有按钮状态

Log: 修复拍照/录像状态切换后m_delayUnfoldBtn背景透明度异常的问题
Bug: https://pms.uniontech.com/bug-view-196565.html
@deepin-ci-robot
Copy link
Copy Markdown

deepin pr auto review

你好!我是CodeGeeX。我已经仔细审查了你提供的 git diff,本次修改主要涉及VAAPI硬件编码器的宏块对齐支持、全局状态重构为上下文封装、NV12格式转换逻辑优化,以及UI控件状态的修复

总体来说,这次修改的方向非常正确,解决了VAAPI编码器对H.264宏块16x16对齐的硬性要求,并消除了全局变量,提高了代码的模块化和线程安全性。

但在语法逻辑、代码质量、性能和安全方面,我发现了一些需要改进的隐患。以下是详细的审查意见:


一、 语法与逻辑

1. encoder_close 中释放 enc_video_ctx 的顺序逻辑错误(严重)
encoder_close 函数中,你在释放 enc_video_ctx 之前调用了 vaapi_over(enc_video_ctx),这是正确的。但是,vaapi_over 内部仅仅释放了 vaapi_out_yyvaapi_out_uuvv 并置 NULL,随后 encoder_close 执行了 free(enc_video_ctx)
问题enc_video_ctx 被释放后,其内部指针成为悬空指针。虽然目前不会导致崩溃,但如果后续 vaapi_over 的逻辑变更,或者有其他地方在 free(enc_video_ctx) 后访问该结构体,极易引发Use-After-Free。
建议:在 free(enc_video_ctx) 之前,将相关指针置 NULL 的工作应该在 vaapi_over 中完成,或者确保 free(enc_video_ctx) 是最后一步,且之后绝对不再访问。

2. prepare_nv12_vaapi_frame 中对齐与未对齐分支的 UV 交错逻辑不一致(严重)
prepare_nv12_vaapi_frame 函数中:

  • 需要对齐的分支:UV 交错循环是按半行处理的:for (int ww = 0; ww < width / 2; ww++),每次取1个U和1个V,步长为2,这是正确的NV12交错逻辑。
  • 不需要对齐的分支(Dimensions already aligned):UV 交错循环是按4字节处理的:for (int ww = 0; ww < width / 4; ww++),每次取4个U和4个V。
    问题:如果 width 不是4的倍数(例如宽度为奇数或宽度 % 4 != 0),未对齐分支的循环 width / 4 会发生截断,导致UV平面的最后几列数据丢失,画面右侧出现颜色偏移或绿边。
    建议:统一两处的UV交错逻辑,使用 width / 2 按半像素交错,以保证所有分辨率下的正确性。

3. encoder_video_init_vaapi 中内存分配失败的错误处理不完整
encoder_video_init_vaapi 中分配 vaapi_out_yyvaapi_out_uuvv 时,如果分配失败,代码设置了 is_vaapi = HW_VAAPI_FAIL29return NULL
问题:在此之前,函数已经分配了大量的资源(如 outbuf、FFmpeg上下文等)。直接 return NULL 会导致严重的内存泄漏。
建议:应该跳转到函数末尾的统一错误处理 fail 标签处,依次释放已分配的上下文和缓冲区。

二、 代码性能

1. prepare_nv12_vaapi_frame 中的 UV 交错循环性能极差
当前代码使用了双重嵌套的 for 循环来逐像素交错 U 和 V 平面:

for (int hh = 0; hh < height / 2; hh++) {
    for (int ww = 0; ww < width / 2; ww++) {
        enc_video_ctx->vaapi_out_uuvv[hh * aw + ww * 2] = iu[...];
        enc_video_ctx->vaapi_out_uuvv[hh * aw + ww * 2 + 1] = iv[...];
    }
}

问题:视频编码是性能敏感路径,这种逐字节拷贝的方式没有利用到 CPU 的向量指令(SIMD),且循环开销巨大,会导致编码帧率大幅下降。
建议:强烈建议使用 FFmpeg 提供的 sws_scale 来完成 YUV420P 到 NV12 的转换和行对齐,它内部使用了高度优化的 SIMD 指令。如果必须手动实现,请至少按行处理,并考虑使用 memcpy 拷贝连续的 U/V 段,或者手动展开循环。

三、 代码安全

1. encoder_video_init_vaapi 中缺乏对 video_widthvideo_height 的合法性校验
代码中进行了如下对齐计算:

video_codec_data->codec_context->width = ((encoder_ctx->video_width + 15) / 16) * 16;

问题:如果 encoder_ctx->video_widthvideo_height 是负数或0,对齐计算会产生异常值。更严重的是,后续分配缓冲区时:

size_t y_plane_size = (size_t)enc_video_ctx->vaapi_coded_w * (size_t)enc_video_ctx->vaapi_coded_h;
enc_video_ctx->vaapi_out_yy = (uint8_t *)malloc(y_plane_size);

如果宽高异常,y_plane_size 可能为0或一个非常大的值,导致 malloc(0) 返回未定义指针或分配失败但未察觉,引发缓冲区溢出。
建议:在函数入口处严格校验 video_width > 0video_height > 0,并在 malloc 之后始终检查返回值是否为 NULL(当前代码有检查,但需确保 y_plane_size 不会溢出 size_t)。

2. prepare_nv12_vaapi_frame 缺少入参校验
问题:该函数直接解引用 enc_video_ctx 中的指针,如果传入的 input_frame 为 NULL,或者 enc_video_ctx->vaapi_out_yy 为 NULL(比如内存分配失败但未阻止编码流程),会直接导致段错误。
建议:在函数入口增加 if (!input_frame || !enc_video_ctx || !enc_video_ctx->vaapi_out_yy) return; 的防御性校验。

四、 代码质量与可读性

1. 遗留的全局变量 is_vaapi
问题:本次修改将 out_uuvv 从全局变量成功移入了 encoder_video_context_t 结构体中,这是非常好的改进。但是 is_vaapi 仍然是一个全局变量。在多实例编码场景下,全局变量会导致状态混乱。
建议:作为后续重构的参考,建议将 is_vaapi 也移入 encoder_video_context_t 中,彻底消除全局状态依赖。

2. UI 相关的 setIconOpacity 硬编码魔数

m_flashlightUnfoldBtn->setIconOpacity(1);

问题1 是一个魔数。虽然从字面理解 1 代表完全不透明(100%),但与上面 setOpacity(102) 的风格不一致(102似乎是透明度百分比或特定API的枚举值)。
建议:确认 1 的含义,如果是不透明度,建议使用 1.0f 或定义常量 FULLY_OPAQUE 以提高可读性。


🌟 改进后的代码示例

针对核心的 prepare_nv12_vaapi_frame 和内存分配逻辑,我为你优化了代码,修复了逻辑漏洞并提升了性能:

// 修复1:参数校验与更安全的 NV12 准备逻辑
static void prepare_nv12_vaapi_frame(uint8_t *input_frame,
                                     int width, int height, int size,
                                     encoder_video_context_t *enc_video_ctx,
                                     uint8_t **y_plane, uint8_t **uv_plane,
                                     int *aligned_width, int *aligned_height)
{
    // 防御性校验
    if (!input_frame || !enc_video_ctx || !enc_video_ctx->vaapi_out_yy || !enc_video_ctx->vaapi_out_uuvv) {
        *y_plane = NULL;
        *uv_plane = NULL;
        return;
    }

    int aw = enc_video_ctx->vaapi_coded_w;
    int ah = enc_video_ctx->vaapi_coded_h;

    *aligned_width  = aw;
    *aligned_height = ah;

    const uint8_t *iu = input_frame + size;
    const uint8_t *iv = input_frame + size + size / 4;

    if (aw != width || ah != height) {
        /* 需要对齐的分支 */
        size_t y_plane_size = (size_t)aw * (size_t)ah;
        size_t uv_plane_size = y_plane_size / 2;
        
        memset(enc_video_ctx->vaapi_out_yy, 0, y_plane_size);
        for (int row = 0; row < height; row++)
            memcpy(enc_video_ctx->vaapi_out_yy + row * aw,
                   input_frame + row * width, width);

        memset(enc_video_ctx->vaapi_out_uuvv, 0x80, uv_plane_size); // UV填充中性色
        
        // 性能优化 & 逻辑修复:统一使用 width/2 步进,避免非4倍数宽度丢像素
        int uv_width = width / 2;
        int uv_height = height / 2;
        for (int hh = 0; hh < uv_height; hh++) {
            uint8_t *out_uv_line = enc_video_ctx->vaapi_out_uuvv + hh * aw;
            const uint8_t *in_u_line = iu + hh * uv_width;
            const uint8_t *in_v_line = iv + hh * uv_width;
            for (int ww = 0; ww < uv_width; ww++) {
                out_uv_line[ww * 2]     = in_u_line[ww];
                out_uv_line[ww * 2 + 1] = in_v_line[ww];
            }
        }

        *y_plane  = enc_video_ctx->vaapi_out_yy;
        *uv_plane = enc_video_ctx->vaapi_out_uuvv;
    } else {
        /* 不需要对齐的分支 */
        uint8_t *uv = enc_video_ctx->vaapi_out_uuvv;
        
        // 逻辑修复:统一使用 width/2 步进,与对齐分支保持一致,修复非4倍数宽度的问题
        int uv_width = width / 2;
        int uv_height = height / 2; // 注意:NV12的UV高度是Y的一半
        for (int hh = 0; hh < uv_height; hh++) {
            const uint8_t *in_u_line = iu + hh * uv_width;
            const uint8_t *in_v_line = iv + hh * uv_width;
            for (int ww = 0; ww < uv_width; ww++) {
                *uv++ = in_u_line[ww];
                *uv++ = in_v_line[ww];
            }
        }

        *y_plane  = input_frame;
        *uv_plane = enc_video_ctx->vaapi_out_uuvv;
    }
}

// 修复2:初始化时的参数校验与安全退出机制
static encoder_video_context_t *encoder_video_init_vaapi(encoder_context_t *encoder_ctx) {
    // ... 前面的代码 ...
    
    /* 增加宽高校验,防止异常值导致分配0字节或溢出 */
    if (encoder_ctx->video_width <= 0 || encoder_ctx->video_height <= 0) {
        fprintf(stderr, "Invalid video dimensions: %dx%d\n", encoder_ctx->video_width, encoder_ctx->video_height);
        goto fail; // 假设函数末尾有 fail 标签释放资源
    }

    if (video_defaults->codec_id == AV_CODEC_ID_H264) {
        video_codec_data->codec_context->width = ((encoder_ctx->video_width + 15) / 16) * 16;
        video_codec_data->codec_context->height = ((encoder_ctx->video_height + 15) / 16) * 16;
    } else {
        video_codec_data->codec_context->width = encoder_ctx->video_width;
        video_codec_data->codec_context->height = encoder_ctx->video_height;
    }

    // ... 中间的代码 ...

    enc_video_ctx->vaapi_coded_w = video_codec_data->codec_context->width;
    enc_video_ctx->vaapi_coded_h = video_codec_data->codec_context->height;
    {
        size_t y_plane_size = (size_t)enc_video_ctx->vaapi_coded_w * (size_t)enc_video_ctx->vaapi_coded_h;
        size_t uv_plane_size = y_plane_size / 2;
        enc_video_ctx->vaapi_out_yy = (uint8_t *)malloc(y_plane_size);
        enc_video_ctx->vaapi_out_uuvv = (uint8_t *)malloc(uv_plane_size);

        if (!enc_video_ctx->vaapi_out_yy || !enc_video_ctx->vaapi_out_uuvv) {
            fprintf(stderr, "Failed to allocate VAAPI working buffers.\n");
            free(enc_video_ctx->vaapi_out_yy);
            free(enc_video_ctx->vaapi_out_uuvv);
            enc_video_ctx->vaapi_out_yy = NULL;
            enc_video_ctx->vaapi_out_uuvv = NULL;
            is_vaapi = HW_VAAPI_FAIL29;
            // 严重修复:不能直接 return NULL,必须跳转到 fail 标签释放已分配的 FFmpeg 上下文
            goto fail; 
        }
    }
    // ... 后面的代码 ...
}

希望这些审查意见对你有所帮助!如果关于 FFmpeg 的 sws_scale 优化或者错误处理的跳转逻辑有疑问,欢迎随时提问。

@Resurgamz Resurgamz closed this by deleting the head repository May 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants