当前位置: 首页 > news >正文

SDL音视频渲染

01-SDL简介

官网:https://www.libsdl.org/
文档:http://wiki.libsdl.org/Introduction

SDL(Simple DirectMedia Layer)是一套开放源代码的跨平台多媒体开发库,使用C语言写成。SDL提供了数种控制图像、声音、输出入的函数,让开发者只要用相同或是相似的代码就可以开发出跨多个平台(Linux、Windows、Mac OS X等)的应用软件。目前SDL多用于开发游戏、模拟器、媒体播放器等多媒体应用领域。

02-Windows环境搭建

下载地址:https://www.libsdl.org/download-2.0.php

先直接下载dll和lib使用

MinGW:Minimalist GNU for Windows

案例

将SDL2-2.0.10拷贝到工程目录
将 SDL2-2.0.10\lib\x86 下的 SDL2.dll 拷贝到工程目录

.prcwin32 {INCLUDEPATH += $$PWD/SDL2-2.0.10/includeLIBS += $$PWD/SDL2-2.0.10/lib/x86/SDL2.lib
}
main.c#include <stdio.h>#include <SDL.h>#undef mainint main()
{printf("Hello World!\n");SDL_Window *window = NULL;      // 声明窗口SDL_Init(SDL_INIT_VIDEO);       // 初始化SDL// 创建SDL Windowwindow = SDL_CreateWindow("Basic Window",           // 标题SDL_WINDOWPOS_UNDEFINED,  // xSDL_WINDOWPOS_UNDEFINED,  // y640,                      // 宽480,                      // 高SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);// 类型if(!window) // 检测是否创建成功{printf("创建 window 失败, err:%s\n", SDL_GetError());return 1;}SDL_Delay(10000);  // 延迟10000msSDL_DestroyWindow(window); // 销毁窗口SDL_Quit(); // 释放资源return 0;
}

03-Linux环境搭建

下载地址:https://www.libsdl.org/download-2.0.php

  1. 下载SDL源码库,SDL2-2.0.10.tar.gz
  2. 解压,然后依次执行命令
    ./configure
    make
    sudo make install.
  3. 如果出现Could not initialize SDL - No available video device(Did you set the DISPLAY variable?)错误
    说明系统中没有安装x11的库文件,因此编译出来的SDL库实际上不能用。
    下载安装
    sudo apt-get install libx11-dev
    sudo apt-get install xorg-dev

04-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:包含上述所有选项

05-SDL Window显示: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系统

06-SDL Windows显示:SDL数据结构简介

◼ SDL_Window 代表了一个“窗口”
◼ SDL_Renderer 代表了一个“渲染器”
◼ SDL_Texture 代表了一个“纹理”
◼ SDL_Rect 一个简单的矩形结构

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

案例

新建工程 02-sdl-window
将SDL2-2.0.10拷贝到工程目录
将 SDL2-2.0.10\lib\x86 下的 SDL2.dll 拷贝到工程目录

.prcwin32 {INCLUDEPATH += $$PWD/SDL2-2.0.10/includeLIBS += $$PWD/SDL2-2.0.10/lib/x86/SDL2.lib
}
main.c#include <stdio.h>
#include <SDL.h>#undef mainint main()
{int run = 1;SDL_Window *window = NULL;SDL_Renderer *renderer = NULL;SDL_Texture *texture = NULL;SDL_Rect rect; // 长方形,原点在左上角rect.w = 50;    //方块大小rect.h = 50;SDL_Init(SDL_INIT_VIDEO);//初始化函数,可以确定希望激活的子系统window = SDL_CreateWindow("2 Window",SDL_WINDOWPOS_UNDEFINED,SDL_WINDOWPOS_UNDEFINED,640,480,SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);// 创建窗口if (!window){return -1;}renderer = SDL_CreateRenderer(window, -1, 0);//基于窗口创建渲染器if (!renderer){return -1;}texture = SDL_CreateTexture(renderer,SDL_PIXELFORMAT_RGBA8888,SDL_TEXTUREACCESS_TARGET,640,480); //创建纹理if (!texture){return -1;}int show_count = 0;while (run){rect.x = rand() % 600;rect.y = rand() % 400;SDL_SetRenderTarget(renderer, texture); // 设置渲染目标为纹理SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255); // 纹理背景为黑色SDL_RenderClear(renderer); //清屏SDL_RenderDrawRect(renderer, &rect); //绘制一个长方形SDL_SetRenderDrawColor(renderer, 0, 255, 255, 255); //长方形为白色SDL_RenderFillRect(renderer, &rect);SDL_SetRenderTarget(renderer, NULL); //恢复默认,渲染目标为窗口SDL_RenderCopy(renderer, texture, NULL, NULL); //拷贝纹理到CPUSDL_RenderPresent(renderer); //输出到目标窗口上SDL_Delay(300);if(show_count++ > 30){run = 0;        // 不跑了}}SDL_DestroyTexture(texture);SDL_DestroyRenderer(renderer);SDL_DestroyWindow(window); //销毁窗口SDL_Quit();return 0;
}

