音視頻處理之FFmpeg+SDL視頻播放器20180409


一、FFmpeg視頻解碼器

1.視頻解碼知識

1).純凈的視頻解碼流程

壓縮編碼數據->像素數據。

例如解碼H.264,就是“H.264碼流->YUV”。

2).一般的視頻解碼流程

視頻碼流一般存儲在一定的封裝格式(例如MP4、AVI等)中。封裝格式中通常還包含音頻碼流等內容。

對於封裝格式中的視頻,需要先從封裝格式中提取中視頻碼流,然后再進行解碼。

例如解碼MKV格式的視頻文件,就是“MKV->H.264碼流->YUV”

 

2.VC下FFmpeg開發環境的搭建

新建控制台工程

打開visual studio

文件 新建 項目 win32控制台應用程序

 

拷貝FFmpeg開發文件

頭文件(*.h)拷貝至項目文件夾的include子文件夾下

導入庫文件(*.lib)拷貝至項目文件夾的lib子文件夾下

動態庫文件(*.dll)拷貝至項目文件夾下

PS:如果直接使用官網上下載的FFmpeg開發文件。則可能還需要將MinGW安裝目錄中的inttypes.h,stdint.h,_mingw.h三個文件拷貝至項目文件夾的include子文件夾下。

 

配置開發文件

打開屬性面板

解決方案資源管理器->右鍵單擊項目->屬性

頭文件配置

配置屬性->C/C++->常規->附加包含目錄,輸入“include”(剛才拷貝頭文件的目錄)

導入庫配置

配置屬性->鏈接器->常規->附加庫目錄,輸入“lib” (剛才拷貝庫文件的目錄)

配置屬性->鏈接器->輸入->附加依賴項,輸入“avcodec.lib; avformat.lib; avutil.lib; avdevice.lib; avfilter.lib; postproc.lib; swresample.lib; swscale.lib”(導入庫的文件名)

動態庫不用配置

 

main()中調用一個FFmpeg的接口函數

例如下面代碼打印出了FFmpeg的配置信息

int main(int argc, char* argv[]){

printf("%s", avcodec_configuration());

return 0;

}

如果運行無誤,則代表FFmpeg已經配置完成。

 

3.FFmpeg簡介

FFmpeg一共包含8個庫:

avcodec:編解碼(最重要的庫)。

avformat:封裝格式處理。

avfilter:濾鏡特效處理。

avdevice:各種設備的輸入輸出。

avutil:工具庫(大部分庫都需要這個庫的支持)。

postproc:后加工。

swresample:音頻采樣數據格式轉換。

swscale:視頻像素數據格式轉換。

 

 

1).FFmpeg解碼流程圖見圖1:

 

 

2).FFmpeg解碼函數簡介

av_register_all():注冊所有組件。

avformat_open_input():打開輸入視頻文件。

avformat_find_stream_info():獲取視頻文件信息。

avcodec_find_decoder():查找解碼器。

avcodec_open2():打開解碼器。

av_read_frame():從輸入文件讀取一幀壓縮數據。

avcodec_decode_video2():解碼一幀壓縮數據。

avcodec_close():關閉解碼器。

avformat_close_input():關閉輸入視頻文件。

 

3).FFmpeg相關結構體(類型)見圖2:

 

I、FFmpeg數據結構簡介

AVFormatContext

封裝格式上下文結構體,也是統領全局的結構體,保存了視頻文件封裝格式相關信息。

AVInputFormat

每種封裝格式(例如FLV, MKV, MP4, AVI)對應一個該結構體。

AVStream

視頻文件中每個視頻(音頻)流對應一個該結構體。

AVCodecContext

編碼器上下文結構體,保存了視頻(音頻)編解碼相關信息。

AVCodec

每種視頻(音頻)編解碼器(例如H.264解碼器)對應一個該結構體。

AVPacket

存儲一幀壓縮編碼數據。

AVFrame

存儲一幀解碼后像素(采樣)數據。

 

