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
- 下载SDL源码库,SDL2-2.0.10.tar.gz
- 解压,然后依次执行命令
./configure
make
sudo make install. - 如果出现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简介 官网:https://www.libsdl.org/ 文档:http://wiki.libsdl.org/Introduction SDL(Simple DirectMedia Layer)是一套开放源代码的跨平台多媒体开发库,使用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整数转罗马数字
罗马数字包含以下七种字符: I, V, X, L,C,D 和 M。 字符 数值 I 1 V 5 X 10 L 50 C 100 D 500 M 1000 例如, 罗马数字 2 写做 II ,即为两个并列的 1。12 写做 XII ,即为…...

docker 部署mysql主从复制
一:环境准备 1:创建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进行吞吐量测试,您可以使用 subprocess 模块来执行IxChariot的TCL命令行。下面是一个简单的示例代码: import subprocess# 定义IxChariot的安装路径和测试脚本路径 ixchariot_path &q…...

51单片机应用从零开始(五)·加减乘除运算
51单片机应用从零开始(一)-CSDN博客 51单片机应用从零开始(二)-CSDN博客 51单片机应用从零开始(三)-CSDN博客 51单片机应用从零开始(四)-CSDN博客 详解 KEIL C51 软件的使用建立工程…...

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

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

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.同步和异步通讯 微服务间通讯有同步和异步两种方式: 同步通讯:就像打电话,需要实时响应。 异步通讯:就像发邮件,不需要马上回复。 两种方式各有优劣,打电话可以立即得到响应&am…...

Ubuntu18.04编译OpenCV时遇到无法下载ADE的问题
安装OpenCV过程中编译时出现下载ADE失败的问题 报错如下: -- 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套》 源码获取 源码获取入口 前言 在复杂社会化网络中,灵活运用社会生活产生的大数据&am…...

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

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

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

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

kubernetes集群编排——istio
官网:https://istio.io/latest/zh/about/service-mesh/ 部署 [rootk8s2 ~]# tar zxf istio-1.19.3-linux-amd64.tar.gz [rootk8s2 ~]# cd istio-1.19.3/[rootk8s2 istio-1.19.3]# export PATH$PWD/bin:$PATH demo专为测试准备的功能集合 [rootk8s2 istio-1.19.3]# i…...

mfc140u.dll丢失的解决方法,以及mfc140u.dll解决方法的优缺点
在使用电脑过程中,有时会遇到一些与动态链接库文件(DLL)相关的错误。其中,mfc140u.dll丢失的错误是较为常见的一种。当这个关键的mfc140u.dll文件丢失或损坏时,可能会导致某些应用程序无法正常运行。在本文中ÿ…...

2源码安装网络协议
2.2源码安装/网络协议 一、源码包应用场景 有时我们所用的内核版本太旧,系统自带的库(如libstdc.so.6)版本低或者依赖的其他软件版 本较低,导致无法安装目标软件。 软件/库其实是对机器汇编指令集的封装,在X86体系下…...

未来服务器操作系统的趋势与展望
摘要: 随着云计算、大数据和人工智能不断的发展,服务器操作系统也需要随之进行新一轮的升级。本文通过分析当前服务器操作系统的现状,探讨了未来服务器操作系统的趋势和展望,并针对一些关键问题提出了解决方案。 一、引言 服务器…...

VB.net WebBrowser网页元素抓取分析方法
在用WebBrowser编程实现网页操作自动化时,常要分析网页Html,例如网页在加载数据时,常会显示“系统处理中,请稍候..”,我们需要在数据加载完成后才能继续下一步操作,如何抓取这个信息的网页html元素变化&…...

自建ES6.2.4切阿里云商业版ES(7.10)整体方案
一、切换目的&阿里云商业版ES版本选择 1.1 升级切换阿里云商业版7.10目的 自建的Elasticsearch服务运维难度高,操作复杂,需要手动调整资源,遇到性能瓶颈时优化难度相对云上Elasticsearch较大。使用阿里云提供的ES服务,提高系统稳定性使用云服务es,易于备份,数据恢复…...

Vue实现封装自定义指令
目录 一、什么是自定义指令? 二、自定义指令的使用 Vue中的自定义指令使用Vue.directive函数进行定义。该函数接受两个参数,第一个是指令名称,第二个是指令选项对象。 上述代码中,我们定义了一个名为my-directive的自定义指令…...
<MySQL> 查询数据进阶操作 -- 聚合查询
目录 一、聚合查询概述 二、聚合函数查询 2.1 常用函数 2.2 使用函数演示 2.3 聚合函数参数为*或列名的查询区别 2.4 字符串不能参与数学运算 2.5 具有误导性的结果集 三、分组查询 group by 四、分组后条件表达式查询 五、MySQL 中各个关键字的执行顺序 一、聚合查询…...

arm开发板
一个简单的hello world程序 minicom用来和开发板之间交互并且可以向开发板传输文件。打印hello world字符串。在linux虚拟机上编译我的代码,使用的交叉编译工具是arm-linux-gnueabihf-gcc (hard float) 可以使用 readelf -h libc.so.6 查看开发板是不是(…...

nodejs+vue教室管理系统的设计与实现-微信小程序-安卓-python-PHP-计算机毕业设计
用户 用户管理:查看,修改自己的个人信息 教室预约:可以预约今天明天的教室,按着时间段预约(可多选),如果当前时间超过预约时间段不能预约该时间段的教室 预约教室的时候要有个预约用途ÿ…...