运行

07-SDL事件

SDL事件

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

◼ 数据结构
• SDL_Event:代表一个事件

案例

新建工程 03-sdl-event
将 SDL2-2.0.10和SDL2-2.0.10\lib\x86 下的 SDL2.dll 拷贝到工程目录

03-sdl-event.proTEMPLATE = app
CONFIG += console
CONFIG -= app_bundle
CONFIG -= qtSOURCES += main.cwin32 {INCLUDEPATH += $$PWD/SDL2-2.0.10/includeLIBS += $$PWD/SDL2-2.0.10/lib/x86/SDL2.lib
}
main.c#include <stdio.h>
#include <SDL.h>#define FF_QUIT_EVENT    (SDL_USEREVENT + 2) // 用户自定义事件#undef mainint main(int argc, char* argv[])
{SDL_Window *window = NULL;              // Declare a pointerSDL_Renderer *renderer = NULL;SDL_Init(SDL_INIT_VIDEO);               // Initialize SDL2// Create an application window with the following settings:window = SDL_CreateWindow("An SDL2 window",                  // window titleSDL_WINDOWPOS_UNDEFINED,           // initial x positionSDL_WINDOWPOS_UNDEFINED,           // initial y position640,                               // width, in pixels480,                               // height, in pixelsSDL_WINDOW_SHOWN | SDL_WINDOW_BORDERLESS// flags - see below);// Check that the window was successfully createdif (window == NULL){// In the case that the window could not be made...printf("Could not create window: %s\n", SDL_GetError());return 1;}/* We must call SDL_CreateRenderer in order for draw calls to affect this window. */renderer = SDL_CreateRenderer(window, -1, 0);/* Select the color for drawing. It is set to red here. */SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255);/* Clear the entire screen to our selected color. */SDL_RenderClear(renderer);/* Up until now everything was drawn behind the scenes.This will show the new, red contents of the window. */SDL_RenderPresent(renderer);SDL_Event event;int b_exit = 0;for (;;){SDL_WaitEvent(&event);switch (event.type){case SDL_KEYDOWN:   /* 键盘事件 */switch (event.key.keysym.sym){case SDLK_a:printf("key down a\n");break;case SDLK_s:printf("key down s\n");break;case SDLK_d:printf("key down d\n");break;case SDLK_q:printf("key down q and push quit event\n");SDL_Event event_q;event_q.type = FF_QUIT_EVENT;SDL_PushEvent(&event_q);break;default:printf("key down 0x%x\n", event.key.keysym.sym);break;}break;case SDL_MOUSEBUTTONDOWN:           /* 鼠标按下事件 */if (event.button.button == SDL_BUTTON_LEFT){printf("mouse down left\n");}else if(event.button.button == SDL_BUTTON_RIGHT){printf("mouse down right\n");}else{printf("mouse down %d\n", event.button.button);}break;case SDL_MOUSEMOTION:       /* 鼠标移动事件 */printf("mouse movie (%d,%d)\n", event.button.x, event.button.y);break;case FF_QUIT_EVENT:printf("receive quit event\n");b_exit = 1;break;}if(b_exit)break;}//destory rendererif (renderer)SDL_DestroyRenderer(renderer);// Close and destroy the windowif (window)SDL_DestroyWindow(window);// Clean upSDL_Quit();return 0;
}

