FFmpeg再学习 -- SDL 环境搭建和视频显示
繼續看雷霄驊的 課程資料 - 基于FFmpeg+SDL的視頻播放器的制作
一、SDL 簡介
參看:WIKI -- Simple DirectMedia Layer
參看:最簡單的視音頻播放示例9:SDL2播放PCM
參看:SDL介紹
SDL (Simple DirectMedia Layer)是一套開源代碼的跨平臺多媒體開發庫,使用C語言寫成。SDL提供了數種控制圖像、聲音、輸出入的函數,讓開發者只要用相同或是相似的代碼就可以開發出跨多個平臺(Linux、Windows、Mac OS等)的應用軟件。目前 SDL 多用于開發游戲、模擬器、媒體播放器等多媒體應用領域。用下面這張圖可以很明確地說明 SDL 的用途。
SDL 分為幾個子系統:
Basics(基本)
? ? 初始化和關閉,配置變量,錯誤處理,日志處理
vedio(視頻)
? ? 顯示和窗口管理,表面功能,渲染加速等
Input Events (輸入事件)
? ? 事件處理,支持鍵盤,鼠標,操縱桿和游戲控制器
Force Feedback (力反饋)
? ? SDL_haptic.h支持“強制反饋”
Audio(音頻)
? ? SDL_audio.h實現音頻設備管理,播放和錄制
Threads(主題)
? ? 多線程:線程管理,線程同步原語,原子操作
Timers (計時器)
? ? 定時器支持
File Abstraction (文件抽象)
? ? 文件系統路徑,文件I / O抽象
Shared Object Support (共享對象支持)
? ? 共享對象加載和功能查找
Platform and CPU Information (平臺和CPU信息)
? ? 平臺檢測,CPU特征檢測,字節順序和字節交換,位操作
Power Mangement (能源管理)
? ? 電源管理狀態
Additional (額外)
? ? 平臺特定的功能
除了這個基本的低級支持之外,還有幾個獨立的官方圖書館提供了更多的功能。 這些包括“標準庫”,并在官方網站上提供,并包含在官方文件中:
SDL_image - 支持多種圖像格式[19]
SDL_mixer - 復合音頻功能,主要用于混音[20]
SDL_net - 網絡支持[21]
SDL_ttf - TrueType字體渲染支持[22]
SDL_rtf - 簡單的富文本格式渲染[23]
二、VS 下 SDL 開發環境的搭建
這是第三遍了啊。。
新建控制臺工程
打開 VS; 文件->新建->項目->Win32控制臺應用程序,點擊完成。 注意,選擇的位置最好不要有 空格或者漢字。拷貝 SDL 開發文件
頭文件( *.h)拷貝至項目文件夾的include子文件夾下導入庫文件( *.lib)拷貝至項目文件夾的lib子文件夾下
動態庫文件( *.dll) 拷貝至項目文件夾下
點擊右鍵,選擇在資源管理器中打開文件夾,進入項目目錄。(注意,如果手動進入注意文件夾位置,我就是沒找好位置,試了半天最后才發現,將上面的這些文件拷貝到錯誤的文件夾下了)
配置開發文件
打開屬性面板
解決方案資源管理器->右鍵單擊項目->屬性頭文件配置
配置屬性->C/C++->常規->附加包含目錄,輸入“ include”(剛才拷貝頭文件的目錄)導入庫配置
配置屬性->鏈接器->常規->附加庫目錄,輸入“ lib” (剛才拷貝庫文件的目錄)配置屬性->鏈接器->輸入->附加依賴項,輸入“ SDL2.lib;SDL2main.lib”(導入庫的文件名)
動態庫不用配置
測試
將 SDL.cpp 添加測試代碼,改為如下:// SDL.cpp : 定義控制臺應用程序的入口點。 // #include <stdio.h> #include "stdafx.h" extern "C" { #include "SDL2/SDL.h" }int main() {if (SDL_Init(SDL_INIT_VIDEO)) {printf("Could not initialize SDL - %s\n", SDL_GetError());}else {printf("Success init SDL");}return 0; } 打斷點,如果不打斷點,調試的時候一閃而過什么也看不到的。在return 0; 左邊點擊一下,出現紅點,即為斷點。
然后點擊本地Windows調試器,出現此項目已經過期,選擇 是。
可看到出現結果,說明 SDL 配置成功。
?這里我用提供的 lib 文件,遇到如下問題 1>------ 已啟動生成: 項目: SDL, 配置: Debug Win32 ------ 1>stdafx.cpp 1>SDL.cpp 1>MSVCRTD.lib(initializers.obj) : warning LNK4098: 默認庫“msvcrt.lib”與其他庫的使用沖突;請使用 /NODEFAULTLIB:library 1>SDL2main.lib(SDL_windows_main.obj) : error LNK2019: 無法解析的外部符號 __imp__fprintf,該符號在函數 _ShowError 中被引用 1>SDL2main.lib(SDL_windows_main.obj) : error LNK2019: 無法解析的外部符號 __imp____iob_func,該符號在函數 _ShowError 中被引用 1>D:\zslfchenjuke\work2017\SDL\SDL\Debug\SDL.exe : fatal error LNK1120: 2 個無法解析的外部命令 1>已完成生成項目“SDL.vcxproj”的操作 - 失敗。 ========== 生成: 成功 0 個,失敗 1 個,最新 0 個,跳過 0 個 ========== 問題分析:因為我用的是 VS2017,而提供的lib實在 VS 2013上執行的。 下載最新的 SDL 擴展庫。 下載:Index of /sdl-builds/sdl-visualstudio 或 下載:SDL-devel-1.2.15-VC.zip (Visual C++) 擴展:Installing SDL
擴展:Introduction to SDL 2.0
三、SDL視頻顯示的函數
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視頻顯示的流程圖如下所示
SDL源代碼分析系列文章列表:
SDL2源代碼分析1:初始化(SDL_Init())
SDL2源代碼分析2:窗口(SDL_Window)
SDL2源代碼分析3:渲染器(SDL_Renderer)
SDL2源代碼分析4:紋理(SDL_Texture)
SDL2源代碼分析5:更新紋理(SDL_UpdateTexture())
SDL2源代碼分析6:復制到渲染器(SDL_RenderCopy())
SDL2源代碼分析7:顯示(SDL_RenderPresent())
SDL2源代碼分析8:視頻顯示總結
四、SDL視頻顯示的數據結構
SDL視頻顯示的數據結構如下所示
SDL數據結構簡介
SDL_Window ? 代表了一個“窗口”SDL_Renderer ?代表了一個“渲染器”
SDL_Texture ? ? 代表了一個“紋理”
SDL_Rect ? ? ? ? ?一個簡單的矩形結構
五、示例解析
使用 SDL 播放 yuv 文件
#include <stdio.h> #include "stdafx.h" extern "C" { #include "SDL2/SDL.h" }const int bpp = 12; //screan 為屏幕長寬,pixel為視頻長寬 int screen_w = 640, screen_h = 272; const int pixel_w = 640, pixel_h = 272;unsigned char buffer[pixel_w*pixel_h*bpp / 8];//Refresh Event #define REFRESH_EVENT (SDL_USEREVENT + 1) //Break #define BREAK_EVENT (SDL_USEREVENT + 2)int thread_exit = 0;int refresh_video(void *opaque) {thread_exit = 0;while (thread_exit == 0) {SDL_Event event;event.type = REFRESH_EVENT;SDL_PushEvent(&event); //發送一個事件SDL_Delay(40); //工具函數,可用于調節播放速度}thread_exit = 0;//BreakSDL_Event event;event.type = BREAK_EVENT;SDL_PushEvent(&event);return 0; }int main(int argc, char* argv[]) {//初始化 SDL 系統if (SDL_Init(SDL_INIT_VIDEO)) {printf("Could not initialize SDL - %s\n", SDL_GetError());return -1;}SDL_Window *screen;//SDL 2.0 Support for multiple windows//創建窗口 SDL_Windowscreen = SDL_CreateWindow("Simplest Video Play SDL2", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,screen_w, screen_h, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);if (!screen) {printf("SDL: could not create window - exiting:%s\n", SDL_GetError());return -1;}//創建渲染器SDL_RendererSDL_Renderer* sdlRenderer = SDL_CreateRenderer(screen, -1, 0);Uint32 pixformat = 0;//IYUV: Y + U + V (3 planes)//YV12: Y + V + U (3 planes)pixformat = SDL_PIXELFORMAT_IYUV;//創建紋理 SDL_TextureSDL_Texture* sdlTexture = SDL_CreateTexture(sdlRenderer, pixformat, SDL_TEXTUREACCESS_STREAMING, pixel_w, pixel_h);FILE *fp = NULL;//打開yuv文件fp = fopen("output.yuv", "rb+");if (fp == NULL) {printf("cannot open this file\n");return -1;}//SDL_Rect srcRect[4];//SDL_Rect sdlRect[4];SDL_Rect sdlRect;//創建一個線程SDL_Thread *refresh_thread = SDL_CreateThread(refresh_video, NULL, NULL);SDL_Event event;while (1) {//等待一個事件SDL_WaitEvent(&event);if (event.type == REFRESH_EVENT) {if (fread(buffer, 1, pixel_w*pixel_h*bpp / 8, fp) != pixel_w*pixel_h*bpp / 8) {// Loopfseek(fp, 0, SEEK_SET);fread(buffer, 1, pixel_w*pixel_h*bpp / 8, fp);}//設置紋理的數據SDL_UpdateTexture(sdlTexture, NULL, buffer, pixel_w); #if 0 //分屏播放srcRect[0].x =0;srcRect[0].y = 0;srcRect[0].w = 320;srcRect[0].h = 176;srcRect[1].x =320;srcRect[1].y = 0;srcRect[1].w = 320;srcRect[1].h = 176;srcRect[2].x =0;srcRect[2].y = 176;srcRect[2].w = 320;srcRect[2].h = 176;srcRect[3].x =320;srcRect[3].y = 176;srcRect[3].w = 320;srcRect[3].h = 176;//FIX: If window is resizesdlRect[0].x = 0;sdlRect[0].y = 0;sdlRect[0].w = 320;sdlRect[0].h = 176;sdlRect[1].x = 330;sdlRect[1].y = 0;sdlRect[1].w = 320;sdlRect[1].h = 176;sdlRect[2].x = 0;sdlRect[2].y = 186;sdlRect[2].w = 320;sdlRect[2].h = 176;sdlRect[3].x = 330;sdlRect[3].y = 186;sdlRect[3].w = 320;sdlRect[3].h = 176;//清空紋理SDL_RenderClear( sdlRenderer );//將紋理的數據拷貝給渲染器SDL_RenderCopy( sdlRenderer, sdlTexture,/*NULL*/ &srcRect[0], &sdlRect[0]);SDL_RenderCopy(sdlRenderer, sdlTexture,/*NULL*/ &srcRect[1], &sdlRect[1]);SDL_RenderCopy(sdlRenderer, sdlTexture,/*NULL*/ &srcRect[2], &sdlRect[2]);SDL_RenderCopy(sdlRenderer, sdlTexture,/*NULL*/ &srcRect[3], &sdlRect[3]); #endifsdlRect.x = 0;sdlRect.y = 0;sdlRect.w = screen_w;sdlRect.h = screen_h;SDL_RenderClear(sdlRenderer);SDL_RenderCopy(sdlRenderer, sdlTexture, NULL, &sdlRect);//顯示SDL_RenderPresent(sdlRenderer);}else if (event.type == SDL_WINDOWEVENT) {//If ResizeSDL_GetWindowSize(screen, &screen_w, &screen_h);}else if (event.type == SDL_QUIT) {thread_exit = 1;}else if (event.type == BREAK_EVENT) {break;}}//退出 SDL 系統SDL_Quit();return 0; }演示結果:
窗口可移動、可調整大小。項目下載:
下載:SDL 項目工程六、示例解析
拷貝FFmpeg的開發文件(lib、include、dll文件),配置VS2017這個我就不再多說了。利用 FFmpeg 把MP4解碼為YUV,然后在 SDL 上播放。
/** * 最簡單的基于FFmpeg的視頻播放器2(SDL升級版) * Simplest FFmpeg Player 2(SDL Update) * * 雷霄驊 Lei Xiaohua * leixiaohua1020@126.com * 中國傳媒大學/數字電視技術 * Communication University of China / Digital TV Technology * http://blog.csdn.net/leixiaohua1020 * * 第2版使用SDL2.0取代了第一版中的SDL1.2 * Version 2 use SDL 2.0 instead of SDL 1.2 in version 1. * * 本程序實現了視頻文件的解碼和顯示(支持HEVC,H.264,MPEG2等)。 * 是最簡單的FFmpeg視頻解碼方面的教程。 * 通過學習本例子可以了解FFmpeg的解碼流程。 * 本版本中使用SDL消息機制刷新視頻畫面。 * This software is a simplest video player based on FFmpeg. * Suitable for beginner of FFmpeg. * * 備注: * 標準版在播放視頻的時候,畫面顯示使用延時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> #include "stdafx.h" #define __STDC_CONSTANT_MACROS#ifdef _WIN32 //Windows extern "C" { #include "libavcodec/avcodec.h" #include "libavformat/avformat.h" #include "libswscale/swscale.h" #include "SDL2/SDL.h" }; #else //Linux... #ifdef __cplusplus extern "C" { #endif #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> #include <libswscale/swscale.h> #include <SDL2/SDL.h> #ifdef __cplusplus }; #endif #endif//Refresh Event #define SFM_REFRESH_EVENT (SDL_USEREVENT + 1)#define SFM_BREAK_EVENT (SDL_USEREVENT + 2)int thread_exit = 0; int thread_pause = 0;int sfp_refresh_thread(void *opaque) {thread_exit = 0;thread_pause = 0;while (!thread_exit) {if (!thread_pause) {SDL_Event event;event.type = SFM_REFRESH_EVENT;SDL_PushEvent(&event); //發送一個事件}SDL_Delay(40); //可用于調節播放速度}thread_exit = 0;thread_pause = 0;//BreakSDL_Event event;event.type = SFM_BREAK_EVENT;SDL_PushEvent(&event);return 0; }int main(int argc, char* argv[]) {AVFormatContext *pFormatCtx;int i, videoindex;AVCodecContext *pCodecCtx;AVCodec *pCodec;AVFrame *pFrame, *pFrameYUV;uint8_t *out_buffer;AVPacket *packet;int ret, got_picture;//------------SDL----------------int screen_w, screen_h;SDL_Window *screen;SDL_Renderer* sdlRenderer;SDL_Texture* sdlTexture;SDL_Rect sdlRect;SDL_Thread *video_tid;SDL_Event event;struct SwsContext *img_convert_ctx;//輸入文件路徑char filepath[] = "Tai.mp4";av_register_all(); //注冊所有組件avformat_network_init(); //初始化網絡pFormatCtx = avformat_alloc_context(); //初始化一個 AVFormatContext 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();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);packet = (AVPacket *)av_malloc(sizeof(AVPacket));//Output Info-----------------------------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);//初始化 SDL 系統if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {printf("Could not initialize SDL - %s\n", SDL_GetError());return -1;}//SDL 2.0 Support for multiple windowsscreen_w = pCodecCtx->width;screen_h = pCodecCtx->height;//創建窗口screen = SDL_CreateWindow("Simplest ffmpeg player's Window", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,screen_w, screen_h, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);if (!screen) {printf("SDL: could not create window - exiting:%s\n", SDL_GetError());return -1;}//創建渲染器sdlRenderer = SDL_CreateRenderer(screen, -1, 0);//IYUV: Y + U + V (3 planes)//YV12: Y + V + U (3 planes)//創建紋理sdlTexture = SDL_CreateTexture(sdlRenderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, screen_w, screen_h);//創建一個線程video_tid = SDL_CreateThread(sfp_refresh_thread, NULL, NULL);//------------SDL End------------//Event Loopfor (;;) {//WaitSDL_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) {sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);//SDL---------------------------//設置紋理的數據SDL_UpdateTexture(sdlTexture, NULL, pFrameYUV->data[0], pFrameYUV->linesize[0]);sdlRect.x = 0;sdlRect.y = 0;sdlRect.w = screen_w;sdlRect.h = screen_h;//清空紋理SDL_RenderClear(sdlRenderer);//SDL_RenderCopy( sdlRenderer, sdlTexture, &sdlRect, &sdlRect ); //將紋理的數據拷貝給渲染器SDL_RenderCopy(sdlRenderer, sdlTexture, NULL, NULL);//顯示SDL_RenderPresent(sdlRenderer);//SDL End-----------------------}}av_free_packet(packet);}else {//Exit Threadthread_exit = 1;}}else if (event.type == SDL_WINDOWEVENT) {//If Resize SDL_GetWindowSize(screen, &screen_w, &screen_h);}else if (event.type == SDL_KEYDOWN) {//Pauseif (event.key.keysym.sym == SDLK_SPACE)thread_pause = !thread_pause;}else if (event.type == SDL_QUIT) {thread_exit = 1;}else if (event.type == SFM_BREAK_EVENT) {break;}}sws_freeContext(img_convert_ctx);SDL_Quit();//--------------av_frame_free(&pFrameYUV);av_frame_free(&pFrame);avcodec_close(pCodecCtx);avformat_close_input(&pFormatCtx);return 0; }示例總結
結合上一篇文章的?ffmpeg再學習 -- FFmpeg解碼知識?將視頻轉 yuv ,然后使用 SDL 播放。 這里遇到了兩個問題。 第一,無法解析的外部符號。 1>------ 已啟動生成: 項目: SDL, 配置: Debug Win32 ------ 1>SDL.obj : error LNK2019: 無法解析的外部符號 _av_malloc,該符號在函數 _SDL_main 中被引用 1>SDL.obj : error LNK2019: 無法解析的外部符號 _av_frame_alloc,該符號在函數 _SDL_main 中被引用 1>SDL.obj : error LNK2019: 無法解析的外部符號 _av_frame_free,該符號在函數 _SDL_main 中被引用 1>SDL.obj : error LNK2019: 無法解析的外部符號 _avcodec_open2,該符號在函數 _SDL_main 中被引用原因是未將FFmpeg 的 dll文件,拷貝到相應的位置。 解決方法:點擊項目->屬性->配置屬性->鏈接器->輸入->附加依賴項(添加。。) 第二,原項目示例,無法調整窗口大小。 原因是創建窗口時,未添加 SDL_WINDOW_RESIZABLE //創建窗口screen = SDL_CreateWindow("Simplest ffmpeg player's Window", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,screen_w, screen_h, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);項目下載:
下載:SDL 播放MP4視頻 項目工程七、脫離開發環境的獨立播放器
動態鏈接庫不能被編譯進應用程序。因而使用應用程序的時候必須在相同目錄下保存用到的動態鏈接庫文件。如上圖,點擊 SDL.exe 即可直接播放視頻。
八、播放任意視頻
注意到上例中只能播放指定的一個視頻, //輸入文件路徑char filepath[] = "Tai.mp4";那么播放任意視頻該如何操作? 利用 main 函數參數,參看:C語言再學習 -- 函數 argc argv:全稱為ARGument Counter 和 ARGument Vector。其中argv存儲了來自于命令行的參數;而argc存儲了參數的個數。
例如,在命令行中輸入“ ffmpeg -i test.mkv test.ts ”,則argc取值為4,而argv[]數組取值如下:
argv[0]="ffmpeg"
argv[1]="-i"
argv[2]="test.mkv"
argv[3]="test.ts"
只需如下更改,即可播放任意視頻。 char *filepath = argv[1]; 然后,利用 cmd 命令窗口打開 SDL.exe? 快捷鍵為 按住 shift + 右鍵,選擇 在此處打開命令窗口(W)?
然后輸入:SDL.exe 視頻名稱 ?即可播放任意視頻
總結
以上是生活随笔為你收集整理的FFmpeg再学习 -- SDL 环境搭建和视频显示的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 产品经理如何催项目进度?
- 下一篇: 机器学习笔记(六)——朴素贝叶斯法的参数