You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多
文档控制台
免费开始使用

使用Raylib无法正常显示解码后的视频帧,求解决思路

问题:Libav转码RGBA帧到Raylib纹理显示异常

我正在探索libav和raylib,理解音视频工作原理的同时用raylib构建界面。实现了一个可解码音视频帧的结构体,视频帧会被转换为32bpp的RGBA格式,相关初始化代码如下:

if (av_image_alloc((uint8_t **)media->dst_frame->data,
                   media->dst_frame->linesize, media->ctxs[0]->width,
                   media->ctxs[0]->height, AV_PIX_FMT_RGBA, 1) < 0) {
    fprintf(stderr, "Failed to setup dest image\n");
    return -1;
}

media->sws_ctx = sws_getContext(
    media->ctxs[0]->width, media->ctxs[0]->height, media->ctxs[0]->pix_fmt,
    media->ctxs[0]->width, media->ctxs[0]->height, AV_PIX_FMT_RGBA,
    SWS_BILINEAR, NULL, NULL, 0);

// 解码函数中的代码:
int ret = sws_scale(media->sws_ctx, media->frame->data,
                        media->frame->linesize, 0, media->frame->height,
                        media->dst_frame->data, media->dst_frame->linesize);

主文件中初始化raylib并完成纹理加载,尝试获取首帧作为视频预览:

Image previewImage =
    GenImageColor(videoArea.width, videoArea.height, BLACK);
// 试图通过转换格式保证兼容性
ImageFormat(&previewImage, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8);

Texture2D videoTexture = LoadTextureFromImage(previewImage);
UnloadImage(previewImage);
if (!state->has_media) {
    DrawText("Drop a video file here!", videoArea.x + 10,
             videoArea.y + 10, 20, GRAY);
} else {
    if (state->first_frame) {
        do {
            decode_packet(state->media);
        } while (!is_frame_video(state->media));

        UpdateTexture(videoTexture, state->media->dst_frame->data[0]);

        state->first_frame = 0;
    }
}

DrawTexture(videoTexture, videoArea.x, videoArea.y, WHITE);

拖入MP4文件后显示效果异常,画面呈现错位乱码,疑似行对齐问题,求正确的解决方向。


解决方向

核心问题是Libav输出帧的行字节数(linesize)与Raylib纹理要求的紧密排列行字节数不匹配

  1. Libav的行对齐机制
    av_image_alloc默认启用对齐(最后一个参数为1),会将linesize设置为16字节的倍数,而非严格的宽度×4(RGBA每个像素4字节)。当视频宽度不是16的倍数时,linesize会比实际像素所需的字节数多,多余的字节是填充用的无效数据。

  2. Raylib的纹理数据要求
    UpdateTexture期望传入的像素数据是紧密无填充的,每行字节数必须等于纹理宽度×4。如果直接传入带对齐填充的Libav帧数据,Raylib会把填充字节当成有效像素读取,导致画面错位、色彩混乱。

具体修复方案:

  • 方案一:手动逐行复制,剔除对齐填充
    提前分配一个与纹理尺寸匹配的紧密缓冲区,将Libav帧的有效像素数据逐行复制到缓冲区后再更新纹理:

    // 初始化阶段分配缓冲区(与纹理同尺寸)
    uint8_t* tight_pixels = malloc(videoTexture.width * videoTexture.height * 4);
    
    // 获取到视频帧后执行复制
    for (int y = 0; y < media->dst_frame->height; y++) {
        memcpy(
            tight_pixels + y * videoTexture.width * 4,
            media->dst_frame->data[0] + y * media->dst_frame->linesize[0],
            videoTexture.width * 4
        );
    }
    
    // 用紧密缓冲区更新纹理
    UpdateTexture(videoTexture, tight_pixels);
    
  • 方案二:让Libav生成无对齐的帧数据
    修改av_image_alloc的最后一个参数为0,禁用自动对齐,这样linesize会严格等于宽度×4

    if (av_image_alloc((uint8_t **)media->dst_frame->data,
                       media->dst_frame->linesize, media->ctxs[0]->width,
                       media->ctxs[0]->height, AV_PIX_FMT_RGBA, 0) < 0) {
        fprintf(stderr, "Failed to setup dest image\n");
        return -1;
    }
    

    注意:部分硬件加速的sws_scale可能对非对齐数据兼容性较差,优先推荐方案一。

  • 额外检查:纹理尺寸匹配
    确保videoTexture的宽高与Libav解码出的视频帧宽高完全一致,不要直接使用videoArea的尺寸生成纹理(除非你需要做缩放适配)。尺寸不匹配同样会导致画面错乱。


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

火山引擎 最新活动