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 初始化
1、int 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 支持渲染纹理
4、SDL_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工程代码