所謂的OSD其實就是在視頻圖像上疊加一些字符信息,比如時間,地點,通道號等,
在圖像上疊加OSD通常有兩種方式: 一種是在前端嵌入式設備上,在圖像數據上疊加OSD,
這樣客戶端這邊只需解碼顯示數據即可。另一種是PC客戶端在接收到前端設備圖像,解碼之后,進行疊加。這兩種都是比較常見的方式。OSD具有字符型(Font-Based)和位圖型(Bit-Map)兩種類型。
字符型OSD:為了節約顯示緩存,早期及低成本的解決方案中使用字符型OSD發生器,其原理是將OSD中顯示內容按照特定的格式(12×18、12×16等)進行分割成塊,例如數字0-9、字母a-z、常用的亮度、對比度符號等,並把這些內容固化在ROM或Flash中,在顯示緩存中僅存放對應的索引號,這樣的“字典”結構可以大幅度減少顯示緩存的需求。
位圖OSD:通過對最終顯示內容上特定區域的每個像素點進行改變,直接將OSD信息疊加到最終的顯示畫面上,其按像素進行控制的方式可以保證具有多色及足夠的表現能力。
最近做一個網絡播放器, 有在播放器實時疊加OSD這個需求,正好借這個機會研究了一下位
最近做一個網絡播放器, 有在播放器實時疊加OSD這個需求,正好借這個機會研究了一下。
先說下大體流程,
- 首先,播放SDK,通過網絡模塊接收前端視頻流(經過壓縮的數據),然后進行解壓,得到一幀完整的YUV圖像,
- 然后,我們在內存中創建一個設備無關的位圖,並指定圖像數據背景色為白色,字體為黑色。通過DrawTextW將字體畫到內存DC上,
- 之后,通過GetDIBit將位圖的二進制位復制到與設備無關的位圖buffer里, 然后掃描此位圖的每一個像素點,判斷每個像素點的R,G,B三個分量之和 ,如果大於384 設置該像素為RGB(255,255,255), 否則設置為RGB(0,0,0),(384表示灰度)
- 然后根據圖像的寬高,創建一個通明通道數組,通過遍歷之前得到的設備無關位圖buffer,獲取每個像素點的R分量,如果R等於0,則設置通明通道數組中對應的值為1, 表示該像素點上需要繪制字體(換句話說,該像素點不是透明色)
這樣我們就記住了臨時圖像上OSD文字每個像素的位置。 - 接下來,我們將構造出來的bmp位圖數據進行轉換,轉換成YUV420數據,存儲在 pOSDYuvBuffer中
-
下面這一步,就是最主要的地方, 即計算OSD反色的算法,
我們遍歷透明通道數組, 若值等於1, 則說明該像素點是字體,需要繪制, 那么,我們就在源圖像(解碼后的YUV圖像)上找到位置想對應的點。並以該點為中心,計算出一個13*13的矩形區域,此區域作為背景參考區, 遍歷該矩形區域 並計算該區域的 Y分量平均值,如果平均值大於128 說明該背景區是亮色,那么,我們設置pOSDYuvBuffer相應像素點的Y分量為1(背景亮,則osd字體為黑色,反之,若背景區為暗色,則設置osd字體像素點的Y為255)
-
這樣掃描結束之后, 就實現了 pOSDYuvBuffer中的OSD字體顏色,根據背景色的反色。然后將我們構造出來的臨時圖像 疊加到源圖像上即可。
- 至於疊加操作,其實很簡單。 同樣掃描通明通道數據,如果發現不是透明色,直接將pOSDYuvBuffer中的YUV復制到 源圖像相應位置即可。
下面是流程:
反色計算算法 圖
int posAx=0, posAy=0; int posDx=0 ,posDy=0; int nBKColor = 0; for(i = 0; i < m_OSDHeigth; i++) { for(j = 0; j < m_OSDWidth; j++) { if( j+_dwStrPosX>=_VideoWidth || i+_dwStrPosY>=_VideoHeight ) continue; // 找到字符 X if( m_palpha[i*m_OSDWidth + j] )// 找到x映射在在源圖像上的13*13 背景塊 { posAx = _dwStrPosX+j - 3; // 計算背景色塊 A的坐標 if ( posAx<0 ) posAx=0; posAy = _dwStrPosY+i -3; if ( posAy<0 ) posAy =0; posDx = _dwStrPosX+j + 3; // 計算背景色塊 點D的坐標 if ( posDx >_VideoWidth) posDx =_VideoWidth; posDy = _dwStrPosY+i +3; if ( posDy >_VideoHeight) posDy =_VideoHeight; // / 計算背景色塊 長,寬 int height_ = posDy-posAy; int width_ = posDx-posAx; UINT16* pTempDesY = (UINT16*)dest_buf + posAx + posAy*_VideoWidth; int nTemp = 0; for ( m=0; m< height_; m++ )//height { for ( n=0; n<width_;n++ ) { nTemp += ( (*pTempDesY) >>2 ); pTempDesY++; } pTempDesY = (UINT16*)dest_buf + posAx + posAy*_VideoWidth + m*_VideoWidth; } // 計算Y 的平均值 nBKColor = nTemp/(height_*width_); if ( nBKColor >128 ) *pY = 0; else *pY = ( 255<<2); } pDesY++; pY++; }//j pDesY = ((UINT16*)dest_buf + _dwStrPosX + _dwStrPosY*_VideoWidth) + i*_VideoWidth; }//i
效果圖:
掃描下方二維碼。關注 【音視頻開發訓練營】
from:http://blog.csdn.net/machh/article/details/52840886