目录
0 参考
Wikipedia: https://en.wikipedia.org/wiki/Simple_DirectMedia_Layer
SDL官网:http://www.libsdl.org/
SDL官网wiki: http://wiki.libsdl.org/FrontPage
雷神Blog: SDL介绍 SDL源代码分析系列
SDL1.2中文教程: http://kelvmiao.info/sdl-tutorial-cn/
慕课网李超的Blog: SDL系列
1 SDL简介
1.1 特点
SDL(Simple DirectMedia Layer)是一个跨平台的多媒体库,它通过OpenGL和2D视频帧缓冲,提供了针对音频、视频、键盘、鼠标、控制杆及3D硬件的低级别的访问接口。它在MPEG播放软件、模拟器以及许多游戏中得到广泛的应用,其中包含了获得大奖的“文明:权力的呼唤”的Linux 版本。
SDL 正式支持Windows, Mac OS X, Linux, iOS, and Android. 同时代码中包含了针对AmigaOS, Dreamcast, Atari, AIX, OSF/Tru64, RISC OS, SymbianOS, 和OS/2的支持,但这些并不是正式的支持。
SDL 是用C编写的,但可以原生地配合C++使用,并且它拥有一些其他程序语言的绑定,这包括:Ada, C#, D, Eiffel, Erlang, Euphoria, Go, Guile, Haskell, Java, Lisp, Lua, ML, Objective C, Pascal, Perl, PHP, Pike, Pliant, Python, Ruby, Smalltalk, 以及 Tcl。
SDL 采用GNU的LGPL第二版许可发行。该许可允许你自由地链接其动态链接库,甚至可以在商业软件中自由地使用。意味着动态链接(dynamic link)其库并不需要开放本身的源代码。
1.2 架构
SDL在结构上是将不同操作系统的库再包装成相同的函数,例如SDL在Windows平台上其实是DirectX的再包装。而在使用X11的平台上(包括Linux),SDL则是与Xlib库沟通来输出图像。虽然SDL本身是使用C语言写成,但是它几乎可以被所有的编程语言所使用,例如:C++、Perl、Python(借由pygame库)、Pascal等等,甚至是Euphoria、Pliant这类较不流行的编程语言也都可行。
1.3 组成
1.3.1 子系统 SubSystem
SDL官方编译好的库分为 Video、Audio、Input Event、Joystick 和 Timer 等若干子系统,各子系统功能介绍如下
Video
- 3D graphics:
- SDL can be used in combination with the OpenGL API or Direct3D API for 3D graphics
- Accelerated 2D render API:
- Supports easy rotation, scaling and alpha blending, all accelerated using modern 3D APIs
- Acceleration is supported using OpenGL and Direct3D, and there is a software fallback
- Create and manage multiple windows
Input Events
- Events and API functions provided for:
- Application and window state changes
- Mouse input
- Keyboard input
- Joystick and game controller input
- Multitouch gestures
-
Each event can be enabled or disabled with SDL_EventState()
- Events are passed through a user-specified filter function before being posted to the internal event queue
- Thread-safe event queue
Force Feedback
- Force feedback is supported under Windows, Mac OS X and Linux
Audio
- Set audio playback of 8-bit and 16-bit audio, mono stereo or 5.1 surround sound, with optional conversion if the format is not supported by the hardware
- Audio runs independently in a separate thread, filled via a user callback mechanism
-
Designed for custom software audio mixers, but SDL_mixer provides a complete audio/music output library
File I/O Abstraction
- General purpose abstraction for opening, reading and writing data
- Built-in support for files and memory
Shared Object Support
- Load shared objects (DLL on Windows, .dylib on Mac OS X, .so on Linux)
- Lookup functions in shared objects
Threads
- Simple thread creation API
- Simple thread local storage API
- Mutexes, semaphores and condition variables
- Atomic operations for lockless programming
Timers
- Get the number of milliseconds elapsed
- Wait a specified number of milliseconds
- Create timers that run alongside your code in a separate thread
- Use high resolution counter for profiling
CPU Feature Detection
- Query the number of CPUs
- Detect CPU features and supported instruction sets
Endian Independence
- Detect the endianness of the current system
- Routines for fast swapping of data values
- Read and write data of a specified endianness
Power Management
- Querying power management status
1.3.2 扩充库
还有一些单独的官方扩充函数库。这些库由官方网站提供,并包含在官方文档中,共同组成了SDL的“标准库”:
- SDL_image—支持时下流行的图像格式:BMP、PPM、XPM、 PCX、GIF、JPEG、PNG、TGA。
- SDL_mixer—更多的声音输出函数以及更多的声音格式支持。
- SDL_net—网络支持。
- SDL_ttf—TrueType字体渲染支持。
- SDL_rtf—简单的RTF渲染支持。
1.4 API分类以及查询
分类查询地址:http://wiki.libsdl.org/APIByCategory
按名称查询地址:http://wiki.libsdl.org/CategoryAPI
2 SDL移植到VS中使用
1. SDL官网下载SDL的开发包,当前为 SDL2-devel-2.0.9-VC.zip,解压后得到头文件include目录,以及lib目录x86以及x64的库;
2. 创建vs工程,工程目录(.vcxproj文件所在目录)创建lib目录,include以及include下的子目录SDL2;
3. 将SDL头文件目录下所有头文件拷贝到步骤2中的SDL2目录下, SDL2.lib以及SDL2main.lib拷贝到lib目录下,SDL2.dll放在工程目录,即xxx.vcxproj同级目录下;
4. 配置vs工程
配置头文件查询路径:工程->右键属性->配置属性->c/c++->常规->附加包含目录->添加“include\SDL2”;
配置.lib文件:工程->右键属性->配置属性->链接器->常规->附加库目录->添加“lib”;
工程->右键属性->配置属性->链接器->输入->附加依赖项->添加“SDL2.lib SDL2main.lib”
5. 以上配置后,在工程中可以正常使用SDL中的功能
注意:虽然SDL是c语言开发的,但在c++中使用,引用sdl的头文件不必加extern "C",因为SDL库的头文件内部就已经包含了
#ifdef __cplusplus
extern "C" {
#endif
.........
#ifdef __cplusplus
}
#endif
3 简单几个SDL示例VS工程
3.1 使用SDL渲染本地图片
#include "SDL.h"
#include "stdio.h"
#include "SDL_image.h"
int main(int argc, char* argv[]) {
// 初始化SDL,由于只是渲染一张图片,因此只需要初始化Video即可
if (SDL_Init(SDL_INIT_VIDEO)) {
printf("Init SDL error: %s", SDL_GetError());
return -1;
}
//为了显示JPG图片,额外使用了图片库,所以要单独初始化
IMG_Init(IMG_INIT_JPG);
// 创建SDL_Window窗体,即作画的画布
SDL_Window* window = SDL_CreateWindow("This is a SDL window to reder",
SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 640, 480, SDL_WINDOW_SHOWN);
if (!window) {
printf("Create SDL window error: %s", SDL_GetError());
return -2;
}
// 创建SDL_Renderer渲染器,即作画的画笔
SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_SOFTWARE);
if (!renderer) {
printf("Create SDL renderer error: %s", SDL_GetError());
return -3;
}
// 加载需要显示的图片,存储到surface
SDL_Surface* surface1 = SDL_LoadBMP("hello.bmp");
if (!surface1) {
printf("Load hello.bmp error: %s", SDL_GetError());
return -4;
}
SDL_Surface* surface2 = IMG_Load("sdl_imag.jpg");
if (!surface2) {
printf("SDL_Image sdl_imag.jpg error: %s", IMG_GetError());
return -5;
}
// 从surface中拷贝图片形成纹理, 即要显示的内容
SDL_Texture* texture1 = SDL_CreateTextureFromSurface(renderer, surface1);
if (!texture1) {
printf("Create texture from surface1 error: %s", SDL_GetError());
return -6;
}
SDL_Texture* texture2 = SDL_CreateTextureFromSurface(renderer, surface2);
if (!texture2) {
printf("Create texture from surface2 error: %s", SDL_GetError());
return -7;
}
// 设置渲染区域
SDL_Rect rect1{ 0, 0, 320, 240 };
SDL_Rect rect2{ 320, 240, 320, 240 };
// 告知渲染器如何在窗体的哪个位置渲染文理
SDL_RenderCopy(renderer, texture1, NULL, &rect1);
SDL_RenderCopy(renderer, texture2, NULL, &rect2);
// 渲染
SDL_RenderPresent(renderer);
// 延迟10s后进入清理程序
SDL_Delay(10000);
// 销毁创建的纹理,表层,渲染器,窗体,注意销毁顺序与创建顺序相反。
SDL_DestroyTexture(texture1);
SDL_DestroyTexture(texture2);
SDL_FreeSurface(surface1);
SDL_FreeSurface(surface2);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
IMG_Quit();
SDL_Quit();
return 0;
}
3.2 使用SDL渲染视频
#include <stdio.h>
#include "SDL.h"
//Video Refresh Event
#define REFRESH_EVENT (SDL_USEREVENT + 1) // 告知SDL消息循环开始渲染新的一帧视频
#define EXIT_REFRESH_EVENT (SDL_USEREVENT + 2) // 告知SDL消息循环刷新线程已经停止,可以优雅的退出消息循环
struct VideoStatus{
bool video_thread_exit{false}; // 刷新线程退出标志位
bool exit{ false }; // SDL消息循环退出标志位
int screen_width{ 352 }; // 视频窗体的宽度
int screen_height{ 288 }; // 视频窗体的宽度
int pixel_w{ 352 }; // 视频的宽度(像素个数)
int pixel_h{ 288 }; // 视频的高度
int bits_per_pixel{ 12 }; // 视频单个像素所占bits,与像素格式有关,本例采用的都是yuv420p的图像,因此单个像素12bits
int loop{ 3 }; // 播放视频循环次数
int delay{ 40 }; // 每隔delay毫秒数播放一帧视频,默认40ms也即1s播放25帧
Uint32 pix_format{ SDL_PIXELFORMAT_IYUV }; // 像素格式,yuv420p的像素格式为SDL_PIXELFORMAT_IYUV /**< Planar mode: Y + U + V (3 planes) */
};
VideoStatus is;
int videoRefreshThread(void *data) {
while (!is.video_thread_exit) { // 每隔delay毫秒发送一次REFRESH_EVENT的自定义SDL消息
SDL_Event event;
event.type = REFRESH_EVENT;
SDL_PushEvent(&event);
SDL_Delay(is.delay);
}
SDL_Event event; // 线程退出前,发送自定义的SDL消息EXIT_REFRESH_EVENT
event.type = EXIT_REFRESH_EVENT;
SDL_PushEvent(&event);
return 0;
}
int main(int argc, char* agrv[]) {
/**
* 初始化SDL
* 由于只是渲染视频,因此只用初始化Video子系统,
* 当然初始化Video子系统,默认也会初始化Event子系统
*/
if (SDL_Init(SDL_INIT_VIDEO)) {
printf("SDL init error: %s", SDL_GetError());
return -1;
}
/**
* 创建SDL窗体,类比画布
* 最后一个参数表示使用OpenGL渲染,其他选项可以参见SDL_CreateWindow函数说明
*/
SDL_Window* window = SDL_CreateWindow("This is my SDL video renderer",
SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, is.screen_width, is.screen_height, SDL_WINDOW_OPENGL);
if (!window) {
printf("SDL create window error: %s", SDL_GetError());
return -2;
}
/**
* 创建SDL渲染器,类比画笔
* 最后一个参数表示使用了软件渲染(占CPU),还可以选择硬件渲染(使用GPU),
* 其他选项见SDL_CreateRenderer说明
*/
SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_SOFTWARE);
if (!renderer) {
printf("SDL create renderer error: %s", SDL_GetError());
return -3;
}
/**
* 创建SDL纹理,类比需要画的内容
* 第二个参数是像素格式 IYUV (由于示例视频使用的是yuv420p格式的,因此决定了像素格式为IYUV)
* 第三个参数是Texture的Access模式,SDL_TEXTUREACCESS_STREAMING表示访问Texture需要加锁,因为是播放视频,需要频繁更新Texture
* 第四 五个参数是文理的宽高(设置为示例视频的宽高)
*/
SDL_Texture* texture = SDL_CreateTexture(renderer, is.pix_format, SDL_TEXTUREACCESS_STREAMING, is.pixel_w, is.pixel_h);
if (!texture) {
printf("SDL create texture error: %s", SDL_GetError());
return -4;
}
/** 打开示例文件 **/
FILE* fp = fopen("BUS_352x288_30_orig_01.yuv", "rb+");
if (!fp){
printf("Cannot open file BUS_352x288_30_orig_01.yuv\n");
return -5;
}
/**
* 计算一帧图像的大小 pixel_w * pixel_h * bits_per_pixel/8
* 图像的(宽*高*单个像素所占比特数/8),由于是yuv420p的图像,所以单个像素所占比特为12(Y为8bits,uv各为2bits)
*/
int frameSize = is.pixel_w * is.pixel_h * is.bits_per_pixel / 8;
unsigned char* frame_buffer = new unsigned char[frameSize]{ 0 };
/**
* 开启刷新线程,控制每隔多长时间(使用SDL_Delay(msec))发送一个刷新的SDL_Event到SDL消息循环,
* 从而控制每隔多长时间去读取并渲染一帧视频。
*/
SDL_CreateThread(videoRefreshThread, "refresh thread", NULL);
SDL_Rect renderArea;
SDL_Event event;
while (!is.exit) {
SDL_WaitEvent(&event);
switch (event.type)
{
case REFRESH_EVENT: { // 刷新event,从文件读取一帧视频进行渲染
if ((frameSize) != fread(frame_buffer, 1, frameSize, fp)) {
if (is.loop--) {
fseek(fp, 0, SEEK_SET);
fread(frame_buffer, 1, frameSize, fp);
}
else {
is.video_thread_exit = true; // 循环播放完毕,使刷新线程退出标志置位
}
}
if (!is.video_thread_exit) {
/**
* 更新文理,将需要显示的视频帧数据
* 最后一个参数是原始一行数据占bytes数,当然是一行像素个数*单个像素所占bits/8
*/
SDL_UpdateTexture(texture, NULL, frame_buffer, is.pixel_w*is.bits_per_pixel/8);
/**
* 当窗体大小变化时,更新记录窗体大小
* 控制渲染区域占据整个窗体
*/
renderArea.x = 0;
renderArea.y = 0;
renderArea.w = is.screen_width;
renderArea.h = is.screen_height;
/**
* 清空renderer,这步不做也没问题,SDL_RenderCopy将执行这个操作
*/
SDL_RenderClear(renderer);
/**
* 将准备好的纹理传递交给渲染器,并告知渲染在窗体的哪个区域
*/
SDL_RenderCopy(renderer, texture, NULL, &renderArea);
/**
* 渲染
*/
SDL_RenderPresent(renderer);
}
break;
}
case SDL_QUIT: { // 响应SDL_QUIT消息,当窗口主动关闭时,不直接退出消息循环,
is.video_thread_exit = true; // 而是给线程退出标志置位,让刷新线程优雅结束。刷新线程结束后,
break; // 推送EXIT_REFRESH_EVENT,在响应此消息时给消息循环退出标志置位
}
case EXIT_REFRESH_EVENT: { // 刷新线程退出后会发送此消息到消息循环,给消息循环退出标志位置位
is.exit = true;
break;
}
case SDL_WINDOWEVENT: { // 响应窗口变化,更新记录窗体大小
SDL_GetWindowSize(window, &is.screen_width, &is.screen_height);
break;
}
default:
break;
}
}
/* 按照与创建相反的顺序销毁对象 */
SDL_DestroyTexture(texture);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
return 0;
}
3.3 使用SDL渲染音频
#include "SDL.h"
#include "stdio.h"
Uint32 audio_data_len = 0;
Uint8 *audio_data_pos = NULL;
static void av_read_audio(void *userdata, Uint8 * stream, int len) {
/**
* len值会等于Audio Buffer的大小4096
*/
SDL_memset(stream, 0, len);
if (audio_data_len == 0) {
return;
}
len = (len > audio_data_len ? audio_data_len : len);
/**
* 从缓存buffer中拷贝数据到Audio Buffer
*/
SDL_MixAudio(stream, audio_data_pos, len, SDL_MIX_MAXVOLUME);
/*修改缓存的指针*/
audio_data_pos += len;
audio_data_len -= len;
}
int main(int argc, char* argv[]) {
/**
* 初始化audio子系统和timer子系统,
* timer子系统中含有SDL_Delay函数,能让线程让渡一定ms数(即暂停运行一定ms)
*/
if (SDL_Init(SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
printf("SDL Init audio and timer system error: %s", SDL_GetError());
return -1;
}
/**
* 注意文件打开方式rb,以只读二进制的形式打开,
* 千万记得b,否则按文本方式打开会无法播放
*/
FILE* file = fopen("pcm_2_s16le_44100_34s.pcm", "rb");
if (!file) {
printf("Open audio file pcm_2_s16L2_44100.pcm error");
return -2;
}
SDL_AudioSpec audioSpec;
audioSpec.freq = 44100; /**< DSP frequency -- samples per second */
audioSpec.format = AUDIO_S16LSB; /**< Audio data format */ /*16位有符号整数,小端格式s16le*/
audioSpec.channels = 2; /**< Number of channels: 1 mono, 2 stereo */
audioSpec.silence = 0; /**< Audio buffer silence value (calculated) */
audioSpec.samples = 1024; /**< Audio buffer size in sample FRAMES (total samples divided by channel count)
按照当前的采样格式,那么缓冲区大小为1024*2*2=4096,采样点数*单个采样点所占bytes*通道数
*/
audioSpec.userdata = NULL; /**< Userdata passed to callback (ignored for NULL callbacks). */
audioSpec.callback = av_read_audio;
SDL_AudioSpec audioSpec2;
/**
* This function opens the audio device with the desired parameters, and
* returns 0 if successful, placing the actual hardware parameters in the
* structure pointed to by \c obtained. If \c obtained is NULL, the audio
* data passed to the callback function will be guaranteed to be in the
* requested format, and will be automatically converted to the hardware
* audio format if necessary. This function returns -1 if it failed
* to open the audio device, or couldn't set up the audio thread.
*/
if (SDL_OpenAudio(&audioSpec, &audioSpec2)) {
printf("SDL_OpenAudio error: %s", SDL_GetError());
return -3;
}
/**
* 打印Audio buffer的大小,此处应该是4096
*/
printf("Audio buffer size : %d ", audioSpec2.size);
/**
* 开启音频播放
*/
SDL_PauseAudio(0);
/**
* 读取文件的缓存大小大于等于Audio buffer,
* 否则播放不正常有噪音等
*/
int buffer_size = 8192;
char* buffer = (char*) malloc(buffer_size);
/**
* 播放的基本思路是从pcm文件中读取buffer_size大小的数据到buffer中
* 主线程进入wait状态
* 等待buffer中的数据被复制到Audio Buffer并播放完后,再次读取数据
* 当读到的数据不足buffer_size大小的时候,设置标志位,表示是最后一次读取数据
* 等待最后一次读取的数据被复制到Audio Buffer并被播放后,退出主线程循环
*/
bool exit = false;
while (!exit) {
int size = fread(buffer, 1, buffer_size, file);
if (size != buffer_size) { // 处理剩余音频数据
exit = true;
}
audio_data_pos = (Uint8 *)buffer;
audio_data_len = size;
while (audio_data_len > 0){
SDL_Delay(1);
}
}
fclose(file);
delete buffer;
SDL_Quit();
getchar();
return 0;
}
3.4 使用总结
4 SDL源码解析
源码解析这块,雷神已经写了很好的分析文章,在此转发,向前行者致敬:
SDL2源代码分析5:更新纹理(SDL_UpdateTexture())
SDL2源代码分析6:复制到渲染器(SDL_RenderCopy())