(4)SDL渲染开发

(4)SDL渲染开发SDL SimpleDirect 是一个跨平台开发库 旨在通过 OpenGL 和 Direct3D 提供对音频 键盘 鼠标 游戏杆和图形硬件的低级访问

大家好,欢迎来到IT知识分享网。

1. SDL简介

2. 环境搭建

https://github.com/libsdl-org/SDL/releases

2.1 windows

  1. 下载SDL2-devel-2.30.3-VC.zip(版本自己选即可) (我使用qt+cmake所以下面这些没用到)
  2. 添加环境变量
  3. 打开VS创建项目,进入VC++目录设置包含目录和库目录
  4. 进入链接器的输入,添加附加依赖项(库目录下的库)

2.2 Linux

  1. 下载 SDL2-2.30.3.tar.gz
  2. 解压然后执行命令
    ./configure (--prefix=...可以选择放置的目录) make sudo make install 
  3. 如果出现Could not initialize SDL – No available video device(Did you set the DISPLAY variable?)需要安装x11库文件
    sudo apt-get install libx11-dev sudo apt-get install xorg-dev 

3. SDL子系统

3.1 eg1 创建窗口

直接看代码, 创建一个持续5s的窗口

  • CMakeLists.txt
cmake_minimum_required(VERSION 3.5) project(01-sdl-basic LANGUAGES C) # 下面两个宏变量代表的是一个地址,在这里,为了记忆,都写在这里了 include_directories(${CMAKE_SOURCE_DIR}/SDL2-2.30.3/include) link_directories(${PROJECT_SOURCE_DIR}/SDL2-2.30.3/lib/x64) add_executable(01-sdl-basic main.c) target_link_libraries(01-sdl-basic SDL2 ) 
  • main.c
#include <stdio.h> #include <SDL.h> #undef main int main() { 
    SDL_Init(SDL_INIT_VIDEO); //初始化SDL为视频子系统 SDL_Window* win = SDL_CreateWindow("my sdl", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 480, 360, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE); //创建窗口 if (!win) { 
    SDL_Log("SDL_CreateWindow error:", SDL_GetError()); SDL_Quit(); return 1; } SDL_Delay(5000); SDL_DestroyWindow(win);//销毁窗口 SDL_Quit(); //释放资源 return 0; } 

4:SDL显示

4.1 显示图片

#include <stdio.h> #include <SDL.h> #undef main //一定要写啊 int main() { 
    if (SDL_Init(SDL_INIT_VIDEO) != 0) { 
    SDL_Log("SDL_Init error", SDL_GetError()); return 1; } SDL_Window* win = SDL_CreateWindow("lena!", SDL_WINDOWPOS_CENTERED, //居中 SDL_WINDOWPOS_CENTERED, 640, 480, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE); if (win == NULL) { 
    SDL_Log("SDL_CreateWindow error", SDL_GetError()); SDL_Quit(); return 1; } // 创建一个将绘制到窗口的渲染器,-1 指定我们要加载任何一个 // 视频驱动程序支持我们传递的标志 // 标志: SDL_RENDERER_ACCELERATED:我们想使用硬件加速渲染 // SDL_RENDERER_PRESENTVSYNC:我们希望渲染器的当前功能(更新屏幕)是与显示器的刷新率同步 SDL_Renderer *ren = SDL_CreateRenderer(win, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); if (ren == NULL) { 
    SDL_Log("SDL_CreateRenderer error", SDL_GetError()); SDL_DestroyWindow(win); SDL_Quit(); return 1; } const char* imagePath = "./LenaRGB.bmp"; SDL_Surface *bmp = SDL_LoadBMP(imagePath); if (bmp == NULL){ 
    SDL_DestroyRenderer(ren); SDL_DestroyWindow(win); SDL_Log("SDL_LoadBMP error", SDL_GetError()); SDL_Quit(); return 1; } //To use a hardware accelerated texture for rendering we can create one from //the surface we loaded SDL_Texture *tex = SDL_CreateTextureFromSurface(ren, bmp); //We no longer need the surface SDL_FreeSurface(bmp); if (tex == NULL){ 
    SDL_DestroyRenderer(ren); SDL_DestroyWindow(win); SDL_Log("SDL_CreateTextureFromSurface error", SDL_GetError()); SDL_Quit(); return 1; } //A sleepy rendering loop, wait for 3 seconds and render and present the screen each time for (int i = 0; i < 3; ++i){ 
    //First clear the renderer SDL_RenderClear(ren); //Draw the texture SDL_RenderCopy(ren, tex, NULL, NULL); //Update the screen SDL_RenderPresent(ren); //Take a quick break after all that hard work SDL_Delay(1000); } //Clean up our objects and quit SDL_DestroyTexture(tex); SDL_DestroyRenderer(ren); SDL_DestroyWindow(win); SDL_Quit(); return 0; } 

