ffmpeg-5、SDL的vs环境部署及图片显示 、视频播放、音频播放

SDL简介;(Simple DirectMedia Layer)库的作用主要是封装了复杂的视音频底层交互工作, 简化了视音频处理的难度。

1、SDL环境部署

vs2015环境部署报错,在头文件目录、 lib目录和依赖库都属性配置正确的情况下仍然报链接错误,如下
在这里插入图片描述
报错
1>SDLmain.lib(SDL_win32_main.obj) : error LNK2019: 无法解析的外部符号 _SDL_main,该符号在函数 _main 中被引用
LNK2019 无法解析的外部符号 __imp__fprintf,该符号在函数 _ShowError 中被引用
LNK2019 无法解析的外部符号 __imp____iob_func,该符号在函数 _ShowError 中被引用
解决办法

/*
解决办法:
包含库的编译器版本低于当前编译版本,需要将包含库源码用vs2017重新编译,由于没有包含库的源码,此路不通。
然后查到说是stdin, stderr, stdout 这几个函数vs2015和以前的定义得不一样,所以报错。
解决方法呢,就是使用{*stdin,*stdout,*stderr}数组自己定义__iob_func()
*/
#pragma comment(lib,"legacy_stdio_definitions.lib")
extern "C" {
    
     FILE __iob_func[3] = {
    
     *stdin,*stdout,*stderr }; }
/*
	这时,其实是main函数定义与sdl库里的不一样,比如:
	int main()
	这时编译时,就会出现上面的出错。需要修改为这样:
	int main(int      argc, char    *argv[])就没有这个出错了。
*/
//int main()
int main(int      argc, char    *argv[])

参考博客;
https://blog.csdn.net/caimouse/article/details/74356842
https://www.freesion.com/article/63721074585/

2、显示的基本流程和一些API介绍

2.1、SDL显示基本流程

显示基本流程其实就是  创建SDL  创建窗口 绑定窗口渲染器  创建纹理 设置纹理到渲染器进行显示
初始化SDL 		extern DECLSPEC int SDLCALL SDL_Init(Uint32 flags);
创建windows窗口 	extern DECLSPEC SDL_Window * SDLCALL SDL_CreateWindow(const char *title,int x, int y, int w, int h, Uint32 flags);
创建对应窗口的渲染器  SDL_CreateRenderer(SDL_Window * window, int index, Uint32 flags);
创建纹理数据  SDL_CreateTextureFromSurface()或者 SDL_CreateTexture();
设置纹理到渲染器上   extern DECLSPEC int SDLCALL SDL_RenderCopy(SDL_Renderer * renderer,  SDL_Texture * texture,const SDL_Rect * srcrect,const SDL_Rect * dstrect);
显示到窗口  extern DECLSPEC void SDLCALL SDL_RenderPresent(SDL_Renderer * renderer);
延迟 SDL_Delay(10000);
清理内存 SDL_DestroyTexture(tex);SDL_DestroyRenderer(ren);SDL_DestroyWindow(win);
退出SDL_Quit();

2.2、API介绍

SDL_Init 初始化

1int SDL_Init(Uint32 flags)
flages:
SDL_INIT_TIMER 定时器子系统
SDL_INIT_AUDIO 音频子系统
SDL_INIT_VIDEO 视频子系统,同时会初始化事件子系统
SDL_INIT_EVENTS 事件子系统
SDL_INIT_EVERYTHING 初始化所有子系统

2、SDL_CreateWindow 创建窗口
SDL_Window* SDL_CreateWindow(const char *title,
                             int x, int y, int w,
                             int h, Uint32 flags);
