/*********************************************************************************************** * * Copyright © DreamWorks Interactive. 1997 * * Contents: * CCAULoad - Abstract base class for loading audio data. * * All audio decompressors should be derived from this class and they must implement all * of the pure functions that this class defines. * * Bugs: * * To do: * * Notes: * The code contained within this file should be 100% thread safe, this is because * streamed audio will be loaded from within a thread. *********************************************************************************************** * * $Log:: /JP2_PC/Source/Lib/Audio/AudioLoad.cpp $ * * 31 10/06/98 2:43p Rwyatt * Stricly verify the TPA file when loading them * * 30 98/09/17 18:40 Speter * Commented out VER_TEST section for now, as it is obviously out of date, and was asserting. * * 29 98/09/17 17:49 Speter * Fixed some compile errors due to VER_TEST now being recognised. * * 28 9/08/98 9:37p Rwyatt * Now lists the sample ID of samples that are not found * * 27 8/26/98 4:45p Shernd * For audio databases in trespasser we don't want GENERIC_WRITE because of the CD * * 26 8/23/98 4:31p Rwyatt * Initialize handle database properly and correctly log memory * * 25 8/22/98 6:51p Pkeet * Added a return to avoid a crash in the 'CAudioDatabase' destructor. * * 24 8/21/98 12:12a Rwyatt * If ADPCM sample is smaller than decompression buffer then we must make the buffer smaller but * it still must be a multiple of the sample block length, * * 23 8/20/98 3:02p Rwyatt * Now calculates the correct size of the deompression buffers * * 22 8/05/98 11:15p Rwyatt * Added memory logs for all loader memory * * 21 8/04/98 4:02p Rwyatt * New version, CAU headers are now in the database sample map. This enables headers to be * obtained with disk activity. * File handles are all created with the database so no open file calls are required. * * 20 7/06/98 8:22p Rwyatt * Added a rest function to reset the audio databses to their initial state * * 19 6/24/98 3:24p Rwyatt * Assert for valid sample properties. * * 18 6/08/98 12:54p Rwyatt * Audio databases can now use a static file handle. This can only be used for databses that do * not support multiple loads. Use this flag if possible because it is more efficient as the * databse file does not have to be re-opened. * * 17 5/26/98 2:53p Rwyatt * Audio load is now asserts on any slight problem. If we get through the loader the Audio * databases must be valid. * * 16 5/22/98 7:09p Rwyatt * New element in collision structure for min time delay. * New version number - Not backwards compatible. * Loader no longer loads old versions. * * 15 5/19/98 10:14p Rwyatt * Moved FileSetPointer to CPP file. * Added support for version 0x121 of TPA file, this includes sample play times in the * index/indentifier list. * * 14 5/07/98 5:44p Rwyatt * Incremented audio database version to 0x120. This is so we can have a identifier count in the * header. The identifier loader code has changed to accomodate this but old TPA files will * still load. * * 13 3/22/98 5:02p Rwyatt * New binary audio collisions * New binary instance hashing with new instance naming * * 12 3/16/98 7:26p Rwyatt * Don't check in a test version! * * 11 3/16/98 7:25p Rwyatt * Fixed stray delete pointer that was causing a crash on exit. * * 10 3/16/98 5:41p Rwyatt * Keeps the audio database closed while it is not being used. * * 9 3/10/98 2:21p Rwyatt * Uses a duplicate file handle for the sample loaders when loading from a pack file. This * enables two streams to be read from the pack file at the same time without the file pointers * getting messed up. * * 8 3/10/98 1:59a Rwyatt * When loading subtitles the file pointer is now adjusted by the CAU start position. This was * causing packed CAU files with subtitles to crash. * * 7 3/09/98 10:54p Rwyatt * New class to handle sound databases. * Modified to read from a database file. * * 6 2/06/98 8:19p Rwyatt * new member to create a subtitle class * * 5 2/03/98 2:28p Rwyatt * Temp check in. * * 4 12/17/97 2:57p Rwyatt * Removed debug new * * 3 11/18/97 3:19p Rwyatt * If a non CAU file is loaded, the loader will return NULL. It used to Assert in debug mode. * * 2 11/14/97 7:19p Rwyatt * Added memory logs * * 1 11/13/97 11:40p Rwyatt * Initial implementation of the audio abstract loader. All loader classes are derived from this * class. * This class gives a consistent interface to any type of audio compression. * ***********************************************************************************************/ // DO NOT INCLUDE COMMON.HPP IN ANY SOUND FILES... #include "Audio.hpp" #include "lib/sys/memorylog.hpp" #include "AudioPCM.hpp" #include "AudioADPCM.hpp" #include "AudioVOICE.hpp" //********************************************************************************************** // setup debug new handler so we get the file and line number of leaks // /*#ifdef _DEBUG void* __cdecl operator new(size_t nSize, const char* lpszFileName, int nLine); #define DEBUG_NEW new(__FILE__, __LINE__) #define new DEBUG_NEW #endif*/ // //************************************** extern bool bIsTrespasser; //********************************************************************************************** // A Local min macro // #define MIN(p1,p2) (p1)<(p2)?(p1):(p2) // //************************************** //********************************************************************************************** // The constructor for a memory CAU file. // CCAULoad::CCAULoad ( SCAUHeader& cauheader ) // //************************************** { MEMLOG_ADD_COUNTER(emlSoundLoader,sizeof(CCAULoad)); u4CAUFileBase = 0; hFile = NULL; padatSource = NULL; pu1Base = (uint8*)&cauheader; pu1Samples = pu1Base + cauheader.u4Offset; u4SampleOffset = 0; u4SampleBlockLength = cauheader.u4DataSize; memcpy(&cauheaderLocal,&cauheader,sizeof(SCAUHeader)); SelectDecoder(); } //********************************************************************************************** // This is the file constructor which either uses direct file IO or memory maps the file into // virtual memory. Once the file is mapped into memory it is identical to the construtor above // but the disk based loader is radically different as it requires a block of memory that it // can pre-load into, this intermediate block of memory is only small and although is a small // performance hit for PCM streams it is essential for compressed streams because it enables // the decompress functions to be detached from the data source medium. // CCAULoad::CCAULoad ( CAudioDatabase* padat, // database holding the CAU file or NULL for a Win32 file HANDLE h_file, // file handle uint32 u4_fpos, // offset of the header in the specified file SCAUHeader& cau // the header at the above offset ) // //************************************** { MEMLOG_ADD_COUNTER(emlSoundLoader,sizeof(CCAULoad)); // Whole file based operations do not have an offset. u4CAUFileBase = u4_fpos; uint32 u4_block_size; padatSource = padat; cauheaderLocal = cau; hFile = h_file; pu1Base = NULL; u4DataChunkRemain = cauheaderLocal.u4DataSize; u4_block_size = cauheaderLocal.u4BlockAlignment; // // an early compression tool set the block alignment to 1 instead of zero for uncompressed // samples so this assert is just to catch those.. // Assert(u4_block_size!=1); // make an optimal sized load buffer that is smaller than the maximum allowed if (u4_block_size == 0) { u4_block_size = AUDIO_FILE_BUFFER; } else { // how many whole blocks can fit into the maximum allowed file buffer ??, allocate // enough memory for the maximum buffers to reduce disk loading setup. uint32 u4_blocks; if (AUDIO_FILE_BUFFERcauheaderLocal.u4DataSize) { u4_block_size = cauheaderLocal.u4DataSize; } pu1Samples = new uint8[u4_block_size]; MEMLOG_ADD_ADRSIZE(emlSoundLoader,pu1Samples); u4SampleBlockLength = u4_block_size; u4SampleOffset = 0xffffffff; // set the file pointer to the start of the data SetFilePointer(h_file,u4CAUFileBase + cauheaderLocal.u4Offset,NULL,FILE_BEGIN); SelectDecoder(); } //********************************************************************************************** // destructor needs to tidy up, close open files, remove possible memory mappings // and free any memory used by the loader. // CCAULoad::~CCAULoad ( ) // //************************************** { MEMLOG_SUB_COUNTER(emlSoundLoader,sizeof(CCAULoad)); // If we are file based we need to delete the loader memory if (hFile) { // delete the intermediate load buffer MEMLOG_SUB_ADRSIZE(emlSoundLoader,pu1Samples); delete pu1Samples; // If this loader was created with a filehandle from a database then close it with the // database from which it came. if (padatSource) { // Call the owing database to close the file, this may close the actual file handle if // it is a multiple stream file or it may do nothing. padatSource->CloseDatabaseFile(hFile); } else { // We have no database so this must be a normal file handle so close it that way. CloseHandle(hFile); } } } //********************************************************************************************** // Decides which of the decoding functions to use and what the silent value is. The silent value // changes depending on the bit depth of the sample, 8 bit samples are unsigned so 128 is the // silent value, 16 bit samples are signed so 0 is the silent value. // // The decoder function is pointed to by u4DecompressBlock, as the sample format cannot change // after it is loaded is seems pointless deciding which decoder to use on a per block basis. // void CCAULoad::SelectDecoder ( ) // //************************************** { // the sample must be mono or stereo.... Assert( (cauheaderLocal.u1Channels==1) || (cauheaderLocal.u1Channels==2) ); // 8 or 16 bit... Assert( (cauheaderLocal.u1Bits==8) || (cauheaderLocal.u1Bits==16) ); if (cauheaderLocal.u1Channels == 1) { // we are mono in some format if (cauheaderLocal.u1Bits == 8) { // mono 8bit u4DecompressBlock = &CCAULoad::u4DecompressMono8bit; u1SilentValue = 128; } else { // mono 16bit u4DecompressBlock = &CCAULoad::u4DecompressMono16bit; u1SilentValue = 0; } } else { // we are stereo.... // we are mono in some format if (cauheaderLocal.u1Bits == 8) { // mono 8bit u4DecompressBlock = &CCAULoad::u4DecompressStereo8bit; u1SilentValue = 128; } else { // mono 16bit u4DecompressBlock = &CCAULoad::u4DecompressStereo16bit; u1SilentValue = 0; } } } //********************************************************************************************** // This is the main abstracted loading function for audio. The caller requests the number of // decompressed bytes required and an address to put them at, the rest is handled by this // function and the audio compression classes that implement the virtual functions of this // class. // It is this function that decides where to get the data from, the current sources of data are: // Disk File // Memory // Memory mapped disk file // uint32 CCAULoad::u4LoadSampleData ( uint8* pu1_dst, uint32 u4_bytes, bool b_looped ) // //************************************** { uint32 u4_remain; uint32 u4_decode; uint32 u4_loaded; uint32 u4_start = u4_bytes; do { // get the number of bytes in the dcompression buffer u4_remain = u4GetRemainingData(); if (u4_remain == 0) { // there are zero bytes in the decompression buffer, so we need to pass another in. // if we are at the end we either need to go back to the start in the case when we // are looped or fill the remaining bytes with zero if (u4SampleOffset>=u4SampleBlockLength) { // are we a memory mapped or real memory CAU file, if we are then we are at the // end of the sample, if we are file based we are at the end of the current // block if (hFile == NULL) { // are we looped??? if (b_looped) { u4SampleOffset = 0; } else { // // this only gets used by streamed samples when they have finised and are // not looped. A stream can only be stopped from within its callback so // if the sample data ends between 2 callbacks (which it is very likely to // do) then we have to fill with zeros upto the next call back when the // stream will be stopped. // // The actual value used for the silent areas depens on the bit depth of // the sample data. If the data is 8bit then data is unsigned and 128 // is the silent value (zero power) and if the sample is 16bit then the // data is signed and zero is the value to use. // memset(pu1_dst,u1SilentValue,u4_bytes); pu1_dst += u4_bytes; u4_bytes = 0; continue; } } else { ReadFile(hFile, pu1Samples, MIN(u4SampleBlockLength,u4DataChunkRemain), (DWORD*)&u4_loaded, NULL); u4DataChunkRemain -= u4_loaded; // if we did not fill the whole block then we must be at the end of the chunk, so we either have to // read from the start and carry on or fill with zeros if (u4_loaded on the line below should not be required on the pointer to function line // below but VC is so bad that we have to insert it...... // This only took about an hour to figure out, 10 out of 10 to Microsoft! // u4_decode = (this->*u4DecompressBlock)(pu1_dst, u4_bytes); u4_bytes -= u4_decode; pu1_dst+=u4_decode; u4SampleOffset += u4_decode; } while (u4_bytes>0); return u4_start - u4_bytes; } //********************************************************************************************** // Create a sub title structure for this sample, if this sample does not have subtitle text or // is an old file version then return NULL // CAudioSubtitle* CCAULoad::pasubCreateSubtitle ( ) // //************************************** { CAudioSubtitle* pasub = NULL; SSubtitleHeader sth; uint32 u4_bytes; uint32 u4_filepos = SetFilePointer(hFile,0,NULL,FILE_CURRENT); // if the cau file is before subtitles then return NULL if (cauheaderLocal.u4Version < AUDIO_LOADER_SUBTITLE_VERSION) return NULL; // if the offset is zero then this file contains no subtitle if (cauheaderLocal.u4SubtitleOffset == 0) return NULL; dprintf("Sample has a subtitle...\n"); uint8* pu1_base; SetFilePointer(hFile,cauheaderLocal.u4SubtitleOffset+u4CAUFileBase,NULL,FILE_BEGIN); // read the header... if (ReadFile(hFile, &sth, sizeof(SSubtitleHeader), (DWORD*)&u4_bytes, NULL) == false) Assert(0); // allocate the block for the subtitle pu1_base = new uint8[sth.u4SubtitleLen + sizeof(uint32) ]; // copy the header into the block... *(SSubtitleHeader*)pu1_base = sth; // read the binary subtitle sections into the block. if (ReadFile(hFile, pu1_base+sizeof(SSubtitleHeader), sth.u4SubtitleLen + sizeof(uint32) - sizeof(SSubtitleHeader) , (DWORD*)&u4_bytes, NULL) == false) { Assert(0); } // create a sub title from this memory block, the sub title is responsible for deleting the // memory, hence the TRUE in the construction. pasub = new CAudioSubtitle((SSubtitleHeader*)pu1_base,true); // put the file pointer back to where it came from SetFilePointer(hFile,u4_filepos,NULL,FILE_BEGIN); return pasub; } //********************************************************************************************** // Static helper function to create a loader class from a CAU file in memory // CCAULoad* CCAULoad::pcauCreateAudioLoader ( SCAUHeader& cau ) // //************************************** { if (cau.u4Magic != 'ROBW') { dprintf("Non CAU file (resident) for audio sample\n"); return NULL; } Assert (cau.u4Version <= AUDIO_LOADER_VERSION); switch (cau.u1Compression) { case AU_COMPRESS_PCM: // PCM Audio return new CAudioPCM(cau); break; case AU_COMPRESS_ADPCM: // ADPCM Audio return new CAudioADPCM(cau); break; case AU_COMPRESS_VOICE: // VOICE Audio Assert(0); // Not Implemented break; default: Assert(0); // unknown compression break; } return NULL; } //********************************************************************************************** // Static helper function to create a loader class from a CAU file on disk. // By default the loader class owns the file that it was created with and will close it when // it is destroyed. // CCAULoad* CCAULoad::pcauCreateAudioLoader ( char* str_fname // filename to open ) // //************************************** { HANDLE h_file; SCAUHeader cauheader; uint32 u4_bytes; h_file = CreateFile( str_fname, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, 0 ); // if we failed to open the file return NULL if(h_file == (HANDLE)INVALID_HANDLE_VALUE ) { dprintf("Open audio file '%s' failed\n",str_fname); return(NULL); } if (ReadFile(h_file, &cauheader, sizeof(SCAUHeader), (DWORD*)&u4_bytes, NULL) == false) Assert(0); Assert(u4_bytes == sizeof(SCAUHeader) ); if (cauheader.u4Magic != 'ROBW') { dprintf("Non CAU file (loaded) for audio sample\n"); CloseHandle(h_file); return NULL; } Assert (cauheader.u4Version <= AUDIO_LOADER_VERSION); switch (cauheader.u1Compression) { case AU_COMPRESS_PCM: // PCM Audio return new CAudioPCM(NULL, h_file, 0, cauheader); break; case AU_COMPRESS_ADPCM: // ADPCM Audio return new CAudioADPCM(NULL, h_file, 0, cauheader); break; case AU_COMPRESS_VOICE: // VOICE Audio Assert(0); // Not Implemented break; default: Assert(0); // unknown compression CloseHandle(h_file); break; } CloseHandle(h_file); return NULL; } //********************************************************************************************** // Static helper function to create a loader class from a CAU file within in a packed file. // By default loader classes own the file that they are created with and will close it when the // loader is destroyed. For packed file loads we must clear the file owenership otherwise we // will close the database. // CCAULoad* CCAULoad::pcauCreateAudioLoader ( CAudioDatabase* padat, TSoundHandle sndhnd ) // //************************************** { MEMLOG_ADD_COUNTER(emlSoundLoader,sizeof(CCAULoad)); // there is no audio loader.. if (padat == NULL) return NULL; SSampleFile* psf_sample = padat->psfFindSample(sndhnd); if ( psf_sample == NULL) { // we have not found the requested sample so complain dprintf("%d Identifier not found in pack file\n", (uint32)sndhnd); // we have not found the sample in this database, play the annoying missing sample psf_sample = padat->psfFindSample(padat->sndhndGetMissingID()); if (psf_sample == NULL) { return NULL; } } // if we fail to duplicate the database handle, return NULL HANDLE h_local = padat->hDuplicateDatabaseFileHandle(); if (h_local == NULL) return NULL; CCAULoad* pcau = NULL; switch (psf_sample->cauheaderIndex.u1Compression) { case AU_COMPRESS_PCM: // PCM Audio pcau = new CAudioPCM(padat, h_local, psf_sample->u4CAUStart, psf_sample->cauheaderIndex); break; case AU_COMPRESS_ADPCM: // ADPCM Audio pcau = new CAudioADPCM(padat, h_local, psf_sample->u4CAUStart, psf_sample->cauheaderIndex); break; case AU_COMPRESS_VOICE: // VOICE Audio Assert(0); // Not Implemented break; default: Assert(0); // unknown compression break; } return pcau; } //********************************************************************************************** void CCAULoad::ResetToStartPosition ( ) //************************************** { if (hFile) { SetFilePointer(hFile,u4CAUFileBase+cauheaderLocal.u4Offset,NULL,FILE_BEGIN); } SetSampleSource(pu1Samples,0); u4SampleOffset = 0; } //********************************************************************************************** // CAudioDatabase::CAudioDatabase ( const char* str_filename, uint32 u4_handle_count // 0 for a single shared handle ) //************************************** { uint32 u4_bytes; uint32 u4; HANDLE h_file; uint32 u4_sample_bytes; uint32 u4_tpa_len; uint32 u4_access; Assert(str_filename); Assert(strlen(str_filename)0); // // Read the header // if (ReadFile(h_file,&pahHeader,sizeof(SPackedAudioHeader),(DWORD*)&u4_bytes, NULL) == false) { goto error; } if (u4_bytes!=sizeof(SPackedAudioHeader)) goto error; // If the disk file version is greater than the version this build knows about the do not // attempt to load it. We can still load old version though. // We have to do this because we have no idea as to what has been modified if (pahHeader.u4Version>u4PACKED_AUDIO_VERSION) { dprintf("TPA Version is greater than the current loader (%s)\n", str_filename); goto error; } // Check if this file is before the minimum version we can load if (pahHeader.u4Version=0.0f); // attenuation is +ve..This is wrong but it is the way it is. Assert(psfIdentifiers[u4].cauheaderIndex.u4Magic == 'ROBW');// make sure the CAU header is valid AddSample(&psfIdentifiers[u4]); Assert(psfFindSample(psfIdentifiers[u4].u4Hash)); // the identifer must now exist /*if ((psfIdentifiers[u4].cauheaderIndex.u4DataSize < 1028) && (psfIdentifiers[u4].cauheaderIndex.u1Compression!=0)) { dprintf("Sample ID = %x needs to be uncompressed.\n", psfIdentifiers[u4].u4Hash); }*/ } // // Null out the collision pointers in case the file has no collision as we do not // want any stray pointers. // pchCollisions = NULL; acolCollisions = NULL; if (pahHeader.u4Collisions) { pchCollisions = new TCollisionHash; acolCollisions = new SAudioCollision[pahHeader.u4Collisions]; MEMLOG_ADD_COUNTER(emlSoundControl,sizeof(TCollisionHash)); MEMLOG_ADD_COUNTER(emlSoundControl,sizeof(SAudioCollision)*pahHeader.u4Collisions); // This file has binary collisions pfcol = new SFileCollision[pahHeader.u4Collisions]; // // Read the collisions in a single operation // SetFilePointer(h_file, pahHeader.u4CollisionFileOffset, NULL, FILE_BEGIN); // All files pointers must be before the end of the file Assert(pahHeader.u4CollisionFileOffset 0) { // Sample 0 must have an identifer Assert(pfcol[u4].colCollision.sndhndSamples[0]!=0); // Sample 0 identifier must exist in the sample index Assert(psfFindSample( pfcol[u4].colCollision.sndhndSamples[0] )); } // do we have collision 2, in this case we must have collision 1?? if (pfcol[u4].colCollision.u4CollisionSamples() > 1) { // Sample 0 identifier must exist in the sample index Assert(psfFindSample( pfcol[u4].colCollision.sndhndSamples[0] )); // Sample 1 must have an identifer Assert(pfcol[u4].colCollision.sndhndSamples[1]!=0); // Sample 1 identifier must exist in the sample index Assert(psfFindSample( pfcol[u4].colCollision.sndhndSamples[1] )); } if (pfcol[u4].colCollision.bSlide()) { // Slide sample must have an identifer Assert(pfcol[u4].colCollision.sndhndSamples[2]!=0); // Slide sample identifier must exist in the sample index Assert(psfFindSample( pfcol[u4].colCollision.sndhndSamples[2] )); } Assert(pfcol[u4].colCollision.fMinTimeDelay>=0.0f); Assert(pfcol[u4].colCollision.fTimeLastUsed == 0.0f); #endif // VER_TEST AddCollision(pfcol[u4].u8Key, &acolCollisions[u4]); Assert( pcolFindCollision(pfcol[u4].u8Key) ); // Collision should now exist } // Delete the block of memory that holds the file collisions. if (pfcol) delete pfcol; } // // Set the identifier that will be used for missing samples // sndhndMissing = sndhndHashIdentifier("MISSING"); if (u4_handle_count == 0) { // Keep the current file handle and use it for all accesses hDatabase = h_file; } else { hDatabase = NULL; // there is no static database handle aahHandles = new SAudioHandle[u4_handle_count]; MEMLOG_ADD_COUNTER(emlSoundControl,sizeof(SAudioHandle)*u4_handle_count); u4HandleCount = u4_handle_count; memset(aahHandles, 0, sizeof(SAudioHandle)*u4_handle_count); aahHandles[0].hFile = h_file; aahHandles[0].bInUse = false; for (uint32 u4=1; u4begin(); i!=pchCollisions->end(); ++i) { (*i).second->fTimeLastUsed = 0.0f; } }