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

Posted Time_Limit

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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 后就能正常播放声音了。

iPhone SDK SDL_openAudio 支持多任务

【中文标题】iPhone SDK SDL_openAudio 支持多任务【英文标题】:iPhone SDK SDL_openAudio with Multitasking Support 【发布时间】:2011-01-14 16:30:46 【问题描述】:

我正在使用 ffmpeg 播放来自在线实时 RTPS 流的音频(因为 Apple 不支持 rtsp 实时流)。

现在我将在后台播放我的 Stream。我在后台启动了一个线程并注册了音乐以获得后台支持。

当应用程序进入后台时,NSThread 会暂停,然后从后台返回后恢复。

如果我开始在使用 Apple 官方框架的应用程序中播放音乐(MP3-Stream),那么当应用程序进入后台时,两个流都会播放。

我能做些什么来解决这个问题?

【问题讨论】:

听起来像是音频会话问题 - 您是否使用过会话属性?见developer.apple.com/library/ios/#documentation/Audio/Conceptual/… 谢谢,这就是解决方案! @Till 将您的评论作为答案。你至少有 +15 等待 ;) 【参考方案1】:

好吧,根据大众的需求;),答案来了;

这听起来很像音频会话问题。您应该尝试调整一下会话属性:AudioSessionProgrammingGuide

【讨论】:

以上是关于SDL:升级 OpenAudio 至 OpenAudioDevice 后不能播放音频的主要内容,如果未能解决你的问题,请参考以下文章

iPhone SDK SDL_openAudio 支持多任务

ubuntu 下SDL 声音无法播放,我的电脑能正常听哥的。 Mix_OpenAudio: No available audio device

Failed to open the file:SDL_OpenAudio:DirectSoundCreate:NO audio device found 视频报错这是啥情况

流媒体开发16SDL-PCM播放

流媒体开发16SDL-PCM播放

SDL 音频在 Linux 上随机失真