我正在编写一个视频会议软件,我有一个 H.264 流,使用 libavcoded 解码为 IYUV,然后在无窗口模式下使用 VMR9 渲染到窗口中。我使用 DirectShow 图形来执行此操作。
为了避免不必要的 RGB 和反转换(请参阅link https://stackoverflow.com/questions/23567621/does-vmr9-support-native-yuv-rendering),我使用 libswscale 将 IYUV 视频转换为 YUY2,然后将其传递到 VMR9。
我注意到视频分辨率为 848x480 时,输出视频会损坏,因此我进一步调查并发现,对于某些分辨率,视频总是会损坏。为了将 libswscale 排除在阐述之外,我添加了对 IYUV+padding 到 IYUV 转换的支持,并且它适用于所有分辨率。
尽管如此,我还是愿意避免缓慢的 IYUV,因此我实现了对 NV12(使用 libswscale)和 YV12(手动,本质上与 IYUV 相同)的支持。在两台不同的计算机上进行一些测试后,我得出了奇怪的结果。
resolution YUY2 NV12 IYUV YV12
PC 1 (my laptop)
640x360 ok broken ok broken
848x480 broken broken ok broken
960x540 broken broken ok broken
1024x576 ok ok ok ok
1280x720 ok ok ok broken
1920x1080 ok broken ok broken
PC 2
640x360 ok ok ok ok
848x480 ok broken ok broken
960x540 ok ok ok ok
1024x576 ok ok ok ok
1280x720 ok broken ok ok
1920x1080 ok ok ok ok
为了排除VMR9故障,我用EVR代替它,但结果相同。
I know that padding is needed for memory alignment, and that the size of padding depends on CPU used (libavcodec doc https://ffmpeg.org/doxygen/trunk/structAVFrame.html#aa52bfc6605f6a3059a0c3226cc0f6567), that may explain difference between two computers(first has Intel i7-3820QM, the second Intel Core 2 Quad Q6600). I suppose it has something to do with padding, because images are corrupted in certain way.
You can see my blue t-shirt in lower part of image, and my face in the upper one.
接下来是转换的代码。 NV12 和 YUY2 转换使用 libswscale 执行,而 IYUV 和 YV12 则手动执行。
int pixels = _outputFrame->width * _outputFrame->height;
if (_outputFormat == "YUY2") {
int stride = _outputFrame->width * 2;
sws_scale(_convertCtx, _outputFrame->data, _outputFrame->linesize, 0, _outputFrame->height, &out, &stride);
}
else if (_outputFormat == "NV12") {
int stride[] = { _outputFrame->width, _outputFrame->width };
uint8_t * dst[] = { out, out + pixels };
sws_scale(_convertCtx, _outputFrame->data, _outputFrame->linesize, 0, _outputFrame->height, dst, stride);
}
else if (_outputFormat == "IYUV") { // clean ffmpeg padding
for (int i = 0; i < _outputFrame->height; i++) // copy Y
memcpy(out + i * _outputFrame->width, _outputFrame->data[0] + i * _outputFrame->linesize[0] , _outputFrame->width);
for (int i = 0; i < _outputFrame->height / 2; i++) // copy U
memcpy(out + pixels + i * _outputFrame->width / 2, _outputFrame->data[1] + i * _outputFrame->linesize[1] , _outputFrame->width / 2);
for (int i = 0; i < _outputFrame->height / 2; i++) // copy V
memcpy(out + pixels + pixels/4 + i * _outputFrame->width / 2, _outputFrame->data[2] + i * _outputFrame->linesize[2] , _outputFrame->width / 2);
}
else if (_outputFormat == "YV12") { // like IYUV, but U is inverted with V plane
for (int i = 0; i < _outputFrame->height; i++) // copy Y
memcpy(out + i * _outputFrame->width, _outputFrame->data[0] + i * _outputFrame->linesize[0], _outputFrame->width);
for (int i = 0; i < _outputFrame->height / 2; i++) // copy V
memcpy(out + pixels + i * _outputFrame->width / 2, _outputFrame->data[2] + i * _outputFrame->linesize[2], _outputFrame->width / 2);
for (int i = 0; i < _outputFrame->height / 2; i++) // copy U
memcpy(out + pixels + pixels / 4 + i * _outputFrame->width / 2, _outputFrame->data[1] + i * _outputFrame->linesize[1], _outputFrame->width / 2);
}
out
是一个输出缓冲区。_outputFrame
是libavcodec输出的AVFrame。_convertCtx
初始化如下。
if (_outputFormat == "YUY2")
_convertCtx = sws_getContext(_width, _height, AV_PIX_FMT_YUV420P,
_width, _height, AV_PIX_FMT_YUYV422, SWS_FAST_BILINEAR, nullptr, nullptr, nullptr);
else if (_outputFormat == "NV12")
_convertCtx = sws_getContext(_width, _height, AV_PIX_FMT_YUV420P,
_width, _height, AV_PIX_FMT_NV12, SWS_FAST_BILINEAR, nullptr, nullptr, nullptr);
问题:
- 手动转换是否正确?
- 我的假设正确吗?
- 前面两个答案都是肯定的,问题出在哪里?尤其是...
- 为什么它只显示某些分辨率而不显示其他分辨率?
- 我可以提供哪些额外信息?