FFMpeg 解码视频流后最右边有一条黑边的解决办法
文章目录
今天在使用 FFMpeg 解码 iPad 过来的视频流时,有一个非常奇怪的现象,即对于分辨率为 406*720 和 450*720 的图像来说,解码一切正常,但是把 YUV420 的数据转为 RGBA 显示出来时,图像的最右边会有一条几个像素的黑竖条,如下图所示:
碰到这个问题,通常的解决思路如下。
第一,检查一下解码是否有错误,这个好办,只要解码不返回错误码就是正常。
第二,如果解码没有问题的话,仔细查看各步的图片格式转化操作是否有问题。
1. 先看看解码出来的 yuv 数据是否就有这条黑边。可以直接把 yuv 保存成二进制文件,然后用 yuvplayer 播放。
如果是使用 FFMpeg 解码,则可以使用如下的代码来保存 yuv (平面格式)数据:
<pre class="src src-cpp"><span style="color: #99968b">//</span><span style="color: #99968b">保存yuv数据</span>
static int fcount = 0; char yuvdumpName[255];
sprintf(yuvdumpName, “E:/temp/yuv/%d.yuv”, fcount); FILE *fp = fopen(yuvdumpName, “ab”); if (fp) {
<span style="color: #92a65e;font-weight: bold">char</span>* <span style="color: #cae682">yuv</span> = (<span style="color: #92a65e;font-weight: bold">char</span>*)src_frame->data[0];
<span style="color: #92a65e;font-weight: bold">int</span> <span style="color: #cae682">stride</span> = src_frame->linesize[0];
<span style="color: #8ac6f2;font-weight: bold">for</span>(<span style="color: #92a65e;font-weight: bold">int</span> <span style="color: #cae682">y</span>=0; y<720; y++) {
fwrite(yuv+stride*y, 1, 540, fp);
}
<span style="color: #92a65e;font-weight: bold">char</span>* <span style="color: #cae682">udata</span> = (<span style="color: #92a65e;font-weight: bold">char</span>*)src_frame->data[1];
<span style="color: #92a65e;font-weight: bold">int</span> <span style="color: #cae682">ustride</span> = src_frame->linesize[1];
<span style="color: #8ac6f2;font-weight: bold">for</span>(<span style="color: #92a65e;font-weight: bold">int</span> <span style="color: #cae682">u</span>=0; u<720/2; u++) {
fwrite(udata+u*ustride, 1, 540/2, fp);
}
<span style="color: #92a65e;font-weight: bold">char</span>* <span style="color: #cae682">vdata</span> = (<span style="color: #92a65e;font-weight: bold">char</span>*)src_frame->data[2];
<span style="color: #92a65e;font-weight: bold">int</span> <span style="color: #cae682">vstride</span> = src_frame->linesize[2];
<span style="color: #8ac6f2;font-weight: bold">for</span>(<span style="color: #92a65e;font-weight: bold">int</span> <span style="color: #cae682">v</span>=0; v<720/2; v++) {
fwrite(vdata+v*vstride, 1, 540/2, fp);
}
fclose(fp);
fcount++;
}
<p>
在我这里有一个特别奇怪的现象,分辨是 540*720,但是解码后得到的 YUV 的 stridesize 是: 576, 288, 288。经过查找资料,原来为了方便各种格式转换及效率,通常是把分辨率的宽度进行了 16 倍整数化,方便硬件加速。
</p>
<p>
在我查看了 yuv 图片后,还是一切正常,真是怪事,只能继续找原因了。
</p></p>
2. 既然 yuv 数据没有黑边,则把 sws_scale 转成 BGRA 数据后的图片保存成位图,看是否有黑边。
可以使用如下的代码来保存 BGRA 的数据:
<pre class="src src-cpp"><span style="color: #92a65e;font-weight: bold">void</span> <span style="color: #cae682">saveBmpFile</span>(<span style="color: #92a65e;font-weight: bold">uint8_t</span> *<span style="color: #cae682">src</span>,<span style="color: #92a65e;font-weight: bold">int</span> <span style="color: #cae682">srcW</span>,<span style="color: #92a65e;font-weight: bold">int</span> <span style="color: #cae682">srcH</span>,<span style="color: #92a65e;font-weight: bold">int</span> <span style="color: #cae682">srcStride</span>)
{ static int fileIndex = 0;
<span style="color: #92a65e;font-weight: bold">char</span> <span style="color: #cae682">filename</span>[256];
sprintf(filename,<span style="color: #95e454">"e:\\temp\\yuv\\bmp\\%d.bmp"</span>, fileIndex++);
<span style="color: #92a65e;font-weight: bold">BITMAPINFOHEADER</span> <span style="color: #cae682">bih</span>;
<span style="color: #92a65e;font-weight: bold">BITMAPFILEHEADER</span> <span style="color: #cae682">bhh</span>;
<span style="color: #92a65e;font-weight: bold">int</span> <span style="color: #cae682">widthStep</span> = srcStride; <span style="color: #99968b">//</span><span style="color: #99968b">(((src->m_width * 24) + 31) & (~31)) / 8 ; </span>
bih.biSize=40; <span style="color: #99968b">// </span><span style="color: #99968b">header size </span>
bih.biWidth= srcW;
bih.biHeight= abs(srcH);
bih.biPlanes=1;
bih.biBitCount=32; <span style="color: #99968b">// </span><span style="color: #99968b">RGB encoded, 24 bit </span>
bih.biCompression=BI_RGB; <span style="color: #99968b">// </span><span style="color: #99968b">no compression</span>
bih.biSizeImage=srcStride*abs(srcH);
bih.biXPelsPerMeter=0;
bih.biYPelsPerMeter=0;
bih.biClrUsed=0;
bih.biClrImportant=0;
bhh.bfType = ((WORD) (<span style="color: #95e454">'M'</span> << 8) | <span style="color: #95e454">'B'</span>); <span style="color: #99968b">//</span><span style="color: #99968b">'BM' </span>
bhh.bfSize = (<span style="color: #92a65e;font-weight: bold">DWORD</span>)<span style="color: #8ac6f2;font-weight: bold">sizeof</span>(BITMAPFILEHEADER) + (<span style="color: #92a65e;font-weight: bold">DWORD</span>)<span style="color: #8ac6f2;font-weight: bold">sizeof</span>(BITMAPINFOHEADER) + widthStep * abs(srcH);
bhh.bfReserved1 = 0;
bhh.bfReserved2 = 0;
bhh.bfOffBits = (<span style="color: #92a65e;font-weight: bold">DWORD</span>)<span style="color: #8ac6f2;font-weight: bold">sizeof</span>(BITMAPFILEHEADER) + (<span style="color: #92a65e;font-weight: bold">DWORD</span>)<span style="color: #8ac6f2;font-weight: bold">sizeof</span>(BITMAPINFOHEADER);
<span style="color: #92a65e;font-weight: bold">FILE</span> *<span style="color: #cae682">fp</span> = fopen(filename, <span style="color: #95e454">"wb"</span>);
<span style="color: #8ac6f2;font-weight: bold">if</span>( fp ) {
src = src + (abs(srcH)-1)*srcStride;
fwrite(&bhh,1,<span style="color: #8ac6f2;font-weight: bold">sizeof</span>(bhh),fp);
fwrite(&bih,1,<span style="color: #8ac6f2;font-weight: bold">sizeof</span>(bih),fp);
<span style="color: #8ac6f2;font-weight: bold">for</span>(<span style="color: #92a65e;font-weight: bold">int</span> <span style="color: #cae682">y</span>=0; y<abs(srcH); y++) {
fwrite(src,1,srcW*4,fp);
src -= srcStride;
}
fclose(fp);
}
}
<p>
如果使用了 Qt 库,也可以直接这样保存图片:
</p>
<pre class="src src-cpp"><span style="color: #92a65e;font-weight: bold">AVFrame</span> *<span style="color: #cae682">pFrameRGB</span> = avcodec_alloc_frame();
//some decode operation
static int index = 0; QImage image( pFrameRGB->data[0], codec->width, codec->height, QImage::Format_RGB32 ); image = image.copy(); image.save(QString(“E:/temp/yuv/jpg/%1.jpg”).arg(index)); index++;
<p>
在我这里,恰恰是第2步使用 sws_scale 转换图片格式后出现的黑边。我原来 sws_scale 的使用方法如下:
</p>
<pre class="src src-cpp"><span style="color: #92a65e;font-weight: bold">SwsContext</span> *<span style="color: #cae682">img_convert_ctx_temp</span>;
img_convert_ctx_temp = sws_getContext( codec->width, codec->height, codec->pix_fmt, dst_w, dst_h, (PixelFormat)dst_fmt, SWS_BICUBIC, NULL, NULL, NULL);
sws_scale(img_convert_ctx_temp, src_frame->data, src_frame->linesize, 0, codec->height, pFrameRGB->data, pFrameRGB->linesize);
<p>
事实上问题就出在这里,是 sws_getContext 这个函数的参数需要带上 SWS_ACCURATE_RND,即修改成如下方式:
</p>
<pre class="src src-cpp"><span style="color: #92a65e;font-weight: bold">SwsContext</span> *<span style="color: #cae682">img_convert_ctx_temp</span>;
img_convert_ctx_temp = sws_getContext( codec->width, codec->height, codec->pix_fmt, dst_w, dst_h, (PixelFormat)dst_fmt, SWS_BICUBIC | SWS_ACCURATE_RND, NULL, NULL, NULL);
<p>
但是加了这个参数后,运算速度就变得巨慢了。
</p>
<p>
有一个更好的解决办法,就是在解码后把图片的 yuv 格式转成 BGRA 格式之前,先考虑一下设置 RGBA 格式的图像的宽度是 16 的倍数:
</p>
<pre class="src src-cpp"><span style="color: #92a65e;font-weight: bold">PixelFormat</span> <span style="color: #cae682">dst_fmt</span> = PIX_FMT_BGRA;
int dst_w = (codec->width/16)*16; int dst_h = codec->height;
<p>
这样在你以后用 sws_scale 时就可以直接使用 dst_w 这样的16的倍数来进行操作。这样,黑边的问题就解决了,黑边得到了裁剪,并且并不会耗费大量的计算时间,不过缺点是图像的分辨率也变小了。*最佳的解决办法,是在压缩流的输出端就保证分辨率的宽度值是 16 的整数倍*。
</p>
<p>
PS:我这里碰到这个问题,有可能是因为使用的 FFMpeg 库被修改过,用 SSE4 加速了 rgb 到 yuv420 的转换操作。原版的 FFMpeg 在这些奇怪的分辨率下是否会是如此,我没有验证。
</p></p>
文章作者 cookwhy
上次更新 2014-11-05