從零開(kāi)始學(xué)習(xí)音視頻編程技術(shù)(五) 使用FFMPEG解碼視頻之保存成圖片

從零開(kāi)始學(xué)習(xí)音視頻編程技術(shù)(五) 使用FFMPEG解碼視頻之保存成圖片-視頻播放器開(kāi)發(fā)-音視頻技術(shù)文章-音視頻編程技術(shù) (yundiantech.com)

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ò)編譯了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末妒茬,一起剝皮案震驚了整個(gè)濱河市担锤,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌乍钻,老刑警劉巖肛循,帶你破解...
    沈念sama閱讀 221,406評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異银择,居然都是意外死亡多糠,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,395評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門(mén)欢摄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)熬丧,“玉大人,你說(shuō)我怎么就攤上這事怀挠∥龊” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 167,815評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵绿淋,是天一觀的道長(zhǎng)闷畸。 經(jīng)常有香客問(wèn)我,道長(zhǎng)吞滞,這世上最難降的妖魔是什么佑菩? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,537評(píng)論 1 296
  • 正文 為了忘掉前任盾沫,我火速辦了婚禮,結(jié)果婚禮上殿漠,老公的妹妹穿的比我還像新娘赴精。我一直安慰自己,他們只是感情好绞幌,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,536評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布蕾哟。 她就那樣靜靜地躺著,像睡著了一般莲蜘。 火紅的嫁衣襯著肌膚如雪谭确。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,184評(píng)論 1 308
  • 那天票渠,我揣著相機(jī)與錄音逐哈,去河邊找鬼。 笑死问顷,一個(gè)胖子當(dāng)著我的面吹牛昂秃,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播择诈,決...
    沈念sama閱讀 40,776評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼械蹋,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了羞芍?” 一聲冷哼從身側(cè)響起哗戈,我...
    開(kāi)封第一講書(shū)人閱讀 39,668評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎荷科,沒(méi)想到半個(gè)月后唯咬,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,212評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡畏浆,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,299評(píng)論 3 340
  • 正文 我和宋清朗相戀三年胆胰,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片刻获。...
    茶點(diǎn)故事閱讀 40,438評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蜀涨,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蝎毡,到底是詐尸還是另有隱情厚柳,我是刑警寧澤,帶...
    沈念sama閱讀 36,128評(píng)論 5 349
  • 正文 年R本政府宣布沐兵,位于F島的核電站别垮,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏扎谎。R本人自食惡果不足惜碳想,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,807評(píng)論 3 333
  • 文/蒙蒙 一烧董、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧胧奔,春花似錦逊移、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,279評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至觅够,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間巷嚣,已是汗流浹背喘先。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,395評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留廷粒,地道東北人窘拯。 一個(gè)月前我還...
    沈念sama閱讀 48,827評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像坝茎,于是被迫代替她去往敵國(guó)和親涤姊。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,446評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容