4.2 绘制长方形显示

存储RGB和存储纹理的区别:比如一个从左到右由红色渐变到蓝色的矩形,用存储RGB的话就需要把矩形中每个点的具体颜色值存储下来;而纹理只是一些描述信息,比如记录了矩形的大小、起始颜色、终止颜色等信息,显卡可以通过这些信息推算出矩形块的详细信息。所以相对于存储RGB而已,存储纹理占用的内存要少的多。

#include <iostream> #include <SDL.h> using namespace std; #undef main int main() { 
    if (SDL_Init(SDL_INIT_VIDEO) != 0) { 
    SDL_Log("SDL_Init error"); return 1; } SDL_Window *win = SDL_CreateWindow("长方形", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 640, 480, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE); if (win == nullptr) { 
    SDL_Log("SDL_CreateWindow error"); SDL_Quit(); return 1; } SDL_Renderer* ren = SDL_CreateRenderer(win, -1, 0); //基于窗口创建渲染器 if (ren == nullptr) { 
    SDL_Log("SDL_CreateRenderer error"); SDL_DestroyWindow(win); SDL_Quit(); return 1; } SDL_Texture* texture = SDL_CreateTexture(ren, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, 640, 480); if (texture == nullptr) { 
    SDL_DestroyRenderer(ren); SDL_DestroyWindow(win); SDL_Quit(); return 1; } SDL_Rect rect{ 
   }; rect.w = 50; rect.h = 50; int cnt = 0; while (true) { 
    rect.x = rand() % 600; rect.y = rand() % 400; //SDL_SetRenderTarget(ren, texture); //设置渲染器为纹理目标 SDL_SetRenderDrawColor(ren, 0, 0, 0, 255); //设置黑色背景 SDL_RenderClear(ren); //清屏为我们设置的颜色 SDL_RenderDrawRect(ren, &rect); //绘制长方形 SDL_SetRenderDrawColor(ren, 255, 255, 255, 255); //白色长方形 SDL_RenderFillRect(ren, &rect); // SDL_SetRenderTarget(ren, nullptr); //恢复默认, 渲染目标为窗口 // SDL_RenderCopy(ren, texture, nullptr, nullptr); //拷贝到cpu SDL_RenderPresent(ren); //输出到窗口 SDL_Delay(300); if (cnt++ > 30) { 
    break; } } SDL_DestroyTexture(texture); SDL_DestroyRenderer(ren); SDL_DestroyWindow(win); SDL_Quit(); return 0; } 

5. SDL事件

