視頻畫面的傳輸中磷醋,由于原始數(shù)據(jù)過大淤井,實際傳輸?shù)臄?shù)據(jù)是已經(jīng)編碼好的數(shù)據(jù)滚局,一般是H264, 當客戶端收到后就需要解碼并顯示出來。
裸流解析成AVPacket
AVCodecParser
AVCodecParser用于解析輸入的數(shù)據(jù)流并把它分成一幀一幀的壓縮編碼數(shù)據(jù)。就像是你把肉塞進火腿,再交給它負責幫你切片域慷,夠一個完整的幀就返回給你處理。
使用
int parser_len = 0;
while(parser_len < buffer.length()){
parser_len += av_parser_parse2(m_pVideoParserContext, m_pVideoDecoder->GetCodecContext(),
&pParsePacket->data, &pParsePacket->size,
(uint8_t *)(buffer.data() + parser_len), buffer.length() - parser_len,
AV_NOPTS_VALUE, AV_NOPTS_VALUE, AV_NOPTS_VALUE);
if(0 == pParsePacket->size){
continue;
}
switch(m_pVideoParserContext->pict_type){
case AV_PICTURE_TYPE_I: qDebug("Type:I\t");break;
case AV_PICTURE_TYPE_P: qDebug("Type:P\t");break;
case AV_PICTURE_TYPE_B: qDebug("Type:B\t");break;
default: qDebug("Type:Other\t");break;
}
//pParsePacket就是一幀的數(shù)據(jù)包AVPacket策泣,這里可以保存作為錄像
NotifyReceiveVideoPacket(pParsePacket);
//丟給解碼器處理
m_pVideoDecoder->Decode(pParsePacket);
av_packet_unref(pParsePacket);
} // parse H264 packet while
每次塞給AVCodecParser的數(shù)據(jù)是buffer中的數(shù)據(jù)衙傀,其中可能包含多個幀抬吟,所以內(nèi)部會有一個while循環(huán),parser_len是已經(jīng)解析了的數(shù)據(jù)的長度统抬,直到解析完一次的buffer火本,再塞新的數(shù)據(jù)到buffer繼續(xù)處理。
解碼為AVFrame
if(0 != avcodec_send_packet(pCodecContext, pPacket)){
qDebug("avcodec_send_packet failed");
return false;
}
while(0 == avcodec_receive_frame(pCodecContext, pFrame)){
//pFrame包含了解碼后的YUV數(shù)據(jù)
...
}
由于AVFrame是ffmpeg的數(shù)據(jù)結(jié)構(gòu)聪建,要分別提取出Y钙畔、U、V三個通道的數(shù)據(jù)才能用于顯示金麸。
YUV數(shù)據(jù)格式
RGB來表示顏色大家都不陌生擎析,R(紅色)、G(綠色)挥下、B(藍色)揍魂,通過這三基色就可以組合成其他需要的顏色。YUV也是一種表示顏色的方式棚瘟,其中Y(亮度)现斋、U(色度)、V(濃度)偎蘸。YUV根據(jù)采樣方式的不同又有YUV444庄蹋、YUV422、YUV420等多種格式迷雪。這里主要介紹YUV420采樣格式限书。
YUV420
YUV420格式是指,每個像素都保留一個Y分量 (亮度全抽樣)章咧,而在水平方向上倦西,不是每行都取U和V分量,而是一行只取U分量慧邮,接著一行就只取V分量调限,以此重復(即4:2:0, 4:0:2, 4:2:0, 4:0:2 .......)舟陆。所以420不是指沒有V,而是指一行采樣只取U耻矮,另一行采樣只取V秦躯。從4x4矩陣列來看,每4個矩陣點Y區(qū)域中裆装,只有一個U和V踱承,所以它們的比值是4:1。
數(shù)據(jù)大小
對于1個像素的信息存儲
- RGB格式:R哨免、G茎活、B各占8位,共24位琢唾,即3byte
- YUV420格式: Y占8位载荔,U、V每4個點共有一個采桃,共8 + 8 / 4 + 8 / 4 = 12位懒熙,即3 / 2byte
對于一張圖像的數(shù)據(jù)大小
- RGB格式:width * height * 3byte
- YUV420格式:width * height * 3 / 2 byte
所以采取YUV420來存儲圖像數(shù)據(jù)比RGB格式節(jié)省了一半的空間。
YUV420P
采樣好了數(shù)據(jù)普办,在存儲YUV數(shù)據(jù)的時候工扎,對于YUV不同的存儲方式又有YUV420P(YV12)、YUV420SP(NV12)等分類衔蹲。
從圖上可以看出肢娘,YUV420P和NV12的區(qū)別就是一個是UV交替存儲,一個是先存U再存V舆驶。這個在解碼的時候就看你解碼格式指定的是哪個了橱健,默認解碼是YUV420P。
從AVFrame中獲取YUV數(shù)據(jù)
YUV420P在AVFrame中的存儲贞远,data[0]存Y分量畴博,data[1]存U分量,data[2]存V分量蓝仲。其中俱病,圖像每一行Y、U袱结、V數(shù)據(jù)的大小分別是linesize[0]亮隙、linesize[1]、linesize[2]垢夹,除了實際的圖像數(shù)據(jù)溢吻,還有一些填充數(shù)據(jù)是不需要的。填充數(shù)據(jù)應該是為了內(nèi)存對齊,保留的話可能會導致花屏促王,具體可以看這里的分析犀盟。
所以,獲取實際的圖像YUV數(shù)據(jù)代碼為:
for(int i = 0; i < pFrame->height; i++)
memcpy(m_pBuffer + pFrame->width * i, pFrame->data[0] + pFrame->linesize[0] * i, pFrame->width);
for(int j = 0; j < pFrame->height / 2; j++)
memcpy(m_pBuffer + pFrame->width / 2 * j + ysize, pFrame->data[1] + pFrame->linesize[1] * j, pFrame->width / 2);
for(int k = 0; k < pFrame->height / 2; k++)
memcpy(m_pBuffer + pFrame->width / 2 * k + ysize * 5 / 4, pFrame->data[2] + pFrame->linesize[2] * k, pFrame->width / 2);
我是直接用一個pBuffer來保存YUV數(shù)據(jù)了蝇狼,記住偏移量就行阅畴,想分開存也沒問題。這里的數(shù)據(jù)就可以直接交給渲染部分來顯示圖像了迅耘。