构建项目
将 SDL2.dll 拷贝到 build-03-sdl-event-Desktop_Qt_5_10_1_MSVC2015_32bit-Debug 目录
运行

08-SDL线程

SDL多线程
◼ 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

案例

新建工程 04-sdl-thread
将 SDL2-2.0.10和SDL2-2.0.10\lib\x86 下的 SDL2.dll 拷贝到工程目录

04-sdl-thread.proTEMPLATE = app
CONFIG += console
CONFIG -= app_bundle
CONFIG -= qtSOURCES += main.cwin32 {INCLUDEPATH += $$PWD/SDL2-2.0.10/includeLIBS += $$PWD/SDL2-2.0.10/lib/x86/SDL2.lib
}
main.c#include <SDL.h>
#include <stdio.h>SDL_mutex *s_lock = NULL;
SDL_cond *s_cond = NULL;int thread_work(void *arg)
{SDL_LockMutex(s_lock);printf("                <============thread_work sleep\n");sleep(10);      // 用来测试获取锁printf("                <============thread_work wait\n");// 释放s_lock资源,并等待signal。之所以释放s_lock是让别的线程能够获取到s_lockSDL_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,"thread_work",NULL);if(!t){printf("  %s",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;
}

构建项目
将 SDL2.dll 拷贝到 build-04-sdl-thread-Desktop_Qt_5_10_1_MSVC2015_32bit-Debug 目录
运行

09-SDL YUV显示:SDL视频显示的流程

案例

新建工程 05-sdl-yuv
将 SDL2-2.0.10和SDL2-2.0.10\lib\x86 下的 SDL2.dll 拷贝到工程目录

