基于 FFmpeg 与SDL 的视频播放器 (3)—SDL视频显示
SDL简介
- SDL(Simple DirectMedia Layer)是一套开放源代码的跨平台多媒体开发库,使用C语言写成。SDL提供了数种控制图像、声音、输出入的函数,让开发者只要用相同或是相似的代码就可以开发出跨多个平台(Linux、Windows、Mac OS X等)的应用软件。目前SDL多用于开发游戏、模拟器、媒体播放器等多媒体应用领域。
- 作用就是封装了复杂的视音频底层操作,简化了视音频处理的难度,它大幅度简化了控制图像、声音、输出入等工作所需撰写的代码
- SDL是调用了DirectX等底层的 API完成了和硬件的交互,在结构上是将不同操作系统的库封装成相同的函数,以此实现其跨平台的特性。
基于QT的FFmpeg+SDL环境搭建
前面FFmpeg环境搭建已经操作过了,这里就不再演示。
-
获取SDL
SDL获取地址:http://www.libsdl.org/
根据自己的设备选择相应的版本
-
在项目中引用SDL
项目的创建过程这里不再描述,只讲解SDL引入部分,首先解压刚才下载的动态库和Dev压缩包,其中动态库压缩包中只有一个项目运行时所需要的.dll文件,Dev压缩包解压后我们只需要其中的include和lib文件夹,直接复制到我们的工程目录下面。
然后就是在.pro文件中引入(包括了之前引入的ffmpeg)
INCLUDEPATH += $$PWD/lib/ffmpeg/include\ $$PWD/lib/SDL2/include LIBS += $$PWD/lib/ffmpeg/lib/avcodec.lib\ $$PWD/lib/ffmpeg/lib/avdevice.lib\ $$PWD/lib/ffmpeg/lib/avfilter.lib\ $$PWD/lib/ffmpeg/lib/avformat.lib\ $$PWD/lib/ffmpeg/lib/avutil.lib\ $$PWD/lib/ffmpeg/lib/postproc.lib\ $$PWD/lib/ffmpeg/lib/swresample.lib\ $$PWD/lib/ffmpeg/lib/swscale.lib\ $$PWD/lib/SDL2/lib/SDL2.lib
由于我们建立的是C++的工程,编译的时候使用的C++的编译器编译,而FFMPEG是C的库,因此这里需要加上extern “C”。
extern "C" { #include <libavcodec\avcodec.h> #include <libavformat\avformat.h> #include <libswscale\swscale.h> #include <libswresample\swresample.h> #include "SDL.h" }
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_Window 代表了一个“窗口”
- SDL_Renderer 代表了一个“渲染器”
- SDL_Texture 代表了一个“纹理”
- SDL_Rect 一个简单的矩形结构
SDL事件
要想了解 SDL 的事件处理,我们必须要知道的一个原理是,SDL将所有事件都存放在一个队列中。所有对事件的操作,其实就是对队列的操作。
- SDL_PollEvent: 将队列头中的事件抛出来。
- SDL_WaitEvent: 当队列中有事件时,抛出事件。否则处于阻塞状态,释放 CPU。
- SDL_WaitEventTimeout: 与SDL_WaitEvent的区别时,当到达超时时间后,退出阻塞状态。
- SDL_PeekEvent: 从队列中取出事件,但该事件不从队列中删除。
- SDL_PushEvent: 向队列中插入事件。
SDL只提供了这样几个简单的API,下面们来介绍几个常见的事件:
- SDL_WindowEvent : Window窗口相关的事件。
- SDL_KeyboardEvent : 键盘相关的事件。
- SDL_MouseMotionEvent : 鼠标移动相关的事件。
- SDL_QuitEvent : 退出事件。
- SDL_UserEvent : 用户自定义事件。
SDL多线程
为什么引入多线程的概念呢,我们知道如果将耗时操作放到主线程中必将会导致界面的卡顿,所以我们在实际开发时需要将一些耗时操作放到子线程中进行,避免界面假死的同时充分发挥硬件的性能。
主要需要了解的函数如下:
- SDL线程创建:SDL_CreateThread
- SDL线程等待:SDL_WaitThead
- SDL互斥锁:SDL_CreateMutex / SDL_DestroyMutex
- SDL锁定互斥:SDL_LockMutex / SDL_UnlockMutex
- SDL 条件变量(信号量):SDL_CreateCond / SDL_DestoryCond
- SDL 条件变量(信号量)等待 / 通知 :SDL_CondWait / SDL_CondSingal
SDL视频显示逻辑
SDL视频显示完整代码
下面代码参考雷神的代码,这里是直接读取的YUV格式纯净文件来进行视频显示的,没有解封装和视频解码操作,这些操作上一篇文章已经进行了详细介绍。
#include <stdio.h>
extern "C"
{
#include "sdl/SDL.h"
};
const int bpp=12;
int screen_w=500,screen_h=500;
const int pixel_w=320,pixel_h=180;
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;
//Break
SDL_Event event;
event.type = BREAK_EVENT;
SDL_PushEvent(&event);
return 0;
}
int main(int argc, char* argv[])
{
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
screen = 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_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_Texture* sdlTexture = SDL_CreateTexture(sdlRenderer,pixformat, SDL_TEXTUREACCESS_STREAMING,pixel_w,pixel_h);
FILE *fp=NULL;
fp=fopen("test_yuv420p_320x180.yuv","rb+");
if(fp==NULL){
printf("cannot open this file\n");
return -1;
}
SDL_Rect sdlRect;
SDL_Thread *refresh_thread = SDL_CreateThread(refresh_video,NULL,NULL);
SDL_Event event;
while(1){
//Wait
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){
// Loop
fseek(fp, 0, SEEK_SET);
fread(buffer, 1, pixel_w*pixel_h*bpp/8, fp);
}
SDL_UpdateTexture( sdlTexture, NULL, buffer, pixel_w);
//FIX: If window is resize
sdlRect.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 Resize
SDL_GetWindowSize(screen,&screen_w,&screen_h);
}else if(event.type==SDL_QUIT){
thread_exit=1;
}else if(event.type==BREAK_EVENT){
break;
}
}
SDL_Quit();
return 0;
}
总结:前面一篇文章讲解了如何通过FFmpeg实现封装格式的视频的解封装和解码操作,将其解密码为YUV或RGB格式的文件,这篇文章描述了简单实现播放YUV格式的视频文件,如何将他们串联在一起,实现能够播放如.avi格式的视频文件呢?简单实现就是讲解码后得到的视频像素数据存到一个队列中,SDL从这个队列中读取进行播放。