SDL2学习:二、音频播放流程及相关api(SDL音频播放的两种模式)

SDL音频播放两种模式

SDL 播放音频文件有两种方法,可以理解成 推(push) 和 拉(pull) 两种模式。
推 就是我们主动向设备缓冲区填充 Buffer ,而 拉 就是由设备拉取 Buffer 填充到缓冲区。

两种方式优缺点对比:

  • 官方推荐使用推送模式
  • 推送延迟较大(推荐前几帧抛弃,待系统稳定时再播放)
  • 推送没有办法精确的进行音视频同步。
  • 推送并不是一来数据就播放。
  • 拉去模式:回调函数和主函数会同时操作缓冲区,可能会发生冲突

SDL音频播放模式一(回调函数——拉取模式)

  • 目前最常用的一种方式

1、初始化

  • 1)初始化SDL
  • 2)根据参数(SDL_AudioSpec)打开音频设备

2、循环播放数据

  • 1)播放音频数据
  • 2)延时等待播放完成

【拉取模式具体流程api和结构体】

【一、初始化】SDL_Init()

使用SDL_Init()初始化SDL。该函数可以确定希望激活的子系统。SDL_Init()函数原型如下:

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

有关SDL_Init()有一点需要注意:初始化的时候尽量做到“够用就好”,而不要用SDL_INIT_EVERYTHING。因为有些情况下使用SDL_INIT_EVERYTHING会出现一些不可预知的问题。例如,在MFC应用程序中播放纯音频,如果初始化SDL的时候使用SDL_INIT_EVERYTHING,那么就会出现听不到声音的情况。后来发现,去掉了SDL_INIT_VIDEO之后,问题才得以解决。

【二、配置音频参数 并 打开音频设备】SDL_OpenAudio()

使用SDL_OpenAudio()打开音频设备。该函数需要传入一个SDL_AudioSpec的结构体。DL_OpenAudio()的原型如下。
【函数原型】

int SDLCALL SDL_OpenAudio(SDL_AudioSpec * desired,
                                          SDL_AudioSpec * obtained);

【参数分析】

  • desired:期望的参数。
  • obtained:实际音频设备的参数,一般情况下设置为NULL即可。
SDL_AudioSpec结构体
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:音频数据的采样率。常用的有48000,44100等。
format:音频数据的格式。举例几种格式:
AUDIO_U16SYS:Unsigned 16-bit samples
AUDIO_S16SYS:Signed 16-bit samples
AUDIO_S32SYS:32-bit integer samples
AUDIO_F32SYS:32-bit floating point samples
channels:声道数。例如单声道取值为1,立体声取值为2。
silence:设置静音的值。
samples:音频缓冲区中的采样个数,要求必须是2的n次方。
padding:考虑到兼容性的一个参数。
size:音频缓冲区的大小,以字节为单位。
callback:填充音频缓冲区的回调函数。
userdata:用户自定义的数据。

在这里记录一下填充音频缓冲区的回调函数的作用。当音频设备需要更多数据的时候会调用该回调函数。回调函数的格式要求如下。

void (SDLCALL * SDL_AudioCallback) (void *userdata, Uint8 * stream,
                                            int len);

回调函数的参数含义如下所示。
userdata:SDL_AudioSpec结构中的用户自定义数据,一般情况下可以不用。
stream:该指针指向需要填充的音频缓冲区。
len:音频缓冲区的大小(以字节为单位)。
在回调函数中可以使用SDL_MixAudio()完成混音等工作。众所周知SDL2和SDL1.x关于视频方面的API差别很大。但是SDL2和SDL1.x关于音频方面的API是一模一样的。唯独在回调函数中,SDL2有一个地方和SDL1.x不一样:SDL2中必须首先使用SDL_memset()将stream中的数据设置为0。

【三、循环播放数据】

【1)播放音频数据】
使用SDL_PauseAudio()可以播放音频数据。SDL_PauseAudio()的原型如下。

void SDLCALL SDL_PauseAudio(int pause_on)

当pause_on设置为0的时候即可开始播放音频数据。设置为1的时候,将会播放静音的值
【2)延时等待播放完成】
这一步就是延时等待音频播放完毕了。使用像SDL_Delay()这样的延时函数即可

SDL音频播放模式二(推送模式)

【一、初始化】SDL_Init() 与模式一相同

使用SDL_Init()初始化SDL。该函数可以确定希望激活的子系统。SDL_Init()函数原型如下:

int SDLCALL SDL_Init(Uint32 flags)

【二、设置音频参数并打开音频设备】与模式一不同

1、设置播放音频参数

使用和模式一相同的结构体
示例:

	/* 音频初始化 */
	SDL_AudioSpec wanted_spec;
	wanted_spec.freq = audFormat->sampleRate;
	wanted_spec.format = AUDIO_S16SYS;
	wanted_spec.channels = audFormat->channelNum;
	wanted_spec.silence = 0;	// 设置静音的值
	wanted_spec.samples = audFormat->frameLength;
	wanted_spec.callback = nullptr; // 推送模式播放音频
	wanted_spec.userdata = NULL;
2、打开音频设备

使用SDL_OpenAudioDevice 方法打开一个音频设备。

函数声明:

