// ================================================================================================= // Copyright Adobe // Copyright 2010 Adobe // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms // of the Adobe license agreement accompanying it. // ================================================================================================= #include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. #include "public/include/XMP_Const.h" #include "source/XIO.hpp" #include "XMPFiles/source/FormatSupport/WAVE/WAVEBehavior.h" #include "XMPFiles/source/FormatSupport/IFF/Chunk.h" #include using namespace IFF_RIFF; // // Static init // const LittleEndian& WAVEBehavior::mEndian = LittleEndian::getInstance(); //----------------------------------------------------------------------------- // // WAVEBehavior::getRealSize(...) // // Purpose: Validate the passed in size value, identify the valid size if the // passed in isn't valid and return the valid size. // Throw an exception if the passed in size isn't valid and there's // no way to identify a valid size. // //----------------------------------------------------------------------------- XMP_Uns64 WAVEBehavior::getRealSize( const XMP_Uns64 size, const ChunkIdentifier& id, IChunkContainer& tree, XMP_IO* stream ) { XMP_Uns64 realSize = size; if( size >= kNormalRF64ChunkSize ) // 4GB { if( this->isRF64( tree ) ) { // // RF64 supports sizes beyond 4GB // DS64* rf64 = this->getDS64( tree, stream ); if( rf64 != NULL ) { // // get 64bit size from RF64 structure // switch( id.id ) { case kChunk_RF64: realSize = rf64->riffSize; break; case kChunk_data: realSize = rf64->dataSize; break; default: { bool found = false; // // try to find size value for passed chunk id in the ds64 table // if( rf64->tableLength > 0 ) { for( std::vector::iterator iter=rf64->table.begin(); iter!=rf64->table.end(); iter++ ) { if( iter->id == id.id ) { realSize = iter->size; found = true; break; } } } if( !found ) { // // no size for passed id available // XMP_Throw( "Unknown size value", kXMPErr_BadFileFormat ); } } } } else { // // no RF64 size info available // XMP_Throw( "Unknown size value", kXMPErr_BadFileFormat ); } } else { // // WAVE doesn't support that size // XMP_Throw( "Unknown size value", kXMPErr_BadFileFormat ); } } return realSize; } //----------------------------------------------------------------------------- // // WAVEBehavior::getMaxChunkSize(...) // // Purpose: Return the maximum size of a single chunk, i.e. the maximum size // of a top-level chunk. // //----------------------------------------------------------------------------- XMP_Uns64 WAVEBehavior::getMaxChunkSize() const { // simple WAVE 4GByte XMP_Uns64 ret = 0x00000000FFFFFFFFLL; if( mIsRF64 ) { // RF64: full possible 64bit size ret = 0xFFFFFFFFFFFFFFFFLL; } return ret; } //----------------------------------------------------------------------------- // // WAVEBehavior::isValidTopLevelChunk(...) // // Purpose: Return true if the passed identifier is valid for top-level chunks // of a certain format. // //----------------------------------------------------------------------------- bool WAVEBehavior::isValidTopLevelChunk( const ChunkIdentifier& id, XMP_Uns32 chunkNo ) { return ( chunkNo == 0 ) && ( ( ( id.id == kChunk_RIFF ) && ( id.type == kType_WAVE ) ) || ( ( id.id == kChunk_RF64 ) && ( id.type == kType_WAVE ) ) ); } //----------------------------------------------------------------------------- // // WAVEBehavior::fixHierarchy(...) // // Purpose: Fix the hierarchy of chunks depending ones based on size changes of // one or more chunks and second based on format specific rules. // Throw an exception if the hierarchy can't be fixed. // //----------------------------------------------------------------------------- void WAVEBehavior::fixHierarchy( IChunkContainer& tree ) { XMP_Validate( tree.numChildren() == 1, "WAVE files should only have one top level chunk (RIFF)", kXMPErr_BadFileFormat); Chunk* riffChunk = tree.getChildAt(0); XMP_Validate( (riffChunk->getType() == kType_WAVE || riffChunk->getType() == kChunk_RF64) , "Invalid type for WAVE/RF64 top level chunk (RIFF)", kXMPErr_BadFileFormat); if( riffChunk->hasChanged() ) { // // move new added chunks to temporary container // Chunk* tmpContainer = Chunk::createChunk( mEndian ); this->moveChunks( *riffChunk, *tmpContainer, riffChunk->numChildren() - mChunksAdded ); // // try to arrange chunks at their current position // this->arrangeChunksInPlace( *riffChunk, *tmpContainer ); // // for all chunks that were moved to the end try to find a FREE chunk for them // this->arrangeChunksInTree( *tmpContainer, *riffChunk ); // // append all remaining new added chunks to the end of the tree // this->moveChunks( *tmpContainer, *riffChunk, 0 ); delete tmpContainer; // // check for FREE chunks at the end // Chunk* endFREE = this->mergeFreeChunks( *riffChunk, riffChunk->numChildren() - 1 ); if( endFREE != NULL ) { riffChunk->removeChildAt( riffChunk->numChildren() - 1 ); delete endFREE; } // // Fix the offset values of all chunks. Throw an exception in the case that // the offset of a non-modified chunk needs to be reset. // XMP_Validate( riffChunk->getOffset() == 0, "Invalid offset for RIFF top level chunk", kXMPErr_InternalFailure ); this->validateOffsets( tree ); // // update the RF64 chunk (if this is RF64) based on the current chunk sizes // this->updateRF64( tree ); } } void WAVEBehavior::insertChunk( IChunkContainer& tree, Chunk& chunk ) { XMP_Validate( tree.numChildren() == 1, "WAVE files should only have one top level chunk (RIFF)", kXMPErr_BadFileFormat); Chunk* riffChunk = tree.getChildAt(0); XMP_Validate( riffChunk->getType() == kType_WAVE , "Invalid type for WAVE top level chunk (RIFF)", kXMPErr_BadFileFormat); // // add new chunk to the end of the RIFF:WAVE // riffChunk->appendChild(&chunk); mChunksAdded++; } bool WAVEBehavior::removeChunk( IChunkContainer& tree, Chunk& chunk ) { // // validate parameter // XMP_Validate( chunk.getID() != kChunk_RIFF, "Can't remove RIFF chunk!", kXMPErr_InternalFailure ); XMP_Validate( chunk.getChunkMode() != CHUNK_UNKNOWN, "Cant' remove UNKNOWN Chunk", kXMPErr_InternalFailure ); XMP_Validate( tree.numChildren() == 1, "WAVE files should only have one top level chunk (RIFF)", kXMPErr_BadFileFormat); // // get top-level chunk // Chunk* riffChunk = tree.getChildAt(0); // // validate top-level chunk // XMP_Validate( (riffChunk->getType() == kType_WAVE || riffChunk->getType() == kChunk_RF64) , "Invalid type for WAVE/RF64 top level chunk (RIFF)", kXMPErr_BadFileFormat); // // calculate index of chunk to remove // XMP_Uns32 i = (XMP_Uns32)(std::find( riffChunk->firstChild(), riffChunk->lastChild(), &chunk ) - riffChunk->firstChild()); // // validate index // XMP_Validate( i < riffChunk->numChildren(), "Invalid chunk in tree", kXMPErr_InternalFailure ); // // adjust new chunks counter // if( i > riffChunk->numChildren() - mChunksAdded - 1 ) { mChunksAdded--; } if( i < riffChunk->numChildren()-1 ) { // // fill gap with free chunk // Chunk* free = this->createFREE( chunk.getPadSize( true ) ); riffChunk->replaceChildAt( i, free ); free->setAsNew(); // // merge JUNK chunks // this->mergeFreeChunks( *riffChunk, i ); } else { // // remove chunk from tree // riffChunk->removeChildAt( i ); } // // if there is an entry in the ds64 table for the removed chunk // then update the ds64 table entry // if( mDS64Data != NULL && mDS64Data->tableLength > 0 ) { for( std::vector::iterator iter=mDS64Data->table.begin(); iter!=mDS64Data->table.end(); iter++ ) { if( iter->id == chunk.getID() ) { // // don't remove entry but set its size to zero // iter->size = 0LL; break; } } } return true; } Chunk* WAVEBehavior::createFREE( XMP_Uns64 chunkSize ) { XMP_Int64 alloc = chunkSize - Chunk::HEADER_SIZE; Chunk* chunk = NULL; // // create a 'JUNK' chunk // if( alloc > 0 ) { XMP_Uns8* data = new XMP_Uns8[static_cast( alloc )]; memset( data, 0, static_cast( alloc ) ); chunk = Chunk::createUnknownChunk( mEndian, kChunk_JUNK, kType_NONE, alloc ); chunk->setData( data, alloc ); delete[] data; } else { chunk = Chunk::createHeaderChunk( mEndian, kChunk_JUNK ); } // force set dirty flag chunk->setChanged(); return chunk; } XMP_Bool WAVEBehavior::isFREEChunk( const Chunk& chunk ) const { // Check for sigature JUNK and JUNQ return ( chunk.getID() == kChunk_JUNK || chunk.getID() == kChunk_JUNQ ); } XMP_Uns64 WAVEBehavior::getMinFREESize() const { // avoid creation of chunks with size==0 return static_cast( Chunk::HEADER_SIZE ) + 2; } //----------------------------------------------------------------------------- // // WAVEBehavior::isRF64(...) // // Purpose: Is the current file a RF64 file // //----------------------------------------------------------------------------- bool WAVEBehavior::isRF64( const IChunkContainer& tree ) { // The file format will not change at runtime // So if the flag is not already set, have a look at the tree if( ! mIsRF64 && tree.numChildren() != 0 ) { Chunk *chunk = tree.getChildAt(0); // Only the TopLevel chunk is interesting mIsRF64 = chunk->getID() == kChunk_RF64 && chunk->getType() == kType_WAVE; } return mIsRF64; } //----------------------------------------------------------------------------- // // WAVEBehavior::getDS64(...) // // Purpose: Return RF64 structure. // //----------------------------------------------------------------------------- WAVEBehavior::DS64* WAVEBehavior::getDS64( IChunkContainer& tree, XMP_IO* stream ) { DS64* ret = mDS64Data; if( ret == NULL ) { // // try to find 'ds64' chunk in the tree // Chunk* ds64 = NULL; Chunk* rf64 = NULL; if( tree.numChildren() > 0 ) { rf64 = tree.getChildAt(0); if( rf64 != NULL && rf64->getID() == kChunk_RF64 && rf64->numChildren() > 0 ) { // // 'ds64' chunk needs to be the very first child of the 'RF64' chunk // ds64 = rf64->getChildAt(0); } // // Try to create 'ds64' chunk by parsing the stream // if( ds64 == NULL && stream != NULL ) { // // remember file position before start reading from the stream // XMP_Uns64 filePos = stream->Offset(); try { ds64 = Chunk::createChunk( mEndian ); ds64->readChunk( stream ); } catch( ... ) { delete ds64; ds64 = NULL; } if( rf64 != NULL && ds64 != NULL && ds64->getID() == kChunk_ds64 ) { // // Successfully read 'ds64' chunk. // Now read its data area as well and // add chunk to the 'RF64' chunk // ds64->cacheChunkData( stream ); rf64->appendChild( ds64, false ); } else { // // Either the reading failed or the 'ds64' chunk // doesn't exists at the expected position. // Now clean up and reject the stream position. // delete ds64; ds64 = NULL; stream->Seek( filePos, kXMP_SeekFromStart ); } } else if( ds64 != NULL && ds64->getID() != kChunk_ds64 ) { // // first child of 'RF64' chunk is NOT 'ds64'! // ds64 = NULL; } } // // parse 'ds64' chunk, store the RF64 struct and return it // if( ds64 != NULL ) { DS64* ds64data = new DS64(); if( this->parseDS64Chunk( *ds64, *ds64data ) ) { mDS64Data = ds64data; ret = mDS64Data; } else { delete ds64data; } } } return ret; } //----------------------------------------------------------------------------- // // WAVEBehavior::updateRF64(...) // // Purpose: update the RF64 chunk (if this is RF64) based on the current chunk sizes // //----------------------------------------------------------------------------- void WAVEBehavior::updateRF64( IChunkContainer& tree ) { if( this->isRF64( tree ) ) { XMP_Validate( mDS64Data != NULL, "Missing DS64 structure", kXMPErr_InternalFailure ); XMP_Validate( tree.numChildren() == 1, "Invalid RF64 tree", kXMPErr_InternalFailure ); // // Check all chunks that sizes have changed and update their related value in the DS64 chunk // Chunk* rf64 = tree.getChildAt(0); XMP_Validate( rf64 != NULL && rf64->getID() == kChunk_RF64 && rf64->numChildren() > 0, "Invalid RF64 chunk", kXMPErr_InternalFailure ); this->doUpdateRF64( *rf64 ); // // try to find 'ds64' chunk in the tree // (needs to be the very first child of the 'RF64' chunk) // Chunk* ds64 = rf64->getChildAt(0); XMP_Validate( ds64 != NULL && ds64->getID() == kChunk_ds64, "Missing 'ds64' chunk", kXMPErr_InternalFailure ); // // serialize DS64 structure and write into ds64 chunk // this->serializeDS64Chunk( *mDS64Data, *ds64 ); } } void WAVEBehavior::doUpdateRF64( Chunk& chunk ) { // // update ds64 entry for chunk if its size has changed // if( chunk.hasChanged() && chunk.getOriginalSize() > kNormalRF64ChunkSize ) { switch( chunk.getID() ) { case kChunk_RF64: mDS64Data->riffSize = chunk.getSize(); break; case kChunk_data: if( chunk.getSize() != chunk.getOriginalSize() ) { XMP_Throw( "Data chunk must not change", kXMPErr_InternalFailure ); } break; default: { bool requireEntry = ( chunk.getSize() > kNormalRF64ChunkSize ); bool found = false; // // try to find entry for passed chunk id in the ds64 table // if( mDS64Data->tableLength > 0 ) { for( std::vector::iterator iter=mDS64Data->table.begin(); iter!=mDS64Data->table.end(); iter++ ) { if( iter->id == chunk.getID() ) { // always set new size even if it's less than 4GB iter->size = chunk.getSize(); found = true; break; } } } // // We can't add new entries to the table. So if we found no entry within 'ds64' // for the passed chunk ID and the size of the chunk is larger than 4GB then // we have to throw an exception // XMP_Validate( found || ( ! found && ! requireEntry ), "Can't update 'ds64' chunk", kXMPErr_Unimplemented ); } } } // // go through all children to update ds64 data // for( XMP_Uns32 i=0; idoUpdateRF64( *child ); } } //----------------------------------------------------------------------------- // // WAVEBehavior::parseRF64Chunk(...) // // Purpose: Parses the data block of the given RF64 chunk into the internal data structures // //----------------------------------------------------------------------------- bool WAVEBehavior::parseDS64Chunk( const Chunk& ds64Chunk, WAVEBehavior::DS64& ds64 ) { bool ret = false; // It is a valid ds64 chunk if( ds64Chunk.getID() == kChunk_ds64 && ds64Chunk.getSize() >= kMinimumDS64ChunkSize ) { const XMP_Uns8* data; XMP_Uns64 size = ds64Chunk.getData(&data); memset( &ds64, 0, kMinimumDS64ChunkSize); // // copy fix input data into RF64 block (except chunk size table) // Safe as fixed size matches size of struct that is #pragma packed(1) // memcpy( &ds64, data, kMinimumDS64ChunkSize ); // If there is more data but the table length is <= 0 then this is not a valid ds64 chunk if( size > kMinimumDS64ChunkSize && ds64.tableLength > 0 ) { // copy chunk sizes table // XMP_Assert( size - kMinimumDS64ChunkSize >= ds64.tableLength * sizeof(ChunkSize64)); XMP_Uns32 offset = kMinimumDS64ChunkSize; ChunkSize64 chunkSize; for( XMP_Uns32 i = 0 ; i < ds64.tableLength ; i++, offset += sizeof(ChunkSize64) ) { chunkSize.id = mEndian.getUns32( data + offset ); chunkSize.size = mEndian.getUns64( data + offset + 4 ); ds64.table.push_back( chunkSize ); } } // remember any existing table buffer ds64.trailingBytes = static_cast(size - kMinimumDS64ChunkSize - ds64.tableLength * sizeof(ChunkSize64)); // Either a table has been correctly parsed or there was no table ret = (size - kMinimumDS64ChunkSize) >= (ds64.tableLength * sizeof(ChunkSize64)); } return ret; } //----------------------------------------------------------------------------- // // WAVEBehavior::serializeRF64Chunk(...) // // Purpose: Serializes the internal RF64 data structures into the data part of the given chunk // //----------------------------------------------------------------------------- bool WAVEBehavior::serializeDS64Chunk( const WAVEBehavior::DS64& ds64, Chunk& ds64Chunk ) { if( ds64Chunk.getID() != kChunk_ds64 ) { return false; // not a valid ds64 chunk } // Calculate needed size XMP_Uns32 size = kMinimumDS64ChunkSize + ds64.tableLength * sizeof(ChunkSize64) + ds64.trailingBytes; // Create tmp buffer XMP_Uns8* data = new XMP_Uns8[size]; memset( data, 0, size ); // copy fix input data into buffer (except chunk sizes table) // Safe as fixed size matches size of struct that is #pragma packed(1) memcpy( data, &ds64, kMinimumDS64ChunkSize ); // copy chunk sizes table if( ds64.tableLength > 0 ) { XMP_Uns32 offset = kMinimumDS64ChunkSize; for( XMP_Uns32 i = 0 ; i < ds64.tableLength ; i++, offset += sizeof(ChunkSize64) ) { mEndian.putUns32( ds64.table.at(i).id, data + offset ); mEndian.putUns64( ds64.table.at(i).size, data + offset + 4 ); } } ds64Chunk.setData( data, size ); // free tmp buffer delete []data; return true; }