title:窗口标题
x,y,w,h:窗口坐标   中间(SDL_WINDOWPOS_CENTERED)还是不指定(SDL_WINDOWPOS_UNDEFINED)
flags:
 ::SDL_WINDOW_FULLSCREEN,//全屏         ::SDL_WINDOW_OPENGL,//使用OpenGL上下文
 ::SDL_WINDOW_HIDDEN, //窗口不可见       ::SDL_WINDOW_BORDERLESS, //无边框
 ::SDL_WINDOW_RESIZABLE,//窗口大小可变    ::SDL_WINDOW_MAXIMIZED, //窗口最大化
 ::SDL_WINDOW_MINIMIZED,//窗口最小化      ::SDL_WINDOW_INPUT_GRABBED,//输入捕获
 
3、SDL_CreateRenderer 创建渲染器
SDL_Renderer* SDL_CreateRenderer(SDL_Window* window,
                                 int index,
                                 Uint32 flags)
window: 指明在哪个窗口里进行渲染
index: 指定渲染驱动的索引号。一般指定为 -1.
flags:
 SDL_RENDERER_SOFTWARE //The renderer is a software fallback 软件备份
 SDL_RENDERER_ACCELERATED //The renderer uses hardware acceleration 硬件加速
 SDL_RENDERER_PRESENTVSYNC //Present is synchronized with the refresh rate 刷新率同步
 SDL_RENDERER_TARGETTEXTURE //The renderer supports rendering to texture 支持渲染纹理

4SDL_CreateTexture()基于渲染器创建一个纹理
SDL_Texture * SDLCALL SDL_CreateTexture(SDL_Renderer * renderer,
                                                        Uint32 format,
                                                        int access, 	int w,int h);
renderer:目标渲染器。
format  :纹理的格式。后面会详述。
access :可以取以下值(定义位于SDL_TextureAccess中)
    SDL_TEXTUREACCESS_STATIC  :变化极少
    SDL_TEXTUREACCESS_STREAMING  :变化频繁
    SDL_TEXTUREACCESS_TARGET :暂时没有理解
w :纹理的宽
h :纹理的高                         
创建成功则返回纹理的ID,失败返回0            

但是其中有部分细节

2.3、加载图片的话利用Surface来加速一下

//加载图片 利用Surface  返回指定渲染器的图片纹理
SDL_Texture * LoadImage(string str_path)
{
    
    
	SDL_Texture * tex = NULL;
	SDL_Surface *bmp = NULL;

	//加载图片到Surface  便于硬件加速
	bmp = SDL_LoadBMP(str_path.c_str());
	if (bmp == NULL)
	{
    
    
		cout << "SDL_LoadBMP error" << endl;
		return tex;
	}

	//在指定渲染器Renderer上创建纹理Texture
	tex = SDL_CreateTextureFromSurface(ren, bmp);
	
	//释放Surface内存
	SDL_FreeSurface(bmp);

	return tex;
}

2.4、需要指定纹理的位置大小 要利用SDL_Rect数据结构

//绘制图像 指定位置
void ApplySurface(int x, int y, SDL_Texture *tex, SDL_Renderer *rend)
{
    
    
	//为了指定纹理Texture的绘制位置  我们需要创建一个SDL_Rect表示一个矩形 有位置宽高参数
	//矩形
	SDL_Rect pos;
	pos.x = x;//坐标
	pos.y = y;

	//通过纹理Texture查询纹理图片的宽高
	SDL_QueryTexture(tex, NULL, NULL, &pos.w, &pos.h);

	//在渲染器ren的pos位置绘制纹理图像tex
	SDL_RenderCopy(ren, tex, NULL, &pos);

	return;
}

2.5、SDL中的数据结构及SDL坐标

在这里插入图片描述
在这里插入图片描述

3、SDL显示图片

#include <iostream>
#include <string>

using namespace std;

#ifdef __cplusplus
extern "C"
{
    
    
#endif // !__cplusplus

#include "SDL.h"
//#include "SDL_main.h"

#ifdef __cplusplus
}
#endif // !__cplusplus

#pragma comment(lib,"SDL2.lib")
#pragma comment(lib,"SDL2main.lib")
#pragma comment(lib,"SDL2test.lib")
#pragma comment(lib,"legacy_stdio_definitions.lib")