SDL_AudioDeviceID SDL_OpenAudioDevice(
                          const char *device,
                          int iscapture,
                          const SDL_AudioSpec *desired,
                          SDL_AudioSpec *obtained,
                          int allowed_changes);

参数说明:

  • *device: 设备名称,可以设为NULL,打开默认设备
  • iscapture: 0表示播放,非0表示录制
  • *desired: desired 和 obtained 都是 SDL_AudioSpec 类型的。
  • *obtained: 我们传入 desired 指定的音频参数,但不一定是 SDL 支持的,所以 SDL 会返回一个它支持的参数信息放在 obtained 里面。
  • allowed_changes:

返回值:

  • 返回一个有效的设备 ID,成功时 > 0,失败时返回 0; 调用 SDL_GetError() 获取更多信息。
  • 为了与 SDL 1.2 兼容,这将永远不会返回 1,因为 SDL 为旧版 SDL_OpenAudio() 函数保留了该 ID。
  • 所以成功返回值不小于2。

完整示例如下:

SDL_AudioSpec audioSpec;
    audioSpec.freq = 44100;
    audioSpec.format = AUDIO_S16SYS;
    audioSpec.channels = 2;
    audioSpec.silence = 0;
    audioSpec.samples = 1024;
    audioSpec.callback = nullptr; // 因为是推模式,所以这里为 nullptr

    SDL_AudioDeviceID deviceId;
    if ((deviceId = SDL_OpenAudioDevice(nullptr,0,&audioSpec, nullptr,SDL_AUDIO_ALLOW_ANY_CHANGE)) < 2){
        cout << "open audio device failed " << endl;
        return -1;
    }

【3、播放或暂停】

使用 SDL_PauseAudioDevice 方法去播放或者暂停音乐

函数声明:

void SDL_PauseAudioDevice(SDL_AudioDeviceID dev,
                          int pause_on);

参数说明:

  • dev: a device opened by SDL_OpenAudioDevice()
  • pause_on: non-zero to pause, 0 to unpause

【4、主动向设备缓冲区填充 Buffer 】关键

使用 SDL_QueueAudio 向设备缓冲区填充Buffer,理论上填充之后处理buffer不会影响音频播放
SDL2 不支持平面音频。在对音频进行排队之前,您需要将平面音频格式重新采样为非平面音频格式
函数声明:

int SDL_QueueAudio(SDL_AudioDeviceID dev, const void *data, Uint32 len);

参数说明:

  • dev: 前面打开的设备id
  • *data: 数据排队到设备供以后播放
  • len: 数据字节数,不是采样点数

返回值:

  • 成功时返回 0,失败时返回负错误代码

示例:

int bufferSize = 4096;
    char* buffer = (char *)malloc(bufferSize);
    // 省略中间代码
    num =  fread(buffer,1,bufferSize,pFile);
    if (num){
        // 填充
        SDL_QueueAudio(deviceId,buffer,bufferSize);
    }

不过这里有要注意的地方,并不是填充了一下 Buffer 就马上会有声音播放出来的,要多填充一些才会有声音播放。

另外,当播放声音时,必须要让程序不能退出,因为音频播放并不是一个阻塞当前主线程的方法,填充完数据就不管了的话,是听不到声音的。要么加个 SDL_Delay 方法要么就把 SDL_QueueAudio 方法放在接受消息队列信息的循环中,我采用的就是后者。

【5、设备缓冲队列相关操作】

【SDL_ClearQueuedAudio】
函数功能:

  • 丢弃所有等待发送到硬件的排队音频数据

函数声明:

void SDL_ClearQueuedAudio(SDL_AudioDeviceID dev);

参数说明:

  • dev: 设备ID

【SDL_GetQueuedAudioSize】
函数功能:

  • 获取正在排队的音频字节数

函数声明:

Uint32 SDL_GetQueuedAudioSize(SDL_AudioDeviceID dev);

参数说明:

  • 设备ID

返回值:

  • Returns the number of bytes (not samples!) of queued audio.

【SDL_DequeueAudio】
函数功能:

  • 在非回调设备上取消更多音频排队。

函数声明:

Uint32 SDL_DequeueAudio(SDL_AudioDeviceID dev, void *data, Uint32 len);

参数说明:

  • dev: 取消音频排队的设备 ID
  • *data: 指向应将音频数据复制到何处的指针
  • len: (数据)指向的字节数

返回值:

  • 返回已取消排队的字节数,该字节数可能小于请求的字节数

如果您希望在非回调播放设备上对音频进行排队以进行输出,则需要改用SDL_QueueAudio()。如果SDL_DequeueAudio() 与播放设备一起使用,它将始终返回 0。

【6、暂停和关闭操作】

【关闭SDL,采用SDL_CloseAudioDevice】
函数说明:

void SDL_CloseAudioDevice(SDL_AudioDeviceID dev);

【其他api】

void SDL_LockAudioDevice(SDL_AudioDeviceID dev);

void SDL_UnlockAudioDevice(SDL_AudioDeviceID dev);

参考

SDL2音频播放
SDL推送模式播放音频

posted @ 2022-08-03 14:32  小超不挑食  阅读(2489)  评论(0编辑  收藏  举报