1. 程式人生 > >100行程式碼實現最簡單的基於FFMPEG+SDL的視訊播放器(SDL1.x)

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=-1for(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=-1for(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。就可以正常顯示了。