extern "C" {
    
     FILE __iob_func[3] = {
    
     *stdin,*stdout,*stderr }; }

const int SCREEN_WIDTH = 640;
const int SCREEN_HEIGHT = 480;

SDL_Window *win = NULL;
SDL_Renderer *ren = NULL;

//加载图片 返回指定渲染器的图片纹理
SDL_Texture * LoadImage(string str_path)
{
    
    
	SDL_Texture * tex = NULL;
	SDL_Surface *bmp = NULL;

	//加载图片到Surface  便于硬件加速
	bmp = SDL_LoadBMP(str_path.c_str());
	if (bmp == NULL)
	{
    
    
		cout << "SDL_LoadBMP error" << endl;
		return tex;
	}

	//在指定渲染器Renderer上创建纹理Texture
	tex = SDL_CreateTextureFromSurface(ren, bmp);
	
	//释放Surface内存
	SDL_FreeSurface(bmp);

	return tex;
}

//绘制图像 指定位置
void ApplySurface(int x, int y, SDL_Texture *tex, SDL_Renderer *rend)
{
    
    
	//为了指定纹理Texture的绘制位置  我们需要创建一个SDL_Rect表示一个矩形 有位置宽高参数
	//矩形
	SDL_Rect pos;
	pos.x = x;//坐标
	pos.y = y;

	//通过纹理Texture查询纹理图片的宽高
	SDL_QueryTexture(tex, NULL, NULL, &pos.w, &pos.h);

	/*
	extern DECLSPEC int SDLCALL SDL_RenderCopy(SDL_Renderer * renderer,
	SDL_Texture * texture,
	const SDL_Rect * srcrect,
	const SDL_Rect * dstrect);
	//两个NULL分别是第一个NULL是一个指向源矩形的指针,从图像上裁剪下的一块矩形;而另一个是指向目标矩形的指针。
	我们将NULL传入这两个参数,
	是告诉SDL绘制整个源图像(第一个NULL),并把它画在屏幕上(0,0 )的位置,
	拉伸这个图像让它填满整个窗口(第二个NULL)
	SDL_RenderCopy(ren, tex, NULL, NULL);
	*/
	//在渲染器ren的pos位置绘制纹理图像tex
	SDL_RenderCopy(ren, tex, NULL, &pos);

	return;
}

