Avoid crashing when a OGG file is broken (thanks to @kblaschke !)

Also added a name tag to Audio for logging so that it's easier to find the audio asset that's causing it
This commit is contained in:
Kae 2024-07-29 09:23:27 +10:00
parent 8b1a2d6f0c
commit e9e87a1c3c
4 changed files with 66 additions and 46 deletions

View File

@ -1241,7 +1241,7 @@ shared_ptr<Assets::AssetData> Assets::loadImage(AssetPath const& path) const {
shared_ptr<Assets::AssetData> Assets::loadAudio(AssetPath const& path) const { shared_ptr<Assets::AssetData> Assets::loadAudio(AssetPath const& path) const {
return unlockDuring([&]() { return unlockDuring([&]() {
auto newData = make_shared<AudioData>(); auto newData = make_shared<AudioData>();
newData->audio = make_shared<Audio>(open(path.basePath)); newData->audio = make_shared<Audio>(open(path.basePath), path.basePath);
newData->needsPostProcessing = newData->audio->compressed(); newData->needsPostProcessing = newData->audio->compressed();
return newData; return newData;
}); });

View File

@ -310,49 +310,54 @@ void Mixer::read(int16_t* outBuffer, size_t frameCount, ExtraMixFunction extraMi
m_mixBuffer[i] = 0; m_mixBuffer[i] = 0;
ramt += silentSamples * channels; ramt += silentSamples * channels;
} }
ramt += audioInstance->m_audio.resample(channels, sampleRate, m_mixBuffer.ptr() + ramt, bufferSize - ramt, pitchMultiplier); try {
while (ramt != bufferSize && !finished) { ramt += audioInstance->m_audio.resample(channels, sampleRate, m_mixBuffer.ptr() + ramt, bufferSize - ramt, pitchMultiplier);
// Only seek back to the beginning and read more data if loops is < 0 while (ramt != bufferSize && !finished) {
// (loop forever), or we have more loops to go, otherwise, the sample is // Only seek back to the beginning and read more data if loops is < 0
// finished. // (loop forever), or we have more loops to go, otherwise, the sample is
if (audioInstance->m_loops != 0) { // finished.
audioInstance->m_audio.seekSample(0); if (audioInstance->m_loops != 0) {
ramt += audioInstance->m_audio.resample(channels, sampleRate, m_mixBuffer.ptr() + ramt, bufferSize - ramt, pitchMultiplier); audioInstance->m_audio.seekSample(0);
if (audioInstance->m_loops > 0) ramt += audioInstance->m_audio.resample(channels, sampleRate, m_mixBuffer.ptr() + ramt, bufferSize - ramt, pitchMultiplier);
--audioInstance->m_loops; if (audioInstance->m_loops > 0)
} else { --audioInstance->m_loops;
finished = true; } else {
} finished = true;
}
if (audioInstance->m_clockStop && *audioInstance->m_clockStop < sampleEndTime) {
for (size_t s = 0; s < ramt / channels; ++s) {
unsigned millisecondsInBuffer = (s * 1000) / sampleRate;
auto sampleTime = sampleStartTime + millisecondsInBuffer;
if (sampleTime > *audioInstance->m_clockStop) {
float volume = 0.0f;
if (audioInstance->m_clockStopFadeOut > 0)
volume = 1.0f - (float)(sampleTime - *audioInstance->m_clockStop) / (float)audioInstance->m_clockStopFadeOut;
if (volume <= 0) {
for (size_t c = 0; c < channels; ++c)
m_mixBuffer[s * channels + c] = 0;
} else {
for (size_t c = 0; c < channels; ++c)
m_mixBuffer[s * channels + c] *= volume;
}
} }
} }
if (sampleEndTime > *audioInstance->m_clockStop + audioInstance->m_clockStopFadeOut) if (audioInstance->m_clockStop && *audioInstance->m_clockStop < sampleEndTime) {
finished = true; for (size_t s = 0; s < ramt / channels; ++s) {
} unsigned millisecondsInBuffer = (s * 1000) / sampleRate;
auto sampleTime = sampleStartTime + millisecondsInBuffer;
if (sampleTime > *audioInstance->m_clockStop) {
float volume = 0.0f;
if (audioInstance->m_clockStopFadeOut > 0)
volume = 1.0f - (float)(sampleTime - *audioInstance->m_clockStop) / (float)audioInstance->m_clockStopFadeOut;
for (size_t s = 0; s < ramt / channels; ++s) { if (volume <= 0) {
float vol = lerp((float)s / frameCount, beginVolume * groupVolume * audioStopVolBegin, endVolume * groupEndVolume * audioStopVolEnd); for (size_t c = 0; c < channels; ++c)
for (size_t c = 0; c < channels; ++c) { m_mixBuffer[s * channels + c] = 0;
float sample = m_mixBuffer[s * channels + c] * vol * audioState.positionalChannelVolumes[c] * audioInstance->m_volume.value; } else {
int16_t& outSample = outBuffer[s * channels + c]; for (size_t c = 0; c < channels; ++c)
outSample = clamp(sample + outSample, -32767.0f, 32767.0f); m_mixBuffer[s * channels + c] *= volume;
}
}
}
if (sampleEndTime > *audioInstance->m_clockStop + audioInstance->m_clockStopFadeOut)
finished = true;
} }
for (size_t s = 0; s < ramt / channels; ++s) {
float vol = lerp((float)s / frameCount, beginVolume * groupVolume * audioStopVolBegin, endVolume * groupEndVolume * audioStopVolEnd);
for (size_t c = 0; c < channels; ++c) {
float sample = m_mixBuffer[s * channels + c] * vol * audioState.positionalChannelVolumes[c] * audioInstance->m_volume.value;
int16_t& outSample = outBuffer[s * channels + c];
outSample = clamp(sample + outSample, -32767.0f, 32767.0f);
}
}
} catch (Star::AudioException const& e) {
Logger::error("Error reading audio '{}': {}", audioInstance->m_audio.name(), e.what());
finished = true;
} }
audioInstance->m_volume.value = audioStopVolEnd; audioInstance->m_volume.value = audioStopVolEnd;

View File

@ -239,17 +239,19 @@ public:
size_t readPartial(int16_t* buffer, size_t bufferSize) { size_t readPartial(int16_t* buffer, size_t bufferSize) {
int bitstream; int bitstream;
int read; int read = OV_HOLE;
// ov_read takes int parameter, so do some magic here to make sure we don't // ov_read takes int parameter, so do some magic here to make sure we don't
// overflow // overflow
bufferSize *= 2; bufferSize *= 2;
do {
#if STAR_LITTLE_ENDIAN #if STAR_LITTLE_ENDIAN
read = ov_read(&m_vorbisFile, (char*)buffer, bufferSize, 0, 2, 1, &bitstream); read = ov_read(&m_vorbisFile, (char*)buffer, bufferSize, 0, 2, 1, &bitstream);
#else #else
read = ov_read(&m_vorbisFile, (char*)buffer, bufferSize, 1, 2, 1, &bitstream); read = ov_read(&m_vorbisFile, (char*)buffer, bufferSize, 1, 2, 1, &bitstream);
#endif #endif
} while (read == OV_HOLE);
if (read < 0) if (read < 0)
throw AudioException("Error in Audio::read"); throw AudioException::format("Error in Audio::read ({})", read);
// read in bytes, returning number of int16_t samples. // read in bytes, returning number of int16_t samples.
return read / 2; return read / 2;
@ -349,7 +351,8 @@ private:
ExternalBuffer m_memoryFile; ExternalBuffer m_memoryFile;
}; };
Audio::Audio(IODevicePtr device) { Audio::Audio(IODevicePtr device, String name) {
m_name = name;
if (!device->isOpen()) if (!device->isOpen())
device->open(IOMode::Read); device->open(IOMode::Read);
@ -579,4 +582,12 @@ size_t Audio::resample(unsigned destinationChannels, unsigned destinationSampleR
} }
} }
String const& Audio::name() const {
return m_name;
}
void Audio::setName(String name) {
m_name = std::move(name);
}
} }

View File

@ -26,7 +26,7 @@ STAR_EXCEPTION(AudioException, StarException);
// instances is not expensive. // instances is not expensive.
class Audio { class Audio {
public: public:
explicit Audio(IODevicePtr device); explicit Audio(IODevicePtr device, String name = "");
Audio(Audio const& audio); Audio(Audio const& audio);
Audio(Audio&& audio); Audio(Audio&& audio);
@ -90,12 +90,16 @@ public:
int16_t* destinationBuffer, size_t destinationBufferSize, int16_t* destinationBuffer, size_t destinationBufferSize,
double velocity = 1.0); double velocity = 1.0);
String const& name() const;
void setName(String name);
private: private:
// If audio is uncompressed, this will be null. // If audio is uncompressed, this will be null.
CompressedAudioImplPtr m_compressed; CompressedAudioImplPtr m_compressed;
UncompressedAudioImplPtr m_uncompressed; UncompressedAudioImplPtr m_uncompressed;
ByteArray m_workingBuffer; ByteArray m_workingBuffer;
String m_name;
}; };
} }