环境:ffmpeg version3.2.2 32bit shared,Windows
使用Windows GDI进行视频画面的渲染
- 在视频解码线程中进行视频的解码, 得到一帧一帧的图像数据,当前格式为YUV
- 将原始YUV格式的帧数据转化为RGB24,用于显示,这一过程在CPU中完成
bool VideoReader::readFrame(FrameInfor& infor) {
AVPacket packet;
av_init_packet(&packet);
for (;;)
{
if (av_read_frame(_formatCtx, &packet))
{
av_free_packet(&packet);
return false;
}
if (packet.stream_index != _videoIndex)
{
continue;
}
int frame_finished = 0;
int res = avcodec_decode_video2(_codecCtx, _frame, &frame_finished, &packet);
if (frame_finished)
{
AVStream* streams = _formatCtx->streams[_videoIndex];
double tmbase = av_q2d(streams->time_base);
int64_t pts = _frame->pts;
int res = sws_scale(
_convertCtx
, (const uint8_t* const*)_frame->data
, _frame->linesize
, 0
, _codecCtx->height
, _frameRGB->data
, _frameRGB->linesize
);
av_packet_unref(&packet);
infor._data = _frameRGB->data[0];
infor._dataSize = _imageSize;
infor._width = _screenW;
infor._height = _screenH;
infor._pts = _frame->pts;
infor._timeBase = av_q2d(streams->time_base);
return true;
}
}
return false;
}
- RGB转化为BGR格式,填充位图:转化格式的原因是Windows GDI的默认像素格式BGR。
BYTE* data = (BYTE*)infor._data;
// Windows GDI的默认像素格式BGR RGB=>BGR
for (int i = 0; i < infor._dataSize; i += 3)
{
// _imageBuf为指向位图实际内存的指针
_imageBuf[i + 0] = data[i + 2];
_imageBuf[i + 1] = data[i + 1];
_imageBuf[i + 2] = data[i + 0];
}
- 触发窗口重绘
// 触发窗口重绘
InvalidateRect(_hWnd,
0, // 无效区域(NULL = 整个窗口
0); // 是否擦除背景
- 主窗口的消息回调函数中处理窗口重绘消息:将在子线程中填充好的离屏位图一次性复制到窗口上显示
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc;
hdc = BeginPaint(hWnd, &ps);
// 将离屏位图一次性复制到窗口上显示
if (g_decode._hMem)
{
BITMAPINFO bmpInfor;
GetObject(g_decode._hBmp, sizeof(bmpInfor), &bmpInfor);
BitBlt(hdc, 0, 0, bmpInfor.bmiHeader.biWidth, bmpInfor.bmiHeader.biHeight, g_decode._hMem, 0, 0, SRCCOPY);
}
EndPaint(hWnd, &ps);
}
break;
播放速率的控制
- 由于解码的时间很快,因此按照视频帧的显示时间戳进行播放,而不是解码的时间。解码快了就进行休眠
bool VideoDecodeThread::run()
{
_timestamp.update();
while (!_exitFlag)
{
FrameInfor infor;
if (!_ffReader.readFrame(infor))
{
break;
}
BYTE* data = (BYTE*)infor._data;
// Windows GDI的默认像素格式BGR RGB=>BGR
for (int i = 0; i < infor._dataSize; i += 3)
{
_imageBuf[i + 0] = data[i + 2];
_imageBuf[i + 1] = data[i + 1];
_imageBuf[i + 2] = data[i + 0];
}
// 触发窗口重绘
InvalidateRect(_hWnd,
0, // 无效区域(NULL = 整个窗口
0); // 是否擦除背景
// 播放速率控制
// 帧的预定显示时间(ms)
double tims = infor._pts * infor._timeBase * 1000;
// 实际经过的时间(ms)
double elsped = _timestamp.getElapsedTimeInMilliSec();
double sleeps = (tims - elsped);
if (sleeps > 1)
{
Sleep((DWORD)sleeps);
}
}
return true;
}
OpenGL可编程渲染管线进行视频画面的渲染
- 在视频解码线程中进行视频的解码, 得到一帧一帧的原始图像数据
- 创建一个单通道纹理存储 Y、U、V,根据YUV坐标的不同进行采样
![image]()
- 通过Windows的消息传递机制将原始图像数据传递到主线程,作为GPU纹理输入
OpenGL可编程渲染管线进行视频画面的渲染优化
- RGB转换放到GPU中处理:fragment shader中进行YUV转RGB
- 利用双PBO进行异步上传纹理数据
帧1:PBO0写入数据 → PBO1上传到纹理
帧2:PBO1写入数据 → PBO0上传到纹理
帧3:PBO0写入数据 → PBO1上传到纹理

浙公网安备 33010602011771号