項目最近需要實作播放視訊功能,這個在上家公司就做過。雖然跟之前的場景不一樣,有以前的功底還是很快可以解決,事實也确實如此。在使用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
<code>bmpInfoHdr.biPlanes = 1;</code>
<code>bmpInfoHdr.biBitCount = 24;</code>
<code>bmpInfoHdr.biWidth = pAvCdcCtx->width;</code>
<code>bmpInfoHdr.biHeight = pAvCdcCtx->height;</code>
<code>bmpInfoHdr.biSizeImage = nBytes;</code>
<code>bmpInfoHdr.biSize =</code><code>sizeof</code><code>(bmpInfoHdr);</code>
<code>//建立DIB HBITMAP</code>
<code>hBmpShow = CreateDIBSection(NULL, (BITMAPINFO*)&bmpInfoHdr, DIB_RGB_COLORS, (</code><code>void</code><code>**)&pRgbData, NULL, 0);</code>
<code>if</code> <code>(!hBmpShow) {</code>
<code> </code><code>itrace(</code><code>"CreateDIBSection failed %d"</code><code>, GetLastError());</code>
<code> </code><code>continue</code><code>;</code>
<code>}</code>
<code>memcpy</code><code>(pRgbData, pBmpRgbData, nBytes);</code>
<code>//顯示圖檔</code>
<code>hBmpBackup = (</code><code>HBITMAP</code><code>)m_memDc.SelectObject(hBmpShow);</code>
<code>m_pShowDc->StretchBlt(0, 0, m_width, m_height, &m_memDc, 0, 0,</code>
<code> </code><code>pAvCdcCtx->width, pAvCdcCtx->height, SRCCOPY);</code>
結果發現現實視訊效果極差,轉而研究ffplay代碼,發現ffplay分讀線程與解碼線程。懷疑是播放前未能讀取足夠的視訊幀進行緩存,導緻視訊在解碼播放過程中出現因讀取視訊占用時間導緻效果極差的原因。于是在代碼中添加了讀/解碼線程,修改後發現播放效果沒有任何改善。于是排除幀緩沖導緻播放問題,這時候看到了yuv viewer代碼,發現其顯示是通過StretchDIBits實作,且不需要通過CreateDIBSection建立HBITMAP對象。嘗試修改代碼,播放效果非常好,代碼如下
<code>m_pShowDc->SetStretchBltMode(STRETCH_DELETESCANS);</code>
<code>StretchDIBits(m_pShowDc->m_hDC, 0, 0, m_width, m_height,</code>
<code> </code><code>0, 0, pAvCdcCtx->width, pAvCdcCtx->height,</code>
<code> </code><code>pBmpRgbData, (BITMAPINFO*)&bmpInfoHdr, DIB_RGB_COLORS, SRCCOPY);</code>
====視訊定位
可以通過前進或者後退多少秒以及百分比對視訊進行定位,其實都是擷取其絕對時間通過av_rescale_q轉成ffmpeg所需要的時間格式,進行視訊定位。
我們知道可以通過av_q2d(m_pAvFmtCtx->streams[i]->time_base)* pAvFrame->best_effort_timestamp來擷取目前播放時間
<code>AVRational bp = {1, AV_TIME_BASE};</code>
<code>target_pos = av_rescale_q(target_pos, bp, m_pAvFmtCtx->streams[idx]->time_base);</code>
<code>av_seek_frame(m_pAvFmtCtx, idx, target_pos, AVSEEK_FLAG_ANY);</code>