在上一篇文章中,對FFmpeg的視頻解碼過程作了一個總結。因爲才接觸FFmpeg,仍是挺陌生的,這裏就解碼過程再作一個總結。
本文的總結分爲如下兩個部分:html
在學習的過程主要參考的是dranger tutorial,因此跟着教程在本文的最後使用SDL2.0將解碼後的數據輸出到屏幕上。git
一個多媒體文件包含有多個流(視頻流 video stream,音頻流 audio stream,字幕等);流是一種抽象的概念,表示一連串的數據元素;
流中的數據元素稱爲幀Frame。也就是說多媒體文件中,主要有兩種數據:流Stream 及其數據元素 幀Frame,在FFmpeg天然有與這兩種數據相對應的抽象:AVStream和AVPacket。github
使用FFmpeg的解碼,數據的傳遞過程可概括以下:緩存
avformat_open_input
打開流,將信息填充到AVFormatContext
中av_read_frame
從流中讀取數據幀到 AVPacket
,AVPacket
保存仍然是未解碼的數據。avcodec_decode_video2
將AVPacket
的數據解碼,並將解碼後的數據填充到AVFrame
中,AVFrame
中保存的是解碼後的原始數據。上述過程可使用下圖表示:
ide
FFmpeg並無垃圾回收機制,所分配的空間都須要本身維護。而因爲視頻處理過程當中數據量是很是大,對於動態內存的使用更要謹慎。
本小節主要介紹解碼過程使用到的結構體存儲空間的分配與釋放。函數
AVFormatContext 在FFmpeg中有很重要的做用,描述一個多媒體文件的構成及其基本信息,存放了視頻編解碼過程當中的大部分信息。一般該結構體由avformat_open_input
分配
存儲空間,在最後調用avformat_input_close
關閉。學習
av_read_frame
建立和填充AVPacket中的數據緩衝區,av_free_apcket
釋放這塊緩衝區。av_frame_alloc
來建立,經過av_frame_free
來釋放。和AVPacket相似,AVFrame中也有一塊數據緩存空間,av_frame_alloc
的時候並不會爲這塊緩存區域分配空間,須要使用其餘的方法。在解碼的過程使用了兩個AVFrame,這兩個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
只是在申請內存空間的時候會考慮到內存對齊(2字節,4字節對齊),av_free
釋放。avformat_close_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);
通過以上的過程,AVFrame中的數據緩存中存放的就是解碼後的原始數據了。整個流程梳理以下:code
使用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顯示了出來。不過,這裏真的只是顯示而已,以可以解碼速度快速的將整個視頻的圖像幀顯示一遍。