05-sdl-yuv.proTEMPLATE = app
CONFIG += console
CONFIG -= app_bundle
CONFIG -= qtSOURCES += main.cwin32 {INCLUDEPATH += $$PWD/SDL2-2.0.10/includeLIBS += $$PWD/SDL2-2.0.10/lib/x86/SDL2.lib
}
main.c#include <stdio.h>
#include <string.h>#include <SDL.h>//自定义消息类型
#define REFRESH_EVENT   (SDL_USEREVENT + 1)     // 请求画面刷新事件
#define QUIT_EVENT      (SDL_USEREVENT + 2)     // 退出事件//定义分辨率
// YUV像素分辨率
#define YUV_WIDTH   320
#define YUV_HEIGHT  240
//定义YUV格式
#define YUV_FORMAT  SDL_PIXELFORMAT_IYUVint s_thread_exit = 0;  // 退出标志 = 1则退出int refresh_video_timer(void *data)
{while (!s_thread_exit){SDL_Event event;event.type = REFRESH_EVENT;SDL_PushEvent(&event);SDL_Delay(40);}s_thread_exit = 0;//push quit eventSDL_Event event;event.type = QUIT_EVENT;SDL_PushEvent(&event);return 0;
}
#undef main
int main(int argc, char* argv[])
{//初始化 SDLif(SDL_Init(SDL_INIT_VIDEO)){fprintf( stderr, "Could not initialize SDL - %s\n", SDL_GetError());return -1;}// SDLSDL_Event event;                            // 事件SDL_Rect rect;                              // 矩形SDL_Window *window = NULL;                  // 窗口SDL_Renderer *renderer = NULL;              // 渲染SDL_Texture *texture = NULL;                // 纹理SDL_Thread *timer_thread = NULL;            // 请求刷新线程uint32_t pixformat = YUV_FORMAT;            // YUV420P,即是SDL_PIXELFORMAT_IYUV// 分辨率// 1. YUV的分辨率int video_width = YUV_WIDTH;int video_height = YUV_HEIGHT;// 2.显示窗口的分辨率int win_width = YUV_WIDTH;int win_height = YUV_WIDTH;// YUV文件句柄FILE *video_fd = NULL;const char *yuv_path = "yuv420p_320x240.yuv";size_t video_buff_len = 0;uint8_t *video_buf = NULL; //读取数据后先把放到buffer里面// 我们测试的文件是YUV420P格式uint32_t y_frame_len = video_width * video_height;uint32_t u_frame_len = video_width * video_height / 4;uint32_t v_frame_len = video_width * video_height / 4;uint32_t yuv_frame_len = y_frame_len + u_frame_len + v_frame_len;//创建窗口window = SDL_CreateWindow("Simplest YUV Player",SDL_WINDOWPOS_UNDEFINED,SDL_WINDOWPOS_UNDEFINED,video_width, video_height,SDL_WINDOW_OPENGL|SDL_WINDOW_RESIZABLE);if(!window){fprintf(stderr, "SDL: could not create window, err:%s\n",SDL_GetError());goto _FAIL;}// 基于窗口创建渲染器renderer = SDL_CreateRenderer(window, -1, 0);// 基于渲染器创建纹理texture = SDL_CreateTexture(renderer,pixformat,SDL_TEXTUREACCESS_STREAMING,video_width,video_height);// 分配空间video_buf = (uint8_t*)malloc(yuv_frame_len);if(!video_buf){fprintf(stderr, "Failed to alloce yuv frame space!\n");goto _FAIL;}// 打开YUV文件video_fd = fopen(yuv_path, "rb");if( !video_fd ){fprintf(stderr, "Failed to open yuv file\n");goto _FAIL;}// 创建请求刷新线程timer_thread = SDL_CreateThread(refresh_video_timer,NULL,NULL);while (1){// 收取SDL系统里面的事件SDL_WaitEvent(&event);if(event.type == REFRESH_EVENT) // 画面刷新事件{video_buff_len = fread(video_buf, 1, yuv_frame_len, video_fd);if(video_buff_len <= 0){fprintf(stderr, "Failed to read data from yuv file!\n");goto _FAIL;}// 设置纹理的数据 video_width = 320, planeSDL_UpdateTexture(texture, NULL, video_buf, video_width);// 显示区域,可以通过修改w和h进行缩放rect.x = 0;rect.y = 0;float w_ratio = win_width * 1.0 /video_width;float h_ratio = win_height * 1.0 /video_height;// 320x240 怎么保持原视频的宽高比例rect.w = video_width * w_ratio;rect.h = video_height * h_ratio;
//            rect.w = video_width * 0.5;
//            rect.h = video_height * 0.5;// 清除当前显示SDL_RenderClear(renderer);// 将纹理的数据拷贝给渲染器SDL_RenderCopy(renderer, texture, NULL, &rect);// 显示SDL_RenderPresent(renderer);}else if(event.type == SDL_WINDOWEVENT){//If ResizeSDL_GetWindowSize(window, &win_width, &win_height);printf("SDL_WINDOWEVENT win_width:%d, win_height:%d\n",win_width,win_height );}else if(event.type == SDL_QUIT) //退出事件{s_thread_exit = 1;}else if(event.type == QUIT_EVENT){break;}}_FAIL:s_thread_exit = 1;      // 保证线程能够退出// 释放资源if(timer_thread)SDL_WaitThread(timer_thread, NULL); // 等待线程退出if(video_buf)free(video_buf);if(video_fd)fclose(video_fd);if(texture)SDL_DestroyTexture(texture);if(renderer)SDL_DestroyRenderer(renderer);if(window)SDL_DestroyWindow(window);SDL_Quit();return 0;}

构建项目
将 SDL2.dll 拷贝到 build-05-sdl-yuv-Desktop_Qt_5_10_1_MinGW_32bit-Debug 目录
将 yuv420p_320x240.yuv 文件 拷贝到build-05-sdl-yuv-Desktop_Qt_5_10_1_MinGW_32bit-Debug目录

运行

10-SDL播放音频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;

11-SDL播放音频PCM-SDL_AudioCallback

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)