II、FFmpeg數據結構分析

AVFormatContext

iformat:輸入視頻的AVInputFormat

nb_streams :輸入視頻的AVStream 個數

streams :輸入視頻的AVStream []數組

duration :輸入視頻的時長(以微秒為單位)

bit_rate :輸入視頻的碼率

AVInputFormat

name:封裝格式名稱

long_name:封裝格式的長名稱

extensions:封裝格式的擴展名

id:封裝格式ID

一些封裝格式處理的接口函數

AVStream

id:序號

codec:該流對應的AVCodecContext

time_base:該流的時基

r_frame_rate:該流的幀率

AVCodecContext

codec:編解碼器的AVCodec

width, height:圖像的寬高(只針對視頻)

pix_fmt:像素格式(只針對視頻)

sample_rate:采樣率(只針對音頻)

channels:聲道數(只針對音頻)

sample_fmt:采樣格式(只針對音頻)

AVCodec

name:編解碼器名稱

long_name:編解碼器長名稱

type:編解碼器類型

id:編解碼器ID

一些編解碼的接口函數

 

AVPacket

pts:顯示時間戳

dts :解碼時間戳

data :壓縮編碼數據

size :壓縮編碼數據大小

stream_index :所屬的AVStream

AVFrame

data:解碼后的圖像像素數據(音頻采樣數據)。

linesize:對視頻來說是圖像中一行像素的大小;對音頻來說是整個音頻幀的大小。

width, height:圖像的寬高(只針對視頻)。

key_frame:是否為關鍵幀(只針對視頻) 。

pict_type:幀類型(只針對視頻) 。例如I,P,B。

 

III、注意事項

裸流文件,即如h264文件,是沒有時長等信息的,封裝格式的才有

 

數據結構第一層為封裝格式相關的,下面層為編解碼相關的

其中

AVStream中的time_base 表示該流的時基,也就是時間的基數,簡單可理解為時間的單位,比如時間數是2,時基是1s,則時間為2s

 

AVPacket:可以理解為h264中的數據包,編碼后的包

其中,pts:顯示時間戳,表示該視頻幀幾分幾秒的時候放到屏幕上。這個值只是一個整數 沒有單位,所以要得到具體的幾分幾秒需要和前面的time_base時基參數相乘得到。

dts:解碼時間戳

stream_index:標識,表示該流屬於音頻流還是視頻流。

data:壓縮編碼的數據,如h264的編碼,就可以取出該數據保存起來形成的文件就是h264文件。

通過av_read_frame可從大的數據結構指針中讀取到AVPacket數據

 

注意播放的順序不一定按照存儲的順序來。即有一個顯示的順序和一個解碼的順序。

 

AVFrame

data:解碼后的圖像像素數據,如YUV等,可形成yuv文件(依次存儲的是y、u、v分量,其中u 寬和高都只有y一半,整個數據量就y的四分之一,v的也一樣)。data是一個指針數組,其中一個分量對應一個數組

數據先通過解碼函數avcodec_decode_video2進行解碼,然后由於解碼出來的數據由於是有黑邊(多出一塊多余的數據),所以還需用sws_scale函數把這塊多余的裁了。

 

4).解碼后的數據要經過sws_scale()函數處理

解碼后YUV格式的視頻像素數據保存在AVFrame的data[0]、data[1]、data[2]中。但是這些像素值並不是連續存儲的,每行有效像素之后存儲了一些無效像素。

以亮度Y數據為例,data[0]中一共包含了linesize[0]*height個數據。但是出於優化等方面的考慮,linesize[0]實際上並不等於寬度width,而是一個比寬度大一些的值。

因此需要使用sws_scale()進行轉換。轉換后去除了無效數據,width和linesize[0] 取值相等。如下圖3:

 

其中,sws_scale()函數需要用到的轉換信息,即第一個參數,是由sws_getContext函數獲得的

 

