DSP

5.基于SDL2播放PCM音频

2019-07-13 17:49发布

接上一篇<基于FFMPEG将音频解码为PCM>,接下来就是需要将PCM音频进行播放,查阅资料是通过SDL进行音视频的播放,因此这里记录一下SDL相关的笔记。。。

一.简介

摘抄自百度百科:
SDL(Simple DirectMedia Layer)是一套开放源代码的跨平台多媒体开发库,使用C语言写成。SDL提供了数种控制图像、声音、输出入的函数,让开发者只要用相同或是相似的代码就可以开发出跨多个平台(Linux、Windows、Mac OS X等)的应用软件。目前SDL多用于开发游戏、模拟器、媒体播放器等多媒体应用领域。
从上面介绍可以得知,SDL是一套开源的多媒体开发库,对内封装了与底层硬件交互的接口,对外提供统一的接口,我们使用者只需要调用接口,而不需要关注平台、底层硬件等参数,即可正常进行的编码工作。 SDL除了用于音视频的播放外,还提供了其他的功能,例如:摇杆、光盘驱动器、视窗管理等等,而我们现在只需要关注的则是音视频的播放,其他的暂且不提。 目前,使用的是SDL2。

二、流程及函数

1)音频播放流程

只涉及到音频的播放流程,初始化SDL—->根据提供参数打开音频设备—>获取音频流—>循环播放—->结束

2)常用函数

1.SDL_Init() 函数原型: int SDLCALL SDL_Init(Uint32 flags); 初始化SDL系统,其中flag可以选择的选项有: /** * ame SDL_INIT_* * * These are the flags which may be passed to SDL_Init(). You should * specify the subsystems which you will be using in your application. */ /* @{ */ #define SDL_INIT_TIMER 0x00000001 #define SDL_INIT_AUDIO 0x00000010 #define SDL_INIT_VIDEO 0x00000020 /**< SDL_INIT_VIDEO implies SDL_INIT_EVENTS */ #define SDL_INIT_JOYSTICK 0x00000200 /**< SDL_INIT_JOYSTICK implies SDL_INIT_EVENTS */ #define SDL_INIT_HAPTIC 0x00001000 #define SDL_INIT_GAMECONTROLLER 0x00002000 /**< SDL_INIT_GAMECONTROLLER implies SDL_INIT_JOYSTICK */ #define SDL_INIT_EVENTS 0x00004000 #define SDL_INIT_NOPARACHUTE 0x00100000 /**< Don't catch fatal signals */ #define SDL_INIT_EVERYTHING ( SDL_INIT_TIMER | SDL_INIT_AUDIO | SDL_INIT_VIDEO | SDL_INIT_EVENTS | SDL_INIT_JOYSTICK | SDL_INIT_HAPTIC | SDL_INIT_GAMECONTROLLER ) /* @} */ 可以根据自己代码里面的需求进行选择,例如只有音频播放,则可以选择SDL_INIT_AUDIO 2.SDL_INIT_AUDIO() 函数原型:int SDLCALL SDL_OpenAudio(SDL_AudioSpec * desired, SDL_AudioSpec * obtained); 根据提供的参数打开音频设备,其中desired 为期望的音频设备参数, obtained为实际的音频设备参数,一般将其置为NULL即可。 结构体参数如下: typedef struct SDL_AudioSpec { int freq; /**< DSP frequency -- samples per second */ SDL_AudioFormat format; /**< Audio data format */ Uint8 channels; /**< Number of channels: 1 mono, 2 stereo */ Uint8 silence; /**< Audio buffer silence value (calculated) */ Uint16 samples; /**< Audio buffer size in samples (power of 2) */ Uint16 padding; /**< Necessary for some compile environments */ Uint32 size; /**< Audio buffer size in bytes (calculated) */ SDL_AudioCallback callback; void *userdata; } SDL_AudioSpec; 主要的参数含义如下:
freq:采样率,例如44K、16K、48K等。
format:音频数据格式,常见的有如下格式: #define AUDIO_U16SYS AUDIO_U16LSB #define AUDIO_S16SYS AUDIO_S16LSB #define AUDIO_S32SYS AUDIO_S32LSB #define AUDIO_F32SYS AUDIO_F32LSB Channels:通道数 1 单声道 2 双声道
silence:用于将缓冲区设置为静默的值,由SDL_OpenAudio()计算
Samples:音频缓冲区所需大小,必需是2的n次方
Size:音频缓冲区的大小
Callback:音频回调函数
userdata:作为回调函数的第一个参数传递。 其中,音频回调函数的原型为: typedef void (SDLCALL * SDL_AudioCallback) (void *userdata, Uint8 * stream, int len); 当开始播放音频的时候,如果音频设备需要音频数据,就会调用此接口用来获取音频数据,
其中,stream是指向音频数据缓冲区的指针,len则为数据的长度。
另外,在SDL2中必须首先使用SDL_memset()将stream中的数据设置为0。
在这个回调函数中,既可以直接将音频数据赋给stream,也可以调用其他函数进行一些音频操作,例如调用SDL_MixAudio进行混音。 3.SDL_PauseAudio 函数原型: void SDLCALL SDL_PauseAudio(int pause_on); 这个函数用于暂停或者取消暂停音频回调处理,当我们开始播放音频的时候,需要pause_on设置为0即可,当设置为1的时候,则不会调用回调函数,即为静音状态。

