FFmpeg學習2:解碼數據結構及函數總結

在上一篇文章中,對FFmpeg的視頻解碼過程作了一個總結。因爲才接觸FFmpeg,仍是挺陌生的,這裏就解碼過程再作一個總結。
本文的總結分爲如下兩個部分:html

  • 數據讀取,主要關注在解碼過程當中所用到的FFmpeg中的結構體。
  • 解碼過程當中所調用的函數

在學習的過程主要參考的是dranger tutorial,因此跟着教程在本文的最後使用SDL2.0將解碼後的數據輸出到屏幕上。git

數據的讀取

一個多媒體文件包含有多個流(視頻流 video stream,音頻流 audio stream,字幕等);流是一種抽象的概念,表示一連串的數據元素;
流中的數據元素稱爲幀Frame。也就是說多媒體文件中,主要有兩種數據:流Stream 及其數據元素 幀Frame,在FFmpeg天然有與這兩種數據相對應的抽象:AVStream和AVPacketgithub

使用FFmpeg的解碼,數據的傳遞過程可概括以下:緩存

  1. 調用avformat_open_input打開流,將信息填充到AVFormatContext
  2. 調用av_read_frame從流中讀取數據幀到 AVPacketAVPacket保存仍然是未解碼的數據
  3. 調用avcodec_decode_video2AVPacket的數據解碼,並將解碼後的數據填充到AVFrame中,AVFrame中保存的是解碼後的原始數據

上述過程可使用下圖表示:
ide

結構體的存儲空間的分配與釋放

FFmpeg並無垃圾回收機制,所分配的空間都須要本身維護。而因爲視頻處理過程當中數據量是很是大,對於動態內存的使用更要謹慎。
本小節主要介紹解碼過程使用到的結構體存儲空間的分配與釋放。函數

  • AVFormatContext 在FFmpeg中有很重要的做用,描述一個多媒體文件的構成及其基本信息,存放了視頻編解碼過程當中的大部分信息。一般該結構體由avformat_open_input分配
    存儲空間,在最後調用avformat_input_close關閉。學習

  • AVStream 描述一個媒體流,在解碼的過程當中,做爲AVFormatContext的一個字段存在,不須要單獨的處理。
  • AVpacket 用來存放解碼以前的數據,它只是一個容器,其data成員指向實際的數據緩衝區,在解碼的過程當中可有av_read_frame建立和填充AVPacket中的數據緩衝區,
    當數據緩衝區再也不使用的時候能夠調用av_free_apcket釋放這塊緩衝區。
  • AVFrame 存放從AVPacket中解碼出來的原始數據,其必須經過av_frame_alloc來建立,經過av_frame_free來釋放。和AVPacket相似,AVFrame中也有一塊數據緩存空間,
    在調用av_frame_alloc的時候並不會爲這塊緩存區域分配空間,須要使用其餘的方法。在解碼的過程使用了兩個AVFrame,這兩個AVFrame分配緩存空間的方法也不相同
    • 一個AVFrame用來存放從AVPacket中解碼出來的原始數據,這個AVFrame的數據緩存空間經過調avcodec_decode_video分配和填充。
    • 另外一個AVFrame用來存放將解碼出來的原始數據變換爲須要的數據格式(例如RGB,RGBA)的數據,這個AVFrame須要手動的分配數據緩存空間。代碼以下:
AVFrame* pFrameYUV;
pFrameYUV = av_frame_alloc();
// 手動爲 pFrameYUV分配數據緩存空間
int numBytes = avpicture_get_size(AV_PIX_FMT_YUV420P,pCodecCtx->widht,pCodecCtx->width);
uint8_t* buffer = (uint8_t*)av_malloc(numBytes * sizeof(uint8_t));
// 將分配的數據緩存空間和AVFrame關聯起來
avpicture_fill((AVPicture *)pFrameYUV, buffer, AV_PIX_FMT_YUV420P,pCodecCtx->width, pCodecCtx->height)