int main(int argc, char *argv[])
{
    
    
	//SDL初始化
	if (SDL_Init(SDL_INIT_VIDEO))
	{
    
    
		cout << "SDL_Init error" << endl;
	}
	else
	{
    
    
		cout << "SDL_Init success" << endl;
	}
	/*SDL创建windows窗口
	extern DECLSPEC SDL_Window * SDLCALL SDL_CreateWindow(const char *title,
	int x, int y, int w,
	int h, Uint32 flags);
	标题 坐标 宽高 属性
	SDL_WINDOW_SHOWN  表示创建之后马上弹出显示
	*/
	//SDL_WINDOWPOS_CENTERED表示sdl把窗口设定到指定坐标轴的中央
	win = SDL_CreateWindow("Paint Picture!" ,SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN);
	if (win == NULL)
	{
    
    
		cout << "SDL_CreateWindow error" << endl;
		return -1;
	}
	/*
	创建渲染器 需指定我们用来绘制的窗口win
	extern DECLSPEC SDL_Renderer * SDLCALL SDL_CreateRenderer(SDL_Window * window,
	int index, Uint32 flags);
	指定渲染器绑定的窗口 指定可选用的显卡驱动 -1表示sdl自动选择 选择额外属性
	SDL_RENDERER_ACCELERATED  表示使用硬件加速 使用显卡
	SDL_RENDERER_PRESENTVSYNC  表示使用显示器的刷新率来更新画面
	*/
	ren = SDL_CreateRenderer(win, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
	if (ren == NULL)
	{
    
    
		cout << "SDL_CreateRenderer error" << endl;
		return -1;
	}

	SDL_Texture *background = NULL, *image = NULL;
	background = LoadImage("4.bmp");
	image = LoadImage("5.bmp");
	if (background == NULL || image == NULL)
	{
    
    
		cout << "LoadImage error" << endl;
		return -1;
	}
		

	SDL_RenderClear(ren);//先清除
	
	int bw, bh;
	SDL_QueryTexture(background, NULL, NULL, &bw, &bh);
	for (int i = 0; i < SCREEN_WIDTH; i+= bw)
	{
    
    
		for (int j = 0; j < SCREEN_HEIGHT; j += bh)
		{
    
    
			ApplySurface(i, j, background, ren);
		}
	}

	SDL_QueryTexture(image, NULL, NULL, &bw, &bh);
	int x = SCREEN_WIDTH / 2 - bw / 2;
	int y = SCREEN_HEIGHT / 2 - bh / 2;
	ApplySurface(x, y, image, ren);

	SDL_RenderPresent(ren);//刷新

	SDL_Delay(10000);//延迟展示

	//清除内存
	SDL_DestroyTexture(background);
	SDL_DestroyTexture(image);
	SDL_DestroyRenderer(ren);
	SDL_DestroyWindow(win);

	SDL_Quit();

	return 0;
}

参考博客SDL显示图片

4、SDL显示视频

#include <iostream>
#include <string>

using namespace std;

#ifdef __cplusplus
extern "C"
{
    
    
#endif // !__cplusplus

#include "SDL.h"
//#include "SDL_main.h"

#ifdef __cplusplus
}
#endif // !__cplusplus

#pragma comment(lib,"SDL2.lib")
#pragma comment(lib,"SDL2main.lib")
#pragma comment(lib,"SDL2test.lib")

//解决VS2015库不对应问题
#pragma comment(lib,"legacy_stdio_definitions.lib")
extern "C" {
    
     FILE __iob_func[3] = {
    
     *stdin,*stdout,*stderr }; }

//每个像素点占的位数  YUV420就是12个位 8+2+2
const int bpp = 12;
//窗口大小
const int screen_w = 640, screen_h = 360;
//视频大小
const int pixel_w = 640, pixel_h = 360;
//每帧图像的内存存放
unsigned char buffer[pixel_w*pixel_h*bpp / 8];

int main(int argc, char *argv[])
{
    
    
	//SDL初始化
	if (SDL_Init(SDL_INIT_VIDEO))
	{
    
    
		cout << "SDL_Init error" << endl;
		return -1;
	}
	else
	{
    
    
		cout << "SDL_Init success" << endl;
	}

	//创建窗口
	//SDL_WINDOWPOS_CENTERED  居中  
	//SDL_WINDOW_OPENGL 使用openGL  SDL_WINDOW_RESIZABLE无边框
	SDL_Window *screen = NULL;
	screen = SDL_CreateWindow("SDL Play Vedio", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
		screen_w, screen_h, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
	if (!screen)
	{
    
    
		cout << "SDL_CreateWindow error" << endl;
		return -1;
	}
	
	//创建窗口的渲染器 -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;  //YUV422   则每个像素占12位
	//创建纹理 之前也可以使用从suface中获取 
	/*
	extern DECLSPEC SDL_Texture * SDLCALL SDL_CreateTexture(SDL_Renderer * renderer,
                                                        Uint32 format, //纹理的格式
                                                        int access,  //SDL_TEXTUREACCESS_STREAMING 表示变化频繁
                                                        int w, int h);//纹理数据的宽高
	*/
	SDL_Texture* sdlTexture = NULL;
	sdlTexture = SDL_CreateTexture(sdlRenderer, pixformat, SDL_TEXTUREACCESS_STREAMING, pixel_w, pixel_h);
	if (sdlTexture == NULL)
	{
    
    
		cout << "SDL_CreateWindow error" << endl;
		return -1;
	}
	
	//打开视频文件
	FILE *fp = NULL;
	fp = fopen("sintel_640_360.yuv", "rb+");
	if (fp == NULL) {
    
    
		printf("cannot open this file\n");
		return -1;
	}

	//使用矩形来定位纹理显示在渲染器的坐标
	SDL_Rect sdlRect;

	while (1) {
    
    
		//每次读取一个帧画面
		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);
		}

		//将数据填充到纹理中
		/*
		extern DECLSPEC int SDLCALL SDL_UpdateTexture(SDL_Texture * texture,
                                              const SDL_Rect * rect,
                                              const void *pixels,//数据内存地址
											  int pitch);//像素数据行之间的字节数
		//填充纹理还可以使用
		extern DECLSPEC int SDLCALL SDL_UpdateYUVTexture(SDL_Texture * texture,
                                                 const SDL_Rect * rect,
                                                 const Uint8 *Yplane, int Ypitch,
                                                 const Uint8 *Uplane, int Upitch,
                                                 const Uint8 *Vplane, int Vpitch);									  
		 */
		SDL_UpdateTexture(sdlTexture, NULL, buffer, pixel_w);

		//显示的位置
		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);//显示刷新

		//Delay 40ms
		SDL_Delay(40);//延迟

	}

	//清除内存
	SDL_DestroyTexture(sdlTexture);
	SDL_DestroyRenderer(sdlRenderer);
	SDL_DestroyWindow(screen);
	//退出
	SDL_Quit();

	return 0;
}

