Use SDL_QueueAudio() instead callbacks on filling sound buffer

Fixes some issues when sound buffer get exhausted before refilling.
This commit is contained in:
Azamat H. Hackimov 2024-07-12 16:04:03 +03:00
parent 87f6ef009f
commit 976f00a1f3
6 changed files with 29 additions and 47 deletions

View File

@ -21,15 +21,6 @@
namespace D3 { namespace D3 {
void MovieSoundDevice::SDLAudioCallback(void *userdata, unsigned char *stream, int len) {
auto device = static_cast<MovieSoundDevice *>(userdata);
for (int i = 0; i < len; i += 2) {
int16_t sample = device->m_sound_buffer->front();
device->m_sound_buffer->pop_front();
memcpy(&stream[i], &sample, 2);
}
}
MovieSoundDevice::MovieSoundDevice(int sample_rate, uint16_t sample_size, uint8_t channels, uint32_t buf_size, MovieSoundDevice::MovieSoundDevice(int sample_rate, uint16_t sample_size, uint8_t channels, uint32_t buf_size,
bool is_compressed) { bool is_compressed) {
SDL_AudioFormat format = (sample_size == 2) ? AUDIO_S16LSB : AUDIO_U8; SDL_AudioFormat format = (sample_size == 2) ? AUDIO_S16LSB : AUDIO_U8;
@ -37,8 +28,8 @@ MovieSoundDevice::MovieSoundDevice(int sample_rate, uint16_t sample_size, uint8_
spec.freq = sample_rate; spec.freq = sample_rate;
spec.format = format; spec.format = format;
spec.channels = channels; spec.channels = channels;
spec.size = 4096; spec.size = buf_size;
spec.callback = &MovieSoundDevice::SDLAudioCallback; spec.callback = nullptr;
spec.userdata = this; spec.userdata = this;
m_device_id = SDL_OpenAudioDevice(nullptr, 0, &spec, nullptr, 0); m_device_id = SDL_OpenAudioDevice(nullptr, 0, &spec, nullptr, 0);
@ -48,16 +39,20 @@ MovieSoundDevice::MovieSoundDevice(int sample_rate, uint16_t sample_size, uint8_
MovieSoundDevice::~MovieSoundDevice() { MovieSoundDevice::~MovieSoundDevice() {
if (m_device_id > 0) { if (m_device_id > 0) {
SDL_CloseAudioDevice(m_device_id); SDL_CloseAudioDevice(m_device_id);
m_device_id = 0;
} }
m_sound_buffer.reset();
} }
void MovieSoundDevice::FillBuffer(char *stream, int len) const {
SDL_QueueAudio(m_device_id, stream, len);
};
void MovieSoundDevice::Play() { SDL_PauseAudioDevice(m_device_id, 0); } void MovieSoundDevice::Play() { SDL_PauseAudioDevice(m_device_id, 0); }
void MovieSoundDevice::Stop() { SDL_PauseAudioDevice(m_device_id, 1); } void MovieSoundDevice::Stop() { SDL_PauseAudioDevice(m_device_id, 1); }
void MovieSoundDevice::Lock() { SDL_LockAudioDevice(m_device_id); }; void MovieSoundDevice::Lock() { SDL_LockAudioDevice(m_device_id); }
void MovieSoundDevice::Unlock() { SDL_UnlockAudioDevice(m_device_id); }; void MovieSoundDevice::Unlock() { SDL_UnlockAudioDevice(m_device_id); }
} // namespace D3 } // namespace D3

View File

@ -49,19 +49,17 @@ public:
[[nodiscard]] bool IsInitialized() const { return m_device_id > 0; } [[nodiscard]] bool IsInitialized() const { return m_device_id > 0; }
/** /**
* Callback for filling SDL audio buffer * Fill internal audio stream to be played
* @param userdata pointer to instance of this class * @param stream source buffer
* @param stream stream that will be filled on callback * @param len length of source buffer
* @param len length of stream
*/ */
void static SDLAudioCallback(void *userdata, unsigned char *stream, int len); void FillBuffer(char *stream, int len) const;
void Play() override; void Play() override;
void Stop() override; void Stop() override;
void Lock() override; void Lock() override;
void Unlock() override; void Unlock() override;
using ISoundDevice::GetBuffer;
using ISoundDevice::IsCompressed; using ISoundDevice::IsCompressed;
}; };

View File

@ -16,8 +16,7 @@
*/ */
#include <algorithm> #include <algorithm>
#include <deque> #include <cstring>
#include <memory>
#include "byteswap.h" #include "byteswap.h"
@ -62,26 +61,23 @@ static int16_t getWord(unsigned char **fin) {
return value; return value;
} }
void mveaudio_process(std::unique_ptr<std::deque<int16_t>> &buffer, unsigned char *data, bool is_compressed) { void mveaudio_process(char *buffer, unsigned char *data, bool is_compressed) {
if (is_compressed) { if (is_compressed) {
int nCurOffsets[2]; int nCurOffsets[2];
data += 4; data += 4;
int swath = getWord(&data) / 2; int samples = getWord(&data) / 2;
// Fill predictors
nCurOffsets[0] = getWord(&data); nCurOffsets[0] = getWord(&data);
nCurOffsets[1] = getWord(&data); nCurOffsets[1] = getWord(&data);
buffer->push_back((int16_t)std::clamp(nCurOffsets[0], -32768, 32767));
buffer->push_back((int16_t)std::clamp(nCurOffsets[1], -32768, 32767));
for (int i = 0; i < swath; i++) { for (int i = 0; i < samples; i++) {
nCurOffsets[i & 1] += audio_exp_table[data[i]]; nCurOffsets[i & 1] = std::clamp(nCurOffsets[i & 1] + audio_exp_table[data[i]], -32768, 32767);
buffer->push_back((int16_t)std::clamp(nCurOffsets[i & 1], -32768, 32767)); memcpy(buffer + i * 2, &nCurOffsets[i & 1], 2);
} }
} else { } else {
data += 2; data += 2;
int samples = getWord(&data); int samples = getWord(&data);
for (int i = 0; i < samples; i++) { memcpy(buffer, &data, samples * 2);
buffer->push_back(getWord(&data));
}
} }
} }

View File

@ -24,6 +24,6 @@
* @param data input data * @param data input data
* @param is_compress true if input data is compressed * @param is_compress true if input data is compressed
*/ */
void mveaudio_process(std::unique_ptr<std::deque<int16_t>> &buffer, unsigned char *data, bool is_compressed = true); void mveaudio_process(char *buffer, unsigned char *data, bool is_compressed = true);
#endif /* INCLUDED_MVE_AUDIO_H */ #endif /* INCLUDED_MVE_AUDIO_H */

View File

@ -184,7 +184,7 @@ static int create_audiobuf_handler(unsigned char major, unsigned char minor, uns
is_compressed = true; is_compressed = true;
} }
snd_ds = std::make_unique<D3::MovieSoundDevice>(sample_rate, sample_size, channels, desired_buffer, is_compressed); snd_ds = std::make_unique<D3::MovieSoundDevice>(sample_rate, sample_size, channels, 4096, is_compressed);
#endif #endif
return 1; return 1;
@ -204,18 +204,17 @@ static int audio_data_handler(unsigned char major, unsigned char minor, unsigned
static const int selected_chan = 1; static const int selected_chan = 1;
if (snd_ds->IsInitialized()) { if (snd_ds->IsInitialized()) {
int chan = get_ushort(data + 2); int chan = get_ushort(data + 2);
int size = get_ushort(data + 4);
if (chan & selected_chan) { if (chan & selected_chan) {
void *buf = malloc(size);
if (major == MVE_OPCODE_AUDIOFRAMEDATA) { if (major == MVE_OPCODE_AUDIOFRAMEDATA) {
snd_ds->Lock(); mveaudio_process((char *)buf, data, snd_ds->IsCompressed());
mveaudio_process(snd_ds->GetBuffer(), data, snd_ds->IsCompressed());
snd_ds->Unlock();
} else { } else {
// SILENCE, MORTALS! // SILENCE, MORTALS!
int nsamp = get_ushort(data + 4); memset(data, 0, size);
snd_ds->Lock();
snd_ds->GetBuffer()->insert(snd_ds->GetBuffer()->end(), nsamp, 0);
snd_ds->Unlock();
} }
snd_ds->FillBuffer((char *)buf, size);
free(buf);
} }
} }
#endif #endif

View File

@ -20,19 +20,15 @@
#define LIBMVE_SOUND_INTERFACE_H_ #define LIBMVE_SOUND_INTERFACE_H_
#include <cstdint> #include <cstdint>
#include <deque>
#include <memory>
namespace D3 { namespace D3 {
/// Abstract class for sound device. /// Abstract class for sound device.
class ISoundDevice { class ISoundDevice {
protected: protected:
std::unique_ptr<std::deque<int16_t>> m_sound_buffer;
bool m_is_compressed = false; bool m_is_compressed = false;
public: public:
ISoundDevice() { this->m_sound_buffer = std::make_unique<std::deque<int16_t>>(); };
/// Play stream /// Play stream
virtual void Play() {}; virtual void Play() {};
/// Stop stream /// Stop stream
@ -42,8 +38,6 @@ public:
/// Unlock buffer /// Unlock buffer
virtual void Unlock() {}; virtual void Unlock() {};
/// Get access to sound buffer
std::unique_ptr<std::deque<int16_t>> &GetBuffer() { return m_sound_buffer; }
/// Check if encoded sound is compressed /// Check if encoded sound is compressed
[[nodiscard]] bool IsCompressed() const { return m_is_compressed; }; [[nodiscard]] bool IsCompressed() const { return m_is_compressed; };