FFmpeg再学习 -- SDL 环境搭建和视频显示

来源:互联网 发布:淘宝运营公司骗局 编辑:程序博客网 时间:2024/06/06 03:59

继续看雷霄骅的 课程资料 - 基于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.cpp1>SDL.cpp1>MSVCRTD.lib(initializers.obj) : warning LNK4098: 默认库“msvcrt.lib”与其他库的使用冲突;请使用 /NODEFAULTLIB:library1>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//Windowsextern "C"{#include "libavcodec/avcodec.h"#include "libavformat/avformat.h"#include "libswscale/swscale.h"#include "SDL2/SDL.h"};#else//Linux...#ifdef __cplusplusextern "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;inti, 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 视频名称  即可播放任意视频



原创粉丝点击