Sounds: Queue more than two buffers if pitch is high (#14515)

Pitch changes playback speed. So always enqueuing 2 buffers did not suffice
(and it was unnecessary complicated).
This commit is contained in:
DS 2024-04-07 22:06:34 +02:00 committed by GitHub
parent 1d673ce075
commit e12db0c182
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 55 additions and 40 deletions

View File

@ -26,6 +26,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "al_extensions.h"
#include "debug.h"
#include "sound_constants.h"
#include <cassert>
#include <cmath>
@ -77,33 +78,27 @@ PlayingSound::PlayingSound(ALuint source_id, std::shared_ptr<ISoundDataOpen> dat
warn_if_al_error("when creating non-streaming sound");
} else {
// Start with 2 buffers
ALuint buf_ids[2];
// Start with first buffer
// If m_next_sample_pos >= len_samples (happens only if not looped), one
// or both of buf_ids will be 0. Queuing 0 is a NOP.
// If m_next_sample_pos >= len_samples (happens only if not looped), buf0
// will be 0. Queuing 0 is a NOP.
auto [buf0, buf0_end, offset_in_buf0] = m_data->getOrLoadBufferAt(m_next_sample_pos);
buf_ids[0] = buf0;
m_next_sample_pos = buf0_end;
if (m_looping && m_next_sample_pos == len_samples)
m_next_sample_pos = 0;
auto [buf1, buf1_end, offset_in_buf1] = m_data->getOrLoadBufferAt(m_next_sample_pos);
buf_ids[1] = buf1;
m_next_sample_pos = buf1_end;
assert(offset_in_buf1 == 0);
alSourceQueueBuffers(m_source_id, 2, buf_ids);
alSourceQueueBuffers(m_source_id, 1, &buf0);
alSourcei(m_source_id, AL_SAMPLE_OFFSET, offset_in_buf0);
// We can't use AL_LOOPING because more buffers are queued later
// looping is therefore done manually
// We can't use AL_LOOPING because more buffers are queued later.
// Looping is therefore done manually.
// Sound is not dead if queue runs empty prematurely
m_stopped_means_dead = false;
warn_if_al_error("when creating streaming sound");
// Enqueue more buffers
stepStream(true);
}
// Set initial pos, volume, pitch
@ -129,23 +124,44 @@ PlayingSound::PlayingSound(ALuint source_id, std::shared_ptr<ISoundDataOpen> dat
setPitch(pitch);
}
bool PlayingSound::stepStream()
bool PlayingSound::stepStream(bool playback_speed_changed)
{
if (isDead())
return false;
// unqueue finished buffers
ALint num_unqueued_bufs = 0;
alGetSourcei(m_source_id, AL_BUFFERS_PROCESSED, &num_unqueued_bufs);
if (num_unqueued_bufs == 0)
return true;
// We always have 2 buffers enqueued at most
SANITY_CHECK(num_unqueued_bufs <= 2);
ALuint unqueued_buffer_ids[2];
alSourceUnqueueBuffers(m_source_id, num_unqueued_bufs, unqueued_buffer_ids);
// Unqueue finished buffers
ALint num_processed_bufs = 0;
alGetSourcei(m_source_id, AL_BUFFERS_PROCESSED, &num_processed_bufs);
if (num_processed_bufs == 0 && !playback_speed_changed)
return true; // Nothing to do
if (num_processed_bufs > 0) {
ALint num_to_unqueue = num_processed_bufs;
ALuint unqueued_buffer_ids[8];
while (num_to_unqueue > 8) {
alSourceUnqueueBuffers(m_source_id, 8, unqueued_buffer_ids);
num_to_unqueue -= 8;
}
alSourceUnqueueBuffers(m_source_id, num_to_unqueue, unqueued_buffer_ids);
}
// Fill up again
for (ALint i = 0; i < num_unqueued_bufs; ++i) {
// Find out how many buffers we want to enqueue
f32 pitch = 1.0f;
alGetSourcef(m_source_id, AL_PITCH, &pitch);
ALint num_queued_bufs = 0;
alGetSourcei(m_source_id, AL_BUFFERS_QUEUED, &num_queued_bufs);
// Min. length of untouched buffers
const f32 playback_left = MIN_STREAM_BUFFER_LENGTH * std::max(0, num_queued_bufs - 1);
// Max. time until next stepStream() call, see also [Streaming of sounds] in
// sound_constants.h.
// Multiplied by pitch because pitch makes playback faster than real time.
// (Does not account for doppler effect, if we had that.)
// +0.1 seconds to accommodate hickups.
const f32 playback_until_next_check = (2.0f * STREAM_BIGSTEP_TIME + 0.1f) * pitch;
const f32 playback_to_fill_up = std::max(0.0f, playback_until_next_check - playback_left);
const int num_bufs_to_enqueue = std::ceil(playback_to_fill_up / MIN_STREAM_BUFFER_LENGTH);
// Fill up
for (int i = 0; i < num_bufs_to_enqueue; ++i) {
if (m_next_sample_pos == m_data->m_decode_info.length_samples) {
// Reached end
if (m_looping) {
@ -256,4 +272,11 @@ f32 PlayingSound::getGain() noexcept
return gain;
}
void PlayingSound::setPitch(f32 pitch)
{
alSourcef(m_source_id, AL_PITCH, pitch);
if (isStreaming())
stepStream(true);
}
} // namespace sound

View File

@ -63,7 +63,7 @@ public:
DISABLE_CLASS_COPY(PlayingSound)
// return false means streaming finished
bool stepStream();
bool stepStream(bool playback_speed_changed = false);
// retruns true if it wasn't fading already
bool fade(f32 step, f32 target_gain) noexcept;
@ -77,7 +77,7 @@ public:
f32 getGain() noexcept;
void setPitch(f32 pitch) noexcept { alSourcef(m_source_id, AL_PITCH, pitch); }
void setPitch(f32 pitch);
bool isStreaming() const noexcept { return m_data->isStreaming(); }

View File

@ -89,14 +89,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
* In the worst case, a sound is stepped at the start of one bigstep and in the
* end of the next bigstep. So between two stepStream()-calls lie at most
* 2 * STREAM_BIGSTEP_TIME seconds.
* As there are always 2 sound buffers enqueued, at least one untouched full buffer
* is still available after the first stepStream().
* If we take a MIN_STREAM_BUFFER_LENGTH > 2 * STREAM_BIGSTEP_TIME, we can hence
* not run into an empty queue.
*
* The MIN_STREAM_BUFFER_LENGTH needs to be a little bigger because of dtime jitter,
* other sounds that may have taken long to stepStream(), and sounds being played
* faster due to Doppler effect.
* We ensure that there are always enough untouched full buffers left such that
* we do not run into an empty queue in this time period, see stepStream().
*
*/
@ -115,8 +109,6 @@ constexpr f32 STREAM_BIGSTEP_TIME = 0.3f;
// step duration for the OpenALSoundManager thread, in seconds
constexpr f32 SOUNDTHREAD_DTIME = 0.016f;
static_assert(MIN_STREAM_BUFFER_LENGTH > STREAM_BIGSTEP_TIME * 2.0f,
"See [Streaming of sounds].");
static_assert(SOUND_DURATION_MAX_SINGLE >= MIN_STREAM_BUFFER_LENGTH * 2.0f,
"There's no benefit in streaming if we can't queue more than 2 buffers.");