[总结]SDL知识介绍

目录

0 参考

1 SDL简介

1.1 特点

1.2 架构

1.3 组成

1.3.1 子系统 SubSystem

1.3.2 扩充库

1.4 API分类以及查询

2 SDL移植到VS中使用

3 简单几个SDL示例VS工程

3.1 使用SDL显示本地的图片

3.2 使用SDL渲染视频

3.3 使用SDL渲染音频

4 SDL源码解析


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源代码分析1:初始化(SDL_Init())

SDL2源代码分析2:窗口(SDL_Window)

SDL2源代码分析3:渲染器(SDL_Renderer)

SDL2源代码分析4:纹理(SDL_Texture)

SDL2源代码分析5:更新纹理(SDL_UpdateTexture())

SDL2源代码分析6:复制到渲染器(SDL_RenderCopy())

SDL2源代码分析7:显示(SDL_RenderPresent())

SDL2源代码分析8:视频显示总结

  • 0
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值