轉換后保存在一個新的幀數據結構體,由於使用的轉換函數而不是解碼函數,所以這個結構體還需要填充其內部的緩沖區,用於存儲像素數據,填充的方法使用avpicture_fill函數:

int avpicture_fill(AVPicture *picture, uint8_t *ptr,int pix_fmt, int width, int height);

這個函數的使用本質上是為已經分配的空間的結構體(AVPicture *)ptFrame掛上一段用於保存數據的空間,

這個結構體中有一個指針數組data[AV_NUM_DATA_POINTERS],掛在這個數組里。一般我們這么使用:

    I、pFrameRGB=avcodec_alloc_frame();

    II、numBytes=avpicture_get_size(PIX_FMT_RGB24, pCodecCtx->width,pCodecCtx->height);

        buffer=(uint8_t *)av_malloc(numBytes*sizeof(uint8_t));

    III、avpicture_fill((AVPicture *)pFrameRGB, buffer, PIX_FMT_RGB24,pCodecCtx->width, pCodecCtx->height);

以上就是為pFrameRGB掛上buffer。這個buffer是用於存緩沖數據的。

 

ptFrame為什么不用fill空間。主要是下面這句:

    avcodec_decode_video(pCodecCtx, pFrame, &frameFinished,packet.data, packet.size);

很可能是ptFrame已經掛上了packet.data,所以就不用fill了。

 

 

 

二、SDL視頻顯示

SDL(Simple DirectMedia Layer)庫的作用說白了就是封裝了復雜的視音頻底層交互工作,簡化了視音頻處理的難度。

主要用來做游戲,現在只用到其視頻顯示部分。

特點:跨平台,開源

 

1.庫的結構圖見圖4:

 

實際上它調用了底層api完成了和硬件的交互,比如linux下,就操作了framebuffer.

 

2.配置vc工程和之前ffmpeg的配置幾乎都是一樣的:

1).新建控制台工程

  打開VC++

  文件->新建->項目->Win32控制台應用程序

2).拷貝SDL開發文件

  頭文件(*.h)拷貝至項目文件夾的include子文件夾下

  導入庫文件(*.lib)拷貝至項目文件夾的lib子文件夾下

  動態庫文件(*.dll)拷貝至項目文件夾下

3).配置開發文件

  打開屬性面板

  解決方案資源管理器->右鍵單擊項目->屬性

4).頭文件配置

 配置屬性->C/C++->常規->附加包含目錄,輸入“include”(剛才拷貝文件的目錄)

5).導入庫配置

  配置屬性->鏈接器->常規->附加庫目錄,輸入“lib” (剛才拷貝文件的目錄)

  配置屬性->鏈接器->輸入->附加依賴項,輸入“SDL2.lib; SDL2main.lib”(導入庫的文件名)

6).動態庫不用配置

 

7).是否配置成功,調用sdl的初始化函數,查看其返回值即可確定:

創建源代碼文件

在工程中創建一個包含main()函數的C/C++文件(如果已經有了可以跳過這一步),后續步驟在該文件中編寫源代碼。

包含頭文件

如果是C語言中使用SDL,則直接使用下面代碼

#include "SDL2/SDL.h"

如果是C++語言中使用SDL,則使用下面代碼

extern "C"

{

#include "SDL2/SDL.h"

}

main()中調用一個SDL的接口函數

例如下面代碼初始化了SDL

int main(int argc, char* argv[]){

if(SDL_Init(SDL_INIT_VIDEO)) {

printf( "Could not initialize SDL - %s\n", SDL_GetError());

} else{

printf("Success init SDL");

}

return 0;

}

如果運行無誤,則代表SDL已經配置完成。

 

 

3.SDL視頻顯示的流程圖見圖5:

 

其中特別要注意的是創建紋理數據時要傳入渲染器,原因是,

紋理數據依賴於渲染器,只有通過渲染器創建才能得到該渲染器合法的紋理數據((數據格式)符合該渲染器的要求),

填充了像素數據且符合要求的紋理數據才可以拷貝給渲染器,並且渲染器才能正確顯示出來。

 

