SwsContext、sws_scale缩放失败,或者缩放后视频乱码

本文深入探讨了使用FFmpeg在Android环境下进行视频缩放时遇到的常见问题,特别是在处理不同分辨率视频时出现的乱码或黑屏现象。通过实验发现,调整NativeWindow尺寸至64倍数能有效避免图像错位,同时揭示了视频高度对播放的影响,提出使用OpenGL或高效库如libyuv来增强播放器兼容性和效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

虽然 ffmpeg的缩放效率比较低,但随着电脑或者硬件设备发展,即使使用低效率的,也可以达到普通播放器的要求了。但在使用的过程中的坑还是很多的。

在android手机中使用SwsContext 和 sws_scale缩放后,视频总视出现乱码或者 干脆一个黑屏。

void  FFMVideoShow::show(AVFrame *avFrame)
{

    if(dataManager->swsContext == nullptr){
        dataManager -> outWidth = avFrame->width;
        dataManager -> outHeight = avFrame->height;

       dataManager->rgb = new char[dataManager -> outWidth * dataManager -> outHeight * 4];
        ANativeWindow_setBuffersGeometry(dataManager->aNativeWindow, dataManager->outWidth,
 dataManager->outHeight, WINDOW_FORMAT_RGBA_8888);

        // 这个开销也比较大,且经常失败,,这里不为空的时候,再去获取,发现视频解码速度快了很多。
        dataManager->swsContext = sws_getCachedContext(dataManager->swsContext,
                                                       avFrame->width,
                                                       avFrame->height,
                                                       (AVPixelFormat)avFrame->format,
                                                       dataManager->outWidth,
                                                       dataManager->outHeight,
                                                       AV_PIX_FMT_RGBA,
                                                       SWS_FAST_BILINEAR,
                                                       0,0,0 );

    }


    if(!dataManager->swsContext){
        LOGE("swsContext failed  %d!", dataManager->swsContext);
    }
    else {
        LOGE("show show 3" );
//        LOGE("swsContext success  %d!", dataManager->swsContext);
        // 指针数组AV_NUM_DATA_POINTERS=8 不一定全用
        uint8_t *data[AV_NUM_DATA_POINTERS]={0};
        if(dataManager->rgb != 0)
        data[0]= (uint8_t *)dataManager->rgb;

        int lines[AV_NUM_DATA_POINTERS]={0};
        lines[0]=dataManager->outWidth*4;
        int h = sws_scale(dataManager->swsContext,
                          (const uint8_t **)avFrame->data,
                          avFrame->linesize,0,
                          avFrame->height,
                          data,
                          lines
        );
        LOGE("show show 4   height =%d , width = %d" , dataManager->outHeight, dataManager->outWidth);
        LOGE("show show 4   h =%d" , h );

        av_frame_free(&avFrame);

        if(h >0)
        {
            LOGE("show show 5" );
            ANativeWindow_lock(dataManager->aNativeWindow, &(dataManager->wbuf), 0);
            uint8_t  *dst = (uint8_t*)dataManager->wbuf.bits;
            memcpy(dst, dataManager->rgb,dataManager->outHeight*dataManager->outWidth*4);
            ANativeWindow_unlockAndPost(dataManager->aNativeWindow);
        }
    }


}

问题:设置了natvieWindow的缓存区大小。而且 outwidth是 和 解码出来的frame的大小设置是一样的,像素格式rgba。但就是这么不缩放,也出现了问题,我的视频是抖音下随便下载的一个视频。宽高是368x640, 然而播放出来确实乱码。换了个标准的1920x1080的然而成了黑屏,sws_scale后h返回值为0。

在网上查资料,没找到类似的问题,作为一名从事图像的程序员,猜到可能是图像格式,特别是宽度在不够的时候,系统或者一些算法自己在后面补充一些空的像素,让图像宽度成为32、64的倍数。这样导致后面scale后,算法自己补充了一些像素,而native窗体已经设置了大小,从而不匹配,导致copy到buf的时候,一些像素丢失或者错位了。

于是,就做了以下实验:将窗体宽度从64开始,每次加2,发现,为64的倍数的时候都不会出现乱码。高度可以 随便scale都不会出现视频混乱清空。

