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

本文讲述了如何解决在使用SDL库进行音频播放时遇到的问题,涉及SDL_Init、SDL_OpenAudioDevice和回调函数fill_audio_data的使用。核心问题在于无效的audio_device_id,通过错误日志追踪和源码分析,最终发现需调整混音函数,从而修复了播放无声的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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 后就能正常播放声音了。

<think>好的,用户在使用SDL2录制音频时遇到了效果差的问题,需要帮助解决。首先,我得了解SDL2音频录制的基本流程,可能的问题点在哪里。常见的问题可能包括采样率设置不当、缓冲区大小不合适、音频格式不匹配,或者回调函数处理不当。 首先,用户可能没有正确配置音频参数。SDL2中需要设置采样率、格式、通道数和缓冲区大小。比如,采样率太低会导致声音失真,通常44100Hz是常用的。格式方面,使用16位有符号整数(AUDIO_S16)比较常见,如果用户用了8位或者浮点可能有问题。 接下来,缓冲区大小的设置也很关键。缓冲区太小可能导致频繁回调,处理不及时,出现卡顿或丢失数据;太大则增加延迟。需要建议用户尝试不同的缓冲区大小,比如1024或2048样本,找到平衡点。 另外,用户可能在回调函数中没有正确处理数据。SDL音频回调需要快速处理数据,不能进行阻塞操作或复杂计算。如果用户在这里做了文件写入或其他耗时操作,就会导致数据丢失,影响录音质量。应该建议用户将数据复制到另一个线程处理,或者使用队列缓冲。 还有可能是音频设备初始化的问题。比如,用户可能没有检查设备支持的格式,直接用了不兼容的参数。这时候需要提醒用户使用SDL_GetAudioDeviceSpec来获取设备支持的格式,并据此调整参数。 用户也可能没有处理音频数据的同步或对齐问题,导致杂音。比如,未正确管理缓冲区的读写指针,或者数据拷贝时出错。需要检查回调函数中的数据复制是否正确,是否覆盖了未处理的数据。 最后,建议用户使用音频分析工具检查录制的原始数据,确认问题出在SDL2的设置还是后续处理步骤。同时,查看SDL的错误日志,是否有初始化失败或参数不支持的提示。 总结下来,可能的解决步骤包括检查参数配置、调整缓冲区大小、优化回调函数、验证设备支持格式,以及调试数据是否正确处理。可能需要用户提供代码片段进一步分析具体问题所在。</think>使用SDL2进行音频录制时出现音质差的问题,通常与以下几个关键配置和实现细节相关。以下是排查和优化的步骤指南: --- ### 一、检查音频参数配置 ```c SDL_AudioSpec desired_spec = { .freq = 44100, // 采样率 (常用44.1kHz) .format = AUDIO_S16, // 16位有符号整数格式 .channels = 2, // 双声道 .samples = 2048, // 缓冲区样本数 .callback = audio_callback // 数据回调函数 }; ``` * **采样率**:确保`freq`与硬件支持的一致(通常44100或48000) * **格式**:优先使用`AUDIO_S16`(16位有符号),避免`AUDIO_U8`(8位无符号)的低精度 * **缓冲区大小**:`samples`值过小会导致频繁回调,建议尝试1024/2048/4096 --- ### 二、验证设备支持参数 ```c SDL_AudioSpec obtained_spec; SDL_OpenAudioDevice(NULL, 1, &desired_spec, &obtained_spec, SDL_AUDIO_ALLOW_FORMAT_CHANGE); if (obtained_spec.format != desired_spec.format) { printf("Format changed! Obtained format: %d\n", obtained_spec.format); } ``` * 使用`SDL_OpenAudioDevice`代替已废弃的`SDL_OpenAudio` * 检查返回的`obtained_spec`是否与预期参数一致 * 处理SDL自动调整参数的情况(如强制转换格式) --- ### 三、优化音频回调函数 ```c void audio_callback(void* userdata, Uint8* stream, int len) { // 立即将数据复制到外部缓冲区 memcpy(&g_audio_buffer[g_buffer_idx], stream, len); g_buffer_idx += len; // 避免在回调中执行耗时操作(如文件写入) } ``` * **数据拷贝**:必须快速将`stream`数据转移到线程安全的缓冲区 * **避免阻塞**:禁止在回调中执行文件I/O、内存分配等操作 * **缓冲区管理**:建议使用环形缓冲区(ring buffer)避免数据竞争 --- ### 四、典型问题解决方案 #### 1. 高频噪音/失真 - 检查格式转换是否正确(如16位转float时的归一化) - 验证是否收到`AUDIO_S16`但按`AUDIO_U8`处理数据 #### 2. 断断续续/丢帧 - 增大`SDL_AudioSpec.samples`值(适当增加延迟换取稳定性) - 检查回调函数执行时间是否过长 #### 3. 低音量/无声 - 确认录音设备权限已开启 - 检查是否初始化了麦克风设备(而非输出设备) --- ### 五、调试建议 1. **原始数据检查**:将录制的PCM数据写入文件,用Audacity等工具检查波形 ```bash audacity --import-raw /path/to/raw.pcm --encoding=signed-integer -b=16 -c=2 -r=44100 ``` 2. **日志调试**: ```c SDL_Log("Obtained spec: freq=%d, format=%d, channels=%d", obtained_spec.freq, obtained_spec.format, obtained_spec.channels); ``` 3. **最小化测试**:尝试单声道、低采样率(16000Hz)配置验证基础功能 --- 如果需要更具体的帮助,建议提供您的音频初始化代码和回调函数实现,我们可以进一步分析问题根源。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值