参考博客
雷神的博客地址
SDL2显示参考博客

5、SDL播放PCM音频

注意制作音频文件可以使用audacity2.3.1.exe软件,输出指定采样率,格式的音频文件

一样的是SDL_init只是传参不同 传递的是

音频和定时器参数
SDL_Init(SDL_INIT_AUDIO | SDL_INIT_TIMER)

与音频相关的数据结构和API

1、
SDL_AudioSpec  设置存放音频播放的系列参数
//在这个结构体中包含了音频的各种参数
typedef struct SDL_AudioSpec
{
    
    
    int freq;                   /**< 音频采样率*/
    SDL_AudioFormat format;     /**< 音频数据格式 */
    Uint8 channels;             /**< 声道数: 1 单声道, 2 立体声 */
    Uint8 silence;              /**< 设置静音的值*/
    Uint16 samples;             /**< 音频缓冲区中的采样个数,要求必须是2的n次*/
    Uint16 padding;             /**< 考虑到兼容性的一个参数*/
    Uint32 size;                /**< 音频缓冲区的大小,以字节为单位*/
    SDL_AudioCallback callback; /**< 填充音频缓冲区的回调函数 */
    void *userdata;             /**< 用户自定义的数据 */
} SDL_AudioSpec;

2、
打开音频设备  利用之前的SDL_AudioSpec音频参数打开播放对应参数的音频设备
int SDLCALL SDL_OpenAudio(SDL_AudioSpec * desired,
                          SDL_AudioSpec * obtained);
// desired:期望的参数。
// obtained:实际音频设备的参数,一般情况下设置为NULL即可。

3、
当音频设备需要更多数据的时候会调用该回调函数。
SDL_AudioCallback  播放需要更多参数的时候调用的回调函数
// userdata:SDL_AudioSpec结构中的用户自定义数据,一般情况下可以不用。
// stream:该指针指向需要填充的音频缓冲区。
// len:音频缓冲区的大小(以字节为单位)。
void (SDLCALL * SDL_AudioCallback) (void *userdata,
                                    Uint8 *stream,
                                    int len);

4、
播放音频数据
// 当pause_on设置为0的时候即可开始播放音频数据。设置为1的时候,将会播放静音的值。
void SDLCALL SDL_PauseAudio(int pause_on)

5、
SDL_MixAudio:混音播放函数
void SDL_MixAudio(Uint8 * dst,
                 const Uint8 * src,
                 Uint32 len,
                 int volume);
