Play sound with OpenAL(Stream)
我需要播放从网络获取的声音。每 10 毫秒,需要 882 字节的声音(单声道,16 位,44100 Hz)。并这样做:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 |
while (!ExitKey)
{ boost::system::error_code error; size_t len = VoiceSocket–>read_some(boost::asio::buffer(buf), error); if (len==0) { continue; } alGenSources(1, &alSource); alGenBuffers(1, &alSampleSet); alBufferData(alSource, AL_FORMAT_MONO16, buf.data(), buf.size(), 44100); alSourcei(alSource, AL_BUFFER, alSampleSet); alSourcePlay(alSource); } |
但是我的声音没有播放。在 buf 变量中我有声音,因为如果我将它发送到文件,我在文件中有声音。
您的代码未显示 alListener。这似乎有点太明显的错误,但是没有听众,没有听到声音,否则发布的代码应该会产生一些声音,假设有来自套接字的非零数据。
编辑:再看一遍,你有一个小错字,后果很严重。 alBufferData(alSource, …) 应该是 alBufferData(alSampleSet, …)。您要填充缓冲区对象,而不是源。否则,您正在播放一个空缓冲区,不出所料,它不会产生声音。
还要注意,您的代码通常效率低下,无法按预期工作。
您绝对不想为每 10 毫秒收到的每个数据包生成一个新的源(和缓冲区)。这会产生一些声音,但不是你想要的。即使没有网络抖动(您将不可避免地遇到),如果没有可听见的间隙,它也不会很好地播放。
在进入循环之前生成一个源和至少 3 个缓冲区(最好多几个,5 或 6 个)。只要您在流式传输,一个源就会一直存在,并且会在这段时间内持续播放。
在进入循环之前至少接收 2 个缓冲区。按照您收到它们的顺序将这些缓冲区排队到源 (alSourceQueueBuffers) 并开始播放。每当您收到新数据包时,将数据读入新缓冲区(从已分配缓冲区的空闲列表中拉出)并将其排队。
当缓冲区空闲时,取消队列并将它们重新循环到您的空闲列表中(首先查询!)。
这样,OpenAL 在播放声音时总是有一些数据要读取,这对于获得良好的结果至关重要。永远不要让它饿死。
- 谢谢。如果我在 alBufferData 中将 alSource 编辑为 alSampleSet,我有声音。但我不了解缓冲区队列。我需要用缓冲区做什么?如果我将第一个缓冲区排队并在第一次迭代中播放,第二次在第二次等中播放。我在扬声器中重复点击。
- 这些点击就是我所说的(“一些声音,但不是你想要的”)。发生点击是因为 OpenAL 在播放时用完了数据——你绝不能让这种情况发生。在开始播放之前先接收几个缓冲区并将这些缓冲区排队以提前给 AL 一些数据。然后(当源仍在播放时)当新数据包进来时,也将它们排队。永远不要停止播放,永远不要切换源,永远不要停止喂食。您的播放可能会在 20-50 毫秒后开始,但这很好,没有人会注意到。然而,每个人都注意到了那些重复的点击。
- 如果我理解你的解释,我就失败了。我的代码如下。
我写了这段代码:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
void Sound::GoChat()
{ device = alcOpenDevice(NULL); // ofstream file; file.open(“TESTSPEAKER”, std::ios_base::binary); context = alcCreateContext(device, NULL); alcMakeContextCurrent(context); // alGetError(); char *alBuffer; ALenum alFormatBuffer; ALsizei alFreqBuffer; long alBufferLen; ALboolean alLoop; unsigned int alSource; unsigned int alSampleSet[3]; boost::array <char, 882> buf; int NumberOfSampleSet = 0; alGenSources(1, &alSource); alGenBuffers(3, alSampleSet); // alBufferData(alSampleSet[0], AL_FORMAT_MONO16, buf.data(), buf.size(), 44100); alBufferData(alSampleSet[1], AL_FORMAT_MONO16, buf.data(), buf.size(), 44100); alBufferData(alSampleSet[2], AL_FORMAT_MONO16, buf.data(), buf.size(), 44100); alSourceQueueBuffers(alSource, 3, alSampleSet); // while (!ExitKey) { alSourceUnqueueBuffers(alSource, 3, alSampleSet); boost::system::error_code error; size_t len = VoiceSocket–>read_some(boost::asio::buffer(buf), error); if (len==0) { continue; } file.write(buf.data(), 882); alBufferData(alSampleSet[0], AL_FORMAT_MONO16, buf.data(), buf.size(), 44100); alSourcei(alSource, AL_BUFFER, alSampleSet[0]); |
但这也行不通(
- 上面对 n = 3 的 alSourceUnqueueBuffers 调用仅在处理了 3 个缓冲区后才有效(请阅读文档!)。这使得首先使用 3 个缓冲区是荒谬的,因为那样你就会遇到与只使用 1 个缓冲区相同的问题。您希望将多个缓冲区排队,始终将一个或多个缓冲区保留在队列中,并在排队新缓冲区时一次最多取消排队 1 个(如果已完成)。这可以通过轮询状态来完成,或者简单地使用参数 1 调用 alSourceUnqueueBuffers。此调用要么失败(缓冲区仍在播放),要么成功(缓冲区未排队)。
- 此外,您只是覆盖了描述符数组,它需要更复杂一些。如果您不想使用队列(可能是矫枉过正),您需要至少在数组中维护一个递增和回绕的索引,因此您将每个未排队的描述符写回一个空的地方(而不是覆盖东西)。
- 行。我需要队列 3 缓冲区。并且在 3 个缓冲区中相同的数据?为什么?或者我在队列中需要 3 个不同的缓冲区,并且在迭代后取消队列 1 个缓冲区,然后排队新的 1 个缓冲区?
- 如果可以,请给我一个小例子,因为我可以理解这件事。我真的不明白这个:(。
因此,如果原始缓冲区中有有效数据,则需要使用 alGetError 检查 OpenAL 函数,以查看其中一些函数是否失败并找出它们失败的原因。从问题描述中我没有看到这个基本检查已经完成。
- 如果我在 alSourcei(…) 之后粘贴这个;我有 AL_INVALID_NAME 错误。
来源:https://www.codenong.com/14932004/