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

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

5.現(xiàn)在根據(jù)視頻流 打開一個解碼器來解碼:

解釋下列代碼

  pCodecCtx = pFormatCtx->streams[videoStream]->codec;
    pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
    if (pCodec == NULL) {
        printf("Codec not found.");
        return -1;
    }
 
    ///打開解碼器
    if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
        printf("Could not open codec.");
        return -1;
    }

該代碼段是使用FFmpeg庫中的函數(shù),對一個視頻文件進行解碼。
首先,它使用視頻文件格式上下文(pFormatCtx)訪問視頻流(videoStream)的解碼器上下文(pCodecCtx)。
然后它使用avcodec_find_decoder函數(shù)在FFmpeg庫中查找對應(yīng)的視頻解碼器(pCodec)。如果無法找到解碼器,則輸出錯誤信息并退出程序。
最后,它使用avcodec_open2函數(shù)打開解碼器并將解碼器上下文與解碼器相關(guān)聯(lián),以準備解碼視頻幀。如果無法打開解碼器,則輸出錯誤信息并退出程序。

6.現(xiàn)在開始讀取視頻了:

解釋下列代碼

int y_size = pCodecCtx->width * pCodecCtx->height;
    AVPacket *packet = (AVPacket *) malloc(sizeof(AVPacket)); //分配一個packet
    av_new_packet(packet, y_size); //分配packet的數(shù)據(jù)
 
    if (av_read_frame(pFormatCtx, packet) < 0)
    {
        break; //這里認為視頻讀取完了
    }

該代碼段是在一個循環(huán)中使用FFmpeg庫函數(shù)讀取視頻幀。
首先,它使用解碼器上下文(pCodecCtx)的寬(width)和高(height)計算一幀視頻的大?。▂_size)。這是為了分配一個足夠大的包(packet)來存儲視頻幀數(shù)據(jù)。
然后,它使用malloc函數(shù)為packet分配內(nèi)存,并使用av_new_packet函數(shù)初始化packet的數(shù)據(jù)緩沖區(qū)大小以存儲剛計算出的y_size大小的數(shù)據(jù)。
接下來,它使用av_read_frame函數(shù)讀取視頻文件中的一幀,如果讀取完畢則跳出循環(huán)。
在這個過程中,packet用來存儲讀取到的幀數(shù)據(jù),可供后續(xù)的解碼器使用。

7.前面我們說過 視頻里面的數(shù)據(jù)是經(jīng)過編碼壓縮的,因此這里我們需要將其解碼:

  {
        ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture,packet);

    if (ret < 0) {
        printf("decode error.");
            return -1;
        }
    }

該代碼段是對讀取的視頻幀數(shù)據(jù)進行解碼的過程。
首先,它檢查剛才讀取的packet中數(shù)據(jù)流(stream_index)是否對應(yīng)于視頻流(videoStream)。如果對應(yīng),說明讀取到的數(shù)據(jù)是視頻數(shù)據(jù),需要進行解碼。如果不是視頻數(shù)據(jù),則忽略該packet。
然后,它使用avcodec_decode_video2函數(shù)將packet中的數(shù)據(jù)解碼到pFrame緩沖區(qū)中,并返回一個狀態(tài)值(ret)表示解碼是否成功。
最后,它檢查解碼返回值(ret)是否小于0,如果是,則說明解碼過程出現(xiàn)了錯誤。此時輸出錯誤信息并返回-1。
需要注意的是,這個過程是在循環(huán)中完成的,因此在每次迭代中都會讀取下一幀視頻數(shù)據(jù)并進行解碼,直到視頻結(jié)束

函數(shù)avcodec_decode_video2用于將視頻包的數(shù)據(jù)解碼為一幀有損壓縮的或無損壓縮的視頻圖像,返回解碼狀態(tài)。
函數(shù)原型為:

int avcodec_decode_video2(AVCodecContext *avctx, AVFrame *picture, int *got_picture_ptr, const AVPacket *avpkt);

參數(shù)說明:

  • avctx: 指向使用的解碼器上下文。
  • picture: 解碼后的視頻幀將被寫入該AVFrame結(jié)構(gòu)中??梢詾镹ULL。
  • got_picture_ptr: 用于標識是否已成功獲得視頻幀。為非零值時成功,為0時失敗/沒有組成一個完整的視頻幀??梢詾镹ULL,表示不關(guān)心是否成功。
  • avpkt: 視頻幀數(shù)據(jù)包。函數(shù)不對該包進行反引用或復(fù)制,所以調(diào)用者不能在解碼過程中釋放該包。
    返回值說明:
  • 成功解碼并組成完整視頻幀時返回值大于等于零,表示成功解碼的字節(jié)數(shù)。
  • 出現(xiàn)解碼錯誤時,返回一個負的錯誤代碼。例如,AVERROR(EINVAL)表示傳遞了無效參數(shù)。
    需要注意的是,此函數(shù)并未分配AVFrame結(jié)構(gòu),所以調(diào)用前必須進行分配。此外,由于該函數(shù)采用了運行緩存,請確保在話頭中設(shè)置AVCodecContext以便正確使用該緩存。
    在解碼后的視頻幀的AVFrame結(jié)構(gòu)中,視頻幀的像素數(shù)據(jù)存儲在該結(jié)構(gòu)的data[0]、data[1]、data[2]等元素中,而各個像素的尺寸和布局則通過該結(jié)構(gòu)的width、height、format元素描述。

