fix: stream stops and dies on underflow

This commit is contained in:
MihailRis 2025-10-30 19:56:07 +03:00
parent ab7bf9c709
commit cf561e78a8
6 changed files with 60 additions and 27 deletions

View File

@ -121,14 +121,7 @@ function on_hud_open()
stream = PCMStream(44100, 1, 16) stream = PCMStream(44100, 1, 16)
stream:share("test-stream") stream:share("test-stream")
local bytes = Bytearray(44100 / 8) streamid = audio.play_stream_2d("test-stream", 2.0, 1.0, "ui")
for i=1,#bytes do
local x = math.sin(i * 0.08) * 1 + 0
bytes[i] = x
end
stream:feed(bytes)
audio.play_stream_2d("test-stream", 2.0, 1.0, "ui")
end end
function on_hud_render() function on_hud_render()

View File

@ -164,10 +164,11 @@ std::unique_ptr<Speaker> ALStream::createSpeaker(bool loop, int channel) {
for (uint i = 0; i < ALStream::STREAM_BUFFERS; i++) { for (uint i = 0; i < ALStream::STREAM_BUFFERS; i++) {
uint free_buffer = al->getFreeBuffer(); uint free_buffer = al->getFreeBuffer();
if (!preloadBuffer(free_buffer, loop)) { if (!preloadBuffer(free_buffer, loop)) {
break; unusedBuffers.push(free_buffer);
} } else {
AL_CHECK(alSourceQueueBuffers(free_source, 1, &free_buffer)); AL_CHECK(alSourceQueueBuffers(free_source, 1, &free_buffer));
} }
}
return std::make_unique<ALSpeaker>(al, free_source, PRIORITY_HIGH, channel); return std::make_unique<ALSpeaker>(al, free_source, PRIORITY_HIGH, channel);
} }
@ -213,11 +214,11 @@ void ALStream::unqueueBuffers(uint alsource) {
uint ALStream::enqueueBuffers(uint alsource) { uint ALStream::enqueueBuffers(uint alsource) {
uint preloaded = 0; uint preloaded = 0;
if (!unusedBuffers.empty()) { if (!unusedBuffers.empty()) {
uint first_buffer = unusedBuffers.front(); uint firstBuffer = unusedBuffers.front();
if (preloadBuffer(first_buffer, loop)) { if (preloadBuffer(firstBuffer, loop)) {
preloaded++; preloaded++;
unusedBuffers.pop(); unusedBuffers.pop();
AL_CHECK(alSourceQueueBuffers(alsource, 1, &first_buffer)); AL_CHECK(alSourceQueueBuffers(alsource, 1, &firstBuffer));
} }
} }
return preloaded; return preloaded;
@ -227,14 +228,14 @@ void ALStream::update(double delta) {
if (this->speaker == 0) { if (this->speaker == 0) {
return; return;
} }
auto p_speaker = audio::get_speaker(this->speaker); auto speaker = audio::get_speaker(this->speaker);
if (p_speaker == nullptr) { if (speaker == nullptr) {
this->speaker = 0; this->speaker = 0;
return; return;
} }
ALSpeaker* alspeaker = dynamic_cast<ALSpeaker*>(p_speaker); ALSpeaker* alspeaker = dynamic_cast<ALSpeaker*>(speaker);
assert(alspeaker != nullptr); assert(alspeaker != nullptr);
if (alspeaker->stopped) { if (alspeaker->manuallyStopped) {
this->speaker = 0; this->speaker = 0;
return; return;
} }
@ -245,11 +246,11 @@ void ALStream::update(double delta) {
uint preloaded = enqueueBuffers(alsource); uint preloaded = enqueueBuffers(alsource);
// alspeaker->stopped is assigned to false at ALSpeaker::play(...) // alspeaker->stopped is assigned to false at ALSpeaker::play(...)
if (p_speaker->isStopped() && !alspeaker->stopped) { //TODO: -V560 false-positive? if (speaker->isStopped() && !alspeaker->manuallyStopped) { //TODO: -V560 false-positive?
if (preloaded || dynamic_cast<MemoryPCMStream*>(source.get())) { if (preloaded) {
p_speaker->play(); speaker->play();
} else { } else if (isStopOnEnd()){
p_speaker->stop(); speaker->stop();
} }
} }
} }
@ -290,6 +291,14 @@ void ALStream::setTime(duration_t time) {
} }
} }
bool ALStream::isStopOnEnd() const {
return stopOnEnd;
}
void ALStream::setStopOnEnd(bool flag) {
stopOnEnd = flag;
}
ALSpeaker::ALSpeaker(ALAudio* al, uint source, int priority, int channel) ALSpeaker::ALSpeaker(ALAudio* al, uint source, int priority, int channel)
: al(al), priority(priority), channel(channel), source(source) { : al(al), priority(priority), channel(channel), source(source) {
} }
@ -356,7 +365,7 @@ void ALSpeaker::setLoop(bool loop) {
void ALSpeaker::play() { void ALSpeaker::play() {
paused = false; paused = false;
stopped = false; manuallyStopped = false;
auto p_channel = get_channel(this->channel); auto p_channel = get_channel(this->channel);
AL_CHECK(alSourcef( AL_CHECK(alSourcef(
source, source,
@ -372,7 +381,7 @@ void ALSpeaker::pause() {
} }
void ALSpeaker::stop() { void ALSpeaker::stop() {
stopped = true; manuallyStopped = true;
if (source) { if (source) {
AL_CHECK(alSourceStop(source)); AL_CHECK(alSourceStop(source));
@ -436,6 +445,11 @@ int ALSpeaker::getPriority() const {
return priority; return priority;
} }
bool ALSpeaker::isManuallyStopped() const {
return manuallyStopped;
}
static bool alc_enumeration_ext = false; static bool alc_enumeration_ext = false;
ALAudio::ALAudio(ALCdevice* device, ALCcontext* context) ALAudio::ALAudio(ALCdevice* device, ALCcontext* context)

View File

@ -58,6 +58,7 @@ namespace audio {
bool keepSource; bool keepSource;
char buffer[BUFFER_SIZE]; char buffer[BUFFER_SIZE];
bool loop = false; bool loop = false;
bool stopOnEnd = false;
bool preloadBuffer(uint buffer, bool loop); bool preloadBuffer(uint buffer, bool loop);
void unqueueBuffers(uint alsource); void unqueueBuffers(uint alsource);
@ -80,6 +81,10 @@ namespace audio {
void setTime(duration_t time) override; void setTime(duration_t time) override;
static inline constexpr uint STREAM_BUFFERS = 3; static inline constexpr uint STREAM_BUFFERS = 3;
bool isStopOnEnd() const override;
void setStopOnEnd(bool stopOnEnd) override;
}; };
class ALInputDevice : public InputDevice { class ALInputDevice : public InputDevice {
@ -117,7 +122,7 @@ namespace audio {
float volume = 0.0f; float volume = 0.0f;
public: public:
ALStream* stream = nullptr; ALStream* stream = nullptr;
bool stopped = true; bool manuallyStopped = true;
bool paused = false; bool paused = false;
uint source; uint source;
duration_t duration = 0.0f; duration_t duration = 0.0f;
@ -157,6 +162,8 @@ namespace audio {
bool isRelative() const override; bool isRelative() const override;
int getPriority() const override; int getPriority() const override;
bool isManuallyStopped() const override;
}; };
class ALAudio : public Backend { class ALAudio : public Backend {

View File

@ -61,6 +61,13 @@ namespace audio {
void setTime(duration_t time) override { void setTime(duration_t time) override {
} }
bool isStopOnEnd() const override {
return false;
}
void setStopOnEnd(bool stopOnEnd) override {
}
}; };
class NoAudio : public Backend { class NoAudio : public Backend {

View File

@ -464,11 +464,18 @@ void audio::update(double delta) {
speaker->update(channel); speaker->update(channel);
} }
if (speaker->isStopped()) { if (speaker->isStopped()) {
auto foundStream = streams.find(it->first);
if (foundStream == streams.end() ||
(!speaker->isManuallyStopped() &&
foundStream->second->isStopOnEnd())) {
streams.erase(it->first); streams.erase(it->first);
it = speakers.erase(it); it = speakers.erase(it);
} else { } else {
it++; it++;
} }
} else {
it++;
}
} }
} }

View File

@ -221,6 +221,9 @@ namespace audio {
/// @brief Set playhead to the selected time /// @brief Set playhead to the selected time
/// @param time selected time /// @param time selected time
virtual void setTime(duration_t time) = 0; virtual void setTime(duration_t time) = 0;
virtual bool isStopOnEnd() const = 0;
virtual void setStopOnEnd(bool stopOnEnd) = 0;
}; };
/// @brief Sound is an audio asset that supposed to support many /// @brief Sound is an audio asset that supposed to support many
@ -355,6 +358,8 @@ namespace audio {
inline bool isStopped() const { inline bool isStopped() const {
return getState() == State::stopped; return getState() == State::stopped;
} }
virtual bool isManuallyStopped() const = 0;
}; };
class Backend { class Backend {