100行程式碼實現最簡單的基於FFMPEG+SDL的視訊播放器(SDL1.x)
=====================================================
最簡單的基於FFmpeg的視訊播放器系列文章列表:
=====================================================
簡介
FFMPEG工程浩大,可以參考的書籍又不是很多,因此很多剛學習FFMPEG的人常常感覺到無從下手。我剛接觸FFMPEG的時候也感覺不知從何學起。
因此我把自己做專案過程中實現的一個非常簡單的視訊播放器(大約100行程式碼)原始碼傳上來,以作備忘,同時方便新手學習FFMPEG。
該播放器雖然簡單,但是幾乎包含了使用FFMPEG播放一個視訊所有必備的API,並且使用SDL顯示解碼出來的視訊。
並且支援流媒體等多種視訊輸入,處於簡單考慮,沒有音訊部分,同時視訊播放採用直接延時40ms的方式
平臺使用VC2010,使用了新版的FFMPEG類庫。
SourceForge專案主頁:
注:本文SDL採用1.x版本。另一版本採用SDL2.0,可參考:
流程圖
沒想到這篇文章中介紹的播放器挺受FFMPEG初學者的歡迎,因此再次更新兩張流程圖,方便大家學習。此外在原始碼上添加了註釋,方便理解。
該播放器解碼的流程用圖的方式可以表示稱如下形式:
SDL顯示YUV影象的流程圖:
簡單解釋幾句:
SDL_Surface就是使用SDL的時候彈出的那個視窗。在SDL1.x版本中,只可以建立一個SDL_Surface。
SDL_Overlay用於顯示YUV資料。一個SDL_Overlay對應一幀YUV資料。
SDL_Rect用於確定SDL_Overlay顯示的位置。注意:一個SDL_Overlay可以指定多個不同的SDL_Rect,這樣就可以在SDL_Surface不同位置顯示相同的內容。
它們的關係如下圖所示:
下圖舉了個例子,指定了4個SDL_Rect,可以實現4分屏的顯示。
simplest_ffmpeg_player(標準版)程式碼
/** * 最簡單的基於FFmpeg的視訊播放器 * Simplest FFmpeg Player * * 雷霄驊 Lei Xiaohua * [email protected] * 中國傳媒大學/數字電視技術 * Communication University of China / Digital TV Technology * http://blog.csdn.net/leixiaohua1020 * * 本程式實現了視訊檔案的解碼和顯示(支援HEVC,H.264,MPEG2等)。 * 是最簡單的FFmpeg視訊解碼方面的教程。 * 通過學習本例子可以瞭解FFmpeg的解碼流程。 * This software is a simplest video player based on FFmpeg. * Suitable for beginner of FFmpeg. */ #include <stdio.h>#define __STDC_CONSTANT_MACROS#ifdef _WIN32//Windowsextern "C"{#include "libavcodec/avcodec.h"#include "libavformat/avformat.h"#include "libswscale/swscale.h"#include "SDL/SDL.h"};#else//Linux...#ifdef __cplusplusextern "C"{#endif#include <libavcodec/avcodec.h>#include <libavformat/avformat.h>#include <libswscale/swscale.h>#include <SDL/SDL.h>#ifdef __cplusplus};#endif#endif//Full Screen#define SHOW_FULLSCREEN 0//Output YUV420P #define OUTPUT_YUV420P 0int main(int argc, char* argv[]){ //FFmpeg AVFormatContext *pFormatCtx; int i, videoindex; AVCodecContext *pCodecCtx; AVCodec *pCodec; AVFrame *pFrame,*pFrameYUV; AVPacket *packet; struct SwsContext *img_convert_ctx; //SDL int screen_w,screen_h; SDL_Surface *screen; SDL_VideoInfo *vi; SDL_Overlay *bmp; SDL_Rect rect; FILE *fp_yuv; int ret, got_picture; char filepath[]="bigbuckbunny_480x272.h265"; av_register_all(); avformat_network_init(); pFormatCtx = avformat_alloc_context(); if(avformat_open_input(&pFormatCtx,filepath,NULL,NULL)!=0){ printf("Couldn't open input stream.\n"); return -1; } if(avformat_find_stream_info(pFormatCtx,NULL)<0){ printf("Couldn't find stream information.\n"); return -1; } videoindex=-1; for(i=0; i<pFormatCtx->nb_streams; i++) if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO){ videoindex=i; break; } if(videoindex==-1){ printf("Didn't find a video stream.\n"); return -1; } pCodecCtx=pFormatCtx->streams[videoindex]->codec; pCodec=avcodec_find_decoder(pCodecCtx->codec_id); if(pCodec==NULL){ printf("Codec not found.\n"); return -1; } if(avcodec_open2(pCodecCtx, pCodec,NULL)<0){ printf("Could not open codec.\n"); return -1; } pFrame=av_frame_alloc(); pFrameYUV=av_frame_alloc(); //uint8_t *out_buffer=(uint8_t *)av_malloc(avpicture_get_size(PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height)); //avpicture_fill((AVPicture *)pFrameYUV, out_buffer, PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height); //SDL---------------------------- if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) { printf( "Could not initialize SDL - %s\n", SDL_GetError()); return -1; } #if SHOW_FULLSCREEN vi = SDL_GetVideoInfo(); screen_w = vi->current_w; screen_h = vi->current_h; screen = SDL_SetVideoMode(screen_w, screen_h, 0,SDL_FULLSCREEN);#else screen_w = pCodecCtx->width; screen_h = pCodecCtx->height; screen = SDL_SetVideoMode(screen_w, screen_h, 0,0);#endif if(!screen) { printf("SDL: could not set video mode - exiting:%s\n",SDL_GetError()); return -1; } bmp = SDL_CreateYUVOverlay(pCodecCtx->width, pCodecCtx->height,SDL_YV12_OVERLAY, screen); rect.x = 0; rect.y = 0; rect.w = screen_w; rect.h = screen_h; //SDL End------------------------ packet=(AVPacket *)av_malloc(sizeof(AVPacket)); //Output Information----------------------------- printf("------------- File Information ------------------\n"); av_dump_format(pFormatCtx,0,filepath,0); printf("-------------------------------------------------\n");#if OUTPUT_YUV420P fp_yuv=fopen("output.yuv","wb+"); #endif SDL_WM_SetCaption("Simplest FFmpeg Player",NULL); img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL); //------------------------------ while(av_read_frame(pFormatCtx, packet)>=0){ if(packet->stream_index==videoindex){ //Decode ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet); if(ret < 0){ printf("Decode Error.\n"); return -1; } if(got_picture){ SDL_LockYUVOverlay(bmp); pFrameYUV->data[0]=bmp->pixels[0]; pFrameYUV->data[1]=bmp->pixels[2]; pFrameYUV->data[2]=bmp->pixels[1]; pFrameYUV->linesize[0]=bmp->pitches[0]; pFrameYUV->linesize[1]=bmp->pitches[2]; pFrameYUV->linesize[2]=bmp->pitches[1]; sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);#if OUTPUT_YUV420P int y_size=pCodecCtx->width*pCodecCtx->height; fwrite(pFrameYUV->data[0],1,y_size,fp_yuv); //Y fwrite(pFrameYUV->data[1],1,y_size/4,fp_yuv); //U fwrite(pFrameYUV->data[2],1,y_size/4,fp_yuv); //V#endif SDL_UnlockYUVOverlay(bmp); SDL_DisplayYUVOverlay(bmp, &rect); //Delay 40ms SDL_Delay(40); } } av_free_packet(packet); } //FIX: Flush Frames remained in Codec while (1) { ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet); if (ret < 0) break; if (!got_picture) break; sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize); SDL_LockYUVOverlay(bmp); pFrameYUV->data[0]=bmp->pixels[0]; pFrameYUV->data[1]=bmp->pixels[2]; pFrameYUV->data[2]=bmp->pixels[1]; pFrameYUV->linesize[0]=bmp->pitches[0]; pFrameYUV->linesize[1]=bmp->pitches[2]; pFrameYUV->linesize[2]=bmp->pitches[1];#if OUTPUT_YUV420P int y_size=pCodecCtx->width*pCodecCtx->height; fwrite(pFrameYUV->data[0],1,y_size,fp_yuv); //Y fwrite(pFrameYUV->data[1],1,y_size/4,fp_yuv); //U fwrite(pFrameYUV->data[2],1,y_size/4,fp_yuv); //V#endif SDL_UnlockYUVOverlay(bmp); SDL_DisplayYUVOverlay(bmp, &rect); //Delay 40ms SDL_Delay(40); } sws_freeContext(img_convert_ctx);#if OUTPUT_YUV420P fclose(fp_yuv);#endif SDL_Quit(); //av_free(out_buffer); av_free(pFrameYUV); avcodec_close(pCodecCtx); avformat_close_input(&pFormatCtx); return 0;}
1.1版之後,新添加了一個工程:simplest_ffmpeg_player_su(SU版)。
標準版在播放視訊的時候,畫面顯示使用延時40ms的方式。這麼做有兩個後果:(1)SDL彈出的視窗無法移動,一直顯示是忙碌狀態(2)畫面顯示並不是嚴格的40ms一幀,因為還沒有考慮解碼的時間。SU(SDL Update)版在視訊解碼的過程中,不再使用延時40ms的方式,而是建立了一個執行緒,每隔40ms傳送一個自定義的訊息,告知主函式進行解碼顯示。這樣做之後:(1)SDL彈出的視窗可以移動了(2)畫面顯示是嚴格的40ms一幀
simplest_ffmpeg_player_su(SU版)程式碼
/** * 最簡單的基於FFmpeg的視訊播放器SU(SDL升級版) * Simplest FFmpeg Player (SDL Update) * * 雷霄驊 Lei Xiaohua * [email protected] * 中國傳媒大學/數字電視技術 * Communication University of China / Digital TV Technology * http://blog.csdn.net/leixiaohua1020 * * 本程式實現了視訊檔案的解碼和顯示(支援HEVC,H.264,MPEG2等)。 * 是最簡單的FFmpeg視訊解碼方面的教程。 * 通過學習本例子可以瞭解FFmpeg的解碼流程。 * 本版本中使用SDL訊息機制重新整理視訊畫面。 * This software is a simplest video player based on FFmpeg. * Suitable for beginner of FFmpeg. * * Version:1.2 * * 備註: * 標準版在播放視訊的時候,畫面顯示使用延時40ms的方式。這麼做有兩個後果: * (1)SDL彈出的視窗無法移動,一直顯示是忙碌狀態 * (2)畫面顯示並不是嚴格的40ms一幀,因為還沒有考慮解碼的時間。 * SU(SDL Update)版在視訊解碼的過程中,不再使用延時40ms的方式,而是建立了 * 一個執行緒,每隔40ms傳送一個自定義的訊息,告知主函式進行解碼顯示。這樣做之後: * (1)SDL彈出的視窗可以移動了 * (2)畫面顯示是嚴格的40ms一幀 * Remark: * Standard Version use's SDL_Delay() to control video's frame rate, it has 2 * disadvantages: * (1)SDL's Screen can't be moved and always "Busy". * (2)Frame rate can't be accurate because it doesn't consider the time consumed * by avcodec_decode_video2() * SU(SDL Update)Version solved 2 problems above. It create a thread to send SDL * Event every 40ms to tell the main loop to decode and show video frames. */#include <stdio.h>#define __STDC_CONSTANT_MACROS#ifdef _WIN32//Windowsextern "C"{#include "libavcodec/avcodec.h"#include "libavformat/avformat.h"#include "libswscale/swscale.h"#include "SDL/SDL.h"};#else//Linux...#ifdef __cplusplusextern "C"{#endif#include <libavcodec/avcodec.h>#include <libavformat/avformat.h>#include <libswscale/swscale.h>#include <SDL/SDL.h>#ifdef __cplusplus};#endif#endif//Refresh#define SFM_REFRESH_EVENT (SDL_USEREVENT + 1)int thread_exit=0;//Threadint sfp_refresh_thread(void *opaque){ SDL_Event event; while (thread_exit==0) { event.type = SFM_REFRESH_EVENT; SDL_PushEvent(&event); //Wait 40 ms SDL_Delay(40); } return 0;}int main(int argc, char* argv[]){ AVFormatContext *pFormatCtx; int i, videoindex; AVCodecContext *pCodecCtx; AVCodec *pCodec; AVFrame *pFrame,*pFrameYUV; AVPacket *packet; struct SwsContext *img_convert_ctx; //SDL int ret, got_picture; int screen_w=0,screen_h=0; SDL_Surface *screen; SDL_Overlay *bmp; SDL_Rect rect; SDL_Thread *video_tid; SDL_Event event; char filepath[]="bigbuckbunny_480x272.h265"; av_register_all(); avformat_network_init(); pFormatCtx = avformat_alloc_context(); if(avformat_open_input(&pFormatCtx,filepath,NULL,NULL)!=0){ printf("Couldn't open input stream.\n"); return -1; } if(avformat_find_stream_info(pFormatCtx,NULL)<0){ printf("Couldn't find stream information.\n"); return -1; } videoindex=-1; for(i=0; i<pFormatCtx->nb_streams; i++) if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO){ videoindex=i; break; } if(videoindex==-1){ printf("Didn't find a video stream.\n"); return -1; } pCodecCtx=pFormatCtx->streams[videoindex]->codec; pCodec=avcodec_find_decoder(pCodecCtx->codec_id); if(pCodec==NULL) { printf("Codec not found.\n"); return -1; } if(avcodec_open2(pCodecCtx, pCodec,NULL)<0) { printf("Could not open codec.\n"); return -1; } pFrame=av_frame_alloc(); pFrameYUV=av_frame_alloc(); //uint8_t *out_buffer=(uint8_t *)av_malloc(avpicture_get_size(PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height)); //avpicture_fill((AVPicture *)pFrameYUV, out_buffer, PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height);//------------SDL---------------- if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) { printf( "Could not initialize SDL - %s\n", SDL_GetError()); return -1; } screen_w = pCodecCtx->width; screen_h = pCodecCtx->height; screen = SDL_SetVideoMode(screen_w, screen_h, 0,0); if(!screen) { printf("SDL: could not set video mode - exiting:%s\n",SDL_GetError()); return -1; } bmp = SDL_CreateYUVOverlay(pCodecCtx->width, pCodecCtx->height,SDL_YV12_OVERLAY, screen); rect.x = 0; rect.y = 0; rect.w = screen_w; rect.h = screen_h; packet=(AVPacket *)av_malloc(sizeof(AVPacket)); printf("---------------File Information------------------\n"); av_dump_format(pFormatCtx,0,filepath,0); printf("-------------------------------------------------\n"); img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL); //-------------- video_tid = SDL_CreateThread(sfp_refresh_thread,NULL); // SDL_WM_SetCaption("Simple FFmpeg Player (SDL Update)",NULL); //Event Loop for (;;) { //Wait SDL_WaitEvent(&event); if(event.type==SFM_REFRESH_EVENT){ //------------------------------ if(av_read_frame(pFormatCtx, packet)>=0){ if(packet->stream_index==videoindex){ ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet); if(ret < 0){ printf("Decode Error.\n"); return -1; } if(got_picture){ SDL_LockYUVOverlay(bmp); pFrameYUV->data[0]=bmp->pixels[0]; pFrameYUV->data[1]=bmp->pixels[2]; pFrameYUV->data[2]=bmp->pixels[1]; pFrameYUV->linesize[0]=bmp->pitches[0]; pFrameYUV->linesize[1]=bmp->pitches[2]; pFrameYUV->linesize[2]=bmp->pitches[1]; sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize); SDL_UnlockYUVOverlay(bmp); SDL_DisplayYUVOverlay(bmp, &rect); } } av_free_packet(packet); }else{ //Exit Thread thread_exit=1; break; } } } SDL_Quit(); sws_freeContext(img_convert_ctx); //-------------- //av_free(out_buffer); av_free(pFrameYUV); avcodec_close(pCodecCtx); avformat_close_input(&pFormatCtx); return 0;}
simplest_ffmpeg_player_su(SU版)中將simplest_ffmpeg_player(標準版)中的迴圈做了更改。標準版中為播放視訊的迴圈如下程式碼所示。
main(){ //... while(av_read_frame(pFormatCtx, packet)>=0) { //Decode... SDL_Delay(40); } //...}
可以看出標準版中使用SDL_Delay(40)控制視訊的播放速度。這樣有一些問題在前文中已經敘述。SU版定義了一個函式專門用於傳送“解碼和顯示”的Event。//自定義事件//重新整理畫面#define SFM_REFRESH_EVENT (SDL_USEREVENT + 1)int thread_exit=0;//Threadint sfp_refresh_thread(void *opaque){ while (thread_exit==0) { SDL_Event event; event.type = SFM_REFRESH_EVENT; SDL_PushEvent(&event); //Wait 40 ms SDL_Delay(40); } return 0;}
主函式形式如下。使用SDL_WaitEvent()等待Event進行解碼和顯示。main(){ //... SDL_Thread *video_tid = SDL_CreateThread(sfp_refresh_thread,NULL); //Event Loop SDL_Event event; for (;;) { //Wait SDL_WaitEvent(&event); if(event.type==SFM_REFRESH_EVENT){ //Decode... } } //...}
結果
軟體執行截圖:
完整工程下載地址:
更新(2014.5.10)==========================
完整工程(更新版)下載地址:
注1:類庫版本2014.5.6,已經支援HEVC以及VP9的解碼,附帶了這兩種視訊編碼的碼流檔案。此外修改了個別變更的API函式,並且提高了一些程式的效率。
注2:新版FFmpeg類庫Release下出現錯誤的解決方法如下:(注:此方法適用於所有近期釋出的FFmpeg類庫)VC工程屬性裡,linker->Optimization->References 選項,改成No(/OPT:NOREF)即可。
更新(2014.8.25)==========================
simplest ffmpeg player 1.1
版本升級至1.1,變為2個專案:
simplest_ffmpeg_player:標準版,FFmpeg學習的開始。simplest_ffmpeg_player_su:SU(SDL Update)版,加入了簡單的SDL的Event。
simplest_ffmpeg_player(標準版)增加了以下兩個選項(當然,程式碼量超過了100行)
1.輸出解碼後的YUV420P畫素資料檔案
2.全屏播放
以上兩項可以通過檔案前面的巨集進行控制:
#define SHOW_FULLSCREEN 0#define OUTPUT_YUV420P 0
另外修補了幾個的函式,例如增加了SDL_Quit()等。
simplest_ffmpeg_player_su(SU版)具體情況在上文中已經說明。
SourceForge上已經更新。
更新(2014.10.4)==========================
simplest ffmpeg player 1.2
版本升級至1.2。
1.新版本在原版本的基礎上增加了“flush_decoder”功能。當av_read_frame()迴圈退出的時候,實際上解碼器中可能還包含剩餘的幾幀資料。因此需要通過“flush_decoder”將這幾幀資料輸出。“flush_decoder”功能簡而言之即直接呼叫avcodec_decode_video2()獲得AVFrame,而不再向解碼器傳遞AVPacket。參考程式碼如下:
//FIX: Flush Frames remained in Codec while (1) { ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet); if (ret < 0) break; if (!got_picture) break; sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize); //處理... }
2.為了更好地適應Linux等其他作業系統,做到可以跨平臺,去除掉了VC特有的一些函式。比如“#include "stdafx.h"”,“_tmain()”等等。
SourceForge上已經更新。
Linux版本=================================
Linux下程式碼下載地址:
這個是Linux下的程式碼,在Ubuntu下測試可以執行,前提是安裝了FFmpeg和SDL(版本1.2)。編譯命令:
gcc simplest_ffmpeg_player.c -g -o smp.out -lSDLmain -lSDL -lavformat -lavcodec -lavutil -lswscale
使用方法:下列命令即可播放同一目錄下的test.flv檔案。
./smp.out test.flv
更新-最終版(2015.2.12)==========================
simplest ffmpeg player 1 final
這是該播放器原始碼的最後一次更新,以後會把更新的重點集中在基於FFmpeg和SDL2.0的視訊播放器。這次考慮到了跨平臺的要求,原始碼的調整幅度比較大。經過這次調整之後,原始碼可以在以下平臺編譯通過:
VC++:開啟sln檔案即可編譯,無需配置。
cl.exe:開啟compile_cl.bat即可命令列下使用cl.exe進行編譯,注意可能需要按照VC的安裝路徑調整腳本里面的引數。編譯命令如下。
::VS2010 Environmentcall "D:\Program Files\Microsoft Visual Studio 10.0\VC\vcvarsall.bat"::[email protected] INCLUDE=include;%INCLUDE%::[email protected] LIB=lib;%LIB%::compile and linkcl simplest_ffmpeg_player.cpp /MD /link SDL.lib SDLmain.lib avcodec.lib ^avformat.lib avutil.lib avdevice.lib avfilter.lib postproc.lib swresample.lib swscale.lib ^/SUBSYSTEM:WINDOWS /OPT:NOREFexit
MinGW:MinGW命令列下執行compile_mingw.sh即可使用MinGW的g++進行編譯。編譯命令如下。
g++ simplest_ffmpeg_player.cpp -g -o simplest_ffmpeg_player.exe \-I /usr/local/include -L /usr/local/lib \-lmingw32 -lSDLmain -lSDL -lavformat -lavcodec -lavutil -lswscale
GCC(Linux):Linux命令列下執行compile_gcc.sh即可使用GCC進行編譯。編譯命令如下。
gcc simplest_ffmpeg_player.cpp -g -o simplest_ffmpeg_player.out \-I /usr/local/include -L /usr/local/lib -lSDLmain -lSDL -lavformat -lavcodec -lavutil -lswscale
GCC(MacOS):Mac終端下執行compile_gcc_mac.sh即可使用Mac 的GCC進行編譯,Mac的GCC和Linux的GCC差別不大,但是使用SDL1.2的時候,必須加上“-framework Cocoa”引數,否則編譯無法通過。編譯命令如下。
gcc simplest_ffmpeg_player.cpp -g -o simplest_ffmpeg_player.out \-framework Cocoa -I /usr/local/include -L /usr/local/lib \-lSDLmain -lSDL -lavformat -lavcodec -lavutil -lswscale
PS:相關的編譯命令已經儲存到了工程資料夾中
此外,該版本修正了在某些系統下(例如部分Ubuntu)SDL綠屏顯示的問題,經過測試已經不再有綠屏現象。
SourceForge上已經更新。
FFMPEG相關學習資料
SDL GUIDE 中文譯本
ffdoc (FFMPEG的最完整教程)如何用FFmpeg編寫一個簡單播放器補充問題
補充1:舊版程式有一個小BUG,就是sws_getContext()之後,需要呼叫sws_freeContext()。否則長時間執行的話,會出現記憶體洩露的狀況。更新版已經修復。
補充2:有人會疑惑,為什麼解碼後的pFrame不直接用於顯示,而是呼叫swscale()轉換之後進行顯示?
如果不進行轉換,而是直接呼叫SDL進行顯示的話,會發現顯示出來的影象是混亂的。關鍵問題在於解碼後的pFrame的linesize裡儲存的不是影象的寬度,而是比寬度大一些的一個值。其原因目前還沒有仔細調查(大概是出於效能的考慮)。例如解析度為480x272的影象,解碼後的視訊的linesize[0]為512,而不是480。以第1行亮度畫素(pFrame->data[0])為例,從0-480儲存的是亮度資料,而從480-512則儲存的是無效的資料。因此需要使用swscale()進行轉換。轉換後去除了無效資料,linesize[0]變為480。就可以正常顯示了。