需求:把两路视频合成一路,即一个画面同时显示两路视频,其中一路缩小成小视频叠在大视频上面,和电视机的画中画效果类似。
思路:用h264编码的视频举例,文件中存储的es流是h264,经过解码成yuv,yuv可以转换成rgb格式。把小视频的rgb复制到大视频需要被覆盖的位置上。将重新合成的rgb转换成yuv,利用ffmpeg 或 x264重新编码出新的视频即可。
方法:编解码还是利用ffmpeg 。 ffmpeg 解码两路视频,解码后都是yuv。利用ffmpeg· 的sws_getContext 函数改变小图的大小。之后利用OpenCV完成两个图片的合成(opencv这种高大上的库被我用成了这样····实在汗颜。其实此处的合并rgb可以自己写算法实现,本质是把小图的rgb复制到大图的对应位置上。)合成好后将rbg转成yuv格式,利用x264重新编码成h264 ,就看到了大视频左上角有个小视频了。
代码思路:
两个线程,各自解码,主视频的线程解码一帧后通知副视频的线程进行解码并转化图片大小,副视频的线程解码完成后通知主线程合成视频并编码。合成视频的时候用opencv很简单,直接把yuv转化成rgb,之后在主视频上设置敏感区,把小视频叠加上就行。
主副线程解码套路一样,ffmpeg的基本使用套路。把yuv转成opencv mat类型的rgb套路也一样,上副视频解码线程的代码进行说明。
全局变量:
cv::Mat littlergb,bigrgb;//大小视频的rgb
cv::Mat littleframe,bigframe;//大小视频的yuv
副视频解码线程内的代码:
while(av_read_frame(pInputFormatContext, &InPack) >=0)
{
len = avcodec_decode_video2(pInputCodecContext, &OutFrame, &nComplete, &InPack);//解码视频
if (nComplete>0)
{
if (GetMessage(&msg, NULL, 0, 0))
{
switch(msg.message)
{
case MY_MSG_DECODE:
sws_scale(m_pSwsContext,OutFrame.data,OutFrame.linesize, 0,OutFrame.height,dst->data,dst->linesize);//转换图片大小
memcpy(littleframe.data,dst->data[0], 640*480); //以下将ffmpeg的yuv数据存到opencv的mat类型中。即opencv存储的yuv数据
memcpy(littleframe.data+640*480,dst->data[1], 640*480/4);
memcpy(littleframe.data+640*480*5/4,dst->data[2], 640*480/4);
SetEvent(hEncodeEvent);
break;
}
}
}
av_free_packet(&InPack);
}
合并图片直接用opencv,代码如下:
cv::cvtColor(littleframe, littlergb,CV_YUV2BGR_I420);
cv::cvtColor(bigframe, bigrgb,CV_YUV2BGR_I420); //以上yuv转rgb
Mat roi(bigrgb,Rect(0,0,640,480));//大图上设置敏感区
littlergb.copyTo(roi); //把小图拷贝过去
Mat outframe;
cv::cvtColor(bigrgb, outframe,CV_BGR2YUV_I420); //rgb到yuv
这样就获得了合并后的图片的yuv。
之后进行编码即可。编码出来的视频就是画中画了。