完美解决OpenCV Mat 与 FFmpeg AVFrame 的相互转换

背景

最近做摄像头和麦克风实时采集推流的项目中,需要用OpenCV做实时美颜。需要将原始数据AVFame转换为Mat数据格式,进行美颜处理后再转成AVFrame进行编码,推流到流媒体服务器。

说明

其它相关博文也有很多转换的介绍,思路是对的,但使用总是有些问题,多数是处理不到位,转换出来的AVFrame显示绿屏,甚至调用直接崩溃。于是自己研究了下原理,更改了处理逻辑。如果有需要转换朋友的不用趟坑了,可以直接复制我的转换代码使用,长时间测试验证绝对好使,如发现有优化处也请不吝赐教。

AVFrame 转 Mat

Mat是OpenCV的图像格式,颜色空间为BGR,对应FFmpeg格式为AV_PIX_FMT_BGR24。AVFrame一般为YUV420P,以此格式为例。 这个通过FFmpeg的格式转换函数就可以解决。转换代码如下

cv::Mat AVFrameToCVMat(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_BICUBIC, 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;
}

Mat 转 AVFrame

借鉴上步,首先也是想利用FFmpeg的转换函数进行转换,但是总有问题,没有定位到具体原因。不过理解原理后,也可以自己处理数据。基本思路是将Mat转换为YUV420格式,将Y、U、V分量分别填充到对应的AVFrame里面就可以了。

AVFrame *CVMatToAVFrame(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, 32);
    if (ret < 0)
    {
        qDebug() << "Could not allocate the video frame data" ;
        return nullptr;
    }
    ret = av_frame_make_writable(frame);
    if (ret < 0)
    {
        qDebug() << "Av frame make writable failed.";
        return nullptr;
    }

    //转换颜色空间为YUV420
    cv::cvtColor(inMat, inMat, cv::COLOR_BGR2YUV_I420);

	//按YUV420格式,设置数据地址
    int frame_size = width * height;
    unsigned char *data = inMat.data;
    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);

    return frame;
}

你可能感兴趣的:(音视频流媒体,opencv,实时音视频)