(4)SDL渲染开发

news2024/12/29 8:41:48

SDL渲染开发

  • 1. SDL简介
  • 2. 环境搭建
    • 2.1 windows
    • 2.2 Linux
  • 3. SDL子系统
    • 3.1 eg1 创建窗口
  • 4:SDL显示
    • 4.1 显示图片
    • 4.2 绘制长方形显示
  • 5. SDL事件
  • 6. SDL多线程
    • 6.1 接口演示
    • 6.2 yuv显示
    • 6.3 pcm播放

1. SDL简介

  SDL(Simple DirectMedia Layer)是一个跨平台开发库,旨在通过 OpenGL 和 Direct3D 提供对音频、键盘、鼠标、游戏杆和图形硬件的低级访问。它被视频播放软件、模拟器和热门游戏(包括Valve的获奖产品和许多Humble Bundle游戏)所使用。SDL 正式支持 Windows、macOS、Linux、iOS 和 Android。其他平台的支持可以在源代码中找到。SDL 是用 C 编写的,可以与 C++ 直接配合使用,并且还有几种其他语言的绑定,包括 C# 和 Python。
  SDL 2.0 在zlib 许可 下发布。此许可允许您在任何软件中自由使用 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子系统

SDL将功能分成下列数个子系统(subsystem):
◼ SDL_INIT_TIMER:定时器
◼ SDL_INIT_AUDIO:音频
◼ SDL_INIT_VIDEO:视频
◼ SDL_INIT_JOYSTICK:摇杆
◼ SDL_INIT_HAPTIC:触摸屏
◼ SDL_INIT_GAMECONTROLLER:游戏控制器
◼ SDL_INIT_EVENTS:事件
◼ SDL_INIT_EVERYTHING:包含上述所有选项

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显示

◼ SDL_Init():初始化SDL系统
◼ SDL_CreateWindow():创建窗口SDL_Window
◼ SDL_CreateRenderer():创建渲染器SDL_Renderer
◼ SDL_CreateTexture():创建纹理SDL_Texture
◼ SDL_UpdateTexture():设置纹理的数据
◼ SDL_RenderCopy():将纹理的数据拷贝给渲染器
◼ SDL_RenderPresent():显示
◼ SDL_Delay():工具函数,用于延时
◼ SDL_Quit():退出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 绘制长方形显示

cmake跟之前一样,注意把sdl.dll放到可执行文件同级目录即可
这里我注释了纹理相关代码,这样也是可以的
SDL_Texture 与SDL_Renderer相似,也是一种缓冲区。只不过它存放的不是真正的像素数据,而是存放的图像的描述信息。当渲染纹理时,SDL以这些描述信息为数据,底层通过OpenGL、D3D 或 Metal操作GPU,最终绘制出与SDL_Renderer一样的图形,且效率更高(因为它是GPU硬件计算的)。

存储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事件

◼ 函数
• SDL_WaitEvent():等待一个事件
• SDL_PushEvent():发送一个事件
• SDL_PumpEvents():将硬件设备产生的事件放入事件队列,用于读取事件,在调用该函数之前,必须调 用SDL_PumpEvents搜集键盘等事件
• SDL_PeepEvents():从事件队列提取一个事件
◼ 数据结构
• SDL_Event:代表一个事件

看代码:显示鼠标的坐标,捕获键盘事件
注意,我这里试了case SDLK_q只能检测到大写字母,而case 'w’则忽略大小写了

#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方法。顾名思义,该方法会阻塞当前调用的线程,直到取出一个消息为止。

SDL_PollEvent:
SDL_PollEvent函数是一个非阻塞函数,它会不断地检查 SDL 事件队列,直到有事件产生。如果有事件到达,它将将其从队列中取出并返回。如果没有事件,SDL_PollEvent会返回0。这种方式可以在事件到达时立即处理,而不需要等待。但是,如果程序需要等待某个特定事件,这种方法可能不够灵活。

SDL_WaitEvent:
SDL_WaitEvent函数是一个阻塞函数,它会等待 SDL 事件队列中的事件。直到有事件到达时,它才会返回。与SDL_PollEvent不同,SDL_WaitEvent会在没有事件时阻塞等待,直到事件到达或超时。这种方式在需要等待特定事件(例如用户输入)时非常有用。但是,如果事件处理程序在等待事件时需要执行其他任务,程序可能会变得不够高效。

