FFmpeg编程(三)SDL开发
一:SDL介绍与安装
(一)SDL介绍
(二)SDL安装
1.源码下载:http://www.libsdl.org/download-2.0.php
2.生成Makefile文件
./configure --prefix=/usr/local
3.安装
sudo make -j 8 && sudo make install
二:SDL的简单使用
SDL播放视频的代码流程如下所示:
初始化:
SDL_Init(): 初始化SDL。
SDL_CreateWindow(): 创建窗口(Window)。
SDL_CreateRenderer(): 基于窗口创建渲染器(Render)。
SDL_CreateTexture(): 创建纹理(Texture)。
循环渲染数据:
SDL_UpdateTexture(): 设置纹理的数据。
SDL_RenderCopy(): 纹理复制给渲染器。
SDL_RenderPresent(): 显示。
(一)基本使用步骤
(二)SDL渲染窗口
1.SDL_Init/SDL_Quit 初始化和退出操作
https://blog.csdn.net/leixiaohua1020/article/details/40680907
int SDLCALL SDL_Init(Uint32 flags)
其中,flags可以取下列值:
SDL_INIT_TIMER:定时器
SDL_INIT_AUDIO:音频
SDL_INIT_VIDEO:视频
SDL_INIT_JOYSTICK:摇杆
SDL_INIT_HAPTIC:触摸屏
SDL_INIT_GAMECONTROLLER:游戏控制器
SDL_INIT_EVENTS:事件
SDL_INIT_NOPARACHUTE:不捕获关键信号(这个不理解)
SDL_INIT_EVERYTHING:包含上述所有选项
2.SDL_CreateWindow()/SDL_DestroyWindow() 创建窗口(比如将图片渲染到窗口)
https://blog.csdn.net/leixiaohua1020/article/details/40701203
SDL_Window * SDLCALL SDL_CreateWindow(const char *title, int x, int y, int w, int h, Uint32 flags);
参数含义如下:https://blog.csdn.net/qq_25333681/article/details/89787867
title :窗口标题
x :窗口位置x坐标。也可以设置为SDL_WINDOWPOS_CENTERED或SDL_WINDOWPOS_UNDEFINED。
y :窗口位置y坐标。同上。
w :窗口的宽
h :窗口的高
flags :支持下列标识。包括了窗口的是否最大化、最小化,能否调整边界等等属性。
::SDL_WINDOW_FULLSCREEN, ::SDL_WINDOW_OPENGL,
::SDL_WINDOW_HIDDEN, ::SDL_WINDOW_BORDERLESS,
::SDL_WINDOW_RESIZABLE, ::SDL_WINDOW_MAXIMIZED,
::SDL_WINDOW_MINIMIZED, ::SDL_WINDOW_INPUT_GRABBED,
::SDL_WINDOW_ALLOW_HIGHDPI.
返回创建完成的窗口的ID。如果创建失败则返回0。
3.SDL_CreateRenderer() 创建渲染器(将图像/视频帧汇聚到窗口)
SDL_Renderer * SDLCALL SDL_CreateRenderer(SDL_Window * window, int index, Uint32 flags);
参数含义如下:
window : 渲染的目标窗口。 index :打算初始化的渲染设备的索引。设置“-1”则初始化默认的渲染设备。 flags :支持以下值(位于SDL_RendererFlags定义中) SDL_RENDERER_SOFTWARE :使用软件渲染 SDL_RENDERER_ACCELERATED :使用硬件加速 SDL_RENDERER_PRESENTVSYNC:和显示器的刷新率同步 SDL_RENDERER_TARGETTEXTURE :不太懂 返回创建完成的渲染器的ID。如果创建失败则返回NULL。
(三)简单实例(未渲染)
#include <stdio.h> #include <SDL.h> int main(int argc,char* argv[]){ SDL_Window* wind = NULL; SDL_Init(SDL_INIT_VIDEO); //进行视频初始化 wind = SDL_CreateWindow("SDL2 Window", 200,200, 640,480, SDL_WINDOW_SHOWN); //显示、有边界 if(!wind){ printf("Failed to Create window!\n"); goto __EXIT; } while(1){ sleep(5000); } SDL_DestroyWindow(wind); __EXIT: SDL_Quit(); return 0; }
gcc 01_sdl_sample.c -o 01ss -I /usr/local/include/SDL2/ -L /usr/local/lib/ -lSDL2
可以看到出现窗口,但是没有渲染,导致出现透明状态,显示屏幕原本信息
(四)简单实例(使用渲染器)
SDL_Renderer表示渲染上下文。这意味着它包含与渲染相关的所有当前设置,以及有关如何渲染当前帧的说明。
要创建渲染上下文,可以使用函数SDL_CreateWindowAndRenderer()或SDL_CreateRenderer()。前者同时创建窗口和渲染器。后者要求我们先创建一个窗口。
在程序中,您将使用SDL_SetRenderDrawColor()等函数更改上下文中的设置,并使用SDL_RenderDrawPoint()等函数执行渲染操作。
int SDL_RenderClear(SDL_Renderer* renderer) ,该函数的作用是用指定的颜色清空缓冲区。renderer是上面创建的渲染器上下文。
void SDL_RenderPresent(SDL_Renderer* renderer),将缓冲区中的内容输出到目标上,也就是 windows 窗口上。注意:渲染的数据也可以是从GPU渲染到屏幕!!
#include <stdio.h> #include <SDL.h> int main(int argc,char* argv[]){ SDL_Window* wind = NULL; SDL_Renderer* rend = NULL; SDL_Init(SDL_INIT_VIDEO); //进行视频初始化 wind = SDL_CreateWindow("SDL2 Window", 200,200, 640,480, SDL_WINDOW_SHOWN); //显示、有边界 if(!wind){ printf("Failed to Create window!\n"); goto __EXIT; } rend = SDL_CreateRenderer(wind,-1,0); //创建Render渲染器 if(!rend){ printf("Failed to create render\n"); goto __DWIND; } SDL_SetRenderDrawColor(rend,0,0,0,255); //设置颜色和透明度(可选操作) SDL_RenderClear(rend); //清屏处理(清空render之前的数据) SDL_RenderPresent(rend); //将渲染器结果放入窗口中显示出来 SDL_Delay(5000); __DWIND: SDL_DestroyWindow(wind); __EXIT: SDL_Quit(); return 0; }
(五)SDL_Surface与SDL_Texture
SDL 渲染的工作原理:
即在SDL_Render对象中有一个视频缓冲区,该缓冲区我们称之为SDL_Surface,它是按照像素存放图像的。
我们一般把真彩色的像素称为RGB24数据。
也就是说,每一个像素由24位组成,每8位代表一种颜色,像素的最终颜色是由RGB三种颜色混合而成的。
SDL_Texture 与SDL_Surface相似,也是一种缓冲区。
只不过它存放的不是真正的像素数据,而是存放的图像的描述信息。这些描述信息通过OpenGL、D3D 或 Metal等技术操作GPU,从而绘制出与SDL_Surface一样的图形,且效率更高(因为它是GPU硬件计算的)。
(六)SDL_Window与SDL_Render
SDL_Window代表的是窗口的逻辑概念,它是存放在主内存中的一个对象。
SDL_Render 是渲染器,它也是主存中的一个对象。对Render操作时实际上分为两个阶段:
一、渲染阶段。在该阶段,用户可以画各种图形渲染到SDL_Surface或SDL_Texture 中;
二、显示阶段。参SDL_Texture为数据,通过OpenGL操作GPU,最终将 SDL_Surfce 或SDL_Texture中的数据输出到显示器上。
三:SDL事件
(一)SDL事件基本原理
(二)SDL事件种类
(三)SDL事件处理
1.poll 论询机制,处理不及时,占CPU 2.wait 事件触发(类似epoll池),处理及时,不占用过多CPU。
对于wait,可能出现线程阻塞,导致无法处理到来的其他事件,应该为每一个时间处理设置Timeout,所以出现了WaitEventTimeOut。
(四)事件机制简单使用
#include <stdio.h> #include <SDL.h> int main(int argc,char* argv[]){ int exitFlag=1; SDL_Window* wind = NULL; SDL_Renderer* rend = NULL; SDL_Event event; SDL_Init(SDL_INIT_VIDEO); //进行视频初始化 wind = SDL_CreateWindow("SDL2 Window", 200,200, 640,480, SDL_WINDOW_SHOWN); //显示、有边界 if(!wind){ printf("Failed to Create window!\n"); goto __EXIT; } rend = SDL_CreateRenderer(wind,-1,0); //创建Render渲染器 if(!rend){ printf("Failed to create render\n"); goto __DWIND; } SDL_SetRenderDrawColor(rend,0,0,0,255); //设置颜色和透明度(可选操作) SDL_RenderClear(rend); //清屏处理(清空render之前的数据) SDL_RenderPresent(rend); //将渲染器结果放入窗口中显示出来 while(exitFlag){ SDL_WaitEvent(&event); //如果没有事件的话,会阻塞在这里的,不会一直while轮询 switch(event.type){ case SDL_QUIT: exitFlag = 0; break; default: SDL_Log("event type is %d",event.type); break; } } __DWIND: SDL_DestroyWindow(wind); __EXIT: SDL_Quit(); return 0; }
gcc 02_sdl_event.c -o 02se -I /usr/local/include/SDL2/ -L /usr/local/lib/ -lSDL2
四:纹理渲染
依据SDL编程方式,这里又分为两种情况:
1. 不使用纹理
即由cpu直接绘制一幅画(cpu需要将最原始的rgb/YUV数据,刷到屏幕上), 相当于学生小A直接在墙上画画
2. 使用纹理
相当于小A同学(cpu)指挥画家(gpu)在纸上画, 然后把纸贴在墙上。
这个过程中画是由画家(gpu)画的, 小A同学负责发号施令(即告诉画家画什么), 纸代表纹理, 画家代表gpu, 所有绘制的操作都是在纹理上进行。
事实上,纹理的概念并不仅仅是一张纸, 还包括小A同学中对这幅画的构思,可以理解成画画的算法, 而纸相当于是一个载体(内存空间,用于保存这些构思)。
gpu根据纹理就可以计算出这幅图每个像素点的颜色( 相当于画家根据小A同学的描述,画出一幅画一样)
可以看出,使用纹理,可以减轻cpu的负担, cpu处于一个发号施令的角色,图片的计算过程交给效率更好的gpu来做,可以提高渲染的效率。
(一)纹理渲染基本原理
图像结果渲染器,变为纹理(即对这张图像的描述数据),将纹理交给显卡GPU,GPU经过计算,将图像显示在窗口中!
(二)SDL纹理相关API
SDL_Texture * SDLCALL SDL_CreateTexture(SDL_Renderer * renderer, Uint32 format, int access, int w, int h);
参数的含义如下:
renderer:目标渲染器。
format :纹理的格式。后面会详述。
access :可以取以下值(定义位于SDL_TextureAccess中)
SDL_TEXTUREACCESS_STATIC :变化极少
SDL_TEXTUREACCESS_STREAMING :变化频繁
SDL_TEXTUREACCESS_TARGET :暂时没有理解
w :纹理的宽
h :纹理的高
创建成功则返回纹理的ID,失败返回0。
(三)渲染相关API
SDL_CreateTexture用于创建纹理, 若要使用SDL_SetRenderTarget()设置渲染到纹理时, 必须使用SDL_TEXTUREACCESS_TARGET方式来创建纹理。
函数SDL_SetRenderTarget()用于在渲染到纹理或屏幕之间进行选择。切换方式如下:
SDL_SetRenderTarget(renderer, texture) // 渲染到纹理 SDL_SetRenderTarget(renderer, NULL) // 渲染到屏幕(默认渲染到窗口/屏幕)
int SDL_RenderCopy(SDL_Renderer* renderer, SDL_Texture* texture, const SDL_Rect* srcrect, const SDL_Rect* dstrect)将纹理拷贝到显卡GPU中去,通过SDL_RenderPresent进行渲染到屏幕
renderer:渲染目标。
texture:输入纹理。
srcrect:选择输入纹理的一块矩形区域作为输入。设置为NULL的时候整个纹理作为输入。
dstrect:选择渲染目标的一块矩形区域作为输出。设置为NULL的时候整个渲染目标作为输出。
(四)纹理使用
#include <stdio.h> #include <SDL.h> int main(int argc,char* argv[]){ int exitFlag=1; SDL_Window* wind = NULL; SDL_Renderer* rend = NULL; SDL_Texture* text = NULL; SDL_Event event; SDL_Rect rect; //创建矩形 SDL_Init(SDL_INIT_VIDEO); //进行视频初始化 wind = SDL_CreateWindow("SDL2 Window", 200,200, 640,480, SDL_WINDOW_SHOWN); //显示、有边界 if(!wind){ printf("Failed to Create window!\n"); goto __EXIT; } rend = SDL_CreateRenderer(wind,-1,0); //创建Render渲染器 if(!rend){ printf("Failed to create render\n"); goto __DWIND; } SDL_SetRenderDrawColor(rend,0,0,0,255); //设置颜色和透明度(可选操作) SDL_RenderClear(rend); //清屏处理(清空render之前的数据) SDL_RenderPresent(rend); //将渲染器结果放入窗口中显示出来 text = SDL_CreateTexture(rend,SDL_PIXELFORMAT_RGBA8888,SDL_TEXTUREACCESS_TARGET, 640,480); //创建纹理 if(!text){ printf("Failed to create texture\n"); goto __DREND; } rect.w = 30; rect.h = 30; while(exitFlag){ SDL_PollEvent(&event); //如果没有事件的话,会一直while轮询 switch(event.type){ case SDL_QUIT: exitFlag = 0; break; default: SDL_Log("event type is %d",event.type); break; } rect.x = rand() % 610; rect.y = rand() % 450; SDL_SetRenderTarget(rend,text); //渲染到纹理,下面的操作都会转为纹理 SDL_SetRenderDrawColor(rend,0,0,0,0); SDL_RenderClear(rend); //绘制矩形 SDL_RenderDrawRect(rend,&rect); SDL_SetRenderDrawColor(rend,255,0,0,0); //为矩形绘制颜色 SDL_RenderFillRect(rend,&rect); //将渲染器设置到矩形中去,使得clear只在矩形中生效 SDL_SetRenderTarget(rend,NULL); //使用默认渲染,到窗口 SDL_RenderCopy(rend,text,NULL,NULL); //拷贝纹理到显卡 SDL_RenderPresent(rend); //将结果显示到窗口 } SDL_DestroyTexture(text); __DREND: SDL_DestroyRenderer(rend); __DWIND: SDL_DestroyWindow(wind); __EXIT: SDL_Quit(); return 0; }
gcc 03_sdl_texture.c -o 03st -I /usr/local/include/SDL2/ -L /usr/local/lib/ -lSDL2
SDL_SetRenderTarget(rend,text); //渲染到纹理,下面的操作都会转为纹理!!!
SDL_SetRenderDrawColor(rend,0,0,0,0); SDL_RenderClear(rend); //绘制矩形 SDL_RenderDrawRect(rend,&rect); SDL_SetRenderDrawColor(rend,255,0,0,0); //为矩形绘制颜色 SDL_RenderFillRect(rend,&rect); //将渲染器设置到矩形中去,使得内部clear只在矩形中生效 SDL_SetRenderTarget(rend,NULL); //使用默认渲染,到窗口;下面的操作会显示在窗口,不经过纹理转换 SDL_RenderCopy(rend,text,NULL,NULL); //拷贝纹理到显卡---在窗口层面处理,而不是纹理 SDL_RenderPresent(rend); //GPU显卡将计算的结果显示到窗口
五:实战YUV播放器
(一)创建线程(提高效率)
创建线程:SDL_Thread* SDL_CreateThread(SDL_ThreadFunction fn, const char* name, void* data)
fn: 线程要运行的函数。
name: 线程名。
data: 函数参数。
等待线程:void SDL_WaitThread(SDL_Thread* thread, int* status) 等待线程结束
创建互斥量:SDL_mutex* SDL_CreateMutex(void) 也就是创建一个稀有资源,这样大家就去抢这个资源。从而达到为真正资源加锁的目的。
销毁互斥量:void SDL_DestroyMutex(SDL_mutex* mutex)
加锁: int SDL_LockMutex(SDL_mutex* mutex)
解锁: int SDL_UnlockMutex(SDL_mutex* mutex)
(二)纹理更新
int SDLCALL SDL_UpdateTexture(SDL_Texture * texture, const SDL_Rect * rect, const void *pixels, int pitch);
参数的含义如下:
texture:目标纹理。 rect:更新像素的矩形区域。设置为NULL的时候更新整个区域。 pixels:像素数据。 pitch:一行像素数据的字节数。 成功的话返回0,失败的话返回-1。
SDL_UpdateTexture()的大致流程如下:
1. 检查输入参数的合理性。例如像素格式是否支持,宽和高是否小于等于0等等。 2. 如果是一些特殊的格式,进行一定的处理: a) 如果输入的像素数据是YUV格式的,则会调用SDL_UpdateTextureYUV()进行处理。 b) 如果输入的像素数据的像素格式不是渲染器支持的格式,则会调用SDL_UpdateTextureNative()进行处理。 3. 调用SDL_Render的UpdateTexture()方法更新纹理。这一步是整个函数的核心。
通过SDL_CreateTexture中指定纹理格式format为SDL_PIXELFORMAT_IYUV即可处理YUV数据!!
(三)编程实现
ffmpeg -i gfxm.mp4 -an -c:v rawvideo -pix_fmt yuv420p out.yuv
#include <stdio.h> #include <string.h> #include <SDL.h> //下面定义自定义事件,SDL_USEREVENT 0x8000之后,到0xFFFF都可以 #define REFRASH_EVENT (SDL_USEREVENT + 1) #define QUIT_EVENT (SDL_USEREVENT + 2) int thread_exit = 0; //控制线程退出 void refresh_video_timer(void* udata){ thread_exit = 0; while(!thread_exit){ SDL_Event event; event.type = REFRASH_EVENT; SDL_PushEvent(&event); SDL_Delay(40); //25帧/S } thread_exit = 0; //开始退出 SDL_Event event; event.type = QUIT_EVENT; SDL_PushEvent(&event); return 0; } int main(int argc,char* argv[]){ int exitFlag=1; //控制循环 char* filename = NULL; FILE* fp = NULL; int len; uint8_t* src_data = NULL,*src_start=NULL; //获取视频帧数据 int v_w,v_h,w_w=1280,w_h=720; //视频宽高、窗口宽高 unsigned int frame_len,frame_temp_len; SDL_Window* wind = NULL; SDL_Renderer* rend = NULL; SDL_Texture* text = NULL; SDL_Event event; SDL_Rect rect; //创建矩形 SDL_Thread* timer_thread = NULL; //线程 if(argc<4){ printf("The number of parameter must be than 3\n"); return 0; } filename = argv[1]; //获取YUV文件----YUV为YUV420数据 v_w = atoi(argv[2]); //获取文件的宽 v_h = atoi(argv[3]); //获取视频的高 frame_temp_len = v_h*v_w*1.5; //YUV420数据大小,需要对数据进行对齐操作 frame_len = frame_temp_len; if(frame_temp_len&0xF){ //后4位存在数据,则表示不是16对齐 frame_len = (frame_temp_len&0xFFF0) + 0x10; //进16 } if(SDL_Init(SDL_INIT_VIDEO)){ //进行视频初始化 printf("Can`t initialize SDL\n"); return -1; } wind = SDL_CreateWindow("YUV Player", SDL_WINDOWPOS_UNDEFINED,SDL_WINDOWPOS_UNDEFINED, //不指定左上位置 w_w,w_h, //设置窗口宽高,与视频一般或者大些都可以 SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE); //窗口可用于OpenGL上下文 窗口可以调整大小 if(!wind){ printf("Failed to Create window!\n"); goto __EXIT; } rend = SDL_CreateRenderer(wind,-1,0); //创建Render渲染器 if(!rend){ printf("Failed to create render\n"); goto __DWIND; } //创建纹理 text = SDL_CreateTexture(rend, SDL_PIXELFORMAT_IYUV, //< Planar mode: Y + U + V (3 planes) SDL_TEXTUREACCESS_TARGET, v_w,v_h); //创建纹理 if(!text){ printf("Failed to create texture\n"); goto __DREND; } //开始读取YUV数据 fp = fopen(filename,"rb"); if(!fp){ printf("Failed to open file:%s\n",filename); goto __TEXT; } src_data = (uint8_t*)malloc(frame_len); //分配空间 if(!src_data){ printf("Failed to alloc memory for src_data!\n"); goto __FILE; } src_start = src_data; //开始位置 //开启线程,处理其他控制事件 timer_thread = SDL_CreateThread(refresh_video_timer,NULL,NULL); //要显示纹理数据的矩形 rect.x = 0; rect.y = 0; while(exitFlag){ SDL_PollEvent(&event); //如果没有事件的话,会一直while轮询 switch(event.type){ case SDL_QUIT: exitFlag = 0; break; case REFRASH_EVENT: //更新纹理 //先读取一帧数据 if((len=fread(src_data,1,frame_len,fp))<=0){ //数据读取完成,退出 thread_exit = 1; continue; } SDL_UpdateTexture(text,NULL,src_start,v_w); //内部设置 rect.w = w_w; //因为窗口可以被重置 rect.h = w_h; SDL_RenderClear(rend); SDL_RenderCopy(rend,text,NULL,&rect); //拷贝纹理到显卡,选择渲染目标的一块矩形区域作为输出 SDL_RenderPresent(rend); //将结果显示到窗口 break; case QUIT_EVENT: //退出播放 exitFlag = 0; break; default: SDL_Log("event type is %d",event.type); break; } } if(src_data) free(src_data); __FILE: if(fp) fclose(fp); __TEXT: SDL_DestroyTexture(text); __DREND: SDL_DestroyRenderer(rend); __DWIND: SDL_DestroyWindow(wind); __EXIT: SDL_Quit(); return 0; }
gcc 04_sdl_yuv.c -o 04sy -I /usr/local/include/SDL2/ -L /usr/local/lib/ -lSDL2
./04sy out.yuv 864 486
六:实战PCM播放器
使用 SDL 的音频 API 来播放声音。其基本流程是,从 pcm 文件一块一块的读数据。然后通过 read_audio_data 这个回调函数给声卡喂数据。
如果一次没用完,SDL会再次调用回调函数读数据。如果audio_buf中的数据用完了,则再次从文件中读一块数据,直到读到文件尾。
(一)播放音频基本流程
(二)播放音频原则
(三)相关API
1.打开音频设备 int SDL_OpenAudio(SDL_AudioSpec* desired,SDL_AudioSpec* obtained)
desired: 设置音频参数。
参数 | 说明 |
---|---|
freq | 每秒采频率 |
SDL_AudioFormat | 音频数据存储格式 |
channels | 通道数 |
silence | 静音值 |
samples | 采样个数 |
size | 音频缓冲区大小 |
SDL_AudioCallback | 回调函数 |
userdata | 回调函数参数指针 |
obtained: 返回参数。
2.关闭音频设备 void SDL_CloseAudio(void)
3.播放与暂停 void SDL_PauseAudio(int pause_on)
pause_on: 0, 暂停播放;1, 播放;
4.喂数据 void SDL_MixAudio(Uint8* dst,const Uint8* src,Uint32 len,int volume)
dst: 目的缓冲区 src: 源缓冲区 len: 音频数据长度 volume: 音量大小,0-128 之间的数。SDL_MIX_MAXVOLUME代表最大音量。
(四)编程实现
ffmpeg -i out.aac -ar 44100 -ac 2 -f s16le out.pcm
#include <stdio.h> #include <string.h> #include <SDL.h> #define BLOCK_SIZE 4096000 //4096k,设置一个较大的数值空间 //将数据存放在全局 static uint8_t* audio_buf = NULL; static uint8_t* audio_pos = NULL; static int buffer_len = 0; //回调函数 void read_audio_data(void* udata,uint8_t* stream,int len){ //回调数据,目的缓冲区,和缓冲区长度 if(buffer_len == 0) return; SDL_memset(stream,0,len); //初始化缓冲区 len = (len<buffer_len)?len:buffer_len; //获取空间长度 SDL_MixAudio(stream,audio_pos,len,SDL_MIX_MAXVOLUME); audio_pos += len; buffer_len -= len; } int main(int argc,char* argv[]){ int exitFlag=1; //控制循环 char* filename = NULL; FILE* fp = NULL; SDL_AudioSpec spec; //用来设置音频参数 if(argc<2){ printf("The number of parameter must be than 1\n"); return 0; } filename = argv[1]; //获取pcm文件 if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)){ //进行视频初始化 printf("Can`t initialize SDL\n"); return -1; } fp = fopen(filename,"rb"); if(!fp){ printf("Failed to open file:%s",filename); goto __EXIT; } audio_buf = (uint8_t*)malloc(BLOCK_SIZE); //分配空间 if(!audio_buf){ printf("Failed to alloc memory for audio buffer\n"); goto __FILE; } //开始开启音频设备 spec.freq = 44100; spec.format = AUDIO_S16SYS; spec.channels = 2; spec.silence = 0; spec.samples = 1024; //每帧数据,单通道的采样数量 spec.callback = read_audio_data; spec.userdata = NULL; if(SDL_OpenAudio(&spec,NULL)){ printf("Failed to open audio device:%s\n",SDL_GetError()); goto __BUFFER; } //开始播放 SDL_PauseAudio(0); while(1){ buffer_len = fread(audio_buf,1,BLOCK_SIZE,fp); if(buffer_len<=0) break; printf("read block size:%d \n",buffer_len); audio_pos = audio_buf; while(audio_pos<(audio_buf+buffer_len)){ //数据没有读取完成 SDL_Delay(1); //延迟1毫秒,等等设备继续读取 } } SDL_CloseAudio(); //关闭音频设备 __BUFFER: if(audio_buf) free(audio_buf); __FILE: if(fp) fclose(fp); __EXIT: SDL_Quit(); return 0; }
gcc 05_sdl_pcm.c -o 05sp -I /usr/local/include/SDL2/ -L /usr/local/lib/ -lSDL2
./05sp out.pcm