#include "Base.h" #include "AudioBuffer.h" #include "FileSystem.h" #ifdef __ANDROID__ extern AAssetManager* __assetManager; #endif namespace gameplay { // Audio buffer cache static std::vector __buffers; #ifndef __ANDROID__ AudioBuffer::AudioBuffer(const char* path, ALuint buffer) : _filePath(path), _alBuffer(buffer) { } #else AudioBuffer::AudioBuffer(const char* path) : _filePath(path) { } #endif AudioBuffer::~AudioBuffer() { #ifndef __ANDROID__ if (_alBuffer) { alDeleteBuffers(1, &_alBuffer); _alBuffer = 0; } #endif } AudioBuffer* AudioBuffer::create(const char* path) { assert(path); // Search the cache for a stream from this file. unsigned int bufferCount = (unsigned int)__buffers.size(); AudioBuffer* buffer = NULL; for (unsigned int i = 0; i < bufferCount; i++) { buffer = __buffers[i]; if (buffer->_filePath.compare(path) == 0) { buffer->addRef(); return buffer; } } #ifndef __ANDROID__ ALuint alBuffer; ALCenum al_error; // Load audio data into a buffer. alGenBuffers(1, &alBuffer); al_error = alGetError(); if (al_error != AL_NO_ERROR) { LOG_ERROR_VARG("AudioBuffer alGenBuffers AL error: %d", al_error); alDeleteBuffers(1, &alBuffer); return NULL; } // Load sound file. FILE* file = FileSystem::openFile(path, "rb"); if (!file) { LOG_ERROR_VARG("Invalid audio buffer file: %s", path); goto cleanup; } // Read the file header char header[12]; if (fread(header, 1, 12, file) != 12) { LOG_ERROR_VARG("Invalid audio buffer file: %s", path); goto cleanup; } // Check the file format if (memcmp(header, "RIFF", 4) == 0) { if (!AudioBuffer::loadWav(file, alBuffer)) { LOG_ERROR_VARG("Invalid wave file: %s", path); goto cleanup; } } else if (memcmp(header, "OggS", 4) == 0) { if (!AudioBuffer::loadOgg(file, alBuffer)) { LOG_ERROR_VARG("Invalid ogg file: %s", path); goto cleanup; } } else { LOG_ERROR_VARG("Unsupported audio file: %s", path); } fclose(file); buffer = new AudioBuffer(path, alBuffer); // Add the buffer to the cache. __buffers.push_back(buffer); return buffer; cleanup: if (file) fclose(file); if (alBuffer) alDeleteBuffers(1, &alBuffer); return NULL; #else // Get the file header in order to determine the type. AAsset* asset = AAssetManager_open(__assetManager, path, AASSET_MODE_RANDOM); char header[12]; if (AAsset_read(asset, header, 12) != 12) { LOG_ERROR_VARG("Invalid audio buffer file: %s", path); return NULL; } // Get the file descriptor for the audio file. off_t start, length; int fd = AAsset_openFileDescriptor(asset, &start, &length); if (fd < 0) { LOG_ERROR_VARG("Failed to open file descriptor for asset: %s", path); return NULL; } AAsset_close(asset); SLDataLocator_AndroidFD data = {SL_DATALOCATOR_ANDROIDFD, fd, start, length}; // Set the appropriate mime type information. SLDataFormat_MIME mime; mime.formatType = SL_DATAFORMAT_MIME; std::string pathStr = path; if (memcmp(header, "RIFF", 4) == 0) { mime.mimeType = (SLchar*)"audio/x-wav"; mime.containerType = SL_CONTAINERTYPE_WAV; } else if (memcmp(header, "OggS", 4) == 0) { mime.mimeType = (SLchar*)"application/ogg"; mime.containerType = SL_CONTAINERTYPE_OGG; } else { LOG_ERROR_VARG("Unsupported audio file: %s", path); } buffer = new AudioBuffer(path); buffer->_data = data; buffer->_mime = mime; // Add the buffer to the cache. __buffers.push_back(buffer); return buffer; #endif } #ifndef __ANDROID__ bool AudioBuffer::loadWav(FILE* file, ALuint buffer) { unsigned char stream[12]; // Verify the wave fmt magic value meaning format. if (fread(stream, 1, 8, file) != 8 || memcmp(stream, "fmt ", 4) != 0 ) return false; unsigned int section_size; section_size = stream[7]<<24; section_size |= stream[6]<<16; section_size |= stream[5]<<8; section_size |= stream[4]; // Check for a valid pcm format. if (fread(stream, 1, 2, file) != 2 || stream[1] != 0 || stream[0] != 1) { LOG_ERROR("Unsupported audio file, not PCM format."); return false; } // Get the channel count (16-bit little-endian) int channels; if (fread(stream, 1, 2, file) != 2) return false; channels = stream[1]<<8; channels |= stream[0]; // Get the sample frequency (32-bit little-endian) ALuint frequency; if (fread(stream, 1, 4, file) != 4) return false; frequency = stream[3]<<24; frequency |= stream[2]<<16; frequency |= stream[1]<<8; frequency |= stream[0]; // The next 6 bytes hold the block size and bytes-per-second. // We don't need that info, so just read and ignore it. // We could use this later if we need to know the duration. if (fread(stream, 1, 6, file) != 6) return false; // Get the bit depth (16-bit little-endian) int bits; if (fread(stream, 1, 2, file) != 2) return false; bits = stream[1]<<8; bits |= stream[0]; // Now convert the given channel count and bit depth into an OpenAL format. ALuint format = 0; if (bits == 8) { if (channels == 1) format = AL_FORMAT_MONO8; else if (channels == 2) format = AL_FORMAT_STEREO8; } else if (bits == 16) { if (channels == 1) format = AL_FORMAT_MONO16; else if (channels == 2) format = AL_FORMAT_STEREO16; } else { LOG_ERROR_VARG("Incompatible format: (%d, %d)", channels, bits); return false; } // Check against the size of the format header as there may be more data that we need to read if (section_size > 16) { unsigned int length = section_size - 16; // extension size is 2 bytes if (fread(stream, 1, length, file) != length) return false; } if (fread(stream, 1, 4, file) != 4) return false; // read the next chunk, could be fact section or the data section if (memcmp(stream, "fact", 4) == 0) { if (fread(stream, 1, 4, file) != 4) return false; section_size = stream[3]<<24; section_size |= stream[2]<<16; section_size |= stream[1]<<8; section_size |= stream[0]; // read in the rest of the fact section if (fread(stream, 1, section_size, file) != section_size) return false; if (fread(stream, 1, 4, file) != 4) return false; } // should now be the data section which holds the decoded sample data if (memcmp(stream, "data", 4) != 0) { LOG_ERROR("WAV file has no data."); return false; } // Read how much data is remaining and buffer it up. unsigned int dataSize; fread(&dataSize, sizeof(int), 1, file); char* data = new char[dataSize]; if (fread(data, sizeof(char), dataSize, file) != dataSize) { LOG_ERROR("WAV file missing data."); SAFE_DELETE_ARRAY(data); return false; } alBufferData(buffer, format, data, dataSize, frequency); SAFE_DELETE_ARRAY(data); return true; } bool AudioBuffer::loadOgg(FILE* file, ALuint buffer) { OggVorbis_File ogg_file; vorbis_info* info; ALenum format; int result; int section; unsigned int size = 0; rewind(file); if ((result = ov_open(file, &ogg_file, NULL, 0)) < 0) { fclose(file); LOG_ERROR("Could not open Ogg stream."); return false; } info = ov_info(&ogg_file, -1); if (info->channels == 1) format = AL_FORMAT_MONO16; else format = AL_FORMAT_STEREO16; // size = #samples * #channels * 2 (for 16 bit) unsigned int data_size = ov_pcm_total(&ogg_file, -1) * info->channels * 2; char* data = new char[data_size]; while (size < data_size) { result = ov_read(&ogg_file, data + size, data_size - size, 0, 2, 1, §ion); if (result > 0) { size += result; } else if (result < 0) { SAFE_DELETE_ARRAY(data); LOG_ERROR("OGG file missing data."); return false; } else { break; } } if (size == 0) { SAFE_DELETE_ARRAY(data); LOG_ERROR("Unable to read OGG data."); return false; } alBufferData(buffer, format, data, data_size, info->rate); SAFE_DELETE_ARRAY(data); ov_clear(&ogg_file); // ov_clear actually closes the file pointer as well file = 0; return true; } #endif }