最近需要实现视频的左右拼接,从而可以实现2路摄像头的同时显示,调研使用了几种实现方式进行了对比。
ffmpeg方式 谈到视频方面的处理,肯定首选 ffmpeg
,想要实现上面的功能,其实 ffmpeg
就能实现,使用其 filter
就可以,大概思路是:
使用 pad
扩展出另外一个图像的空间
使用 overlay
复制另外一个图像到扩展出来的空间
整体实现不难,后续再讲一下这个,因为可以实现很多功能,但是对于我们目前的需求来说,这个功能致命之处在于 ffmpeg
需要两次处理才能实现,本机实测大概需要 110 毫秒,对于直播来说肯定是致命的。
因此就想到了另外一种方式,即手动进行内容拼接,毕竟两个图像大小相同且不需要其他处理。
OpenCV方式 OpenCV
也是处理图像的专业库,不过其使用的是自身的 cv::Mat
数据结构,以下为把 ffmpeg
解码出来的一帧进行复制拼接
struct SwsContext *sws_ctx = NULL ;sws_ctx = sws_getContext(avframe->width, avframe->height, (enum AVPixelFormat)avframe->format, w, h, AV_PIX_FMT_BGR24, SWS_BICUBIC, NULL , NULL , NULL ); cv::Mat mat; mat.create(cv::Size(w, h), CV_8UC3); AVFrame *bgr24frame = av_frame_alloc(); bgr24frame->data[0 ] = (uint8_t *)mat.data; avpicture_fill((AVPicture *)bgr24frame, bgr24frame->data[0 ], AV_PIX_FMT_BGR24, w, h); sws_scale(sws_ctx, (const uint8_t * const *)avframe->data, avframe->linesize, 0 , avframe->height, bgr24frame->data, bgr24frame->linesize); av_free(bgr24frame); sws_freeContext(sws_ctx);
这样就搞定了,不过还得将 cv::Mat
再转回 AVFrame
来回的数据交换也挺耗时的,放弃。
手动方式 手动方式就直接拼接,不过 YUV
格式和我们熟知的 RGB
格式并不相同,其使用 Y
U
V
三个分量,按其排列方式等可分为 YUV420P
等等,至于详细的介绍,请自行百度,将会明白下面的处理逻辑。
平时最常见的是 YUV420P
我们就以该格式为例说明
网络上有不少相同例子,不过只有个别代码是对的,因为错误的例子没有考虑 AVFrame 的 linesize
for (int i = 0 ; i < frame_height; i++) { memcpy (frame->data[0 ] + i * frame_width * 2 , frame1->data[0 ] + i * frame1->linesize[0 ], frame_width); memcpy (frame->data[0 ] + frame_width + i * frame_width * 2 , frame2->data[0 ] + i * frame2->linesize[0 ], frame_width); } uint8_t * pU = frame->data[1 ];for (int j = 0 ; j < frame_height / 2 ; j++){ memcpy (pU + (frame_width / 2 ) * j * 2 , frame1->data[1 ] + frame1->linesize[1 ] * j, frame_width / 2 ); memcpy (pU + (frame_width / 2 ) * (j * 2 + 1 ), frame2->data[1 ] + frame2->linesize[1 ] * j, frame_width / 2 ); } uint8_t * pV = frame->data[2 ];for (int k = 0 ; k < frame_height / 2 ; k++){ memcpy (pV + frame_width / 2 * k * 2 , frame1->data[2 ] + frame1->linesize[2 ] * k, frame_width / 2 ); memcpy (pV + frame_width / 2 * (k * 2 + 1 ), frame2->data[2 ] + frame2->linesize[2 ] * k, frame_width / 2 ); }
经测试使用和上面的 ffmpeg
采用相同的逻辑和数据,基本耗时在 10 毫秒左右,性能差距还是挺大的。
效果图:
关于YUV保存 操作YUV之后,我们需要看结果,那么就需要将文件保存,保存方法如下
void SaveAvFrame (AVFrame *avFrame, const char * file) { FILE *fDump = fopen(file, "wb" ); uint32_t pitchY = avFrame->linesize[0 ]; uint32_t pitchU = avFrame->linesize[1 ]; uint32_t pitchV = avFrame->linesize[2 ]; uint8_t *avY = avFrame->data[0 ]; uint8_t *avU = avFrame->data[1 ]; uint8_t *avV = avFrame->data[2 ]; for (uint32_t i = 0 ; i < avFrame->height; i++) { fwrite(avY, avFrame->width, 1 , fDump); avY += pitchY; } for (uint32_t i = 0 ; i < avFrame->height / 2 ; i++) { fwrite(avU, avFrame->width / 2 , 1 , fDump); avU += pitchU; } for (uint32_t i = 0 ; i < avFrame->height / 2 ; i++) { fwrite(avV, avFrame->width / 2 , 1 , fDump); avV += pitchV; } fclose(fDump); }