首先計算須要緩存空間大小,調用av_malloc分配緩存空間,最後調用avpicture_fill將分配的緩存空間和AVFrame關聯起來。
調用av_frame_free來釋放AVFrame,該函數不止釋放AVFrame自己的空間,還會釋放掉包含在其內的其餘對象動態申請的空間,例如上面的緩存空間。ui

  • av_malloc和av_free,FFmpeg並無提供垃圾回收機制,全部的內存管理都要手動進行。av_malloc只是在申請內存空間的時候會考慮到內存對齊(2字節,4字節對齊),
    其申請的空間要調用av_free釋放。

調用的函數

  • av_register_all 這個函數不用多說了,註冊庫所支持的容器格式及其對應的CODEC。
  • avformat_open_input 打開多媒體文件流,並讀取文件的頭,將讀取到的信息填充到AVFormatContext結構體中。在使用結束後,要調用avformat_close_input關閉打開的流
  • avformat_find_stream_info 上面提到,avformat_open_input只是讀取文件的頭來獲得多媒體文件的信息,可是有些文件沒有文件頭或者文件頭的格式不正確,這就形成只調用
    avformat_open_input可能得不到解碼所須要的必要信息,須要調用avformat_find_stream_info進一步獲得流的信息。

經過上面的三個函數已經獲取了對多媒體文件進行解碼的所須要信息,下面要作的就是根據這些信息獲得相應的解碼器。
結構體AVCodecContext描述了編解碼器的上下文信息,包含了流中所使用的關於編解碼器的全部信息,能夠經過 AVFormatContext->AVStream->AVCodecContext來獲得,在有了AVCodecContext後,能夠經過codec_id來找到相應的解碼器,具體代碼以下:指針

AVCodec* pCodec = nullptr;
pCodecCtxOrg = pFormatCtx->streams[videoStream]->codec; // codec context
// 找到video stream的 decoder
pCodec = avcodec_find_decoder(pCodecCtxOrg->codec_id);
  • avcodec_find_decoder 能夠經過codec_id或者名稱來找到相應的解碼器,返回值是一個AVCodec的指針。
  • avcodec_open2 打開相應的編解碼器
  • av_read_frame 從流中讀取數據幀暫存到AVPacket中
  • avcodec_decode_video2 從AVPacket中解碼數據到AVFrame中

通過以上的過程,AVFrame中的數據緩存中存放的就是解碼後的原始數據了。整個流程梳理以下:code

使用SDL2.0顯示視頻

使用SDL2.0,dranger tutorial中的顯示視頻部分的代碼就不是很適用了,須要作一些修改。不過,SDL2.0顯示圖像仍是挺簡單的。

SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER);
    SDL_Window* window = SDL_CreateWindow("FFmpeg Decode", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
        pCodecCtx->width, pCodecCtx->height, SDL_WINDOW_OPENGL | SDL_WINDOW_MAXIMIZED);
    SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, 0);
    SDL_Texture* bmp = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12, SDL_TEXTUREACCESS_STREAMING,
        pCodecCtx->width, pCodecCtx->height);
    SDL_Rect rect;
    rect.x = 0;
    rect.y = 0;
    rect.w = pCodecCtx->width;
    rect.h = pCodecCtx->height;
    SDL_Event event;

上述代碼爲初始化後SDL顯示圖像所須要的環境,在使用FFmpeg解碼數據後

int frameFinished = 0;
    avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);
    if (frameFinished)
    {
         sws_scale(sws_ctx, (uint8_t const * const *)pFrame->data, pFrame->linesize, 0,
              pCodecCtx->height, pFrameRGB->data, pFrameRGB->linesize);
         SDL_UpdateTexture(bmp, &rect, pFrameRGB->data[0], pFrameRGB->linesize[0]);
         SDL_RenderClear(renderer);
         SDL_RenderCopy(renderer, bmp, &rect, &rect);
         SDL_RenderPresent(renderer);
    }

上面代碼就將解碼獲得的圖像幀使用SDL顯示了出來。不過,這裏真的只是顯示而已,以可以解碼速度快速的將整個視頻的圖像幀顯示一遍。

本節示例代碼 FFmpeg1.cpp

相關文章
相關標籤/搜索