#include <iostream> #include <SDL.h> using namespace std; #define FF_SDL_EVENT SDL_USEREVENT+1 #undef main int main() { 
    if (SDL_Init(SDL_INIT_VIDEO) != 0) { 
    SDL_Log("SDL_Init error"); return 1; } SDL_Window *win = SDL_CreateWindow("事件", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 640, 480, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE); if (win == nullptr) { 
    SDL_Log("SDL_CreateWindow error"); SDL_Quit(); return 1; } SDL_Renderer* ren = SDL_CreateRenderer(win, -1, 0); //基于窗口创建渲染器 if (ren == nullptr) { 
    SDL_Log("SDL_CreateRenderer error"); SDL_DestroyWindow(win); SDL_Quit(); return 1; } SDL_SetRenderDrawColor(ren, 0, 0, 255, 255); //设置蓝色背景 SDL_RenderClear(ren); //清屏为我们设置的颜色 SDL_RenderPresent(ren); //输出到窗口 SDL_Event event; bool exit = false; while (!exit) { 
    while(SDL_PollEvent(&event)) { 
    if (event.type == SDL_KEYUP) { 
    switch (event.key.keysym.sym) { 
    case 'w': printf("key w down=============\n"); SDL_Event event_w; event_w.type = FF_SDL_EVENT; SDL_PushEvent(&event_w); break; case SDLK_q: printf("key q down=============\n"); SDL_Event event_q; event_q.type = FF_SDL_EVENT; SDL_PushEvent(&event_q); break; case SDLK_ESCAPE: printf("================="); SDL_Event event_esc; event_esc.type = FF_SDL_EVENT; SDL_PushEvent(&event_esc); break; default: printf("key down 0x%x\n", event.key.keysym.sym); break; } } else if (event.type == SDL_MOUSEMOTION) { 
    //printf("mouse movie (%d,%d)\n", event.button.x, event.button.y); } else if (event.type == FF_SDL_EVENT) { 
    exit = true; } } } SDL_DestroyRenderer(ren); SDL_DestroyWindow(win); SDL_Quit(); return 0; } 

SDL_PollEvent和SDL_WaitEvent区别
除了SDL_PollEvent方法去取消息外,还有SDL_WaitEvent方法。顾名思义,该方法会阻塞当前调用的线程,直到取出一个消息为止。

6. SDL多线程

6.1 接口演示

#include <stdio.h> #include <unistd.h> #include <SDL.h> SDL_mutex* s_lock = NULL; SDL_cond* s_cond = NULL; int thread_work(void *arg) { 
    SDL_LockMutex(s_lock); printf("<=============thread working sleep\n"); flushall(); sleep(10); //用来测试获取锁 printf("<=============thread working wait\n"); //释放锁,并等待signal SDL_CondWait(s_cond, s_lock); //另一个线程(1)发送signal和(2)释放lock后,这个函数退出 printf("<===========thread_work receive signal, continue to do ~_~!!!\n"); printf("<===========thread_work end\n"); SDL_UnlockMutex(s_lock); return 0; } #undef main int main() { 
    s_lock = SDL_CreateMutex(); s_cond = SDL_CreateCond(); SDL_Thread *t = SDL_CreateThread(thread_work, "hello world", NULL); if (!t) { 
    SDL_Log("error: ", SDL_GetError()); return -1; } for (int i = 0; i < 2; ++i) { 
    sleep(2); printf("main execute------>\n"); } printf("main SDL_LockMutex(s_lock) before ====================>\n"); SDL_LockMutex(s_lock); // 获取锁,但是子线程还拿着锁 printf("main ready send signal====================>\n"); printf("main SDL_CondSignal(s_cond) before ====================>\n"); SDL_CondSignal(s_cond); // 发送信号,唤醒等待的线程 printf("main SDL_CondSignal(s_cond) after ====================>\n"); sleep(10); SDL_UnlockMutex(s_lock);// 释放锁,让其他线程可以拿到锁 printf("main SDL_UnlockMutex(s_lock) after ====================>\n"); SDL_WaitThread(t, NULL); SDL_DestroyMutex(s_lock); SDL_DestroyCond(s_cond); return 0; } 

6.2 yuv显示

(4)SDL渲染开发

#include <stdio.h>
#include <SDL.h>

#define REFRESH_EVENT (SDL_USEREVENT + 1) //请求刷新事件
#define QUIT_EVENT (SDL_USEREVENT + 2) //请求刷新事件

int thread_exit_flag = 0;
static int yuv_width = 320;
static int yuv_height = 240;

int refresh_video_timer(void* data)
{ 
    
    while (!thread_exit_flag) { 
    
        SDL_Event event;
        event.type = REFRESH_EVENT;
        SDL_PushEvent(&event);
        SDL_Delay(40);
    }

    thread_exit_flag = 0;

    SDL_Event event;
    event.type = QUIT_EVENT;
    SDL_PushEvent(&event);
    return 0;
}

