//----------------------------------------------------------------------------- // Copyright (c) 2012 GarageGames, LLC // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to // deal in the Software without restriction, including without limitation the // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or // sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. //----------------------------------------------------------------------------- #include "sfx/media/sfxSndStream.h" #include "core/stream/fileStream.h" #include "core/stream/stream.h" #include "console/console.h" #define MAX_BUFFER 4096 bool SFXSndStream::_readHeader() { mCurPos = 0; mBytesRead = 0; dMemset(&sfinfo, 0, sizeof(SF_INFO)); vio.get_filelen = sndFileLen; vio.seek = sndSeek; vio.read = sndRead; vio.write = sndWrite; vio.tell = sndTell; vio_data.length = 0; vio_data.offset = 0; vio_data.data = mStream; vio_data.sampleBlockAlign = 1; vio_data.byteBlockAlign = 0; if ((sndFile = sf_open_virtual(&vio, SFM_READ, &sfinfo, &vio_data)) == NULL) { Con::printf("SFXSndStream - _readHeader failed: %s", sf_strerror(sndFile)); return false; } S32 bitsPerSample = 16; switch (sfinfo.format & SF_FORMAT_SUBMASK) { case SF_FORMAT_PCM_S8: case SF_FORMAT_PCM_U8: bitsPerSample = 8; mSampleType = Sample_Int8; // Still decode using sf_readf_short() break; case SF_FORMAT_PCM_16: bitsPerSample = 16; mSampleType = Sample_Int16; break; case SF_FORMAT_PCM_24: case SF_FORMAT_PCM_32: case SF_FORMAT_FLOAT: case SF_FORMAT_DOUBLE: case SF_FORMAT_VORBIS: case SF_FORMAT_OPUS: case SF_FORMAT_ALAC_20: case SF_FORMAT_ALAC_24: case SF_FORMAT_ALAC_32: case 0x0080/*SF_FORMAT_MPEG_LAYER_I*/: case 0x0081/*SF_FORMAT_MPEG_LAYER_II*/: case 0x0082/*SF_FORMAT_MPEG_LAYER_III*/: bitsPerSample = 32; mSampleType = Sample_Float; break; case SF_FORMAT_IMA_ADPCM: bitsPerSample = 16; mSampleType = Sample_IMA4; break; case SF_FORMAT_MS_ADPCM: bitsPerSample = 16; mSampleType = Sample_MSADPCM; break; default: bitsPerSample = 16; mSampleType = Sample_Int16; break; } //------------------------------------------------------------ // Block alignment logic for ADPCM formats //------------------------------------------------------------ int byteBlockAlign = 0; int sampleBlockAlign = 1; if (mSampleType == Sample_IMA4 || mSampleType == Sample_MSADPCM) { SF_CHUNK_INFO inf = { "fmt ", 4, 0, NULL }; SF_CHUNK_ITERATOR* iter = sf_get_chunk_iterator(sndFile, &inf); if (!iter || sf_get_chunk_size(iter, &inf) != SF_ERR_NO_ERROR || inf.datalen < 14) mSampleType = Sample_Int16; else { uint8_t* fmtbuf = (uint8_t*)malloc(inf.datalen); inf.data = fmtbuf; if (sf_get_chunk_data(iter, &inf) != SF_ERR_NO_ERROR) mSampleType = Sample_Int16; else { byteBlockAlign = fmtbuf[12] | (fmtbuf[13] << 8); int ch = sfinfo.channels; if (mSampleType == Sample_IMA4) sampleBlockAlign = (byteBlockAlign / ch - 4) / 4 * 8 + 1; else sampleBlockAlign = (byteBlockAlign / ch - 7) * 2 + 2; } dFree(fmtbuf); } } //------------------------------------------------------------ // Commit alignment to class + VIO data //------------------------------------------------------------ if (mSampleType == Sample_Int16) { sampleBlockAlign = 1; byteBlockAlign = sfinfo.channels * 2; } else if (mSampleType == Sample_Float) { sampleBlockAlign = 1; byteBlockAlign = sfinfo.channels * 4; } vio_data.sampleBlockAlign = sampleBlockAlign; vio_data.byteBlockAlign = byteBlockAlign; mSampleBlockAlign = sampleBlockAlign; mByteBlockAlign = byteBlockAlign; mFormat.set(sfinfo.channels, bitsPerSample * sfinfo.channels, sfinfo.samplerate, mSampleType); mSamples = sfinfo.frames; return true; } void SFXSndStream::_close() { if (!sndFile) return; sf_close(sndFile); } SFXSndStream* SFXSndStream::create(Stream* stream) { SFXSndStream* sfxStream = new SFXSndStream(); if (sfxStream->open(stream, true)) return sfxStream; delete sfxStream; return NULL; } void SFXSndStream::reset() { vio_data.offset = 0; } U32 SFXSndStream::read(U8* buffer, U32 length) { if (!sndFile) { Con::errorf("SFXSndStream - read: Called on uninitialized stream."); return 0; } const U32 bytesPerFrame = mFormat.getBytesPerSample(); U32 framesToRead = length / bytesPerFrame; // Enforce sample block alignment (important for ADPCM / compressed) if (mSampleBlockAlign > 1) framesToRead -= (framesToRead % mSampleBlockAlign); if (framesToRead == 0) return 0; U32 framesRead = 0; switch (mSampleType) { case SFXSampleType::Sample_Int8: framesRead = sf_readf_int(sndFile, (int*)buffer, framesToRead); break; case SFXSampleType::Sample_Int16: framesRead = sf_readf_short(sndFile, (short*)buffer, framesToRead); break; case SFXSampleType::Sample_Float: framesRead = sf_readf_float(sndFile, (float*)buffer, framesToRead); break; case SFXSampleType::Sample_IMA4: case SFXSampleType::Sample_MSADPCM: framesRead = sf_read_raw(sndFile, buffer, framesToRead); break; default: break; } if (framesRead != framesToRead) { Con::errorf("SFXSndStream - read: %s", sf_strerror(sndFile)); } // (convert to frames) - number of frames available < MAX_BUFFER? reset if (((getPosition() / mFormat.getBytesPerSample()) - sfinfo.frames) < MAX_BUFFER) { // reset stream setPosition(0); } return framesRead * mFormat.getBytesPerSample(); } bool SFXSndStream::isEOS() const { return (Parent::isEOS() || (mStream && vio_data.length == vio_data.offset)); } U32 SFXSndStream::getPosition() const { return vio_data.offset; } void SFXSndStream::setPosition(U32 offset) { sf_seek(sndFile, offset / mFormat.getBytesPerSample(), SEEK_SET); } sf_count_t SFXSndStream::sndSeek(sf_count_t offset, int whence, void* user_data) { VIO_DATA* vf = (VIO_DATA*)user_data; Stream* stream = reinterpret_cast(vf->data); switch (whence) { case SEEK_SET: vf->offset = offset; break; case SEEK_CUR: vf->offset = vf->offset + offset; break; case SEEK_END: vf->offset = vf->length - offset; break; default: break; }; return stream->setPosition(vf->offset) ? 0 : -1; } sf_count_t SFXSndStream::sndRead(void* ptr, sf_count_t count, void* user_data) { VIO_DATA* vf = (VIO_DATA*)user_data; Stream* stream = reinterpret_cast(vf->data); if (vf->offset + count > vf->length) count = vf->length - vf->offset; stream->read((U32)(count), ptr); vf->offset += count; return count; } sf_count_t SFXSndStream::sndWrite(const void* ptr, sf_count_t count, void* user_data) { return sf_count_t(); } sf_count_t SFXSndStream::sndTell(void* user_data) { VIO_DATA* vf = (VIO_DATA*)user_data; return vf->offset; } sf_count_t SFXSndStream::sndFileLen(void* user_data) { VIO_DATA* vf = (VIO_DATA*)user_data; Stream* stream = reinterpret_cast(vf->data); vf->length = stream->getStreamSize(); return vf->length; }