一、FFmpeg視頻解碼器
1.視頻解碼知識
1).純凈的視頻解碼流程
壓縮編碼數(shù)據(jù)->像素數(shù)據(jù)。
例如解碼H.264,就是“H.264碼流->YUV”。
2).一般的視頻解碼流程
視頻碼流一般存儲在一定的封裝格式(例如MP4、AVI等)中。封裝格式中通常還包含音頻碼流等內(nèi)容。
對于封裝格式中的視頻,需要先從封裝格式中提取中視頻碼流,然后再進(jìn)行解碼。
例如解碼MKV格式的視頻文件,就是“MKV->H.264碼流->YUV”
2.VC下FFmpeg開發(fā)環(huán)境的搭建
新建控制臺工程
打開visual studio
文件 新建 項目 win32控制臺應(yīng)用程序
拷貝FFmpeg開發(fā)文件
頭文件(*.h)拷貝至項目文件夾的include子文件夾下
導(dǎo)入庫文件(*.lib)拷貝至項目文件夾的lib子文件夾下
動態(tài)庫文件(*.dll)拷貝至項目文件夾下
PS:如果直接使用官網(wǎng)上下載的FFmpeg開發(fā)文件。則可能還需要將MinGW安裝目錄中的inttypes.h,stdint.h,_mingw.h三個文件拷貝至項目文件夾的include子文件夾下。
配置開發(fā)文件
打開屬性面板
解決方案資源管理器->右鍵單擊項目->屬性
頭文件配置
配置屬性->C/C++->常規(guī)->附加包含目錄,輸入“include”(剛才拷貝頭文件的目錄)
導(dǎo)入庫配置
配置屬性->鏈接器->常規(guī)->附加庫目錄,輸入“lib” (剛才拷貝庫文件的目錄)
配置屬性->鏈接器->輸入->附加依賴項,輸入“avcodec.lib; avformat.lib; avutil.lib; avdevice.lib; avfilter.lib; postproc.lib; swresample.lib; swscale.lib”(導(dǎo)入庫的文件名)
動態(tài)庫不用配置
main()中調(diào)用一個FFmpeg的接口函數(shù)
例如下面代碼打印出了FFmpeg的配置信息
int main(int argc, char* argv[]){ printf("%s", avcodec_configuration()); return 0; }如果運行無誤,則代表FFmpeg已經(jīng)配置完成。
3.FFmpeg簡介
FFmpeg一共包含8個庫:
avcodec:編解碼(最重要的庫)。
avformat:封裝格式處理。
avfilter:濾鏡特效處理。
avdevice:各種設(shè)備的輸入輸出。
avutil:工具庫(大部分庫都需要這個庫的支持)。
postproc:后加工。
swresample:音頻采樣數(shù)據(jù)格式轉(zhuǎn)換。
swscale:視頻像素數(shù)據(jù)格式轉(zhuǎn)換。
1).FFmpeg解碼流程圖見圖1:
2).FFmpeg解碼函數(shù)簡介
av_register_all():注冊所有組件。
avformat_open_input():打開輸入視頻文件。
avformat_find_stream_info():獲取視頻文件信息。
avcodec_find_decoder():查找解碼器。
avcodec_open2():打開解碼器。
av_read_frame():從輸入文件讀取一幀壓縮數(shù)據(jù)。
avcodec_decode_video2():解碼一幀壓縮數(shù)據(jù)。
avcodec_close():關(guān)閉解碼器。
avformat_close_input():關(guān)閉輸入視頻文件。
3).FFmpeg相關(guān)結(jié)構(gòu)體(類型)見圖2:
I、FFmpeg數(shù)據(jù)結(jié)構(gòu)簡介
AVFormatContext
封裝格式上下文結(jié)構(gòu)體,也是統(tǒng)領(lǐng)全局的結(jié)構(gòu)體,保存了視頻文件封裝格式相關(guān)信息。
AVInputFormat
每種封裝格式(例如FLV, MKV, MP4, AVI)對應(yīng)一個該結(jié)構(gòu)體。
AVStream
視頻文件中每個視頻(音頻)流對應(yīng)一個該結(jié)構(gòu)體。
AVCodecContext
編碼器上下文結(jié)構(gòu)體,保存了視頻(音頻)編解碼相關(guān)信息。
AVCodec
每種視頻(音頻)編解碼器(例如H.264解碼器)對應(yīng)一個該結(jié)構(gòu)體。
AVPacket
存儲一幀壓縮編碼數(shù)據(jù)。
AVFrame
存儲一幀解碼后像素(采樣)數(shù)據(jù)。
II、FFmpeg數(shù)據(jù)結(jié)構(gòu)分析
AVFormatContext
iformat:輸入視頻的AVInputFormat
nb_streams :輸入視頻的AVStream 個數(shù)
streams :輸入視頻的AVStream []數(shù)組
duration :輸入視頻的時長(以微秒為單位)
bit_rate :輸入視頻的碼率
AVInputFormat
name:封裝格式名稱
long_name:封裝格式的長名稱
extensions:封裝格式的擴(kuò)展名
id:封裝格式ID
一些封裝格式處理的接口函數(shù)
AVStream
id:序號
codec:該流對應(yīng)的AVCodecContext
time_base:該流的時基
r_frame_rate:該流的幀率
AVCodecContext
codec:編解碼器的AVCodec
width, height:圖像的寬高(只針對視頻)
pix_fmt:像素格式(只針對視頻)
sample_rate:采樣率(只針對音頻)
channels:聲道數(shù)(只針對音頻)
sample_fmt:采樣格式(只針對音頻)
AVCodec
name:編解碼器名稱
long_name:編解碼器長名稱
type:編解碼器類型
id:編解碼器ID
一些編解碼的接口函數(shù)
AVPacket
pts:顯示時間戳
dts :解碼時間戳
data :壓縮編碼數(shù)據(jù)
size :壓縮編碼數(shù)據(jù)大小
stream_index :所屬的AVStream
AVFrame
data:解碼后的圖像像素數(shù)據(jù)(音頻采樣數(shù)據(jù))。
linesize:對視頻來說是圖像中一行像素的大小;對音頻來說是整個音頻幀的大小。
width, height:圖像的寬高(只針對視頻)。
key_frame:是否為關(guān)鍵幀(只針對視頻) 。
pict_type:幀類型(只針對視頻) 。例如I,P,B。
III、注意事項
裸流文件,即如h264文件,是沒有時長等信息的,封裝格式的才有
數(shù)據(jù)結(jié)構(gòu)第一層為封裝格式相關(guān)的,下面層為編解碼相關(guān)的
其中
AVStream中的time_base 表示該流的時基,也就是時間的基數(shù),簡單可理解為時間的單位,比如時間數(shù)是2,時基是1s,則時間為2s
AVPacket:可以理解為h264中的數(shù)據(jù)包,編碼后的包
其中,pts:顯示時間戳,表示該視頻幀幾分幾秒的時候放到屏幕上。這個值只是一個整數(shù) 沒有單位,所以要得到具體的幾分幾秒需要和前面的time_base時基參數(shù)相乘得到。
dts:解碼時間戳
stream_index:標(biāo)識,表示該流屬于音頻流還是視頻流。
data:壓縮編碼的數(shù)據(jù),如h264的編碼,就可以取出該數(shù)據(jù)保存起來形成的文件就是h264文件。
通過av_read_frame可從大的數(shù)據(jù)結(jié)構(gòu)指針中讀取到AVPacket數(shù)據(jù)
注意播放的順序不一定按照存儲的順序來。即有一個顯示的順序和一個解碼的順序。
AVFrame
data:解碼后的圖像像素數(shù)據(jù),如YUV等,可形成yuv文件(依次存儲的是y、u、v分量,其中u 寬和高都只有y一半,整個數(shù)據(jù)量就y的四分之一,v的也一樣)。data是一個指針數(shù)組,其中一個分量對應(yīng)一個數(shù)組
數(shù)據(jù)先通過解碼函數(shù)avcodec_decode_video2進(jìn)行解碼,然后由于解碼出來的數(shù)據(jù)由于是有黑邊(多出一塊多余的數(shù)據(jù)),所以還需用sws_scale函數(shù)把這塊多余的裁了。
4).解碼后的數(shù)據(jù)要經(jīng)過sws_scale()函數(shù)處理
解碼后YUV格式的視頻像素數(shù)據(jù)保存在AVFrame的data[0]、data[1]、data[2]中。但是這些像素值并不是連續(xù)存儲的,每行有效像素之后存儲了一些無效像素。
以亮度Y數(shù)據(jù)為例,data[0]中一共包含了linesize[0]*height個數(shù)據(jù)。但是出于優(yōu)化等方面的考慮,linesize[0]實際上并不等于寬度width,而是一個比寬度大一些的值。
因此需要使用sws_scale()進(jìn)行轉(zhuǎn)換。轉(zhuǎn)換后去除了無效數(shù)據(jù),width和linesize[0] 取值相等。如下圖3:
其中,sws_scale()函數(shù)需要用到的轉(zhuǎn)換信息,即第一個參數(shù),是由sws_getContext函數(shù)獲得的
轉(zhuǎn)換后保存在一個新的幀數(shù)據(jù)結(jié)構(gòu)體,由于使用的轉(zhuǎn)換函數(shù)而不是解碼函數(shù),所以這個結(jié)構(gòu)體還需要填充其內(nèi)部的緩沖區(qū),用于存儲像素數(shù)據(jù),填充的方法使用avpicture_fill函數(shù):
int avpicture_fill(AVPicture *picture, uint8_t *ptr,int pix_fmt, int width, int height);
這個函數(shù)的使用本質(zhì)上是為已經(jīng)分配的空間的結(jié)構(gòu)體(AVPicture *)ptFrame掛上一段用于保存數(shù)據(jù)的空間,
這個結(jié)構(gòu)體中有一個指針數(shù)組data[AV_NUM_DATA_POINTERS],掛在這個數(shù)組里。一般我們這么使用:
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是用于存緩沖數(shù)據(jù)的。
ptFrame為什么不用fill空間。主要是下面這句:
avcodec_decode_video(pCodecCtx, pFrame, &frameFinished,packet.data, packet.size);很可能是ptFrame已經(jīng)掛上了packet.data,所以就不用fill了。
二、SDL視頻顯示
SDL(Simple DirectMedia Layer)庫的作用說白了就是封裝了復(fù)雜的視音頻底層交互工作,簡化了視音頻處理的難度。
主要用來做游戲,現(xiàn)在只用到其視頻顯示部分。
特點:跨平臺,開源
1.庫的結(jié)構(gòu)圖見圖:
實際上它調(diào)用了底層api完成了和硬件的交互,比如linux下,就操作了framebuffer.
2.配置vc工程和之前ffmpeg的配置幾乎都是一樣的:
1).新建控制臺工程
打開VC++
文件->新建->項目->Win32控制臺應(yīng)用程序
2).拷貝SDL開發(fā)文件
頭文件(*.h)拷貝至項目文件夾的include子文件夾下
導(dǎo)入庫文件(*.lib)拷貝至項目文件夾的lib子文件夾下
動態(tài)庫文件(*.dll)拷貝至項目文件夾下
3).配置開發(fā)文件
打開屬性面板
解決方案資源管理器->右鍵單擊項目->屬性
4).頭文件配置
配置屬性->C/C++->常規(guī)->附加包含目錄,輸入“include”(剛才拷貝文件的目錄)
5).導(dǎo)入庫配置
配置屬性->鏈接器->常規(guī)->附加庫目錄,輸入“lib” (剛才拷貝文件的目錄)
配置屬性->鏈接器->輸入->附加依賴項,輸入“SDL2.lib; SDL2main.lib”(導(dǎo)入庫的文件名)
6).動態(tài)庫不用配置
7).是否配置成功,調(diào)用sdl的初始化函數(shù),查看其返回值即可確定:
創(chuàng)建源代碼文件
在工程中創(chuàng)建一個包含main()函數(shù)的C/C++文件(如果已經(jīng)有了可以跳過這一步),后續(xù)步驟在該文件中編寫源代碼。
包含頭文件
如果是C語言中使用SDL,則直接使用下面代碼
#include "SDL2/SDL.h"如果是C++語言中使用SDL,則使用下面代碼
extern "C" { #include "SDL2/SDL.h" }main()中調(diào)用一個SDL的接口函數(shù)
例如下面代碼初始化了SDL
int main(int argc, char* argv[]){ if(SDL_Init(SDL_INIT_VIDEO)) { printf( "Could not initialize SDL - %sn", SDL_GetError()); } else{ printf("Success init SDL"); } return 0; }如果運行無誤,則代表SDL已經(jīng)配置完成。
3.SDL視頻顯示的流程圖見圖5:
其中特別要注意的是創(chuàng)建紋理數(shù)據(jù)時要傳入渲染器,原因是,
紋理數(shù)據(jù)依賴于渲染器,只有通過渲染器創(chuàng)建才能得到該渲染器合法的紋理數(shù)據(jù)((數(shù)據(jù)格式)符合該渲染器的要求),
填充了像素數(shù)據(jù)且符合要求的紋理數(shù)據(jù)才可以拷貝給渲染器,并且渲染器才能正確顯示出來。
1).SDL視頻顯示函數(shù)簡介
SDL_Init():初始化SDL系統(tǒng)
SDL_CreateWindow():創(chuàng)建窗口SDL_Window
SDL_CreateRenderer():創(chuàng)建渲染器SDL_Renderer
SDL_CreateTexture():創(chuàng)建紋理SDL_Texture
SDL_UpdateTexture():設(shè)置紋理的數(shù)據(jù)
SDL_RenderCopy():將紋理的數(shù)據(jù)拷貝給渲染器
SDL_RenderPresent():顯示
SDL_Delay():工具函數(shù),用于延時。
SDL_Quit():退出SDL系統(tǒng)
其中SDL_Delay 延時函數(shù),控制顯示的速度,即控制幀率。通常每秒25幀,所以通常延時也就是40ms
2).SDL視頻顯示的數(shù)據(jù)結(jié)構(gòu)
I、SDL視頻顯示的數(shù)據(jù)結(jié)構(gòu)如圖6所示:
II、SDL數(shù)據(jù)結(jié)構(gòu)簡介
SDL_Window
代表了一個“窗口”
SDL_Renderer
代表了一個“渲染器”
SDL_Texture
代表了一個“紋理”
SDL_Rect
一個簡單的矩形結(jié)構(gòu)
具體來說,
SDL_Window 窗口,類似彈出的窗口
SDL_Renderer 渲染器,把紋理數(shù)據(jù)畫(渲染)到window上
一個window上不僅僅只有一副畫面,類似多組監(jiān)控畫面,即一個window可對應(yīng)多個yuv數(shù)據(jù)
SDL_Rect,正方形結(jié)構(gòu),存了矩形的坐標(biāo),長寬,以便確定紋理數(shù)據(jù)畫在哪個位置,確定位置用,比如畫在左上角就用這個來確定。被渲染器調(diào)用
SDL_Rect中的x y值是左上角為圓點開始的坐標(biāo)值,調(diào)整x y值以及w h值,就可以實現(xiàn)在窗口的指定位置顯示,沒有畫面的地方為黑框。
當(dāng)x y等于0,w h等于窗口的寬高時即為全屏顯示,此時調(diào)整寬高大小,只需調(diào)整窗口大小即可。
4.進(jìn)階-SDL中事件和多線程
1).SDL多線程
函數(shù)
SDL_CreateThread():創(chuàng)建一個線程
數(shù)據(jù)結(jié)構(gòu)
SDL_Thread:線程的句柄
SDL事件
函數(shù)
SDL_WaitEvent()等待一個事件
SDL_PushEvent()發(fā)送一個事件
數(shù)據(jù)結(jié)構(gòu)
SDL_Event:代表一個事件
SDL中事件和多線程,可用于解決上個程序播放過程中鼠標(biāo)不能動的問題,
使用事件,等待事件中,就會響應(yīng)鼠標(biāo)鍵盤等事件,就不會卡在那了
將延時操作放到一個子線程中,用事件通知主線程,這樣主線程就不用延時了,就可以及時響應(yīng)事件了
SDL_WINDOWENVENT sdl系統(tǒng)自帶的事件,當(dāng)拉伸窗口的時候會觸發(fā)
SDL_QUIT 也是SDL自帶的事件,當(dāng)點擊窗口的×時觸發(fā)
三、FFmpeg+SDL視頻播放器
1.FFmpeg+SDL整合之后實現(xiàn):視頻文件->YUV->屏幕
2.SDL_UpdateTexture函數(shù)最后一個參數(shù)表示的是,一行像素數(shù)據(jù)的數(shù)據(jù)量
窗口的寬和高無所謂,但是紋理數(shù)據(jù)的寬和高要和視頻數(shù)據(jù)的寬和高一致。
3.對于大數(shù)據(jù)的調(diào)試,如果直接打印出來,數(shù)據(jù)太多不好分析,一般是寫入到文件,然后用工具打開文件去分析。
比如視頻像素數(shù)據(jù),可以寫入到y(tǒng)uv文件,再用yuv分析工具來分析。
4.脫離開發(fā)環(huán)境的獨立播放器
在解決方案中的debug目錄下的*.exe文件即為編譯好的可執(zhí)行文件。
執(zhí)行這個文件就可以脫離開發(fā)環(huán)境運行。
5.調(diào)試時,在屬性->調(diào)試->命令參數(shù) 中填的東西,就是程序的輸入?yún)?shù),也就是argv中的內(nèi)容
平時獨立的exe程序,只需在執(zhí)行的時候后面加上參數(shù)就是輸入?yún)?shù)了,如ffplay.exe那樣。
四、FFmpeg+SDL視頻播放器代碼實現(xiàn)
主要是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,該符號在函數(shù) _ShowError 中被引用 24 25 原因: 26 ……這是鏈接庫問題 27 就是工程里面沒有添加那兩個函數(shù)需要的庫,#progma這個是代碼鏈接庫 28 第二句是vs2015兼容的問題。 29 lib庫的vs編譯版本 和 工程的vs開發(fā)版本 不一致。 30 導(dǎo)出函數(shù)定義變了。所以要人為加一個函數(shù)導(dǎo)出。 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);//發(fā)送事件給其他線程 80 SDL_Delay(20);//延時函數(shù) 填40的時候,視頻會有種卡的感覺 81 } 82 //Break 83 g_iThreadExitFlag = 0; 84 tEvent.type = PLAY_BREAK_EVENT; 85 SDL_PushEvent(&tEvent);//發(fā)送事件給其他線程 發(fā)送一個事件 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;//封裝格式上下文,內(nèi)部包含所有的視頻信息 105 int i = 0; 106 int iVideoindex=0;//純視頻信息在音視頻流中的位置,也就是指向音視頻流數(shù)組中的視頻元素 107 AVCodecContext *ptCodecContext;//編碼器相關(guān)信息上下文,內(nèi)部包含編碼器相關(guān)的信息,指向AVFormatContext中的streams成員中的codec成員 108 AVCodec *ptCodec;//編碼器,使用函數(shù)avcodec_find_decoder或者,該函數(shù)需要的id參數(shù),來自于ptCodecContext中的codec_id成員 109 AVFrame *ptFrame=NULL;//存儲一幀解碼后像素(采樣)數(shù)據(jù) 110 AVFrame *ptFrameAfterScale=NULL;//存儲(解碼數(shù)據(jù))轉(zhuǎn)換后的像素(采樣)數(shù)據(jù) 111 unsigned char *pucFrameAfterScaleBuf=NULL;//用于存儲ptFrameAfterScale中的像素(采樣)緩沖數(shù)據(jù) 112 AVPacket *ptPacket=NULL;//存儲一幀壓縮編碼數(shù)據(jù) 113 int iRet =0; 114 int iGotPicture=0;//解碼函數(shù)的返回參數(shù),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渲染器,把紋理數(shù)據(jù)畫(渲染)到window上 120 SDL_Texture* ptSdlTexture=NULL;//sdl紋理數(shù)據(jù),用于存放像素(采樣)數(shù)據(jù),然后給渲染器 121 SDL_Rect tSdlRect ={0};//正方形矩形結(jié)構(gòu),存了矩形的坐標(biāo),長寬,以便確定紋理數(shù)據(jù)畫在哪個位置,確定位置用,比如畫在左上角就用這個來確定。被渲染器調(diào)用 122 SDL_Thread *ptVideoControlTID=NULL;//sdl線程id,線程的句柄 123 SDL_Event tSdlEvent = {0};//sdl事件,代表一個事件 124 125 /*------------像素數(shù)據(jù)處理----------------*/ 126 struct SwsContext *ptImgConvertInfo;//圖像轉(zhuǎn)換(上下文)信息,圖像轉(zhuǎn)換函數(shù)sws_scale需要的參數(shù),由sws_getContext函數(shù)賦值 127 128 129 130 /*------------FFmpeg----------------*/ 131 av_register_all();//注冊FFmpeg所有組件 132 avformat_network_init();//初始化網(wǎng)絡(luò)組件 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 //獲取編碼器相關(guān)信息上下文,并賦值給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));//分配保存解碼前數(shù)據(jù)的空間 175 ptFrame = av_frame_alloc();//分配結(jié)構(gòu)體空間,結(jié)構(gòu)體內(nèi)部的指針指向的數(shù)據(jù)暫未分配,用于保存圖像轉(zhuǎn)換前的像素數(shù)據(jù) 176 177 /*------------像素數(shù)據(jù)處理----------------*/ 178 ptFrameAfterScale = av_frame_alloc();//分配結(jié)構(gòu)體空間,結(jié)構(gòu)體內(nèi)部的指針指向的數(shù)據(jù)暫未分配,用于保存圖像轉(zhuǎn)換后的像素數(shù)據(jù) 179 pucFrameAfterScaleBuf = (uint8_t *)av_malloc(avpicture_get_size(PIX_FMT_YUV420P, ptCodecContext->width, ptCodecContext->height));//分配保存數(shù)據(jù)的空間 180 /*int avpicture_fill(AVPicture *picture, uint8_t *ptr,int pix_fmt, int width, int height); 181 這個函數(shù)的使用本質(zhì)上是為已經(jīng)分配的空間的結(jié)構(gòu)體(AVPicture *)ptFrame掛上一段用于保存數(shù)據(jù)的空間, 182 這個結(jié)構(gòu)體中有一個指針數(shù)組data[AV_NUM_DATA_POINTERS],掛在這個數(shù)組里。一般我們這么使用: 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是用于存緩沖數(shù)據(jù)的。 188 ptFrame為什么不用fill空間。主要是下面這句: 189 avcodec_decode_video(pCodecCtx, pFrame, &frameFinished,packet.data, packet.size); 190 很可能是ptFrame已經(jīng)掛上了packet.data,所以就不用fill了。*/ 191 avpicture_fill((AVPicture *)ptFrameAfterScale, pucFrameAfterScaleBuf, PIX_FMT_YUV420P, ptCodecContext->width, ptCodecContext->height); 192 //sws開頭的函數(shù)用于處理像素(采樣)數(shù)據(jù) 193 ptImgConvertInfo = sws_getContext(ptCodecContext->width, ptCodecContext->height, ptCodecContext->pix_fmt, 194 ptCodecContext->width, ptCodecContext->height, PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);//獲取圖像轉(zhuǎn)換(上下文)信息 195 196 /*------------SDL----------------*/ 197 if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER)) 198 {//初始化SDL系統(tǒng) 199 printf("Could not initialize SDL - %sn", 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);//創(chuàng)建窗口SDL_Window 207 208 if (!ptSdlWindow) 209 { 210 printf("SDL: could not create window - exiting:%sn", SDL_GetError()); 211 return -1; 212 } 213 ptSdlRenderer = SDL_CreateRenderer(ptSdlWindow, -1, 0);//創(chuàng)建渲染器SDL_Renderer 214 //IYUV: Y + U + V (3 planes) 215 //YV12: Y + V + U (3 planes) 216 //創(chuàng)建紋理SDL_Texture 217 ptSdlTexture = SDL_CreateTexture(ptSdlRenderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, ptCodecContext->width, ptCodecContext->height); 218 219 tSdlRect.x = 0;//x y值是左上角為圓點開始的坐標(biāo)值,調(diào)整x y值以及w h值,就可以實現(xiàn)在窗口的指定位置顯示,沒有畫面的地方為黑框 220 tSdlRect.y = 0;//當(dāng)x y等于0,w h等于窗口的寬高時即為全屏顯示,此時調(diào)整寬高大小,只需調(diào)整窗口大小即可 221 tSdlRect.w = iScreenWidth; 222 tSdlRect.h = iScreenHeight; 223 224 ptVideoControlTID = SDL_CreateThread(RefreshPlayThread, NULL, NULL);//創(chuàng)建一個線程 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) //從輸入文件讀取一幀壓縮數(shù)據(jù) 233 { 234 if (ptPacket->stream_index == iVideoindex) 235 { 236 iRet = avcodec_decode_video2(ptCodecContext, ptFrame, &iGotPicture, ptPacket);//解碼一幀壓縮數(shù)據(jù) 237 if (iRet < 0) 238 { 239 printf("Decode Error.n"); 240 return -1; 241 } 242 if (iGotPicture) 243 { 244 //圖像轉(zhuǎn)換,sws_scale()函數(shù)需要用到的轉(zhuǎn)換信息,即第一個參數(shù),是由sws_getContext函數(shù)獲得的 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]);//設(shè)置(更新)紋理的數(shù)據(jù) 249 SDL_RenderClear(ptSdlRenderer);//先清除渲染器里的數(shù)據(jù) 250 //SDL_RenderCopy( ptSdlRenderer, ptSdlTexture, &tSdlRect, &tSdlRect ); //將紋理的數(shù)據(jù)拷貝給渲染器 251 SDL_RenderCopy(ptSdlRenderer, ptSdlTexture, NULL, NULL);//將紋理的數(shù)據(jù)拷貝給渲染器 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自帶的事件,當(dāng)點擊窗口的×時觸發(fā)//SDL_WINDOWENVENT sdl系統(tǒng)自帶的事件,當(dāng)拉伸窗口的時候會觸發(fā) 263 { 264 g_iThreadExitFlag = 1; 265 } 266 else if (tSdlEvent.type == PLAY_BREAK_EVENT) //自定義退出播放事件 267 { 268 break; 269 } 270 271 } 272 273 /*------------像素數(shù)據(jù)處理----------------*/ 274 sws_freeContext(ptImgConvertInfo);//釋放空間 275 276 /*------------SDL----------------*/ 277 SDL_Quit();//退出SDL系統(tǒng) 278 279 /*------------FFmpeg----------------*/ 280 av_frame_free(&ptFrameAfterScale);//釋放空間 281 av_frame_free(&ptFrame);//釋放空間 282 avcodec_close(ptCodecContext);//關(guān)閉解碼器 283 avformat_close_input(&ptFormatContext);//關(guān)閉輸入視頻文件 284 285 return 0; 286 } FFmpegAndSDL.cpp具體代碼見github:
https://github.com/fengweiyu/FFmpegAndSDL