基于 FFmpeg 与SDL 的视频播放器 (3)—SDL视频显示

基于 FFmpeg 与SDL 的视频播放器 (3)—SDL视频显示

SDL简介

  • SDL(Simple DirectMedia Layer)是一套开放源代码的跨平台多媒体开发库,使用C语言写成。SDL提供了数种控制图像、声音、输出入的函数,让开发者只要用相同或是相似的代码就可以开发出跨多个平台(Linux、Windows、Mac OS X等)的应用软件。目前SDL多用于开发游戏、模拟器、媒体播放器等多媒体应用领域。
  • 作用就是封装了复杂的视音频底层操作,简化了视音频处理的难度,它大幅度简化了控制图像、声音、输出入等工作所需撰写的代码
  • SDL是调用了DirectX等底层的 API完成了和硬件的交互,在结构上是将不同操作系统的库封装成相同的函数,以此实现其跨平台的特性。

在这里插入图片描述

基于QT的FFmpeg+SDL环境搭建

​ 前面FFmpeg环境搭建已经操作过了,这里就不再演示。

  1. 获取SDL

    SDL获取地址:http://www.libsdl.org/

    在这里插入图片描述

    根据自己的设备选择相应的版本

  2. 在项目中引用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中的时间和多线程介绍

函数
  1. SDL_Init():初始化SDL系统
  2. SDL_CreateWindow():创建窗口SDL_Window
  3. SDL_CreateRenderer():创建渲染器SDL_Renderer
  4. SDL_CreateTexture():创建纹理SDL_Texture
  5. SDL_UpdateTexture():设置纹理的数据
  6. SDL_RenderCopy():将纹理的数据拷贝给渲染器
  7. SDL_RenderPresent():显示
  8. SDL_Delay():工具函数,用于延时。
  9. SDL_Quit():退出SDL系统
数据结构

在这里插入图片描述

  1. SDL_Window 代表了一个“窗口”
  2. SDL_Renderer 代表了一个“渲染器”
  3. SDL_Texture 代表了一个“纹理”
  4. 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从这个队列中读取进行播放。

发布了24 篇原创文章 · 获赞 5 · 访问量 3930

猜你喜欢

转载自blog.csdn.net/qq_41345281/article/details/104237900