拼接YUV420P图像

最近需要实现视频的左右拼接,从而可以实现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, // from cols=0,all rows trans
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++) { //Y
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);
}
更早的文章

FISCOBCOS~2.4动态群组试用

续前文,近期 FISCO BCOS 2.4 版本发布了,最核心的特性是动态群组管理,这个功能免去了手动部署新群组的过程,极大的方便了群组管理。 新版本部署FISCO BCOS 2.4 版本部署前一版本基本没有区别,注意 2.3 版本部署的时候配置文件稍有变化(依然兼容以前配置)。按照以前的部署方法 …

技术 继续阅读