三、测试代码

测试代码使用的是雷博的代码,在回调函数中没有采用混音,直接将音频数据赋值给stream。 /** * 最简单的SDL2播放音频的例子(SDL2播放PCM) * Simplest Audio Play SDL2 (SDL2 play PCM) * * 雷霄骅 Lei Xiaohua * leixiaohua1020@126.com * 中国传媒大学/数字电视技术 * Communication University of China / Digital TV Technology * http://blog.csdn.net/leixiaohua1020 * * 本程序使用SDL2播放PCM音频采样数据。SDL实际上是对底层绘图 * API(Direct3D,OpenGL)的封装,使用起来明显简单于直接调用底层 * API。 * * 函数调用步骤如下: * * [初始化] * SDL_Init(): 初始化SDL。 * SDL_OpenAudio(): 根据参数(存储于SDL_AudioSpec)打开音频设备。 * SDL_PauseAudio(): 播放音频数据。 * * [循环播放数据] * SDL_Delay(): 延时等待播放完成。 * * This software plays PCM raw audio data using SDL2. * SDL is a wrapper of low-level API (DirectSound). * Use SDL is much easier than directly call these low-level API. * * The process is shown as follows: * * [Init] * SDL_Init(): Init SDL. * SDL_OpenAudio(): Opens the audio device with the desired * parameters (In SDL_AudioSpec). * SDL_PauseAudio(): Play Audio. * * [Loop to play data] * SDL_Delay(): Wait for completetion of playback. */ #include //#include extern "C" { #include "sdl/SDL.h" }; //Buffer: //|-----------|-------------| //chunk-------pos---len-----| static Uint8 *audio_chunk; static Uint32 audio_len; static Uint8 *audio_pos; /* Audio Callback * The audio function callback takes the following parameters: * stream: A pointer to the audio buffer to be filled * len: The length (in bytes) of the audio buffer * */ void fill_audio(void *udata,Uint8 *stream,int len){ //SDL 2.0 SDL_memset(stream, 0, len); if(audio_len==0) /* Only play if we have data left */ return; len=(len>audio_len?audio_len:len); /* Mix as much data as possible */ //SDL_MixAudio(stream,audio_pos,len,SDL_MIX_MAXVOLUME); memcpy(stream, audio_pos, len); audio_pos += len; audio_len -= len; } int main(int argc, char* argv[]) { //Init if(SDL_Init(SDL_INIT_AUDIO | SDL_INIT_TIMER)) { printf( "Could not initialize SDL - %s ", SDL_GetError()); return -1; } //SDL_AudioSpec SDL_AudioSpec wanted_spec; wanted_spec.freq = 44100; wanted_spec.format = AUDIO_S16SYS; wanted_spec.channels = 2; wanted_spec.silence = 0; wanted_spec.samples = 1024; wanted_spec.callback = fill_audio; if (SDL_OpenAudio(&wanted_spec, NULL)<0){ printf("can't open audio. "); return -1; } FILE *fp=fopen("NocturneNo2inEflat_44.1k_s16le.pcm","rb+"); if(fp==NULL){ printf("cannot open this file "); return -1; } int pcm_buffer_size=4096; char *pcm_buffer=(char *)malloc(pcm_buffer_size); int data_count=0; //Play SDL_PauseAudio(0); while(1){ if (fread(pcm_buffer, 1, pcm_buffer_size, fp) != pcm_buffer_size){ // Loop fseek(fp, 0, SEEK_SET); fread(pcm_buffer, 1, pcm_buffer_size, fp); data_count=0; } printf("Now Playing %10d Bytes data. ",data_count); data_count+=pcm_buffer_size; //Set audio buffer (PCM data) audio_chunk = (Uint8 *) pcm_buffer; //Audio buffer length audio_len =pcm_buffer_size; audio_pos = audio_chunk; while(audio_len>0)//Wait until finish SDL_Delay(1); } free(pcm_buffer); SDL_Quit(); return 0; }