完美解决OpenCV Mat 与 FFmpeg AVFrame 的相互转换 (任意尺寸)

最近做毕设用到了 OpenCV 和 FFmpeg 两个库,需求是将任意大小的 cv::Mat 转换到 AVFrame 做推流。

显然 “任意大小” 这个需求不是很常见,但我碰上了。

在学习过 完美解决OpenCV Mat 与 FFmpeg AVFrame 的相互转换 之后,能够满足基本的使用需求。

然而对于尺寸(宽)不是 64 的倍数 时,还是会出现底部绿屏的效果,猜测应该是数据丢失或者数据对齐的问题。

在阅读了 FFmpeg原理 后,发现 FFmpeg 中数据的 内存对齐 问题。便在原来帖子的基础上改了一下,主要就是在 cv::Mat 转 AVFrame 时,考虑是否需要内存对齐的问题。

inline
cv::Mat AVFrame_to_CVMat(AVFrame* yuv420Frame)
{
    //得到AVFrame信息
    int srcW = yuv420Frame->width;
    int srcH = yuv420Frame->height;
    SwsContext* swsCtx = sws_getContext(
        srcW, srcH, (AVPixelFormat)yuv420Frame->format, 
        srcW, srcH, (AVPixelFormat)AV_PIX_FMT_BGR24, 
        SWS_FAST_BILINEAR, NULL, NULL, NULL
    );

    //生成Mat对象
    cv::Mat mat;
    mat.create(cv::Size(srcW, srcH), CV_8UC3);


    //格式转换,直接填充Mat的数据data
    AVFrame* bgr24Frame = av_frame_alloc();
    av_image_fill_arrays(bgr24Frame->data, bgr24Frame->linesize, (uint8_t*)mat.data, (AVPixelFormat)AV_PIX_FMT_BGR24, srcW, srcH, 1);
    sws_scale(swsCtx, (const uint8_t* const*)yuv420Frame->data, yuv420Frame->linesize, 0, srcH, bgr24Frame->data, bgr24Frame->linesize);

    //释放
    av_frame_free(&bgr24Frame);
    sws_freeContext(swsCtx);

    return mat;
}

inline
AVFrame* CVMat_to_AVFrame(cv::Mat& inMat)
{
    //得到Mat信息
    AVPixelFormat dstFormat = AV_PIX_FMT_YUV420P;
    int width = inMat.cols;
    int height = inMat.rows;

    //创建AVFrame填充参数 注:调用者释放该frame
    AVFrame* frame = av_frame_alloc();
    frame->width = width;
    frame->height = height;
    frame->format = dstFormat;

    //初始化AVFrame内部空间
    int ret = av_frame_get_buffer(frame, 0);
    if (ret < 0)
    {
        std::cerr << "Could not allocate the video frame data";
        return nullptr;
    }

    ret = av_frame_make_writable(frame);
    if (ret < 0)
    {
        std::cerr << "Av frame make writable failed.";
        return nullptr;
    }

    cv::Size osize = inMat.size();
    //转换颜色空间为YUV420
    cv::cvtColor(inMat, inMat, cv::COLOR_BGR2YUV_I420);
    cv::Size nsize = inMat.size();

    // 按YUV420格式,设置数据地址,注意内存对齐
    int frame_width = frame->linesize[0];
    int frame_size = width * height;
    unsigned char* data = inMat.data;
    // 无填充情况
    if (frame_width == width) {
        memcpy(frame->data[0], data, frame_size);
        memcpy(frame->data[1], data + frame_size, frame_size / 4);
        memcpy(frame->data[2], data + frame_size * 5 / 4, frame_size / 4);
    }
    else {
        memset(frame->data[0], 0, frame->linesize[0] * height);
        memset(frame->data[1], 0, frame->linesize[0] * height / 4);
        memset(frame->data[2], 0, frame->linesize[0] * height / 4);
        // Y Panel
        for (int i = 0; i < height; i++) {
            memcpy(frame->data[0] + i * frame->linesize[0], data + i * width, width);
        }
        unsigned char* udata = data + frame_size;
        unsigned char* vdata = udata + frame_size / 4;
        // UV Panel
        for (int i = 0; i < height / 2; i++) {
            memcpy(frame->data[1] + i * frame->linesize[1], udata + i * width / 2, width / 2);
            memcpy(frame->data[2] + i * frame->linesize[2], vdata + i * width / 2, width / 2);
        }
    }

    return frame;
}

在 av_frame_get_buffer() 后,要比较一下实际分配的存储空间 linesize[0] 是否和原宽度 width 一致,不一致则说明每行的数据并非 width 那么长,而是做了填充,通常是 16 或 64 的整数倍。分配后存储形式如下:

根据新的内存空间将 cv::Mat 中数据正确拷贝即可

另外注意图像宽高均为 偶数 以满足 yuv420 采样格式

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值