8.基本上所有解碼器解碼之后得到的圖像數(shù)據(jù)都是YUV420的格式,而這里我們需要將其保存成圖片文件,因此需要將得到的YUV420數(shù)據(jù)轉(zhuǎn)換成RGB格式,轉(zhuǎn)換格式也是直接使用FFMPEG來完成:

詳細解析下列代碼 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,進入if語句塊中。
在if語句中,它使用FFmpeg庫函數(shù)sws_scale進行顏色空間轉(zhuǎn)換和大小調(diào)整。sws_scale可以將原始視頻幀中的像素數(shù)據(jù)轉(zhuǎn)換為指定的目標像素格式,這里是將視頻幀的像素數(shù)據(jù)轉(zhuǎn)換為RGB格式。
具體地,它將源視頻幀pFrame中的像素數(shù)據(jù)指針(data)和每行像素數(shù)據(jù)的大小(linesize)作為輸入,同時將轉(zhuǎn)換后的結(jié)果寫入目標RGB幀pFrameRGB的像素數(shù)據(jù)指針(data)和每行像素數(shù)據(jù)的大?。╨inesize)。
這里需要注意的是,sws_scale不會自動為輸出RGB幀分配緩沖區(qū),因此要在使用該函數(shù)之前確保輸出RGB幀的像素數(shù)據(jù)指針已分配好存儲空間。
img_convert_ctx是使用sws_getContext函數(shù)創(chuàng)建的SwsContext上下文,用于保存轉(zhuǎn)換器的配置參數(shù),包括源像素格式、目標像素格式、調(diào)整大小等參數(shù)。
最終,經(jīng)過這個過程,解碼器就可以輸出RGB格式的視頻幀供后續(xù)操作使用了。

9.得到RGB數(shù)據(jù)之后就是直接寫入文件了:

SaveFrame(pFrameRGB,     pCodecCtx->width,pCodecCtx->height,index++); //保存圖片     if (index > 50) return 0; //這里我們就保存50張圖片

該代碼片段中,使用 SaveFrame 函數(shù)保存了轉(zhuǎn)換后的 RGB 格式的視頻幀,函數(shù)的原型及參數(shù)說明如下:

void SaveFrame(AVFrame* pFrame, int width, int height, int index) {
    FILE* pFile;
    char szFilename[32];
    int  y;

    // 打開輸出文件
    sprintf(szFilename, "frame%d.ppm", index);
    pFile = fopen(szFilename, "wb");
    if (pFile == NULL)
        return;

    // 寫入頭信息
    fprintf(pFile, "P6\n%d %d\n255\n", width, height);

    // 寫入像素數(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",然后是圖像的寬度和高度,最后是像素實際的顏色值。
在 SaveFrame 函數(shù)中,首先通過 sprintf 函數(shù)構(gòu)造了要保存的 PPM 文件名,index 可以用于區(qū)分不同幀的文件名。然后通過 fopen 函數(shù)打開文件,如果打開失敗,函數(shù)就提前返回。
接下來,函數(shù)先寫入 PPM 文件的頭信息,即 "P6\n"、寬度、高度、顏色值最大值等元信息。然后,函數(shù)使用 fwrite 函數(shù)將轉(zhuǎn)換后的視頻幀數(shù)據(jù)寫入文件,覆蓋了原有的顏色信息。這里需要注意,一個像素占用 3 個字節(jié)空間,對于每一行,像素數(shù)據(jù)以行為單位排列,每行按從左到右、從上到下的順序?qū)懭胱止?jié)流中。具體來說,y 表示當前處理的行數(shù),對于每一行,都通過 fwrite 函數(shù)將當前行的像素數(shù)據(jù)寫入文件中。
最后,函數(shù)通過 fclose 函數(shù)關(guān)閉文件句柄,并結(jié)束函數(shù)的執(zhí)行。如果需要保存多張文件,可以用一個計數(shù)器(如 index 變量),實現(xiàn)保存指定數(shù)量的文件,如這里的 50 張視頻幀對應(yīng)了 50 個文件。在保存完指定數(shù)量的圖片后,代碼通過條件語句執(zhí)行了一個簡單的退出程序操作:返回 0,結(jié)束了對該視頻的處理。

ubuntu中的QT中報錯:‘AV_PIX_FMTRGB24’ was not declared in this scope

如果僅僅是缺乏某些宏定義而導(dǎo)致編譯錯誤,可以在代碼文件或編譯選項中手動添加這些宏定義,如:

#define AV_PIX_FMT_RGB24 AV_PIX_FMT_BGR24

這里將已廢棄的“AV_PIX_FMT_RGB24”宏定義重定向到了未廢棄的“AV_PIX_FMT BGR24”宏定義,這樣代碼就能夠通過編譯了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容