1).SDL視頻顯示函數簡介

SDL_Init():初始化SDL系統

SDL_CreateWindow():創建窗口SDL_Window

SDL_CreateRenderer():創建渲染器SDL_Renderer

SDL_CreateTexture():創建紋理SDL_Texture

SDL_UpdateTexture():設置紋理的數據

SDL_RenderCopy():將紋理的數據拷貝給渲染器

SDL_RenderPresent():顯示

SDL_Delay():工具函數,用於延時。

SDL_Quit():退出SDL系統

其中SDL_Delay 延時函數,控制顯示的速度,即控制幀率。通常每秒25幀,所以通常延時也就是40ms

 

2).SDL視頻顯示的數據結構

I、SDL視頻顯示的數據結構如圖6所示:

 

II、SDL數據結構簡介

SDL_Window

代表了一個“窗口”

SDL_Renderer

代表了一個“渲染器”

SDL_Texture

代表了一個“紋理”

SDL_Rect

一個簡單的矩形結構

具體來說,

SDL_Window 窗口,類似彈出的窗口

SDL_Renderer 渲染器,把紋理數據畫(渲染)到window上

一個window上不僅僅只有一副畫面,類似多組監控畫面,即一個window可對應多個yuv數據

SDL_Rect,正方形結構,存了矩形的坐標,長寬,以便確定紋理數據畫在哪個位置,確定位置用,比如畫在左上角就用這個來確定。被渲染器調用

 

SDL_Rect中的x y值是左上角為圓點開始的坐標值,調整x y值以及w h值,就可以實現在窗口的指定位置顯示,沒有畫面的地方為黑框。

當x y等於0,w h等於窗口的寬高時即為全屏顯示,此時調整寬高大小,只需調整窗口大小即可。                

 

 

4.進階-SDL中事件和多線程

1).SDL多線程

函數

SDL_CreateThread():創建一個線程

數據結構

SDL_Thread:線程的句柄

SDL事件

函數

SDL_WaitEvent()等待一個事件

SDL_PushEvent()發送一個事件

數據結構

SDL_Event:代表一個事件

SDL中事件和多線程,可用於解決上個程序播放過程中鼠標不能動的問題,

使用事件,等待事件中,就會響應鼠標鍵盤等事件,就不會卡在那了

將延時操作放到一個子線程中,用事件通知主線程,這樣主線程就不用延時了,就可以及時響應事件了

SDL_WINDOWENVENT sdl系統自帶的事件,當拉伸窗口的時候會觸發

SDL_QUIT 也是SDL自帶的事件,當點擊窗口的×時觸發

 

 

 

三、FFmpeg+SDL視頻播放器

 

1.FFmpeg+SDL整合之后實現:視頻文件->YUV->屏幕

 

2.SDL_UpdateTexture函數最后一個參數表示的是,一行像素數據的數據量

窗口的寬和高無所謂,但是紋理數據的寬和高要和視頻數據的寬和高一致。

 

3.對於大數據的調試,如果直接打印出來,數據太多不好分析,一般是寫入到文件,然后用工具打開文件去分析。

比如視頻像素數據,可以寫入到yuv文件,再用yuv分析工具來分析。

 

4.脫離開發環境的獨立播放器

在解決方案中的debug目錄下的*.exe文件即為編譯好的可執行文件。

執行這個文件就可以脫離開發環境運行。

 

5.調試時,在屬性->調試->命令參數 中填的東西,就是程序的輸入參數,也就是argv中的內容

平時獨立的exe程序,只需在執行的時候后面加上參數就是輸入參數了,如ffplay.exe那樣。

 

 

四、FFmpeg+SDL視頻播放器代碼實現

