项目最近需要实现播放视频功能,这个在上家公司就做过。虽然跟之前的场景不一样,有以前的功底还是很快可以解决,事实也确实如此。在使用DShow处理完视频分割与合并后,继续使用DShow显示视频,很快即完成。然而在播放dvr录制的视频文件时,发现播放帧率不对,分析发现是dvr存储的视频文件不是按标准格式进行存储(使用ffplay效果还好点,media player根本没法播放),于是重写代码。
先简要说明一下项目:client是delphi开发的GUI程序,视频所有操作功能都由mfc dll实现,这个dll也就是由我实现。delphi只传入要显示视频的窗口句柄、操作类型、文件名,这个跟我在以前设计但未能完工的显示流媒体库有不少借鉴作用,因此在此记录一下。
使用ffmpeg一直到读取文件每一帧、解码,剩下就是显示的工作:解码每一帧的rgb数据在CDC上显示,显示过程中一开始通过CreateDIBSection创建一个HBITMAP对象,memorydc中选入,然后在显示cdc中StretchBlt,代码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
bmpInfoHdr.biPlanes = 1; bmpInfoHdr.biBitCount = 24; bmpInfoHdr.biWidth = pAvCdcCtx->width; bmpInfoHdr.biHeight = pAvCdcCtx->height; bmpInfoHdr.biSizeImage = nBytes; bmpInfoHdr.biSize = sizeof (bmpInfoHdr); //创建DIB HBITMAP hBmpShow = CreateDIBSection(NULL, (BITMAPINFO*)&bmpInfoHdr, DIB_RGB_COLORS, ( void **)&pRgbData, NULL, 0); if (!hBmpShow) { itrace( "CreateDIBSection failed %d" , GetLastError()); continue ; } memcpy (pRgbData, pBmpRgbData, nBytes); //显示图片 hBmpBackup = ( HBITMAP )m_memDc.SelectObject(hBmpShow); m_pShowDc->StretchBlt(0, 0, m_width, m_height, &m_memDc, 0, 0, pAvCdcCtx->width, pAvCdcCtx->height, SRCCOPY); |
结果发现现实视频效果极差,转而研究ffplay代码,发现ffplay分读线程与解码线程。怀疑是播放前未能读取足够的视频帧进行缓存,导致视频在解码播放过程中出现因读取视频占用时间导致效果极差的原因。于是在代码中添加了读/解码线程,修改后发现播放效果没有任何改善。于是排除帧缓冲导致播放问题,这时候看到了yuv viewer代码,发现其显示是通过StretchDIBits实现,且不需要通过CreateDIBSection创建HBITMAP对象。尝试修改代码,播放效果非常好,代码如下
1
2
3
4
|
m_pShowDc->SetStretchBltMode(STRETCH_DELETESCANS); StretchDIBits(m_pShowDc->m_hDC, 0, 0, m_width, m_height, 0, 0, pAvCdcCtx->width, pAvCdcCtx->height, pBmpRgbData, (BITMAPINFO*)&bmpInfoHdr, DIB_RGB_COLORS, SRCCOPY); |
====视频定位
可以通过前进或者后退多少秒以及百分比对视频进行定位,其实都是获取其绝对时间通过av_rescale_q转成ffmpeg所需要的时间格式,进行视频定位。
我们知道可以通过av_q2d(m_pAvFmtCtx->streams[i]->time_base)* pAvFrame->best_effort_timestamp来获取当前播放时间
1
2
3
|
AVRational bp = {1, AV_TIME_BASE}; target_pos = av_rescale_q(target_pos, bp, m_pAvFmtCtx->streams[idx]->time_base); av_seek_frame(m_pAvFmtCtx, idx, target_pos, AVSEEK_FLAG_ANY); |