5.現(xiàn)在根據(jù)視頻流 打開(kāi)一個(gè)解碼器來(lái)解碼:
解釋下列代碼
pCodecCtx = pFormatCtx->streams[videoStream]->codec;
pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
if (pCodec == NULL) {
printf("Codec not found.");
return -1;
}
///打開(kāi)解碼器
if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
printf("Could not open codec.");
return -1;
}
該代碼段是使用FFmpeg庫(kù)中的函數(shù),對(duì)一個(gè)視頻文件進(jìn)行解碼酱讶。
首先退盯,它使用視頻文件格式上下文(pFormatCtx)訪問(wèn)視頻流(videoStream)的解碼器上下文(pCodecCtx)。
然后它使用avcodec_find_decoder函數(shù)在FFmpeg庫(kù)中查找對(duì)應(yīng)的視頻解碼器(pCodec)泻肯。如果無(wú)法找到解碼器渊迁,則輸出錯(cuò)誤信息并退出程序。
最后灶挟,它使用avcodec_open2函數(shù)打開(kāi)解碼器并將解碼器上下文與解碼器相關(guān)聯(lián)琉朽,以準(zhǔn)備解碼視頻幀。如果無(wú)法打開(kāi)解碼器稚铣,則輸出錯(cuò)誤信息并退出程序箱叁。
6.現(xiàn)在開(kāi)始讀取視頻了:
解釋下列代碼
int y_size = pCodecCtx->width * pCodecCtx->height;
AVPacket *packet = (AVPacket *) malloc(sizeof(AVPacket)); //分配一個(gè)packet
av_new_packet(packet, y_size); //分配packet的數(shù)據(jù)
if (av_read_frame(pFormatCtx, packet) < 0)
{
break; //這里認(rèn)為視頻讀取完了
}
該代碼段是在一個(gè)循環(huán)中使用FFmpeg庫(kù)函數(shù)讀取視頻幀。
首先惕医,它使用解碼器上下文(pCodecCtx)的寬(width)和高(height)計(jì)算一幀視頻的大懈(y_size)。這是為了分配一個(gè)足夠大的包(packet)來(lái)存儲(chǔ)視頻幀數(shù)據(jù)抬伺。
然后螟够,它使用malloc函數(shù)為packet分配內(nèi)存,并使用av_new_packet函數(shù)初始化packet的數(shù)據(jù)緩沖區(qū)大小以存儲(chǔ)剛計(jì)算出的y_size大小的數(shù)據(jù)。
接下來(lái)妓笙,它使用av_read_frame函數(shù)讀取視頻文件中的一幀若河,如果讀取完畢則跳出循環(huán)。
在這個(gè)過(guò)程中给郊,packet用來(lái)存儲(chǔ)讀取到的幀數(shù)據(jù)牡肉,可供后續(xù)的解碼器使用。
7.前面我們說(shuō)過(guò) 視頻里面的數(shù)據(jù)是經(jīng)過(guò)編碼壓縮的淆九,因此這里我們需要將其解碼:
{
ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture,packet);
if (ret < 0) {
printf("decode error.");
return -1;
}
}
該代碼段是對(duì)讀取的視頻幀數(shù)據(jù)進(jìn)行解碼的過(guò)程统锤。
首先,它檢查剛才讀取的packet中數(shù)據(jù)流(stream_index)是否對(duì)應(yīng)于視頻流(videoStream)炭庙。如果對(duì)應(yīng)饲窿,說(shuō)明讀取到的數(shù)據(jù)是視頻數(shù)據(jù),需要進(jìn)行解碼焕蹄。如果不是視頻數(shù)據(jù)逾雄,則忽略該packet。
然后腻脏,它使用avcodec_decode_video2函數(shù)將packet中的數(shù)據(jù)解碼到pFrame緩沖區(qū)中鸦泳,并返回一個(gè)狀態(tài)值(ret)表示解碼是否成功。
最后永品,它檢查解碼返回值(ret)是否小于0做鹰,如果是,則說(shuō)明解碼過(guò)程出現(xiàn)了錯(cuò)誤鼎姐。此時(shí)輸出錯(cuò)誤信息并返回-1钾麸。
需要注意的是,這個(gè)過(guò)程是在循環(huán)中完成的炕桨,因此在每次迭代中都會(huì)讀取下一幀視頻數(shù)據(jù)并進(jìn)行解碼饭尝,直到視頻結(jié)束
函數(shù)avcodec_decode_video2用于將視頻包的數(shù)據(jù)解碼為一幀有損壓縮的或無(wú)損壓縮的視頻圖像,返回解碼狀態(tài)献宫。
函數(shù)原型為:
int avcodec_decode_video2(AVCodecContext *avctx, AVFrame *picture, int *got_picture_ptr, const AVPacket *avpkt);
參數(shù)說(shuō)明:
- avctx: 指向使用的解碼器上下文钥平。
- picture: 解碼后的視頻幀將被寫(xiě)入該AVFrame結(jié)構(gòu)中℃⑼荆可以為NULL帖池。
- got_picture_ptr: 用于標(biāo)識(shí)是否已成功獲得視頻幀。為非零值時(shí)成功吭净,為0時(shí)失敗/沒(méi)有組成一個(gè)完整的視頻幀‰鹊椋可以為NULL寂殉,表示不關(guān)心是否成功。
- avpkt: 視頻幀數(shù)據(jù)包原在。函數(shù)不對(duì)該包進(jìn)行反引用或復(fù)制友扰,所以調(diào)用者不能在解碼過(guò)程中釋放該包彤叉。
返回值說(shuō)明: - 成功解碼并組成完整視頻幀時(shí)返回值大于等于零,表示成功解碼的字節(jié)數(shù)村怪。
- 出現(xiàn)解碼錯(cuò)誤時(shí)秽浇,返回一個(gè)負(fù)的錯(cuò)誤代碼。例如甚负,AVERROR(EINVAL)表示傳遞了無(wú)效參數(shù)柬焕。
需要注意的是,此函數(shù)并未分配AVFrame結(jié)構(gòu)梭域,所以調(diào)用前必須進(jìn)行分配斑举。此外,由于該函數(shù)采用了運(yùn)行緩存病涨,請(qǐng)確保在話頭中設(shè)置AVCodecContext以便正確使用該緩存富玷。
在解碼后的視頻幀的AVFrame結(jié)構(gòu)中,視頻幀的像素?cái)?shù)據(jù)存儲(chǔ)在該結(jié)構(gòu)的data[0]既穆、data[1]赎懦、data[2]等元素中,而各個(gè)像素的尺寸和布局則通過(guò)該結(jié)構(gòu)的width幻工、height励两、format元素描述。
8.基本上所有解碼器解碼之后得到的圖像數(shù)據(jù)都是YUV420的格式会钝,而這里我們需要將其保存成圖片文件伐蒋,因此需要將得到的YUV420數(shù)據(jù)轉(zhuǎn)換成RGB格式,轉(zhuǎn)換格式也是直接使用FFMPEG來(lái)完成:
詳細(xì)解析下列代碼 if (got_picture) {
sws_scale(img_convert_ctx,
(uint8_t const * const *) pFrame->data,
pFrame->linesize, 0, pCodecCtx->height, pFrameRGB->data,
pFrameRGB->linesize);
}
該代碼段用于將解碼得到的視頻幀轉(zhuǎn)換成RGB格式迁酸。
如果解碼成功先鱼,并且包含了完整的一幀視頻幀,got_picture的值為非0奸鬓,進(jìn)入if語(yǔ)句塊中焙畔。
在if語(yǔ)句中,它使用FFmpeg庫(kù)函數(shù)sws_scale進(jìn)行顏色空間轉(zhuǎn)換和大小調(diào)整串远。sws_scale可以將原始視頻幀中的像素?cái)?shù)據(jù)轉(zhuǎn)換為指定的目標(biāo)像素格式宏多,這里是將視頻幀的像素?cái)?shù)據(jù)轉(zhuǎn)換為RGB格式。
具體地澡罚,它將源視頻幀pFrame中的像素?cái)?shù)據(jù)指針(data)和每行像素?cái)?shù)據(jù)的大猩斓(linesize)作為輸入,同時(shí)將轉(zhuǎn)換后的結(jié)果寫(xiě)入目標(biāo)RGB幀pFrameRGB的像素?cái)?shù)據(jù)指針(data)和每行像素?cái)?shù)據(jù)的大辛羯Α(linesize)更胖。
這里需要注意的是,sws_scale不會(huì)自動(dòng)為輸出RGB幀分配緩沖區(qū),因此要在使用該函數(shù)之前確保輸出RGB幀的像素?cái)?shù)據(jù)指針已分配好存儲(chǔ)空間却妨。
img_convert_ctx是使用sws_getContext函數(shù)創(chuàng)建的SwsContext上下文饵逐,用于保存轉(zhuǎn)換器的配置參數(shù),包括源像素格式彪标、目標(biāo)像素格式倍权、調(diào)整大小等參數(shù)。
最終捞烟,經(jīng)過(guò)這個(gè)過(guò)程薄声,解碼器就可以輸出RGB格式的視頻幀供后續(xù)操作使用了。
9.得到RGB數(shù)據(jù)之后就是直接寫(xiě)入文件了:
SaveFrame(pFrameRGB, pCodecCtx->width,pCodecCtx->height,index++); //保存圖片 if (index > 50) return 0; //這里我們就保存50張圖片
該代碼片段中坷襟,使用 SaveFrame 函數(shù)保存了轉(zhuǎn)換后的 RGB 格式的視頻幀奸柬,函數(shù)的原型及參數(shù)說(shuō)明如下:
void SaveFrame(AVFrame* pFrame, int width, int height, int index) {
FILE* pFile;
char szFilename[32];
int y;
// 打開(kāi)輸出文件
sprintf(szFilename, "frame%d.ppm", index);
pFile = fopen(szFilename, "wb");
if (pFile == NULL)
return;
// 寫(xiě)入頭信息
fprintf(pFile, "P6\n%d %d\n255\n", width, height);
// 寫(xiě)入像素?cái)?shù)據(jù)
for (y = 0; y < height; y++) {
fwrite(pFrame->data[0] + y * pFrame->linesize[0], 1, width * 3, pFile);
}
// 關(guān)閉輸出文件
fclose(pFile);
}
具體而言,這段代碼將轉(zhuǎn)換后的 RGB 格式的視頻幀保存為 PPM 格式的文件婴程。PPM 是常用的一種圖像格式廓奕,通常使用 P6 格式表示,其中第一行為 "P6"档叔,然后是圖像的寬度和高度桌粉,最后是像素實(shí)際的顏色值。
在 SaveFrame 函數(shù)中衙四,首先通過(guò) sprintf 函數(shù)構(gòu)造了要保存的 PPM 文件名铃肯,index 可以用于區(qū)分不同幀的文件名。然后通過(guò) fopen 函數(shù)打開(kāi)文件传蹈,如果打開(kāi)失敗押逼,函數(shù)就提前返回。
接下來(lái)惦界,函數(shù)先寫(xiě)入 PPM 文件的頭信息挑格,即 "P6\n"、寬度沾歪、高度漂彤、顏色值最大值等元信息。然后灾搏,函數(shù)使用 fwrite 函數(shù)將轉(zhuǎn)換后的視頻幀數(shù)據(jù)寫(xiě)入文件挫望,覆蓋了原有的顏色信息。這里需要注意狂窑,一個(gè)像素占用 3 個(gè)字節(jié)空間媳板,對(duì)于每一行,像素?cái)?shù)據(jù)以行為單位排列泉哈,每行按從左到右拷肌、從上到下的順序?qū)懭胱止?jié)流中到旦。具體來(lái)說(shuō),y 表示當(dāng)前處理的行數(shù)巨缘,對(duì)于每一行,都通過(guò) fwrite 函數(shù)將當(dāng)前行的像素?cái)?shù)據(jù)寫(xiě)入文件中采呐。
最后若锁,函數(shù)通過(guò) fclose 函數(shù)關(guān)閉文件句柄,并結(jié)束函數(shù)的執(zhí)行斧吐。如果需要保存多張文件又固,可以用一個(gè)計(jì)數(shù)器(如 index 變量),實(shí)現(xiàn)保存指定數(shù)量的文件煤率,如這里的 50 張視頻幀對(duì)應(yīng)了 50 個(gè)文件仰冠。在保存完指定數(shù)量的圖片后,代碼通過(guò)條件語(yǔ)句執(zhí)行了一個(gè)簡(jiǎn)單的退出程序操作:返回 0蝶糯,結(jié)束了對(duì)該視頻的處理洋只。
ubuntu中的QT中報(bào)錯(cuò):‘AV_PIX_FMTRGB24’ was not declared in this scope
如果僅僅是缺乏某些宏定義而導(dǎo)致編譯錯(cuò)誤,可以在代碼文件或編譯選項(xiàng)中手動(dòng)添加這些宏定義昼捍,如:
#define AV_PIX_FMT_RGB24 AV_PIX_FMT_BGR24
這里將已廢棄的“AV_PIX_FMT_RGB24”宏定義重定向到了未廢棄的“AV_PIX_FMT BGR24”宏定義识虚,這樣代碼就能夠通過(guò)編譯了。