主要是FFmpegAndSDL.cpp文件,代碼如下(基本上每一行都有注釋):

  1 /*****************************************************************************
  2 * Copyright (C) 2017-2020 Hanson Yu  All rights reserved.
  3 ------------------------------------------------------------------------------
  4 * File Module       :     FFmpegAndSDL.cpp
  5 * Description       :     FFmpegAndSDL Demo
  6 
  7 
  8 * Created           :     2017.09.21.
  9 * Author            :     Yu Weifeng
 10 * Function List     :     
 11 * Last Modified     :     
 12 * History           :     
 13 * Modify Date      Version         Author           Modification
 14 * -----------------------------------------------
 15 * 2017/09/21      V1.0.0         Yu Weifeng       Created
 16 ******************************************************************************/
 17 
 18 #include "stdafx.h"
 19 #include <stdio.h>
 20 
 21 
 22 /*解決錯誤:
 23 LNK2019    無法解析的外部符號 __imp__fprintf,該符號在函數 _ShowError 中被引用
 24 
 25 原因:
 26 ……這是鏈接庫問題
 27 就是工程里面沒有添加那兩個函數需要的庫,#progma這個是代碼鏈接庫
 28 第二句是vs2015兼容的問題。
 29 lib庫的vs編譯版本 和 工程的vs開發版本 不一致。
 30 導出函數定義變了。所以要人為加一個函數導出。
 31 */
 32 #pragma comment(lib, "legacy_stdio_definitions.lib")
 33 extern "C" { FILE __iob_func[3] = { *stdin,*stdout,*stderr }; }
 34 
 35 /*
 36 __STDC_LIMIT_MACROS and __STDC_CONSTANT_MACROS are a workaround to allow C++ programs to use stdint.h
 37 macros specified in the C99 standard that aren't in the C++ standard. The macros, such as UINT8_MAX, INT64_MIN,
 38 and INT32_C() may be defined already in C++ applications in other ways. To allow the user to decide
 39 if they want the macros defined as C99 does, many implementations require that __STDC_LIMIT_MACROS
 40 and __STDC_CONSTANT_MACROS be defined before stdint.h is included.
 41 
 42 This isn't part of the C++ standard, but it has been adopted by more than one implementation.
 43 */
 44 #define __STDC_CONSTANT_MACROS
 45 
 46 extern "C"
 47 {
 48 #include "libavcodec/avcodec.h"
 49 #include "libavformat/avformat.h"
 50 #include "libswscale/swscale.h"
 51 #include "SDL2/SDL.h"
 52 };
 53 
 54 
 55 //Refresh Event 自定義事件
 56 #define PLAY_REFRESH_EVENT       (SDL_USEREVENT + 1)//自定義刷新圖像(播放)事件
 57 #define PLAY_BREAK_EVENT         (SDL_USEREVENT + 2) //自定義退出播放事件
 58 
 59 
 60 static int g_iThreadExitFlag = 0;
 61 /*****************************************************************************
 62 -Fuction        : RefreshPlayThread
 63 -Description    : RefreshPlayThread
 64 -Input          : 
 65 -Output         : 
 66 -Return         : 
 67 * Modify Date      Version         Author           Modification
 68 * -----------------------------------------------
 69 * 2017/09/21      V1.0.0         Yu Weifeng       Created
 70 ******************************************************************************/
 71 int RefreshPlayThread(void *opaque) 
 72 {
 73     g_iThreadExitFlag = 0;
 74     SDL_Event tEvent={0};
 75     
 76     while (!g_iThreadExitFlag) 
 77     {
 78         tEvent.type = PLAY_REFRESH_EVENT;
 79         SDL_PushEvent(&tEvent);//發送事件給其他線程
 80         SDL_Delay(20);//延時函數 填40的時候,視頻會有種卡的感覺
 81     }
 82     //Break
 83     g_iThreadExitFlag = 0;
 84     tEvent.type = PLAY_BREAK_EVENT;
 85     SDL_PushEvent(&tEvent);//發送事件給其他線程 發送一個事件
 86 
 87     return 0;
 88 }
 89 
 90 /*****************************************************************************
 91 -Fuction        : main
 92 -Description    : main
 93 -Input          : 
 94 -Output         : 
 95 -Return         : 
 96 * Modify Date      Version         Author           Modification
 97 * -----------------------------------------------
 98 * 2017/09/21      V1.0.0         Yu Weifeng       Created
 99 ******************************************************************************/
