diff --git a/src/audio/openal/al_audio_system.c b/src/audio/openal/al_audio_system.c index a2223b03f..104505843 100644 --- a/src/audio/openal/al_audio_system.c +++ b/src/audio/openal/al_audio_system.c @@ -6,9 +6,9 @@ #include "stb_vorbis.c" #include "al_audio_system.h" +#include "binary_utils.h" #include "data_win.h" #include "utils.h" -#include "wave.h" #include #include @@ -167,10 +167,25 @@ static void maInit(AudioSystem* audio, DataWin* dataWin, FileSystem* fileSystem) ma->fileSystem = fileSystem; ma->alDevice = alcOpenDevice(nullptr); + if (ma->alDevice == nullptr) { + fprintf(stderr, "Audio: Failed to open OpenAL device (error %d)\n", alGetError()); + return; + } + ma->alContext = alcCreateContext(ma->alDevice, nullptr); - alcMakeContextCurrent(ma->alContext); - if (ma->alDevice == nullptr || ma->alContext == nullptr) { - fprintf(stderr, "Audio: Failed to initialize OpenAL engine (error %d)\n", alGetError()); + if (ma->alContext == nullptr) { + fprintf(stderr, "Audio: Failed to create OpenAL context (error %d)\n", alGetError()); + alcCloseDevice(ma->alDevice); + ma->alDevice = nullptr; + return; + } + + if (!alcMakeContextCurrent(ma->alContext)) { + fprintf(stderr, "Audio: Failed to make OpenAL context current (error %d)\n", alGetError()); + alcDestroyContext(ma->alContext); + alcCloseDevice(ma->alDevice); + ma->alContext = nullptr; + ma->alDevice = nullptr; return; } @@ -278,6 +293,28 @@ static void maUpdate(AudioSystem* audio, float deltaTime) { } } +// Walk RIFF chunks in a WAV container to find the 'data' chunk payload offset and length. +// Returns pointer to the first byte of audio data, or nullptr on failure. +// audioDataLenOut receives the chunk's data length. +static const uint8_t* findWavDataChunk(const uint8_t* data, uint32_t dataSize, uint32_t* audioDataLenOut) { + if (data == nullptr || dataSize < 12) return nullptr; + if (data[0] != 'R' || data[1] != 'I' || data[2] != 'F' || data[3] != 'F') return nullptr; + uint32_t offset = 12; + while (offset + 8 <= dataSize) { + uint32_t chunkLen = data[offset+4] | (data[offset+5] << 8) | (data[offset+6] << 16) | (data[offset+7] << 24); + uint32_t chunkDataOffset = offset + 8; + uint32_t available = dataSize - chunkDataOffset; + if (chunkLen > available) chunkLen = available; + if (data[offset] == 'd' && data[offset+1] == 'a' && data[offset+2] == 't' && data[offset+3] == 'a') { + *audioDataLenOut = chunkLen; + return data + chunkDataOffset; + } + offset = chunkDataOffset + chunkLen; + if (offset & 1) offset++; + } + return nullptr; +} + static int32_t maPlaySound(AudioSystem* audio, int32_t soundIndex, int32_t priority, bool loop) { AlAudioSystem* ma = (AlAudioSystem*) audio; @@ -338,6 +375,15 @@ static int32_t maPlaySound(AudioSystem* audio, int32_t soundIndex, int32_t prior alGenSources(1, &slot->alSource); alGenBuffers(AL_STREAM_BUFFER_COUNT, slot->streamBuffers); + if (alGetError() != AL_NO_ERROR) { + fprintf(stderr, "Audio: alGenSources/alGenBuffers failed for stream\n"); + stb_vorbis_close(v); + free(slot->decodeScratch); + slot->streaming = false; + slot->vorbis = nullptr; + slot->decodeScratch = nullptr; + return -1; + } int primed = 0; for (int i = 0; AL_STREAM_BUFFER_COUNT > i; i++) { @@ -360,6 +406,10 @@ static int32_t maPlaySound(AudioSystem* audio, int32_t soundIndex, int32_t prior } else { alGenSources(1, &slot->alSource); alGenBuffers(1, &slot->alBuffer); + if (alGetError() != AL_NO_ERROR) { + fprintf(stderr, "Audio: alGenSources/alGenBuffers failed for sound %d\n", soundIndex); + return -1; + } bool isRegular = (sound->flags & AUDIO_ENTRY_FLAG_REGULAR) == AUDIO_ENTRY_FLAG_REGULAR; bool isEmbedded = (sound->flags & AUDIO_ENTRY_FLAG_IS_EMBEDDED) != 0; bool isCompressed = (sound->flags & AUDIO_ENTRY_FLAG_IS_COMPRESSED) != 0; @@ -373,31 +423,76 @@ static int32_t maPlaySound(AudioSystem* audio, int32_t soundIndex, int32_t prior } AudioEntry* entry = &ma->base.audioGroups[sound->audioGroup]->audo.entries[sound->audioFile]; - WAVFile wav = WAV_ParseFileData(entry->data); + + uint32_t channels = 0; + uint32_t sampleRate = 0; + uint32_t bitsPerSample = 0; + const uint8_t* audioData = nullptr; + uint32_t audioDataLen = 0; + bool hasWavHeader = (entry->dataSize >= 12 && + entry->data[0] == 'R' && entry->data[1] == 'I' && + entry->data[2] == 'F' && entry->data[3] == 'F'); + + if (hasWavHeader) { + channels = entry->data[22] | (entry->data[23] << 8); + sampleRate = entry->data[24] | (entry->data[25] << 8) | + (entry->data[26] << 16) | (entry->data[27] << 24); + bitsPerSample = entry->data[34] | (entry->data[35] << 8); + audioData = findWavDataChunk(entry->data, entry->dataSize, &audioDataLen); + } else { + channels = 2; + sampleRate = 44100; + bitsPerSample = 16; + audioData = entry->data; + audioDataLen = entry->dataSize; + } + + if (audioData == nullptr || audioDataLen == 0) { + fprintf(stderr, "Audio: No audio data for '%s'\n", sound->name); + return -1; + } + if (channels == 0 || sampleRate == 0 || bitsPerSample == 0) { + fprintf(stderr, "Audio: Invalid audio params for '%s' ch=%u rate=%u bits=%u\n", + sound->name, channels, sampleRate, bitsPerSample); + return -1; + } uint32_t format; - if (wav.header.number_of_channels == 1) + if (channels == 1) { - if (wav.header.bits_per_sample == 8) + if (bitsPerSample == 8) format = AL_FORMAT_MONO8; else format = AL_FORMAT_MONO16; } else { - if (wav.header.bits_per_sample == 8) + if (bitsPerSample == 8) format = AL_FORMAT_STEREO8; else format = AL_FORMAT_STEREO16; } - alBufferData( - slot->alBuffer, - format, - wav.data, - wav.data_length, - wav.header.sample_rate - ); +#if defined(IS_BIG_ENDIAN) + if (bitsPerSample == 16) { + int16_t* swapped = (int16_t*)safeMalloc(audioDataLen); + memcpy(swapped, audioData, audioDataLen); + for (uint32_t i = 0, n = audioDataLen / sizeof(int16_t); i < n; i++) { + swapped[i] = (int16_t)BinaryUtils_bswap16((uint16_t)swapped[i]); + } + alBufferData(slot->alBuffer, format, swapped, audioDataLen, sampleRate); + free(swapped); + } else { + alBufferData(slot->alBuffer, format, audioData, audioDataLen, sampleRate); + } +#else + alBufferData(slot->alBuffer, format, audioData, audioDataLen, sampleRate); +#endif + ALenum alErr = alGetError(); + if (alErr != AL_NO_ERROR) { + fprintf(stderr, "Audio: alBufferData failed for '%s' format=0x%x len=%u rate=%u err=%d\n", + sound->name, format, audioDataLen, sampleRate, alErr); + return -1; + } alSourcei(slot->alSource, AL_BUFFER, slot->alBuffer); - if(wav.data != NULL) free(wav.data); } else { // External audio: load from file char* path = resolveExternalPath(ma, sound); @@ -410,6 +505,11 @@ static int32_t maPlaySound(AudioSystem* audio, int32_t soundIndex, int32_t prior int sample_rate; short* data = NULL; int len = stb_vorbis_decode_filename(path, &channels, &sample_rate, &data); + if (len <= 0 || data == nullptr) { + fprintf(stderr, "Audio: stb_vorbis_decode failed for '%s' path='%s' len=%d\n", sound->name, path, len); + free(path); + return -1; + } alBufferData( slot->alBuffer, (channels == 2) ? AL_FORMAT_STEREO16 : AL_FORMAT_MONO16, @@ -417,6 +517,12 @@ static int32_t maPlaySound(AudioSystem* audio, int32_t soundIndex, int32_t prior len*channels*sizeof(uint16_t), sample_rate ); + if (alGetError() != AL_NO_ERROR) { + fprintf(stderr, "Audio: alBufferData failed for external '%s'\n", sound->name); + free(data); + free(path); + return -1; + } alSourcei(slot->alSource, AL_BUFFER, slot->alBuffer); if(data != NULL) free(data); free(path); @@ -451,6 +557,9 @@ static int32_t maPlaySound(AudioSystem* audio, int32_t soundIndex, int32_t prior ma->nextInstanceCounter++; alSourcePlay(slot->alSource); + if (alGetError() != AL_NO_ERROR) { + fprintf(stderr, "Audio: alSourcePlay failed for sound %d (stream=%d)\n", soundIndex, isStream); + } return slot->instanceId; } @@ -774,11 +883,29 @@ static float maGetSoundLength(AudioSystem* audio, int32_t soundOrInstance) { if (inAudo) { if (0 > sound->audioFile || (uint32_t) sound->audioFile >= ma->base.audioGroups[sound->audioGroup]->audo.count) return 0.0f; AudioEntry* entry = &ma->base.audioGroups[sound->audioGroup]->audo.entries[sound->audioFile]; - WAVFile wav = WAV_ParseFileData(entry->data); - float seconds = 0.0f; - if (wav.header.byte_rate > 0) seconds = (float) wav.header.data_size / (float) wav.header.byte_rate; - if (wav.data != nullptr) free(wav.data); - return seconds; + + bool hasWavHeader = (entry->dataSize >= 12 && + entry->data[0] == 'R' && entry->data[1] == 'I' && + entry->data[2] == 'F' && entry->data[3] == 'F'); + + if (hasWavHeader) { + uint32_t channels = entry->data[22] | (entry->data[23] << 8); + uint32_t sampleRate = entry->data[24] | (entry->data[25] << 8) | + (entry->data[26] << 16) | (entry->data[27] << 24); + uint32_t bitsPerSample = entry->data[34] | (entry->data[35] << 8); + uint32_t audioDataLen = 0; + findWavDataChunk(entry->data, entry->dataSize, &audioDataLen); + if (channels > 0 && sampleRate > 0 && bitsPerSample > 0 && audioDataLen > 0) { + uint32_t bytesPerSample = channels * (bitsPerSample / 8); + if (bytesPerSample > 0) + return (float) audioDataLen / (float)(sampleRate * bytesPerSample); + } + } else { + // Raw PCM: assume stereo 16-bit 44100 Hz + uint32_t audioDataLen = entry->dataSize; + return (float) audioDataLen / (float)(44100 * 2 * 2); + } + return 0.0f; } char* path = resolveExternalPath(ma, sound); diff --git a/src/audio/openal/wave.c b/src/audio/openal/wave.c deleted file mode 100644 index 032e2b29c..000000000 --- a/src/audio/openal/wave.c +++ /dev/null @@ -1,92 +0,0 @@ -// From https://gist.github.com/SteelPh0enix/e44d4a030dd8816309af84809ed75604 - -#include "wave.h" -#include -#include -#include "binary_utils.h" -#include "utils.h" - -// Convert 32-bit unsigned little-endian value to big-endian from byte array -static inline uint32_t little2big_u32(uint8_t const* data) { - return data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24); -} - -// Convert 16-bit unsigned little-endian value to big-endian from byte array -static inline uint16_t little2big_u16(uint8_t const* data) { - return data[0] | (data[1] << 8); -} - -// Copy n bytes from source to destination and terminate the destination with -// null character. Destination must be at least (amount + 1) bytes big to -// account for null character. -static inline void bytes_to_string(uint8_t const* source, - char* destination, - size_t amount) { - memcpy(destination, source, amount); - destination[amount] = '\0'; -} - -// Parse the header of WAV file and return WAVFile structure with header and -// pointer to data -WAVFile WAV_ParseFileData(uint8_t const* data) { - WAVFile file; - uint8_t const* data_ptr = data; - - bytes_to_string(data_ptr, file.header.file_id, 4); - data_ptr += 4; - - file.header.file_size = little2big_u32(data_ptr); - data_ptr += 4; - - bytes_to_string(data_ptr, file.header.format, 4); - data_ptr += 4; - - bytes_to_string(data_ptr, file.header.subchunk_id, 4); - data_ptr += 4; - - file.header.subchunk_size = little2big_u32(data_ptr); - data_ptr += 4; - - file.header.audio_format = little2big_u16(data_ptr); - data_ptr += 2; - - file.header.number_of_channels = little2big_u16(data_ptr); - data_ptr += 2; - - file.header.sample_rate = little2big_u32(data_ptr); - data_ptr += 4; - - file.header.byte_rate = little2big_u32(data_ptr); - data_ptr += 4; - - file.header.block_align = little2big_u16(data_ptr); - data_ptr += 2; - - file.header.bits_per_sample = little2big_u16(data_ptr); - data_ptr += 2; - - bytes_to_string(data_ptr, file.header.data_id, 4); - data_ptr += 4; - - file.header.data_size = little2big_u32(data_ptr); - data_ptr += 4; - - // memcpy so we don't byteswap the original data more than once - file.data = (uint8_t *)safeMalloc(file.header.data_size); - memcpy(file.data, data_ptr, file.header.data_size); - file.data_length = file.header.data_size; - - if (file.header.bits_per_sample == 16) { - for (size_t i = 0; i < (file.header.data_size / 2); i++) { - uint8_t* p = (uint8_t*)file.data + i * 2; - - uint16_t val = (uint16_t)(p[0] | (p[1] << 8)); - val = BinaryUtils_toLittle16(val); - - p[0] = (uint8_t)(val & 0xFF); - p[1] = (uint8_t)(val >> 8); - } - } - - return file; -} diff --git a/src/audio/openal/wave.h b/src/audio/openal/wave.h deleted file mode 100644 index 83e77fe6f..000000000 --- a/src/audio/openal/wave.h +++ /dev/null @@ -1,70 +0,0 @@ -// From https://gist.github.com/SteelPh0enix/e44d4a030dd8816309af84809ed75604 - -#ifndef WAVE_H -#define WAVE_H -#include -#include - -// Normalized WAV file header structure -typedef struct WAVHeader_t { - // Should contain the letters "RIFF" - char file_id[5]; - - // The size of entire file in bytes, minus 8 bytes for chunk_id and - // chunk_size. - uint32_t file_size; - - // Should contain the letters "WAVE" - char format[5]; - - // Should contain the letters "fmt " - char subchunk_id[5]; - - // 16 for PCM. This is the size of the rest of the sunchunk which follows this - // number. - uint32_t subchunk_size; - - // PCM = 1, values other than 1 indicate some form of compression - uint16_t audio_format; - - // mono = 1, stereo = 2, etc. - uint16_t number_of_channels; - - // self-explanatory - uint32_t sample_rate; - - // sample_rate * number of channels * bits per sample / 8 - uint32_t byte_rate; - - // number of channels * bits per sample / 8. Number of bytes for one sample - // including all channels. - uint16_t block_align; - - // self-explanatory. BITS, not BYTES. - uint16_t bits_per_sample; - - // Should contain the letters "data" - char data_id[5]; - - // number of samples * number of channels * bits per sample / 8 - // Actual number of bytes in the sound data. - uint32_t data_size; -} WAVHeader; - -// WAV file with header and pointer to data -typedef struct WAVFile_t { - // WAV header (copy re-constructed from the file with proper byte aligment) - WAVHeader header; - - // Pointer to audio data - uint8_t* data; - - // Length of data, copy of header.data_size field - uint32_t data_length; -} WAVFile; - -// Parse the header of WAV file and return WAVFile structure with header and -// pointer to data -WAVFile WAV_ParseFileData(uint8_t const* data); - -#endif