引言:H.264編碼技術是俱樂部在過去一段時間內研究的一個方向,對該編碼技術進行過實際的開發和應用,並取得了很大的收穫。下面將重點介紹H.264視頻編碼在VC++.Net中的實現。
1. H.264編碼的介紹
H.264是一種視頻高壓縮技術,全稱是MPEG-4 AVC,用中文說是“活動圖像專家組-4的高等視頻編碼”,或稱爲MPEG-4 Part10。它是由國際電信標準化部門ITU-T和規定MPEG的國際標準化組織ISO/國際電工協會IEC共同制訂的一種活動圖像編碼方式的國際標準格式。由於H.264在制定時就充分考慮了多媒體通信對視頻編解碼的各種要求,並借鑑了H系列和MPEG系列視頻標準的研究成果,因而具有明顯的優勢。H.264作爲最新的國際建議標準,在IP視頻監控系統中有着重要的意義。它與目前的Mpeg4和H.263編碼相比較,優勢表現在以下幾個方面:
1) 壓縮率和圖像質量方面
H.264通過對傳統的幀內預測、幀間預測、變換編碼和熵編碼等算法的改進來進一步提高編碼效率和圖像質量。在相同的重建圖像質量下,H.264比H.263節約50%左右的碼率,比Mpeg4節約35%左右。
2) 網絡適應性方面
H.264支持不同網絡資源下的分級編碼傳輸,從而獲得平穩的圖像質量。H.264能適應於不同網絡中的視頻傳輸,網絡親和性好。H.264的基本系統無需使用版權,具有開放的性質,能很好地適應IP和無線網絡的使用,這對目前的因特網傳輸多媒體信息、移動網中傳輸寬帶信息等都具有重要的意義。
3) 抗丟包和抗誤碼方面
H.264具有較強的抗誤碼特性,可適應丟包率高、干擾嚴重的信道中的視頻傳輸。
實際應用中,實時性和較好的圖像質量,較低的網絡帶寬佔用以及帶寬適應能力是監控系統的主要考慮因素。H.264 相比較以前的視頻編碼標準,主要在網絡接口友好性和高的壓縮性能上有了很大的提高。綜合以上因素在本系統中採用H.264作爲視頻數據的編碼方式。
2. H.264編碼的實現原理
1) 量化
H. 264爲了提高碼率控制的能力, 量化步長的變化的幅度控制在12.5 %左右, 而不是以不變的增幅變化。變換系數幅度的歸一化被放在反量化過程中處理以減少計算的複雜性。爲了強調彩色的逼真性,對色度係數採用了較小量化步長。
2) 運動補償
宏塊劃分方式: H. 264 支持形狀不等的宏塊劃分, 其劃分方法有: 16×16, 16×8, 8×16, 8×8, 8×4, 4×8, 4×4. 這種更小的、更多形狀的的宏塊劃分,更切合圖形中實際運動物體的形狀, 改善運動補償的精度, 更好的實現運動隔離, 提高圖像質量和編碼效率. 搜索精度: H. 264 支持1/4 和1/8 精度的運動補償, 使用一個6 抽頭濾波器從整像素樣本的到1/2 像素樣本, 用線性插值獲得1/4 像素樣本, 用8 抽頭濾波器實現1/8 像素精度。 多參考幀模式: H. 264 在對週期性的運動或背景切換進行預測時, 多參考幀可以提供更好的預測效果。
3) 幀內預測
4) 變換編碼
5) 熵編碼
3 H.264編碼算法的實現
在H.264編碼具體實現過程中,採用了目前國際上應用最廣泛的開源編碼器X.264作爲實現的基礎。X.264和JM系列編碼器、T.264編碼器相比有着優秀的性能和出色效果。由於X.264沒有提供直接的開發API,所以在本系統中的編碼部分重新封裝了X.264的編碼API,便於軟件系統的設計和使用。以下是本系統中H.264編碼的具體實現過程:
1) RGB和YUV顏色空間的轉換
在系統中通過Logitech攝像頭獲得的視頻數據爲RGB24格式,但是X.264的輸入流爲標準的YUV(4:2:0)的圖像子採樣格式。因此,在編碼前需要將RGB顏色空間轉換爲YUV的顏色空間。實現的函數調用有InitLookupTable()用於初始化色彩空間轉換;
RGB2YUV420(int x_dim, int y_dim, unsigned char *bmp, unsigned char *yuv, int flip);用於實際的轉換。由於人眼的生理特性,經過圖像子採樣後,實際的圖像大小已經減小爲採樣前的1.5個樣本點,即減小了一半的數據量。
2) 設置H.264編碼參數
使用x264_param_default(x264_param_t *param)對當前需要編碼的圖像參數進行設置。包括數據幀數量(param .i_frame_total)、採樣圖像的長寬度和高度(param .i_width,param .i_height)、視頻數據比特率(param .rc.i_bitrate) 、視頻數據幀率(param .i_fps_num)等參數進行設置,以完成編碼前預設置。
3) 初始化編碼器
將上步中的設置作爲編碼器初始化的參數,x264_t *x264_encoder_open ( x264_param_t *param )。如果初始化失敗將返回NULL,在這裏需要對編碼器初始化結果進行處理。
4) 分配編碼空間
如果編碼器初始化成功,則需要爲本次處理分配內存空間
Void x264_picture_alloc(x264_picture_t *pic, int i_csp, int i_width, int i_height)。
5) 圖像編碼
將以上步驟初始化後的數據作爲編碼輸入,使用下面的方法進行編碼:
int x264_encoder_encode( x264_t *h,x264_nal_t **pp_nal, int *pi_nal,x264_picture_t *pic_in,x264_picture_t *pic_out );
6) 資源回收
編碼完成後,需要回收系統資源和關閉編碼器,使用以下函數調用實現回收。
void x264_picture_clean( x264_picture_t *pic );
void x264_encoder_close( x264_t *h );
至此,完成了H.264編碼,編碼後的數據量將大大減小。我們可以對編碼後的數據做相關的進一步處理。
4 H.264編碼算法的完整源代碼
文件:VideoEncoderX264.h
class CVideoEncoderX264 :
{
public:
CVideoEncoderX264(void);
~CVideoEncoderX264(void);
virtual bool Connect(CVideoEnDecodeNotify* pNotify, const CVideoEnDecodeItem& Item);
virtual void Release(void);
virtual void Encode(BYTE* pInData, int nLen, BYTE* pOutBuf, int& nOutLen, int& nKeyFrame);
private:
x264_picture_t m_Pic;
x264_t *h;
x264_param_t param;
void Flush(void);
};
文件:VideoEncoderX264.cpp
bool CVideoEncoderX264::Connect(CVideoEnDecodeNotify* pNotify, const CVideoEnDecodeItem& Item)
{
CBase::Connect(pNotify, Item);
ParseSize(Item.m_stSize);
x264_param_default( ¶m );
param.i_threads = 1;
param.i_frame_total = 0;
param.i_width = m_nWidth;
param.i_height = m_nHeight;
param.i_keyint_min = Item.m_nKeyInterval;
param.i_keyint_max = Item.m_nKeyInterval * 10;
param.i_fps_num = Item.m_nFps;*/
param.i_log_level = X264_LOG_NONE;
if( ( h = x264_encoder_open( ¶m ) ) == NULL )
{
return false;
}
/* Create a new pic */
x264_picture_alloc( &m_Pic, X264_CSP_I420, param.i_width, param.i_height );
return true;
}
void CVideoEncoderX264::Release(void)
{
Flush();
x264_picture_clean( &m_Pic );
x264_encoder_close( h );
CBase::Release();
}
void CVideoEncoderX264::Encode(BYTE* pInData, int nLen, BYTE* pOutBuf, int& nOutLen, int& nKeyFrame)
{
if(nLen != param.i_width * param.i_height * 3)
return;
param.i_frame_total ++;
memcpy(m_Pic.img.plane[0], pInData, param.i_width * param.i_height);
memcpy(m_Pic.img.plane[1], pInData + param.i_width * param.i_height, param.i_width * param.i_height / 4);
memcpy(m_Pic.img.plane[2], pInData + param.i_width * param.i_height * 5 / 4, param.i_width * param.i_height / 4);
m_Pic.i_pts = (int64_t)param.i_frame_total * param.i_fps_den;
static x264_picture_t pic_out;
x264_nal_t *nal = NULL;
int i_nal, i;
if( &m_Pic )
{
m_Pic.i_type = X264_TYPE_AUTO;
m_Pic.i_qpplus1 = 0;
}
//TraceTime("x264_encoder_encode begin");
if( x264_encoder_encode( h, &nal, &i_nal, &m_Pic, &pic_out ) < 0 ) {
return;
}
//TraceTime("x264_encoder_encode end");
int nOutCanUse = nOutLen;
nOutLen = 0;
for( i = 0; i < i_nal; i++ )
{
int i_size = 0;
if( ( i_size = x264_nal_encode( pOutBuf + nOutLen, &nOutCanUse, 1, &nal[i] ) ) > 0 )
{
nOutLen += i_size;
nOutCanUse -= i_size;
}
}
nKeyFrame = pic_out.i_type==X264_TYPE_IDR;// || (pic_out.i_type==X264_TYPE_I && coCfg->x264_max_ref_frames==1);
}
void CVideoEncoderX264::Flush(void)
{
x264_picture_t pic_out;
x264_nal_t *nal;
int i_nal, i;
int i_file = 0;
if( x264_encoder_encode( h, &nal, &i_nal, NULL, &pic_out ) < 0 ){
}
}