12-SDL播放音频PCM-代码

案例

新建工程 06-sdl-pcm
将 SDL2-2.0.10和SDL2-2.0.10\lib\x86 下的 SDL2.dll 拷贝到工程目录

06-sdl-pcm.proTEMPLATE = app
CONFIG += console
CONFIG -= app_bundle
CONFIG -= qtSOURCES += main.cwin32 {INCLUDEPATH += $$PWD/SDL2-2.0.10/includeLIBS += $$PWD/SDL2-2.0.10/lib/x86/SDL2.lib
}
main.c/*** SDL2播放PCM** 本程序使用SDL2播放PCM音频采样数据。SDL实际上是对底层绘图* API(Direct3D,OpenGL)的封装,使用起来明显简单于直接调用底层* API。* 测试的PCM数据采用采样率44.1k, 采用精度S16SYS, 通道数2** 函数调用步骤如下:** [初始化]* SDL_Init(): 初始化SDL。* SDL_OpenAudio(): 根据参数(存储于SDL_AudioSpec)打开音频设备。* SDL_PauseAudio(): 播放音频数据。** [循环播放数据]* SDL_Delay(): 延时等待播放完成。**/#include <stdio.h>
#include <SDL.h>// 每次读取2帧数据, 以1024个采样点一帧 2通道 16bit采样点为例
#define PCM_BUFFER_SIZE (1024*2*2*2)// 音频PCM数据缓存
static Uint8 *s_audio_buf = NULL;
// 目前读取的位置
static Uint8 *s_audio_pos = NULL;
// 缓存结束位置
static Uint8 *s_audio_end = NULL;//音频设备回调函数
void fill_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;  // 移动缓存指针
}// 提取PCM文件
// ffmpeg -i input.mp4 -t 20 -codec:a pcm_s16le -ar 44100 -ac 2 -f s16le 44100_16bit_2ch.pcm
// 测试PCM文件
// ffplay -ar 44100 -ac 2 -f s16le 44100_16bit_2ch.pcm
#undef main
int main(int argc, char *argv[])
{int ret = -1;FILE *audio_fd = NULL;SDL_AudioSpec spec;const char *path = "44100_16bit_2ch.pcm";// 每次缓存的长度size_t read_buffer_len = 0;//SDL initializeif(SDL_Init(SDL_INIT_AUDIO))    // 支持AUDIO{fprintf(stderr, "Could not initialize SDL - %s\n", SDL_GetError());return ret;}//打开PCM文件audio_fd = fopen(path, "rb");if(!audio_fd){fprintf(stderr, "Failed to open pcm file!\n");goto _FAIL;}s_audio_buf = (uint8_t *)malloc(PCM_BUFFER_SIZE);// 音频参数设置SDL_AudioSpecspec.freq = 44100;          // 采样频率spec.format = AUDIO_S16SYS; // 采样点格式spec.channels = 2;          // 2通道spec.silence = 0;spec.samples = 1024;       // 23.2ms -> 46.4ms 每次读取的采样数量,多久产生一次回调和 samplesspec.callback = fill_audio_pcm; // 回调函数spec.userdata = NULL;//打开音频设备if(SDL_OpenAudio(&spec, NULL)){fprintf(stderr, "Failed to open audio device, %s\n", SDL_GetError());goto _FAIL;}//play audioSDL_PauseAudio(0);int data_count = 0;while(1){// 从文件读取PCM数据read_buffer_len = fread(s_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 = s_audio_buf + read_buffer_len;    // 更新buffer的结束位置s_audio_pos = s_audio_buf;  // 更新buffer的起始位置//the main thread wait for a momentwhile(s_audio_pos < s_audio_end){SDL_Delay(10);  // 等待PCM数据消耗}}printf("play PCM finish\n");// 关闭音频设备SDL_CloseAudio();_FAIL://release some resourcesif(s_audio_buf)free(s_audio_buf);if(audio_fd)fclose(audio_fd);//quit SDLSDL_Quit();return 0;
}

