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

时间:2022-07-29 12:09:50

继续看雷霄骅的 课程资料 - 基于FFmpeg+SDL的视频播放器的制作

一、SDL 简介

参看:WIKI -- Simple DirectMedia Layer

参看:最简单的视音频播放示例9:SDL2播放PCM

参看:SDL介绍

SDL (Simple DirectMedia Layer)是一套开源代码的跨平台多媒体开发库,使用C语言写成。SDL提供了数种控制图像、声音、输出入的函数,让开发者只要用相同或是相似的代码就可以开发出跨多个平台(Linux、Windows、Mac OS等)的应用软件。目前 SDL 多用于开发游戏、模拟器、媒体播放器等多媒体应用领域。用下面这张图可以很明确地说明 SDL 的用途。

ffmpeg再学习 -- 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控制台应用程序,点击完成。 注意,选择的位置最好不要有 空格或者汉字。 ffmpeg再学习 -- SDL 环境搭建和视频显示
ffmpeg再学习 -- SDL 环境搭建和视频显示
ffmpeg再学习 -- SDL 环境搭建和视频显示

拷贝 SDL 开发文件

头文件( *.h)拷贝至项目文件夹的include子文件夹下
导入库文件( *.lib)拷贝至项目文件夹的lib子文件夹下
动态库文件( *.dll) 拷贝至项目文件夹下

点击右键,选择在资源管理器中打开文件夹,进入项目目录。(注意,如果手动进入注意文件夹位置,我就是没找好位置,试了半天最后才发现,将上面的这些文件拷贝到错误的文件夹下了)ffmpeg再学习 -- SDL 环境搭建和视频显示
ffmpeg再学习 -- SDL 环境搭建和视频显示

配置开发文件

打开属性面板

解决方案资源管理器->右键单击项目->属性
ffmpeg再学习 -- SDL 环境搭建和视频显示

头文件配置

配置属性->C/C++->常规->附加包含目录,输入“ include”(刚才拷贝头文件的目录)
ffmpeg再学习 -- SDL 环境搭建和视频显示

导入库配置

配置属性->链接器->常规->附加库目录,输入“ lib” (刚才拷贝库文件的目录)
ffmpeg再学习 -- SDL 环境搭建和视频显示
配置属性->链接器->输入->附加依赖项,输入“ SDL2.lib;SDL2main.lib”(导入库的文件名)ffmpeg再学习 -- SDL 环境搭建和视频显示

动态库不用配置

测试

将 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; 左边点击一下,出现红点,即为断点。ffmpeg再学习 -- SDL 环境搭建和视频显示
然后点击本地Windows调试器,出现此项目已经过期,选择 是。
ffmpeg再学习 -- SDL 环境搭建和视频显示
ffmpeg再学习 -- SDL 环境搭建和视频显示
可看到出现结果,说明 SDL 配置成功。
ffmpeg再学习 -- 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视频显示的流程图如下所示

ffmpeg再学习 -- 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视频显示的数据结构如下所示

ffmpeg再学习 -- 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;}

演示结果:

窗口可移动、可调整大小。ffmpeg再学习 -- SDL 环境搭建和视频显示

项目下载:

下载:SDL 项目工程

六、示例解析

利用 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视频 项目工程