参数一:目标数据,这个是回调函数里面的stream指针指向的,直接使用回调的stream指针即可。
参数二:音频数据,这个是将需要播放的音频数据混到stream里面去,那么这里就是我们需要填充的播放的数据。
参数三:音频数据的长度,这个是我们填充过去的长度。
参数四:音量,0~128范围,SAL_MIX_MAXVOLUME为128,设置的是软音量,不是硬件的音响。

SDL播放PCM音频文件工程代码

#include <iostream>
#include <string>

using namespace std;

#ifdef __cplusplus
extern "C"
{
    
    
#endif // !__cplusplus

#include "SDL.h"
//#include "SDL_main.h"

#ifdef __cplusplus
}
#endif // !__cplusplus

#pragma comment(lib,"SDL2.lib")
#pragma comment(lib,"SDL2main.lib")
#pragma comment(lib,"SDL2test.lib")

//解决VS2015库不对应问题
#pragma comment(lib,"legacy_stdio_definitions.lib")
extern "C" {
    
     FILE __iob_func[3] = {
    
     *stdin,*stdout,*stderr }; }

static Uint8 *audio_chunk;
static Uint32 audio_len;
static Uint8 *audio_pos;
int pcm_buffer_size = 4096;

//回调函数,音频设备需要更多数据的时候会调用该回调函数
void read_audio_data(void *udata, Uint8 *stream, int len)
{
    
    
	SDL_memset(stream, 0, len);
	if (audio_len == 0)
		return;
	len = (len > audio_len ? audio_len : len);
	//混音播放函数
	SDL_MixAudio(stream, audio_pos, len, SDL_MIX_MAXVOLUME);
	audio_pos += len;
	audio_len -= len;
}

int main(int argc, char *argv[])
{
    
    
	//初始化音频的SDL  SDL_INIT_TIMER定时器
	if (SDL_Init(SDL_INIT_AUDIO | SDL_INIT_TIMER))
	{
    
    
		cout << "SDL_Init error" << endl;
		return -1;
	}
	
	//SDL音频播放就是需要该结构体进行参数记录
	SDL_AudioSpec spec;
	spec.freq = 44100;//根据你录制的PCM采样率决定
	spec.format = AUDIO_S16SYS;//音频样式  16位
	spec.channels = 1; //单声道
	spec.silence = 0;
	spec.samples = 1024;
	spec.callback = read_audio_data;//回调函数  有对应格式的
	spec.userdata = NULL;

	//打开音频设备
	if (SDL_OpenAudio(&spec, NULL) < 0) {
    
    
		cout << "SDL_OpenAudio error" << endl;
		return -1;
	}

	FILE *fp = fopen("1.wav", "rb+");
	if (fp == NULL) {
    
    
		cout << "cannot open this file\n" << endl;
		return -1;
	}
	char *pcm_buffer = (char *)malloc(pcm_buffer_size);

	//播放 传入0播放  1暂停
	SDL_PauseAudio(0);

	while (1) {
    
    
		//从文件中读取数据,剩下的就交给音频设备去完成了,它播放完一段数据后会执行回调函数,获取等多的数据
		if (fread(pcm_buffer, 1, pcm_buffer_size, fp) != pcm_buffer_size) {
    
     
			break;
		}

		audio_chunk = (Uint8 *)pcm_buffer;
		audio_len = pcm_buffer_size; //长度为读出数据长度,在read_audio_data中做减法
		audio_pos = audio_chunk;//音频数据存放的地址

		while (audio_len > 0) //判断是否播放完毕
			SDL_Delay(1);
	}

	free(pcm_buffer);
	// 步骤:播放完毕
    SDL_CloseAudio();
	//退出
	SDL_Quit();

	return 0;
}

参考博客;
SDL2播放PCM音频
音频基础介绍、使用SDL播放音频

SDL相关代码链接;Git工程代码

猜你喜欢

转载自blog.csdn.net/zw1996/article/details/115271107