构建项目
将 SDL2.dll 拷贝到 build-06-sdl-pcm-Desktop_Qt_5_10_1_MinGW_32bit-Debug 目录
将 44100_16bit_2ch.pcm 文件 拷贝到 build-06-sdl-pcm-Desktop_Qt_5_10_1_MinGW_32bit-Debug 目录

运行

相关文章:

SDL音视频渲染

01-SDL简介 官网&#xff1a;https://www.libsdl.org/ 文档&#xff1a;http://wiki.libsdl.org/Introduction SDL&#xff08;Simple DirectMedia Layer&#xff09;是一套开放源代码的跨平台多媒体开发库&#xff0c;使用C语言写成。SDL提供了数种控制图像、声音、输出入的函…...

2311rust到27版本更新

1.23 从Rust1.0开始,有叫AsciiExt的特征来提供u8,char,[u8]和str上的ASCII相关功能.要使用它,需要如下编写代码: use std::ascii::AsciiExt; let ascii a; let non_ascii ; let int_ascii 97; assert!(ascii.is_ascii()); assert!(!non_ascii.is_ascii()); assert!(int_a…...

网络运维Day18

文章目录 环境准备导入数据确认表导入成功练习用表解析表格结构设计 查询语句进阶什么是MySQL函数常用功能函数数学计算流程控制函数查询结果处理 连接查询(联表查询)表关系什么是连接查询连接查询分类笛卡尔积内连接(INNER)外连接 子查询什么是子查询子查询出现的位置子查询练…...

leetcode刷题日志-13整数转罗马数字

罗马数字包含以下七种字符&#xff1a; I&#xff0c; V&#xff0c; X&#xff0c; L&#xff0c;C&#xff0c;D 和 M。 字符 数值 I 1 V 5 X 10 L 50 C 100 D 500 M 1000 例如&#xff0c; 罗马数字 2 写做 II &#xff0c;即为两个并列的 1。12 写做 XII &#xff0c;即为…...

docker 部署mysql主从复制

一&#xff1a;环境准备 1&#xff1a;创建mysql主库镜像 docker run -p 3307:3306 --name mysql_m \ -v /opt/mysql_m/log:/var/log/mysql \ -v /opt/mysql_m/data:/var/lib/mysql \ -v /opt/mysql_m/conf:/etc/mysql \ -e MYSQL_ROOT_PASSWORD123456 \ -d mysql:5.7 2&…...

C++打怪升级(十一)- STL之list

~~~~ 前言1. list是什么2. list接口函数的使用1. 构造相关默认构造n个val构造迭代器范围构造拷贝构造 2 赋值运算符重载函数2 析构函数3 迭代器相关begin 和 endrbegin 和rend 4 容量相关emptysize 5 元素访问相关frontback 6 修改相关push_backpop_backpush_frontpop_frontins…...

Python编程陷阱(七)

陷阱26:不要使用list.reverse方法来反转列表 列表是Python中最常用的数据结构之一,它可以存储任意类型的元素,并且可以动态地增加或删除元素。有时候,我们需要将列表中的元素反转,比如打印或排序它们的值,就需要使用list.reverse方法或[::-1]切片来反转列表。但是,如果我…...

Python如何调用ixchariot进行吞吐量测试

Python如何调用ixchariot进行吞吐量测试 要使用Python调用IxChariot进行吞吐量测试&#xff0c;您可以使用 subprocess 模块来执行IxChariot的TCL命令行。下面是一个简单的示例代码&#xff1a; import subprocess# 定义IxChariot的安装路径和测试脚本路径 ixchariot_path &q…...

51单片机应用从零开始(五)·加减乘除运算

51单片机应用从零开始&#xff08;一&#xff09;-CSDN博客 51单片机应用从零开始&#xff08;二&#xff09;-CSDN博客 51单片机应用从零开始&#xff08;三&#xff09;-CSDN博客 51单片机应用从零开始&#xff08;四&#xff09;-CSDN博客 详解 KEIL C51 软件的使用建立工程…...

Meta降本增效大招之:弃用产品

今晚无意间进入去哪儿技术沙龙的直播间&#xff0c;听到他们要删除50%的代码和停掉50%的服务。我就想起Meta公司最近写的这篇博客&#xff1a;Automating product deprecation。 这篇博客对于效能平台的建设非常具有指导意义。文章最后有原文链接和我个人的总结。 这是一个系列…...

Adobe Illustrator——原创设计的宝藏软件

今天&#xff0c;我们来谈谈一款在Adobe系列中曾经多次给大家都提到的原创性极强的设计理念丰富的矢量图形编辑软件——Adobe Illustrator。 Adobe Illustrator&#xff0c;其定位是一款与Photoshop相类似对矢量图形进行编辑的软件。 Adobe Illustrator&#xff0c;作为全球最著…...

LEEDCODE 220 存在重复元素3

class Solution { public:int getId(int a, int valuediff){// 值// return a/(valuediff1);return a < 0 ? (a ) -) / (valuediff 1) - 1 : a / (valuediff 1);}public: unordered_map<int, int> bucket;bool containsNearbyAlmostDuplicate(vector<int>&am…...

从内网到公网:使用Axure RP和内网穿透技术发布静态web页面的完整指南

文章目录 前言1.在AxureRP中生成HTML文件2.配置IIS服务3.添加防火墙安全策略4.使用cpolar内网穿透实现公网访问4.1 登录cpolar web ui管理界面4.2 启动website隧道4.3 获取公网URL地址4.4. 公网远程访问内网web站点4.5 配置固定二级子域名公网访问内网web站点4.5.1创建一条固定…...

第三天课程 RabbitMQ

RabbitMQ 1.初识MQ 1.1.同步和异步通讯 微服务间通讯有同步和异步两种方式&#xff1a; 同步通讯&#xff1a;就像打电话&#xff0c;需要实时响应。 异步通讯&#xff1a;就像发邮件&#xff0c;不需要马上回复。 两种方式各有优劣&#xff0c;打电话可以立即得到响应&am…...

Ubuntu18.04编译OpenCV时遇到无法下载ADE的问题

安装OpenCV过程中编译时出现下载ADE失败的问题 报错如下&#xff1a; -- ADE: Downloading v0.1.2a.zip from https://github.com/opencv/ade/archive/v0.1.2a.zip -- Try 1 failed CMake Warning at cmake/OpenCVDownload.cmake:248 (message):ADE: Download failed: 28;&quo…...

基于JavaWeb+SSM+社区居家养老服务平台—颐养者端微信小程序系统的设计和实现

基于JavaWebSSM社区居家养老服务平台—颐养者端微信小程序系统的设计和实现 源码获取入口前言主要技术系统设计功能截图Lun文目录订阅经典源码专栏Java项目精品实战案例《500套》 源码获取 源码获取入口 前言 在复杂社会化网络中&#xff0c;灵活运用社会生活产生的大数据&am…...

算法实战:亲自写红黑树之五 删除erase的平衡

本文承接自&#xff1a; 算法实战&#xff1a;亲自写红黑树之一-CSDN博客 算法实战&#xff1a;亲自写红黑树之二 完整代码-CSDN博客 算法实战&#xff1a;亲自写红黑树之三 算法详解-CSDN博客 算法实战&#xff1a;亲自写红黑树之四 插入insert的平衡-CSDN博客 目录 一、入口…...

春秋云境靶场CVE-2021-41402漏洞复现(任意代码执行漏洞)

文章目录 前言一、CVE-2021-41402描述二、CVE-2021-41402漏洞复现1、信息收集1、方法一弱口令bp爆破2、方法二7kb扫路径&#xff0c;后弱口令爆破 2、找可能可以进行任意php代码执行的地方3、漏洞利用找flag 总结 前言 此文章只用于学习和反思巩固渗透测试知识&#xff0c;禁止…...

12 Go的接口

概述 在上一节的内容中&#xff0c;我们介绍了Go的作用域&#xff0c;包括&#xff1a;局部作用域、全局作用域、命名空间作用域等。在本节中&#xff0c;我们将介绍Go的接口。Go语言中的接口是一种类型&#xff0c;它定义了一组函数的集合。接口是一种抽象的描述&#xff0c;它…...

Python编程-----并行处理应用程序

目录 一.进程 二.线程 三.Python标准库中并行处理的相关模块 Threading模块 &#xff08;1&#xff09;使用Thread对象创建线程 &#xff08;2&#xff09;自定义派生于Thread的对象 &#xff08;3&#xff09;线程加入join() (4)用户线程和daemon线程 (5)Timer线程 线…...

Linux 文件类型,目录与路径,文件与目录管理

文件类型 后面的字符表示文件类型标志 普通文件&#xff1a;-&#xff08;纯文本文件&#xff0c;二进制文件&#xff0c;数据格式文件&#xff09; 如文本文件、图片、程序文件等。 目录文件&#xff1a;d&#xff08;directory&#xff09; 用来存放其他文件或子目录。 设备…...

如何在看板中体现优先级变化

在看板中有效体现优先级变化的关键措施包括&#xff1a;采用颜色或标签标识优先级、设置任务排序规则、使用独立的优先级列或泳道、结合自动化规则同步优先级变化、建立定期的优先级审查流程。其中&#xff0c;设置任务排序规则尤其重要&#xff0c;因为它让看板视觉上直观地体…...

Maven 概述、安装、配置、仓库、私服详解

目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

基于 TAPD 进行项目管理

起因 自己写了个小工具&#xff0c;仓库用的Github。之前在用markdown进行需求管理&#xff0c;现在随着功能的增加&#xff0c;感觉有点难以管理了&#xff0c;所以用TAPD这个工具进行需求、Bug管理。 操作流程 注册 TAPD&#xff0c;需要提供一个企业名新建一个项目&#…...

return this;返回的是谁

一个审批系统的示例来演示责任链模式的实现。假设公司需要处理不同金额的采购申请&#xff0c;不同级别的经理有不同的审批权限&#xff1a; // 抽象处理者&#xff1a;审批者 abstract class Approver {protected Approver successor; // 下一个处理者// 设置下一个处理者pub…...

JavaScript基础-API 和 Web API

在学习JavaScript的过程中&#xff0c;理解API&#xff08;应用程序接口&#xff09;和Web API的概念及其应用是非常重要的。这些工具极大地扩展了JavaScript的功能&#xff0c;使得开发者能够创建出功能丰富、交互性强的Web应用程序。本文将深入探讨JavaScript中的API与Web AP…...

基于PHP的连锁酒店管理系统

有需要请加文章底部Q哦 可远程调试 基于PHP的连锁酒店管理系统 一 介绍 连锁酒店管理系统基于原生PHP开发&#xff0c;数据库mysql&#xff0c;前端bootstrap。系统角色分为用户和管理员。 技术栈 phpmysqlbootstrapphpstudyvscode 二 功能 用户 1 注册/登录/注销 2 个人中…...

【WebSocket】SpringBoot项目中使用WebSocket

1. 导入坐标 如果springboot父工程没有加入websocket的起步依赖&#xff0c;添加它的坐标的时候需要带上版本号。 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId> </dep…...

C# winform教程(二)----checkbox

一、作用 提供一个用户选择或者不选的状态&#xff0c;这是一个可以多选的控件。 二、属性 其实功能大差不差&#xff0c;除了特殊的几个外&#xff0c;与button基本相同&#xff0c;所有说几个独有的 checkbox属性 名称内容含义appearance控件外观可以变成按钮形状checkali…...

验证redis数据结构

一、功能验证 1.验证redis的数据结构&#xff08;如字符串、列表、哈希、集合、有序集合等&#xff09;是否按照预期工作。 2、常见的数据结构验证方法&#xff1a; ①字符串&#xff08;string&#xff09; 测试基本操作 set、get、incr、decr 验证字符串的长度和内容是否正…...