100 int main(int argc, char* argv[])
101 {
102     /*------------FFmpeg----------------*/
103     const char *strFilePath = "屌絲男士.mov";
104     AVFormatContext    *ptFormatContext = NULL;//封裝格式上下文,內部包含所有的視頻信息
105     int                i = 0; 
106     int             iVideoindex=0;//純視頻信息在音視頻流中的位置,也就是指向音視頻流數組中的視頻元素
107     AVCodecContext    *ptCodecContext;//編碼器相關信息上下文,內部包含編碼器相關的信息,指向AVFormatContext中的streams成員中的codec成員
108     AVCodec            *ptCodec;//編碼器,使用函數avcodec_find_decoder或者,該函數需要的id參數,來自於ptCodecContext中的codec_id成員
109     AVFrame            *ptFrame=NULL;//存儲一幀解碼后像素(采樣)數據
110     AVFrame            *ptFrameAfterScale=NULL;//存儲(解碼數據)轉換后的像素(采樣)數據
111     unsigned char   *pucFrameAfterScaleBuf=NULL;//用於存儲ptFrameAfterScale中的像素(采樣)緩沖數據
112     AVPacket        *ptPacket=NULL;//存儲一幀壓縮編碼數據
113     int             iRet =0;
114     int             iGotPicture=0;//解碼函數的返回參數,got_picture_ptr Zero if no frame could be decompressed, otherwise, it is nonzero
115 
116     /*------------SDL----------------*/
117     int iScreenWidth=0, iScreenHeight=0;//視頻的寬和高,指向ptCodecContext中的寬和高
118     SDL_Window *ptSdlWindow=NULL;//用於sdl顯示視頻的窗口(用於顯示的屏幕)
119     SDL_Renderer* ptSdlRenderer=NULL;//sdl渲染器,把紋理數據畫(渲染)到window上
120     SDL_Texture* ptSdlTexture=NULL;//sdl紋理數據,用於存放像素(采樣)數據,然后給渲染器
121     SDL_Rect tSdlRect ={0};//正方形矩形結構,存了矩形的坐標,長寬,以便確定紋理數據畫在哪個位置,確定位置用,比如畫在左上角就用這個來確定。被渲染器調用
122     SDL_Thread *ptVideoControlTID=NULL;//sdl線程id,線程的句柄
123     SDL_Event tSdlEvent = {0};//sdl事件,代表一個事件
124 
125     /*------------像素數據處理----------------*/
126     struct SwsContext *ptImgConvertInfo;//圖像轉換(上下文)信息,圖像轉換函數sws_scale需要的參數,由sws_getContext函數賦值
127 
128 
129 
130     /*------------FFmpeg----------------*/
131     av_register_all();//注冊FFmpeg所有組件
132     avformat_network_init();//初始化網絡組件
133     
134     ptFormatContext = avformat_alloc_context();//分配空間給ptFormatContext
135     if (avformat_open_input(&ptFormatContext, strFilePath, NULL, NULL) != 0) 
136     {//打開輸入視頻文件
137         printf("Couldn't open input stream.\n");
138         return -1;
139     }
140     if (avformat_find_stream_info(ptFormatContext, NULL)<0) 
141     {//獲取視頻文件信息
142         printf("Couldn't find stream information.\n");
143         return -1;
144     }
145     //獲取編碼器相關信息上下文,並賦值給ptCodecContext
146     iVideoindex = -1;
147     for (i = 0; i<ptFormatContext->nb_streams; i++)
148     {
149         if (ptFormatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) 
150         {
151             iVideoindex = i;
152             break;
153         }
154     }
155     if (iVideoindex == -1) 
156     {
157         printf("Didn't find a video stream.\n");
158         return -1;
159     }
160     ptCodecContext = ptFormatContext->streams[iVideoindex]->codec;
161     
162     ptCodec = avcodec_find_decoder(ptCodecContext->codec_id);//查找解碼器
163     if (ptCodec == NULL) 
164     {
165         printf("Codec not found.\n");
166         return -1;
167     }
168     if (avcodec_open2(ptCodecContext, ptCodec, NULL)<0) 
169     {//打開解碼器
170         printf("Could not open codec.\n");
171         return -1;
172     }
173     
174     ptPacket = (AVPacket *)av_malloc(sizeof(AVPacket));//分配保存解碼前數據的空間
175     ptFrame = av_frame_alloc();//分配結構體空間,結構體內部的指針指向的數據暫未分配,用於保存圖像轉換前的像素數據
176     
177     /*------------像素數據處理----------------*/
178     ptFrameAfterScale = av_frame_alloc();//分配結構體空間,結構體內部的指針指向的數據暫未分配,用於保存圖像轉換后的像素數據
179     pucFrameAfterScaleBuf = (uint8_t *)av_malloc(avpicture_get_size(PIX_FMT_YUV420P, ptCodecContext->width, ptCodecContext->height));//分配保存數據的空間
180      /*int avpicture_fill(AVPicture *picture, uint8_t *ptr,int pix_fmt, int width, int height);
181     這個函數的使用本質上是為已經分配的空間的結構體(AVPicture *)ptFrame掛上一段用於保存數據的空間,
182     這個結構體中有一個指針數組data[AV_NUM_DATA_POINTERS],掛在這個數組里。一般我們這么使用:
183     1) pFrameRGB=avcodec_alloc_frame();
184     2) numBytes=avpicture_get_size(PIX_FMT_RGB24, pCodecCtx->width,pCodecCtx->height);
185         buffer=(uint8_t *)av_malloc(numBytes*sizeof(uint8_t));
186     3) avpicture_fill((AVPicture *)pFrameRGB, buffer, PIX_FMT_RGB24,pCodecCtx->width, pCodecCtx->height);
187     以上就是為pFrameRGB掛上buffer。這個buffer是用於存緩沖數據的。
188     ptFrame為什么不用fill空間。主要是下面這句:
189     avcodec_decode_video(pCodecCtx, pFrame, &frameFinished,packet.data, packet.size);
190     很可能是ptFrame已經掛上了packet.data,所以就不用fill了。*/
191     avpicture_fill((AVPicture *)ptFrameAfterScale, pucFrameAfterScaleBuf, PIX_FMT_YUV420P, ptCodecContext->width, ptCodecContext->height);    
192     //sws開頭的函數用於處理像素(采樣)數據
193     ptImgConvertInfo = sws_getContext(ptCodecContext->width, ptCodecContext->height, ptCodecContext->pix_fmt,
194         ptCodecContext->width, ptCodecContext->height, PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);//獲取圖像轉換(上下文)信息
195 
196     /*------------SDL----------------*/
197     if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER)) 
198     {//初始化SDL系統
199         printf("Could not initialize SDL - %s\n", SDL_GetError());
200         return -1;
201     }
202     //SDL 2.0 Support for multiple windows
203     iScreenWidth = ptCodecContext->width;
204     iScreenHeight = ptCodecContext->height;
205     ptSdlWindow = SDL_CreateWindow("Simplest ffmpeg player's Window", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
206         iScreenWidth, iScreenHeight, SDL_WINDOW_OPENGL);//創建窗口SDL_Window
207 
208     if (!ptSdlWindow) 
209     {
210         printf("SDL: could not create window - exiting:%s\n", SDL_GetError());
211         return -1;
212     }
213     ptSdlRenderer = SDL_CreateRenderer(ptSdlWindow, -1, 0);//創建渲染器SDL_Renderer
214     //IYUV: Y + U + V  (3 planes)
215     //YV12: Y + V + U  (3 planes)
216     //創建紋理SDL_Texture
217     ptSdlTexture = SDL_CreateTexture(ptSdlRenderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, ptCodecContext->width, ptCodecContext->height);
218 
219     tSdlRect.x = 0;//x y值是左上角為圓點開始的坐標值,調整x y值以及w h值,就可以實現在窗口的指定位置顯示,沒有畫面的地方為黑框
220     tSdlRect.y = 0;//當x y等於0,w h等於窗口的寬高時即為全屏顯示,此時調整寬高大小,只需調整窗口大小即可
221     tSdlRect.w = iScreenWidth;
222     tSdlRect.h = iScreenHeight;
223 
224     ptVideoControlTID = SDL_CreateThread(RefreshPlayThread, NULL, NULL);//創建一個線程
225     
226     while (1) 
227     {//Event Loop        
228         SDL_WaitEvent(&tSdlEvent);//Wait,等待其他線程過來的事件
229         if (tSdlEvent.type == PLAY_REFRESH_EVENT) //自定義刷新圖像(播放)事件
230         {
231             /*------------FFmpeg----------------*/
232             if (av_read_frame(ptFormatContext, ptPacket) >= 0) //從輸入文件讀取一幀壓縮數據
233             {
234                 if (ptPacket->stream_index == iVideoindex) 
235                 {
236                     iRet = avcodec_decode_video2(ptCodecContext, ptFrame, &iGotPicture, ptPacket);//解碼一幀壓縮數據
237                     if (iRet < 0) 
238                     {
239                         printf("Decode Error.\n");
240                         return -1;
241                     }
242                     if (iGotPicture) 
243                     {
244                         //圖像轉換,sws_scale()函數需要用到的轉換信息,即第一個參數,是由sws_getContext函數獲得的
245                         sws_scale(ptImgConvertInfo, (const uint8_t* const*)ptFrame->data, ptFrame->linesize, 0, ptCodecContext->height, ptFrameAfterScale->data, ptFrameAfterScale->linesize);
246 
247                         /*------------SDL----------------*/
248                         SDL_UpdateTexture(ptSdlTexture, NULL, ptFrameAfterScale->data[0], ptFrameAfterScale->linesize[0]);//設置(更新)紋理的數據
249                         SDL_RenderClear(ptSdlRenderer);//先清除渲染器里的數據
250                         //SDL_RenderCopy( ptSdlRenderer, ptSdlTexture, &tSdlRect, &tSdlRect );  //將紋理的數據拷貝給渲染器
251                         SDL_RenderCopy(ptSdlRenderer, ptSdlTexture, NULL, NULL);//將紋理的數據拷貝給渲染器
252                         SDL_RenderPresent(ptSdlRenderer);//顯示
253                     }
254                 }
255                 av_free_packet(ptPacket);//釋放空間
256             }
257             else 
258             {                
259                 g_iThreadExitFlag = 1;//Exit Thread
260             }
261         }
262         else if (tSdlEvent.type == SDL_QUIT) //也是SDL自帶的事件,當點擊窗口的×時觸發//SDL_WINDOWENVENT sdl系統自帶的事件,當拉伸窗口的時候會觸發
263         {
264             g_iThreadExitFlag = 1;
265         }
266         else if (tSdlEvent.type == PLAY_BREAK_EVENT) //自定義退出播放事件
267         {
268             break;
269         }
270 
271     }
272     
273     /*------------像素數據處理----------------*/
274     sws_freeContext(ptImgConvertInfo);//釋放空間
275     
276     /*------------SDL----------------*/
277     SDL_Quit();//退出SDL系統
278 
279     /*------------FFmpeg----------------*/
280     av_frame_free(&ptFrameAfterScale);//釋放空間
281     av_frame_free(&ptFrame);//釋放空間
282     avcodec_close(ptCodecContext);//關閉解碼器
283     avformat_close_input(&ptFormatContext);//關閉輸入視頻文件
284 
285     return 0;
286 }
FFmpegAndSDL.cpp

 

具體代碼見github:

https://github.com/fengweiyu/FFmpegAndSDL


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM