mirror of
https://github.com/minetest/minetest.git
synced 2024-11-23 08:03:45 +01:00
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:
parent
1d673ce075
commit
e12db0c182
@ -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
|
||||
|
@ -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(); }
|
||||
|
||||
|
@ -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.");
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user