Simple DirectMedia Layer是一个跨平台开发库,旨在通过OpenGL和Direct3D提供对音频、键盘、鼠标、操纵杆和图形硬件的低级访问。它被视频播放软件、模拟器和流行游戏使用,包括Valve的获奖目录和许多Humble Bundle游戏。
SDL正式支持Windows、macOS、Linux、iOS和Android。 对其他平台的支持可以在源代码中找到。SDL是用C编写的,可以在C++中原生运行,并且有几种其他语言的绑定,包括C#和Python。
SDL基本使用方法
- 初始化/退出API SDL_Init/SDL_Quit()
- 创建窗口/释放窗口SDL_CreateWindow()/SDL_DestoryWindow()
- 创建渲染器 SDL_CreateRender()
使用例子
#include <SDL.h>
int main(int argc, char const *argv[])
{
// 初始化SDL
SDL_Init(SDL_INIT_VIDEO);
// 退出SDL
SDL_Quit();
return 0;
}
找到SDL2的动态连接库和头文件地址:
// 找到对应的链接库
pkg-config --libs sdl2
-L/usr/local/lib -Wl,-rpath,/usr/local/lib -Wl,--enable-new-dtags -pthread -lSDL2
// 找到对应的头文件
pkg-config --cflags --libs sdl2
-D_REENTRANT -I/usr/local/include/SDL2 -L/usr/local/lib -Wl,-rpath,/usr/local/lib -Wl,--enable-new-dtags -pthread -lSDL2
SDL进行渲染
- SDL_CreateRender/SDL_DestoryRenderer
- SDL_RenderClear 清空数据
- SDL_RenderPresent 把数据推送出去
例子:
#include <SDL.h>
#include <stdio.h>
int main(int argc, char const *argv[])
{
// 创建窗口
SDL_Window * window = NULL;
// 创建对应的渲染
SDL_Renderer * render = NULL;
// 初始化SDL
SDL_Init(SDL_INIT_VIDEO);
// 窗口的具体的创建过程
window = SDL_CreateWindow("SDL2 window",200,200,480,640,SDL_WINDOW_SHOWN);
if(window == NULL){
printf("Couldn't create SDL2 window\n");
goto __EXIT;
}
// 创建对应渲染
render = SDL_CreateRenderer(window,-1,0 );
if(!render){
printf("Couldn't create renderer");
goto __EXIT_WINDOWS;
}
// 推送Render
SDL_SetRenderDrawColor(render,255,255,0,50 );
// 清空render数据
SDL_RenderClear(render);
SDL_RenderPresent(render);
// 延时延时
SDL_Delay(3000);
// 销毁窗口
SDL_DestroyWindow(window);
__EXIT_WINDOWS:
// 销毁对应的render
SDL_DestroyWindow(window);
__EXIT:
// 退出SDL
SDL_Quit();
return 0;
}
编译结果:
SDL事件处理
SDL将所有的事件放在一个对应的事件的队列中,所有对事件的操作都是对队列的操作。
事件的类型
- SDL_WindowEvent : Window窗口相关的事件。
- SDL_KeyboardEvent : 键盘相关的事件。
- SDL_MouseMotionEvent : 鼠标移动相关的事件。
- SDL_QuitEvent : 退出事件。
- SDL_UserEvent : 用户自定义事件。
事件处理的类型
- SDL_PollEvent:传统事件的处理方式,以轮询的方式进行处理,事件的处理需要的进行进行事件周期,需要进行等待。
- SDL_WaitEvent:现代事件的处理方式,监听特定的时间,进行休眠等待,会阻塞
#include <SDL.h>
#include <stdio.h>
int main(int argc, char const *argv[])
{
int quit = 1;
// 创建窗口
SDL_Window * window = NULL;
// 创建对应的渲染
SDL_Renderer * render = NULL;
// 初始化SDL
SDL_Init(SDL_INIT_VIDEO);
// 窗口的具体的创建过程
window = SDL_CreateWindow("SDL2 window",200,200,480,640,SDL_WINDOW_SHOWN);
if(window == NULL){
printf("Couldn't create SDL2 window\n");
goto __EXIT;
}
// 创建对应渲染
render = SDL_CreateRenderer(window,-1,0 );
if(!render){
printf("Couldn't create renderer");
goto __EXIT_WINDOWS;
}
// 推送Render
SDL_SetRenderDrawColor(render,255,255,0,50 );
// 清空render数据
SDL_RenderClear(render);
SDL_RenderPresent(render);
// 事件处理
do{
SDL_Event event;
SDL_WaitEvent(&event);
switch(event.type){
case SDL_QUIT:
quit = 0;
break;
default:
SDL_Log("event type is: %d", event.type);
}
}while(quit);
// 销毁窗口
SDL_DestroyWindow(window);
__EXIT_WINDOWS:
// 销毁对应的render
SDL_DestroyWindow(window);
__EXIT:
// 退出SDL
SDL_Quit();
return 0;
}
该例子会打印各种事件类型。
纹理渲染
纹理用于图像信息的表示,占用内存很少,而且计算在的GPU中,之后通过显示。
常用API
- SDL_CreateTexture():创建纹理 format:对应的数据类型,access:访问类型 Target(常用),Stream(流式)
- SDL_DestoryTexture():销毁纹理
// 设置纹理的渲染目标
-
SDL_SetRenderTarget :设置一个纹理作为当前渲染目标。
/**
* Set a texture as the current rendering target.
*
* Before using this function, you should check the
* `SDL_RENDERER_TARGETTEXTURE` bit in the flags of SDL_RendererInfo to see if
* render targets are supported.
*
* The default render target is the window for which the renderer was created.
* To stop rendering to a texture and render to the window again, call this
* function with a NULL `texture`.
*
* \param renderer 渲染上下文
* \param texture 目标纹理,必须使用.
* \returns 0 on success or a negative error code on failure; call
* SDL_GetError() for more information.
*
* \since This function is available since SDL 2.0.0.
*
* \sa SDL_GetRenderTarget
*/
extern DECLSPEC int SDLCALL SDL_SetRenderTarget(SDL_Renderer *renderer,
SDL_Texture *texture);
-
SDL_RenderClear()
/**
* 用绘图颜色清除当前渲染目标。
*
* This function clears the entire rendering target, ignoring the viewport and
* the clip rectangle.
*
* \param renderer 渲染上下文
* \returns 0 on success or a negative error code on failure; call
* SDL_GetError() for more information.
*
* \since This function is available since SDL 2.0.0.
*
* \sa SDL_SetRenderDrawColor
*/
extern DECLSPEC int SDLCALL SDL_RenderClear(SDL_Renderer * renderer);
-
SDL_RenderCopy()拷贝纹理数数据到GPU中,进行计算最终的图像
/**
* 将纹理的一部分复制到当前渲染目标。 *
* The texture is blended with the destination based on its blend mode set
* with SDL_SetTextureBlendMode().
*
* The texture color is affected based on its color modulation set by
* SDL_SetTextureColorMod().
*
* The texture alpha is affected based on its alpha modulation set by
* SDL_SetTextureAlphaMod().
*
* \param renderer the rendering context
* \param texture the source texture
* \param srcrect the source SDL_Rect structure or NULL for the entire texture
* \param dstrect the destination SDL_Rect structure or NULL for the entire
* rendering target; the texture will be stretched to fill the
* given rectangle
* \returns 0 on success or a negative error code on failure; call
* SDL_GetError() for more information.
*
* \since This function is available since SDL 2.0.0.
*
* \sa SDL_RenderCopyEx
* \sa SDL_SetTextureAlphaMod
* \sa SDL_SetTextureBlendMode
* \sa SDL_SetTextureColorMod
*/
extern DECLSPEC int SDLCALL SDL_RenderCopy(SDL_Renderer * renderer,
SDL_Texture * texture,
const SDL_Rect * srcrect,
const SDL_Rect * dstrect);
-
SDL_RenderPresent() 计算好的图像进行显示
/**
* 使用自上次调用以来执行的任何渲染更新屏幕。 *
* SDL's rendering functions operate on a backbuffer; that is, calling a
* rendering function such as SDL_RenderDrawLine() does not directly put a
* line on the screen, but rather updates the backbuffer. As such, you compose
* your entire scene and *present* the composed backbuffer to the screen as a
* complete picture.
*
* Therefore, when using SDL's rendering API, one does all drawing intended
* for the frame, and then calls this function once per frame to present the
* final drawing to the user.
*
* The backbuffer should be considered invalidated after each present; do not
* assume that previous contents will exist between frames. You are strongly
* encouraged to call SDL_RenderClear() to initialize the backbuffer before
* starting each new frame's drawing, even if you plan to overwrite every
* pixel.
*
* \param renderer the rendering context
*
* \since This function is available since SDL 2.0.0.
*
* \sa SDL_RenderClear
* \sa SDL_RenderDrawLine
* \sa SDL_RenderDrawLines
* \sa SDL_RenderDrawPoint
* \sa SDL_RenderDrawPoints
* \sa SDL_RenderDrawRect
* \sa SDL_RenderDrawRects
* \sa SDL_RenderFillRect
* \sa SDL_RenderFillRects
* \sa SDL_SetRenderDrawBlendMode
* \sa SDL_SetRenderDrawColor
*/
extern DECLSPEC void SDLCALL SDL_RenderPresent(SDL_Renderer * renderer);
例子
#include <SDL.h>
#include <stdio.h>
int main(int argc, char const *argv[])
{
int quit = 1;
SDL_Event event;
// 创建窗口
SDL_Window * window = NULL;
// 创建对应的渲染
SDL_Renderer * render = NULL;
// 创建纹理
SDL_Texture * texture = NULL;
SDL_Rect rect;
rect.w= 30;
rect.h= 30;
// 初始化SDL
SDL_Init(SDL_INIT_VIDEO);
// 窗口的具体的创建过程
window = SDL_CreateWindow("SDL2 window",200,200,480,640,SDL_WINDOW_SHOWN);
if(window == NULL){
printf("Couldn't create SDL2 window\n");
goto __EXIT;
}
// 创建对应渲染
render = SDL_CreateRenderer(window,-1,0 );
if(!render){
printf("Couldn't create renderer");
goto __EXIT_WINDOWS;
}
// 推送Render
SDL_SetRenderDrawColor(render,255,255,0,50 );
// 清空render数据
SDL_RenderClear(render);
SDL_RenderPresent(render);
// 创建初始化纹理
texture= SDL_CreateTexture(render,SDL_PIXELFORMAT_RGBA8888,SDL_TEXTUREACCESS_TARGET,600,440);
// 事件处理
do{
SDL_PollEvent(&event);
switch(event.type){
case SDL_QUIT:
quit = 0;
break;
default:
SDL_Log("event type is: %d", event.type);
}
rect.x = rand()%600;
rect.y = rand()%440;
// 设置渲染目标为给定的纹理。这意味着接下来的绘制操作将在这个纹理上进行而不是直接在屏幕上
SDL_SetRenderTarget(render,texture);
SDL_SetRenderDrawColor(render,0,0,0,0);
SDL_RenderClear(render);
SDL_RenderDrawRect(render, &rect);
// 设置绘制颜色为红色。
SDL_SetRenderDrawColor(render,255,0,0,0);
// 用先前设置的颜色填充矩形,使其变为红色实心矩形。
SDL_RenderFillRect(render, &rect);
// 将渲染目标重置为默认目标,即屏幕
SDL_SetRenderTarget(render,NULL);
// 将纹理复制到渲染目标上。在这里,texture 是之前设置的渲染目标,NULL 表示整个纹理都被复制,没有裁剪。
SDL_RenderCopy(render,texture,NULL,NULL);
SDL_RenderPresent(render);
}while(quit);
// 销毁纹理
SDL_DestroyTexture(texture);
// 销毁窗口
SDL_DestroyWindow(window);
__EXIT_WINDOWS:
// 销毁对应的render
SDL_DestroyRenderer(render);
__EXIT:
// 退出SDL
SDL_Quit();
return 0;
}
输出的结果为:
SDL_CreateThread 创建线程
参数:parame1 fn:执行的函数
参数:parame2 name:线程的名称
参数:parame3 data:执行函数的参数
更新纹理:
SDL_UpdateTexture();
SDL_UpdateYUVTexture();
SDL播放YUV图像
#include <stdio.h>
#include <string.h>
#include <SDL.h>
#define BLOCK_SIZE 4096000
// 自定义事件
#define REFRESH_EVENT (SDL_USEREVENT + 1)
#define QUIT_EVENT (SDL_USEREVENT + 2)
int thread_exit=0;
// 定时器事件
int refresh_video_timer(void *udata){
thread_exit=0;
while (!thread_exit) {
SDL_Event event;
event.type = REFRESH_EVENT;
SDL_PushEvent(&event);
SDL_Delay(40);
}
thread_exit=0;
//push quit event
SDL_Event event;
event.type = QUIT_EVENT;
SDL_PushEvent(&event);
return 0;
}
int main(int argc, char* argv[])
{
FILE *video_fd = NULL;
SDL_Event event;
SDL_Rect rect;
Uint32 pixformat = 0;
SDL_Window *win = NULL;
SDL_Renderer *renderer = NULL;
SDL_Texture *texture = NULL;
SDL_Thread *timer_thread = NULL;
int w_width = 640, w_height = 360;
const int video_width = 640, video_height = 360;
Uint8 *video_pos = NULL;
Uint8 *video_end = NULL;
unsigned int remain_len = 0;
unsigned int video_buff_len = 0;
unsigned int blank_space_len = 0;
Uint8 video_buf[BLOCK_SIZE];
const char *path = "/home/ss/learn/sql_learn/output.yuv";
const unsigned int yuv_frame_len = video_width * video_height * 12 / 8;
//initialize SDL
if(SDL_Init(SDL_INIT_VIDEO)) {
fprintf( stderr, "Could not initialize SDL - %s\n", SDL_GetError());
return -1;
}
win = SDL_CreateWindow("YUV Player",
SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED,
w_width, w_height,
SDL_WINDOW_OPENGL|SDL_WINDOW_RESIZABLE);
if(!win) {
fprintf(stderr, "Failed to create window, %s\n",SDL_GetError());
goto __FAIL;
}
renderer = SDL_CreateRenderer(win, -1, 0);
pixformat= SDL_PIXELFORMAT_IYUV;
texture = SDL_CreateTexture(renderer,
pixformat,
SDL_TEXTUREACCESS_STREAMING,
video_width,
video_height);
video_fd = fopen(path, "r");
if( !video_fd ){
fprintf(stderr, "Failed to open yuv file\n");
goto __FAIL;
}
if((video_buff_len = fread(video_buf, 1, BLOCK_SIZE, video_fd)) <= 0){
fprintf(stderr, "Failed to read data from yuv file!\n");
goto __FAIL;
}
video_pos = video_buf;
video_end = video_buf + video_buff_len;
blank_space_len = BLOCK_SIZE - video_buff_len;
timer_thread = SDL_CreateThread(refresh_video_timer,
NULL,
NULL);
do {
SDL_WaitEvent(&event);
if(event.type==REFRESH_EVENT){
if((video_pos + yuv_frame_len) > video_end){
remain_len = video_end - video_pos;
if(remain_len && !blank_space_len) {
//copy data to header of buffer
memcpy(video_buf, video_pos, remain_len);
blank_space_len = BLOCK_SIZE - remain_len;
video_pos = video_buf;
video_end = video_buf + remain_len;
}
//at the end of buffer, so rotate to header of buffer
if(video_end == (video_buf + BLOCK_SIZE)){
video_pos = video_buf;
video_end = video_buf;
blank_space_len = BLOCK_SIZE;
}
//read data from yuv file to buffer
if((video_buff_len = fread(video_end, 1, blank_space_len, video_fd)) <= 0){
fprintf(stderr, "eof, exit thread!");
thread_exit = 1;
continue;// to wait event for exiting
}
//reset video_end
video_end += video_buff_len;
blank_space_len -= video_buff_len;
printf("not enought data: pos:%p, video_end:%p, blank_space_len:%d\n", video_pos, video_end, blank_space_len);
}
SDL_UpdateTexture( texture, NULL, video_pos, video_width);
//FIX: If window is resize
rect.x = 0;
rect.y = 0;
rect.w = w_width;
rect.h = w_height;
SDL_RenderClear( renderer );
SDL_RenderCopy( renderer, texture, NULL, &rect);
SDL_RenderPresent( renderer );
printf("not enought data: pos:%p, video_end:%p, blank_space_len:%d\n", video_pos, video_end, blank_space_len);
video_pos += yuv_frame_len;
}else if(event.type==SDL_WINDOWEVENT){
//If Resize
SDL_GetWindowSize(win, &w_width, &w_height);
}else if(event.type==SDL_QUIT){
thread_exit=1;
}else if(event.type==QUIT_EVENT){
break;
}
}while ( 1 );
__FAIL:
//close file
if(video_fd){
fclose(video_fd);
}
SDL_Quit();
return 0;
}
SDL播放音频数据
相关的处理流程
声卡需要向用户要数据并非主动的推给声卡的
数据量的大小由音频的参数的确定的
相关的API
// 打开音频数据
SDL_OpenAudio();
// 关闭音频数据
SDL_CloseAudio()
// 暂停/播放
SDL_PauseAudio();
// 混合音频数据
SDL_MixAudio()