从而得出一个结论:为了播放器的健壮性,窗体大小最好设置为64的倍数,为了保证视频的清晰度,可以尽可能靠近frame的宽度。

那么剩下一个问题,1920 是为64的倍数,为何我的视频scale 后 h为0,于是打印了log,查看了手机屏幕 和 surfaceview的大小,大小为:2012x1080 而我们的窗体大小为1920x1080

于是做了下面实验:

1、将高1080 修改为1079,就 是减去1个像素,视频就正常播放了。

2、将宽1920 - 64 后,1080不变也可以正常播放。

解决方案:

1、可以将宽度设置为linesize[0],这样就不会乱码了,但存在意外,视频1920*1080的时候太大,也无法显示。

2、缩放是根据宽度,android平台最好是64倍数,高度也是有相应的限制。为了播放器的 兼容性,最好使用opengl来渲染、或者利用 高效率libyuv 库来进行格式转换显示,libyuv高效转化git地址https://github.com/hurtnotbad/FFmpegDemo

具体的 解决方法请参考博客FFMpeg opengl显示解码avframe

<think>好的,用户让我帮忙实现一个用OpenCV保存AVFrame为JPEG的函数,也就是save_frame_to_jpeg。首先,我需要理清楚用户提供的代码上下文。用户已经使用FFmpeg的swscale库将YUV420P转换为RGB24,并将结果存储在AVFrame结构体rgb_frame中。现在需要将这个rgb_frame保存为JPEG图片,用户希望用OpenCV来实现这个保存功能。 首先,我得确认几个关键点。AVFrame中的数据格式是RGB24,对应OpenCV中的颜色格式。OpenCV默认使用BGR顺序,所以如果直接处理RGB数据,可能需要转换颜色空间。不过,用户这里用的是RGB,所以在保存时可能需要指定正确的颜色转换,或者确保OpenCV以RGB格式读取。不过,JPEG文件通常存储的是RGB数据,但OpenCV的imwrite函数在处理RGB数据时会当作BGR来保存,这样会导致颜色错误。因此,这里可能需要将RGB转换为BGR,或者调整通道顺序。 接下来,需要将AVFrame的数据转换为OpenCV的Mat对象。AVFrame的data数组包含指向各个平面的指针,linesize是每个平面的行字节数。对于RGB24格式,通常只有一个平面,数据是连续的,每行有linesize[0]字节。需要注意的是,OpenCV的Mat要求数据是连续的,而AVFrame可能存在对齐导致的padding,所以linesize可能大于width*3。这时候直接创建Mat可能会导致图像错位,所以可能需要处理这种情况,比如复制数据到连续的缓冲区,或者调整Mat的step参数。 然后,创建cv::Mat对象。假设AVFrame的data[0]包含RGB数据,那么Mat的高度是rgb_frame->height,宽度是rgb_frame->width,类型是CV_8UC3(每个像素3个字节)。构造Mat时,可以使用data[0]作为数据指针,并指定step为linesize[0]。但需要注意,OpenCV的step是以字节为单位的,而linesize[0]可能包含填充字节,所以必须正确指定step,否则图像可能会变形。 颜色空间的转换是关键。因为OpenCV默认使用BGR顺序,而这里的数据是RGB,所以在保存JPEG之前,需要将RGB转换为BGR。这可以通过cvtColor函数实现,使用COLOR_RGB2BGR标志。不过,如果用户的数据实际上是BGR格式,可能需要不同的处理。但根据代码中的注释,用户将输出格式设为RGB24,所以这里应该需要转换颜色空间。 另外,还需要确保数据是连续的。如果AVFrame中存在填充字节(比如对齐到某些字节数),直接使用data[0]可能会导致OpenCV读取到多余的数据,从而图像底部出现乱码。这时候可能需要将数据复制到一个新的连续缓冲区,或者调整Mat的构造方式。例如,使用cv::Mat::create来分配连续的内存,并将AVFrame的数据复制进去。 最后,使用cv::imwrite函数保存Mat为JPEG文件。需要检查文件路径是否正确,以及是否有写入权限。用户提供的代码中,filename是通过snprintf生成的,但具体路径需要确认是否存在,否则可能导致保存失败。 可能的错误点包括:颜色通道顺序错误、图像数据不连续导致的行错位、文件路径不存在等。因此,在实现函数时,需要处理这些问题,并添加必要的错误检查,比如检查AVFrame是否有效,数据指针是否非空,以及OpenCV是否成功写入文件。 总结步骤: 1. 将AVFrame的RGB数据转换为OpenCV的Mat,注意数据连续性和行步长。 2. 转换颜色空间从RGB到BGR,因为OpenCV默认使用BGR。 3. 使用imwrite保存Mat为JPEG文件。 4. 处理可能的内存复制以避免对齐问题。 5. 添加错误处理,确保函数健壮性。</think>以下是基于 OpenCV 实现 `save_frame_to_jpeg` 函数的具体步骤和代码: --- ### **1. 函数功能说明** 将 FFmpeg 的 `AVFrame`(RGB24 格式)通过 OpenCV 保存为 JPEG 文件,需要解决 **数据格式兼容性** 和 **颜色空间转换** 问题。 --- ### **2. 完整代码实现** ```cpp #include <opencv2/opencv.hpp> void save_frame_to_jpeg(AVFrame* rgb_frame, const char* filename) { // 检查输入有效性 if (!rgb_frame || !rgb_frame->data[0] || !filename) { fprintf(stderr, "Invalid AVFrame or filename\n"); return; } // 创建 OpenCV Mat cv::Mat img( rgb_frame->height, // 图像高度 rgb_frame->width, // 图像宽度 CV_8UC3, // 数据类型: 8位无符号, 3通道 (RGB) rgb_frame->data[0], // 原始数据指针 rgb_frame->linesize[0] // 行字节数 (可能包含对齐填充) ); // 关键: 将 RGB 转换为 BGR (OpenCV默认使用BGR顺序) cv::cvtColor(img, img, cv::COLOR_RGB2BGR); // 保存为JPEG if (!cv::imwrite(filename, img)) { fprintf(stderr, "Failed to write image: %s\n", filename); } } ``` --- ### **3. 关键问题解释** #### **(1) 颜色空间转换** - **问题**:FFmpeg 输出的 `RGB24` 是 `R-G-B` 顺序,而 OpenCV 默认使用 `B-G-R` 顺序。 - **解决**:通过 `cv::cvtColor(img, img, cv::COLOR_RGB2BGR)` 转换颜色通道。 #### **(2) 数据对齐兼容性** - **问题**:`AVFrame` 的 `linesize[0]` 可能包含内存对齐填充(如 16/32 字节对齐)。 - **解决**:OpenCV 的 `Mat` 构造函数允许指定行步长 (`rgb_frame->linesize[0]`),避免图像变形。 #### **(3) 数据连续性** - **问题**:若 `AVFrame` 数据不连续(例如经过缩放或滤镜处理),需显式复制数据: ```cpp cv::Mat continuous_img; cv::Mat img(...); // 原始非连续Mat img.copyTo(continuous_img); // 强制复制为连续内存 ``` --- ### **4. 调用示例** 在你的代码中直接替换注释部分: ```cpp // 保存为JPEG char filename[256]; snprintf(filename, sizeof(filename), "%s/frame_%04d.jpg", argv[2], frame_count++); save_frame_to_jpeg(rgb_frame, filename); // 调用函数 ``` --- ### **5. 依赖和编译** - **OpenCV 安装**:确保已安装 OpenCV 并配置头文件/库路径。 - **编译命令**:链接 OpenCV 库(如 `-lopencv_core -lopencv_imgcodecs -lopencv_imgproc`)。 --- ### **6. 常见问题处理** | 问题现象 | 解决方案 | |-------------------------|-----------------------------------| | 图像颜色异常(红蓝反色) | 检查是否遗漏 `cv::COLOR_RGB2BGR` | | 图像底部出现乱码 | 检查 `linesize[0]` 是否正确传入 | | 保存失败 | 检查文件路径权限和磁盘空间 | 通过上述实现,即可安全地将 FFmpeg 解码后的帧保存为 JPEG 文件。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值