項目最近需要實現播放視頻功能,這個在上家公司就做過。雖然跟之前的場景不一樣,有以前的功底還是很快可以解決,事實也確實如此。在使用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);
|