#undef main
int main()
{ 
    
    //初始化SDL
    if (SDL_Init(SDL_INIT_VIDEO) != 0) { 
    
        SDL_Log("SDL_Init error");
        return -1;
    }

    // 创建窗口
    SDL_Window* win = SDL_CreateWindow("yuv播放器", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 320, 240, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
    if (!win) { 
    
        SDL_Log("SDL_CreateWindow error");
        goto FLAG;
    }

    //创建渲染器
    SDL_Renderer* ren = SDL_CreateRenderer(win, -1, 0);
    if (!ren) { 
    
        SDL_Log("SDL_CreateRenderer error");
        goto FLAG;
    }

    // 基于渲染器创建纹理
    SDL_Texture* texture = SDL_CreateTexture(ren, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, 320, 240);
    if (!texture) { 
    
        SDL_Log("SDL_CreateTexture error");
        goto FLAG;
    }

    //分配空间
    // 我们测试的文件是YUV420P格式
    uint32_t y_frame_len = yuv_width * yuv_height;
    uint32_t u_frame_len = yuv_width * yuv_height / 4;
    uint32_t v_frame_len = yuv_width * yuv_height / 4;
    uint32_t yuv_frame_len = y_frame_len + u_frame_len + v_frame_len;
    uint8_t* video_buf = (uint8_t*)malloc(yuv_frame_len);
    if (!video_buf) { 
    
        SDL_Log("分配空间失败");
        goto FLAG;
    }

    //打开yuv文件
    FILE* fd = fopen("./yuv420p_320x240.yuv", "rb");
    if (!fd) { 
    
        goto FLAG; //C和C++的goto不太一样
    }

    // 创建刷新线程
    SDL_Thread* t = SDL_CreateThread(refresh_video_timer, NULL, NULL);

    SDL_Event event;                            // 事件
    SDL_Rect rect;                              // 矩形
    int win_width = yuv_width, win_height = yuv_height;
    while (1) { 
    
        // 接收事件
        SDL_WaitEvent(&event);

        if (event.type == REFRESH_EVENT) { 
    
            size_t video_buff_len = fread(video_buf, 1, yuv_frame_len, fd);
            if (video_buff_len <= 0) { 
    
                SDL_Log("从文件读取数据失败");
                goto FLAG;
            }

            // 设置纹理的数据
            SDL_UpdateTexture(texture, NULL, video_buf, yuv_width);

            // 显示的区域
            rect.x = 0;
            rect.y = 0;
            float w_ratio = win_width * 1.0 /yuv_width;
            float h_ratio = win_height * 1.0 /yuv_height;
            rect.w = 320 * w_ratio;
            rect.h = 240 * h_ratio;
            // 清除当前显示
            SDL_RenderClear(ren);
            // 将纹理的数据拷贝给渲染器
            SDL_RenderCopy(ren, texture, NULL, &rect);
            // 显示
            SDL_RenderPresent(ren);
        } else if (event.type == SDL_WINDOWEVENT) { 
    
            //If Resize
            SDL_GetWindowSize(win, &win_width, &win_height);
            printf("SDL_WINDOWEVENT win_width:%d, win_height:%d\n",win_width,
                   win_height );

        } else if (event.type == SDL_QUIT) { 
    
            thread_exit_flag = 1;
        } else if (event.type == QUIT_EVENT) { 
    
            break;
        }
    }

FLAG:
    thread_exit_flag = 1;      // 保证线程能够退出
    // 释放资源
    if(t)
        SDL_WaitThread(t, NULL); // 等待线程退出
    if(video_buf)
        free(video_buf);
    if(fd)
        fclose(fd);
    if(texture)
        SDL_DestroyTexture(texture);
    if(ren)
        SDL_DestroyRenderer(ren);
    if(win)
        SDL_DestroyWindow(win);

    SDL_Quit();
    return 0;
}

6.3 pcm播放

打开音频设备

int SDLCALL SDL_OpenAudio(SDL_AudioSpec * desired, 
SDL_AudioSpec * obtained); 
// desired:期望的参数。
// obtained:实际音频设备的参数,一般情况下设置为NULL即可。

主要用到的结构体SDL_AudioSpec:

 typedef struct SDL_AudioSpec { 
    
	int freq; // 音频采样率
	SDL_AudioFormat format; // 音频数据格式
	Uint8 channels; // 声道数: 1 单声道, 2 立体声
	Uint8 silence; // 设置静音的值,因为声音采样是有符号的,所以0当然就是这个值
	Uint16 samples; // 音频缓冲区中的采样个数,要求必须是2的n次
	Uint16 padding; // 考虑到兼容性的一个参数
	Uint32 size; // 音频缓冲区的大小,以字节为单位
	SDL_AudioCallback callback; // 填充音频缓冲区的回调函数
	void *userdata; // 用户自定义的数据
} SDL_AudioSpec;

SDL_AudioCallback

// userdata:SDL_AudioSpec结构中的用户自定义数据,一般情况下可以不用。
// stream:该指针指向需要填充的音频缓冲区。
// len:音频缓冲区的大小(以字节为单位)1024*2*2。
void (SDLCALL * SDL_AudioCallback) (void *userdata, Uint8 *stream, int len);

播放音频数据

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

代码示例

#include <stdio.h>
#include <SDL.h>

// 每次读取2帧数据, 以1024个采样点一帧 2通道 16bit采样点为例
#define PCM_BUFFER_SIZE (1024*2*2*2)

// 目前读取的位置
static Uint8 *s_audio_pos = NULL;
// 缓存结束位置
static Uint8 *s_audio_end = NULL;

//音频设备回调函数
void handler_audio_pcm(void *udata, Uint8 *stream, int len)
{ 
    
    SDL_memset(stream, 0, len);

    if(s_audio_pos >= s_audio_end) // 数据读取完毕
    { 
    
        return;
    }

    // 数据够了就读预设长度,数据不够就只读部分(不够的时候剩多少就读取多少)
    int remain_buffer_len = s_audio_end - s_audio_pos;
    len = (len < remain_buffer_len) ? len : remain_buffer_len;
    // 拷贝数据到stream并调整音量
    SDL_MixAudio(stream, s_audio_pos, len, SDL_MIX_MAXVOLUME/8);
    printf("len = %d\n", len);
    s_audio_pos += len;  // 移动缓存指针
}

#undef main
int main()
{ 
    
    if (SDL_Init(SDL_INIT_AUDIO)) { 
    
        SDL_Log("sdl init err");
        return -1;
    }

    // 打开pcm
    FILE* audio_fd = fopen("./44100_16bit_2ch.pcm", "rb");
    if (!audio_fd) { 
    
        SDL_Log("fopen err");
        goto FLAG;
    }

    uint8_t* audio_buf = (uint8_t*)malloc(PCM_BUFFER_SIZE);

    // 音频参数设置
    SDL_AudioSpec spec;
    spec.freq = 44100;
    spec.format = AUDIO_S16SYS;
    spec.channels = 2;
    spec.samples = 1024;
    spec.callback = handler_audio_pcm;
    spec.userdata = NULL;

    //打开音频设备
    if(SDL_OpenAudio(&spec, NULL))
    { 
    
        fprintf(stderr, "Failed to open audio device, %s\n", SDL_GetError());
        goto FLAG;
    }

    //play audio
    SDL_PauseAudio(0);

    int data_count = 0;
    // 每次缓存的长度
    size_t read_buffer_len = 0;
    while(1)
    { 
    
        // 从文件读取PCM数据
        read_buffer_len = fread(audio_buf, 1, PCM_BUFFER_SIZE, audio_fd);
        if(read_buffer_len == 0)
        { 
    
            break;
        }
        data_count += read_buffer_len; // 统计读取的数据总字节数
        printf("now playing %10d bytes data.\n",data_count);
        s_audio_end = audio_buf + read_buffer_len;    // 更新buffer的结束位置
        s_audio_pos = audio_buf;  // 更新buffer的起始位置
        //the main thread wait for a moment
        while(s_audio_pos < s_audio_end)
        { 
    
            SDL_Delay(10);  // 等待PCM数据消耗
        }
    }
    printf("play PCM finish\n");
    // 关闭音频设备
    SDL_CloseAudio();

FLAG:
	if (audio_buf) { 
    
		free(audio_buf);
	}
    if (audio_fd) { 
    
        fclose(audio_fd);
    }

    SDL_Quit();
    return 0;
}

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/127807.html

(0)
上一篇 2025-09-04 22:20
下一篇 2025-09-04 22:26

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信