6. SDL多线程

6.1 接口演示

◼ SDL线程创建:SDL_CreateThread
◼ SDL线程等待:SDL_WaitThead
◼ SDL互斥锁:SDL_CreateMutex/SDL_DestroyMutex
◼ SDL锁定互斥:SDL_LockMutex/SDL_UnlockMutex
◼ SDL条件变量(信号量):SDL_CreateCond/SDL_DestoryCond
◼ SDL条件变量(信号量)等待/通知:SDL_CondWait/SDL_CondSingal

#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显示

#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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1829517.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据库概述1

数据&#xff1a;描述事物的符号记录称为数据&#xff1b; 包括数字、图片、音频等&#xff1b; 数据库&#xff1a;长期储存在计算机内有组织、可共享的大量数据的集合&#xff1b;数据库中的数据按照一定的数据模型组织、描述和存储&#xff0c;具有较小的数据冗余、较高的数…

【支持向量机】问题梳理

学完支持向量机后我有些地方不太清楚&#xff0c;故做如下梳理&#xff1a; 1.为什么支持向量机模型认为一个点划分正确的标志是y(wxb)>1呢&#xff0c;为什么不是y(wxb)>0&#xff0c;比如y为1&#xff0c;wxb为0.5&#xff0c;大于0&#xff0c;则预测正确。 2.所以意思…

[Python]Anaconda相关命令

