SDL:升级 OpenAudio 至 OpenAudioDevice 后不能播放音频

OpenAudio 与 OpenAudioDevice 简介

使用 SDL 库可以快捷实现 PCM 音频文件的播放功能。以播放 44100Hz,双声道,S16 格式的音频数据为例:

SDL_Init(SDL_INIT_TIMER|SDL_INIT_AUDIO); // 初始化

SDL_AudioSpec audio_spec;
audio_spec.format = AUDIO_S16SYS; // 设置格式
audio_spec.freq = 44100; // 设置频率
audio_spec.channels = 2; // 设置声道数
audio_spec.callback = fill_audio_data; // 设置回调函数
audio_spec.samples = 1024; // 设置缓冲区采样数量
audio_spec.userdata = nullptr;

SDL_OpenAudio(&audio_spec); // 打开音频设备

SDL_PauseAudio(0); // 开始播放

// 其他逻辑:如等待播放结束,清理数据,退出等。

其中回调函数 fill_audio_data 实现如下:

size_t cursor = 0; // 已播放的字节数
std::vector<uint8_t> pcm_data; // 设需播放的pcm数据都存放在 pcm_data 中。
bool end_flag = false;

void fill_audio_data(void *userdata, Uint8 *stream, int len) {
	SDL_memset(stream, 0, len);	// 先清理缓冲区,避免影响后续数据

	// 检查剩余数据是否可填满缓冲区,若不能则只填充一部分。
	if (len > pcm_data.size() - cursor) {
		len = pcm_data.size() - cursor;
	}
	
	if (len == 0) {
		// 无数据可播了,那就不填数据了。
		// 顺便更新下结束标志,以便主线程处理。
		end_flag = true; 
		return;
	}
	// 填充数据
	SDL_MixAudio(stream, &pcm_data[cursor], len, SDL_MIX_MAXVOLUME);
	
	// 更新已播放字节数
	cursor += len;
}

以上代码就是播放PCM功能的主要部分啦。但 SDL_OpenAudio 只能打开一个音频设备,对于同时需要多个音频设备的场景,需用 SDL_OpenAudioDevice

SDL_OpenAudioDevice 的函数签名如下:

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

一般使用方式如下:

SDL_AudioSpec audio_spec;
audio_spec.format = AUDIO_S16SYS; // 设置格式
audio_spec.freq = 44100; // 设置频率
audio_spec.channels = 2; // 设置声道数
audio_spec.callback = fill_audio_data; // 设置回调函数
audio_spec.samples = 1024; // 设置缓冲区采样数量
audio_spec.userdata = nullptr;

// SDL_OpenAudio(&audio_spec); // 打开音频设备
auto device_id = SDL_OpenAudioDevice(nullptr, 0, &audio_spec, nullptr, 0);
// SDL_PauseAudio(0); // 开始播放
SDL_PauseAudioDevice(device_id, 0); // 控制 device_id 管理的设备开始播放

问题描述

升级之后发现不能播放了。现象是:

  • 有返回值的函数的返回值都正常。如:
    • SDL_OpenAudioDevice 的返回值 device_id2
    • SDL_Init 的返回值为 0。
  • 回调函数 fill_audio_data 有调用。
  • 没有声音。。。

解决方案

度娘给的方案

度娘给了几个解决方案,尝试玩之后还是没的声音。。

1. SDL_Init 不要加 SDL_INIT_VIDEO

SDL_Init 不要加 SDL_INIT_VIDEO。即按如下方式调用:

SDL_Init(SDL_INIT_TIMER|SDL_INIT_AUDIO)

2. OpenAudioDeviceallowed_changes 参数设为 SDL_AUDIO_ALLOW_ANY_CHANGE

我的发现

浏览了 SDL_OpenAudioSDL_OpenAudioDevice 的源码,没发现端倪 ,其实也没看懂多少 。不过发现了一个函数 SDL_SetError,在很多错误返回的地方都会调用该函数记录原因。

于是,我抱着试一试的心态,在回调函数fill_audio_data 中加了一行日志:

void fill_audio_data(void *userdata, Uint8 *stream, int len) {
	std::cerr << SDL_GetError() << std::endl;
	...
}

然后就得到了内容为 Invalid audio device ID 的错误日志。通过这行日志在源码中找到了 get_audio_device 函数:

static SDL_AudioDevice *
get_audio_device(SDL_AudioDeviceID id)
{
    id--;
    if ((id >= SDL_arraysize(open_devices)) || (open_devices[id] == NULL)) {
        SDL_SetError("Invalid audio device ID");
        return NULL;
    }

    return open_devices[id];
}

然后使用 lldb 调试,打印调用栈。发现是在 SDL_audio.c:1787 行调进来的,id = 0。这就不太对啦,理论上 id = 2 (和 SDL_OpenAudioDevice 的返回值)才对。

继续阅读 SDL_audio.c:1787 的代码。发现里面写死了 id = 1,基本猜到 SDL_MixAudio 是和 SDL_OpenAudio 配套使用得了。

1783 void
1784 SDL_MixAudio(Uint8 * dst, const Uint8 * src, Uint32 len, int volume)
1785 {
1786     /* Mix the user-level audio format */
1787     SDL_AudioDevice *device = get_audio_device(1);
1788     if (device != NULL) {
1789         SDL_MixAudioFormat(dst, src, device->callbackspec.format, len, volume);
1790     }
1791 }

尝试改造回调函数,用 SDL_MixAudioFormat 替换 SDL_MixAudio

size_t cursor = 0; // 已播放的字节数
std::vector<uint8_t> pcm_data; // 设需播放的pcm数据都存放在 pcm_data 中。
bool end_flag = false;

void fill_audio_data(void *userdata, Uint8 *stream, int len) {
	SDL_memset(stream, 0, len);	// 先清理缓冲区,避免影响后续数据

	// 检查剩余数据是否可填满缓冲区,若不能则只填充一部分。
	if (len > pcm_data.size() - cursor) {
		len = pcm_data.size() - cursor;
	}
	
	if (len == 0) {
		// 无数据可播了,那就不填数据了。
		// 顺便更新下结束标志,以便主线程处理。
		end_flag = true; 
		return;
	}
	// 填充数据
	SDL_MixAudioFormat(stream,
		&pcm_data[cursor], audio_spec.format, len,
		SDL_MIX_MAXVOLUME);
	
	// 更新已播放字节数
	cursor += len;
}

如上,将 SDL_MixAudio 替换为 SDL_MixAudioFormat 后就能正常播放声音了。

  • 6
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值