最近做摄像头和麦克风实时采集推流的项目中,需要用OpenCV做实时美颜。需要将原始数据AVFame转换为Mat数据格式,进行美颜处理后再转成AVFrame进行编码,推流到流媒体服务器。
其它相关博文也有很多转换的介绍,思路是对的,但使用总是有些问题,多数是处理不到位,转换出来的AVFrame显示绿屏,甚至调用直接崩溃。于是自己研究了下原理,更改了处理逻辑。如果有需要转换朋友的不用趟坑了,可以直接复制我的转换代码使用,长时间测试验证绝对好使,如发现有优化处也请不吝赐教。
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;
}
借鉴上步,首先也是想利用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;
}