环境操作相关命令 查看所有环境 conda env list创建环境 conda create --name cahttts python3.10激活环境 conda activate cahttts安装依赖文件 pip install -r requirements.txt查看GPU型号 nvidia-smi -LGPU 0: NVIDIA A10 (UUID: GPU-9f1fc9cf-582a-25ac-849c-2f77343…

FFmpeg编解码的那些事(3)-视频硬解码的基础知识

目录 前言&#xff1a; 1.iso/os x平台 2.windows平台 3.linux平台 4.Tips&#xff1a; 5.结论&#xff1a; 前言&#xff1a; 视频硬解码的过程就是把视频提取成图片变显示出来&#xff0c;就是播放器播放视频的过程&#xff0c;就可以理解为解码的过程。 在不同的系统…

R进阶使用技巧

Introduction 分享一些R进阶使用的技巧&#xff0c;相当于是之前写的R语言学习的实践和总结了。 Online slide: https://asa-blog.netlify.app/R_tips_for_advanced_use_byAsa/R_tips.html 下载slide和相关的各种test文件: https://asa-blog.netlify.app/R_tips_for_advanced…

【论文阅读】AttnDreamBooth | 面向文本对齐的个性化图片生成

文章目录 1 动机2 方法3 实验 1 动机 使用灵活的文本控制可以实现一些特定的概念的注入从而实现个性化的图片生成。 最经典的比如一些好玩的动漫人物的概念&#xff0c;SD大模型本身是不知道这些概念的&#xff0c;但是通过概念注入是可以实现的从而生成对应的动漫人物 两个…

element-plus表单组件之自动补全组件el-autocomplete和级联选择器组件el-cascader

el-autocomplete 自动补全组件 自补全组件的功能和可以根据输入过滤的el-select组件有些类似。 fetch-suggestions 根据输入框的输入获取建议的内容&#xff0c;其接受值是一个函数&#xff0c;有2个参数&#xff0c;querystring:输入的内容&#xff0c;callback内置函数&…

爱心代码来喽

今天给大家分享一个爱心代码&#xff0c;送给我的粉丝们。愿你们天天开心&#xff0c;事事顺利&#xff0c;学业和事业有成。 下面是运行代码&#xff1a; #include<stdio.h> #include<Windows.h> int main() { system(" color 0c"); printf(&q…

代码随想录-Day32

122. 买卖股票的最佳时机 II 给你一个整数数组 prices &#xff0c;其中 prices[i] 表示某支股票第 i 天的价格。 在每一天&#xff0c;你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买&#xff0c;然后在 同一天 出售。 返回 你能…

【算法专题--链表】删除排序链表中的重复元素II -- 高频面试题(图文详解,小白一看就懂!!)

目录 一、前言 二、题目描述 三、解题方法 ⭐ 双指针 -- 采用 哨兵位头节点 &#x1f95d; 什么是哨兵位头节点&#xff1f; &#x1f34d; 解题思路 &#x1f34d; 案例图解 四、总结与提炼 五、共勉 一、前言 删除排序链表中的重复元素II元素这道题&#xff0c…

MySQL之优化服务器设置(五)

优化服务器设置 高级InnoDB设置 innodb_old_blocks_time InnoDB有两段缓冲池LRU(最近最少使用)链表&#xff0c;设计目的是防止换出长期很多次的页面。像mysqldump产生的这种一次性的(大)查询&#xff0c;通常会读取页面到缓冲池的LRU列表&#xff0c;从中读取需要的行&…

ubuntu中安装docker并换源

使用 Ubuntu 的仓库安装 Docker sudo apt update现在&#xff0c;你可以使用以下命令在 Ubuntu 中安装 Docker 以及 Docker Compose&#xff1a; sudo apt install docker.io docker-composeDocker 包被命名为 docker.io&#xff0c;因为在 Docker 出现之前就已经存在一个名为…

C++ virtual public(虚继承类)

这个"virtual"有什么作用&#xff1f; 由于C支持多重继承&#xff0c;所以对于一个派生类中有几个直接父类&#xff0c;而几个直接父类中有几个可能分别继承自某一个基类&#xff08;就是父类的父类&#xff09;&#xff0c;这样在构造最终派生类时&#xff0c;会出现…

Nginx + Tomcat 负载均衡、动静分离

前言 Tomcat简介 最初是由Sun的软件构架师詹姆斯邓肯戴维森开发 安装Tomcat后&#xff0c;安装路径下面的目录和文件&#xff0c;是使用或者配置Tomcat的重要文件 Nginx 应用 Nginx是一款非常优秀的HTTP服务器软件 &#xff08;1&#xff09;支持高达50 000个并发连接数的响应…

单目标应用:基于三角拓扑聚合优化算法TTAO的微电网优化(MATLAB代码)

一、微电网模型介绍 微电网多目标优化调度模型简介_vmgpqv-CSDN博客 参考文献&#xff1a; [1]李兴莘,张靖,何宇,等.基于改进粒子群算法的微电网多目标优化调度[J].电力科学与工程, 2021, 37(3):7 二、三角拓扑聚合优化算法求解微电网 2.1算法简介 三角拓扑聚合优化算法&…

毕业了校园卡怎么改套餐?

毕业了校园卡怎么改套餐&#xff1f; 毕业生校园卡99元套餐变更8元保号套餐教程 学弟学妹们恭喜毕业呀&#x1f393; 校园卡绑定了好多东西注销不掉又不想交高额月租的看过来。 今天一招教你更改校园卡套餐。 中国移动/电信/联通App 打开App&#xff0c;在首页右上角点击人工…

LVS负载均衡集群企业级应用实战-LVS-DR(四)

目录 LVS-DR 一. 环境准备 二. 对虚拟主机操作 三. 对真实服务器操作 四. 打开网页测试 LVS-DR 一. 环境准备 三台虚拟机&#xff0c;都要在同一网段内&#xff0c;统一关闭防火墙和selinux&#xff0c;时间同步&#xff0c;配置好YUM源。系统用centos和roucky都行。 主…

【Netty】ByteBuffer原理与使用

Buffer则用来缓冲读写数据&#xff0c;常见的buffer有&#xff1a; ByteBuffer MappedByBuffer DirectByteBuffer HeapByteBuffer hortBuffer IntBuffer LongBuffer FloatBuffer DoubleBuffer CharBuffer 有一个普通文本文件data.txt,内容为&#xff1a; 1234567890a…

CSS实现前端小组件随笔

一.CSSJS实现打字机效果 1.1实现效果 1.2HTML部分 <span class"bottom-text"></span> 1.3CSS部分 .bottom-text {font-fanmily: "fangsong";display:inline-block;position:relative;font-size:20px;height:20px;inline-height:20px;color…

【Qt项目专栏】贪吃蛇小游戏1.0

博客主页&#xff1a;Duck Bro 博客主页系列专栏&#xff1a;Qt 专栏关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ 贪吃蛇小游戏1.0 项目编号&#xff1a;01 文章目录 贪吃蛇小游戏1.0一…