2 * riff.cc library for RIFF file format i/o
3 * Copyright (C) 2000 - 2002 Arne Schirmacher <arne@schirmacher.de>
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software Foundation,
17 * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
48 /** make a 32 bit "string-id"
50 \param s a pointer to 4 chars
51 \return the 32 bit "string id"
52 \bugs It is not checked whether we really have 4 characters
54 Some compilers understand constants like int id = 'ABCD'; but I
55 could not get it working on the gcc compiler so I had to use this
56 workaround. We can now use id = make_fourcc("ABCD") instead. */
58 FOURCC make_fourcc( const char *s )
63 return *( ( FOURCC* ) s );
67 RIFFDirEntry::RIFFDirEntry()
78 RIFFDirEntry::RIFFDirEntry ( FOURCC t, FOURCC n, int l, int o, int p ) : type( t ), name( n ), length( l ), offset( o ), parent( p ), written( 0 )
82 /** Creates the object without an output file.
86 RIFFFile::RIFFFile() : fd( -1 )
88 pthread_mutex_init( &file_mutex, NULL );
94 Duplicate the file descriptor
97 RIFFFile::RIFFFile( const RIFFFile& riff ) : fd( -1 )
103 directory = riff.directory;
107 /** Destroys the object.
109 If it has an associated opened file, close it. */
111 RIFFFile::~RIFFFile()
114 pthread_mutex_destroy( &file_mutex );
118 RIFFFile& RIFFFile::operator=( const RIFFFile& riff )
127 directory = riff.directory;
133 /** Creates or truncates the file.
135 \param s the filename
138 bool RIFFFile::Create( const char *s )
140 fd = open( s, O_RDWR | O_NONBLOCK | O_CREAT | O_TRUNC, 00644 );
149 /** Opens the file read only.
151 \param s the filename
154 bool RIFFFile::Open( const char *s )
156 fd = open( s, O_RDONLY | O_NONBLOCK );
165 /** Destroys the object.
167 If it has an associated opened file, close it. */
169 void RIFFFile::Close()
179 /** Adds an entry to the list of containers.
181 \param type the type of this entry
183 \param length the length of the data in the container
184 \param list the container in which this object is contained.
185 \return the ID of the newly created entry
187 The topmost object is not contained in any other container. Use
188 the special ID RIFF_NO_PARENT to create the topmost object. */
190 int RIFFFile::AddDirectoryEntry( FOURCC type, FOURCC name, off_t length, int list )
192 /* Put all parameters in an RIFFDirEntry object. The offset is
193 currently unknown. */
195 RIFFDirEntry entry( type, name, length, 0 /* offset */, list );
197 /* If the new chunk is in a list, then get the offset and size
198 of that list. The offset of this chunk is the end of the list
199 (parent_offset + parent_length) plus the size of the chunk
202 if ( list != RIFF_NO_PARENT )
204 RIFFDirEntry parent = GetDirectoryEntry( list );
205 entry.offset = parent.offset + parent.length + RIFF_HEADERSIZE;
208 /* The list which this new chunk is a member of has now increased in
209 size. Get that directory entry and bump up its length by the size
210 of the chunk. Since that list may also be contained in another
211 list, walk up to the top of the tree. */
213 while ( list != RIFF_NO_PARENT )
215 RIFFDirEntry parent = GetDirectoryEntry( list );
216 parent.length += RIFF_HEADERSIZE + length;
217 SetDirectoryEntry( list, parent );
218 list = parent.parent;
221 directory.insert( directory.end(), entry );
223 return directory.size() - 1;
227 /** Modifies an entry.
229 \param i the ID of the entry which is to modify
230 \param type the type of this entry
232 \param length the length of the data in the container
233 \param list the container in which this object is contained.
234 \note Do not change length, offset, or the parent container.
235 \note Do not change an empty name ("") to a name and vice versa */
237 void RIFFFile::SetDirectoryEntry( int i, FOURCC type, FOURCC name, off_t length, off_t offset, int list )
239 RIFFDirEntry entry( type, name, length, offset, list );
241 assert( i >= 0 && i < ( int ) directory.size() );
243 directory[ i ] = entry;
247 /** Modifies an entry.
249 The entry.written flag is set to false because the contents has been modified
251 \param i the ID of the entry which is to modify
252 \param entry the new entry
253 \note Do not change length, offset, or the parent container.
254 \note Do not change an empty name ("") to a name and vice versa */
256 void RIFFFile::SetDirectoryEntry( int i, RIFFDirEntry &entry )
258 assert( i >= 0 && i < ( int ) directory.size() );
260 entry.written = false;
261 directory[ i ] = entry;
265 /** Retrieves an entry.
267 Gets the most important member variables.
269 \param i the ID of the entry to retrieve
276 void RIFFFile::GetDirectoryEntry( int i, FOURCC &type, FOURCC &name, off_t &length, off_t &offset, int &list ) const
280 assert( i >= 0 && i < ( int ) directory.size() );
282 entry = directory[ i ];
285 length = entry.length;
286 offset = entry.offset;
291 /** Retrieves an entry.
293 Gets the whole RIFFDirEntry object.
295 \param i the ID of the entry to retrieve
298 RIFFDirEntry RIFFFile::GetDirectoryEntry( int i ) const
300 assert( i >= 0 && i < ( int ) directory.size() );
302 return directory[ i ];
306 /** Calculates the total size of the file
308 \return the size the file in bytes
311 off_t RIFFFile::GetFileSize( void ) const
314 /* If we have at least one entry, return the length field
315 of the FILE entry, which is the length of its contents,
316 which is the actual size of whatever is currently in the
317 AVI directory structure.
319 Note that the first entry does not belong to the AVI
322 If we don't have any entry, the file size is zero. */
324 if ( directory.size() > 0 )
325 return directory[ 0 ].length;
331 /** prints the attributes of the entry
333 \param i the ID of the entry to print
336 void RIFFFile::PrintDirectoryEntry ( int i ) const
343 /* Get all attributes of the chunk object. If it is contained
344 in a list, get the name of the list too (otherwise the name of
345 the list is blank). If the chunk object doesnĀ“t have a name (only
346 LISTs and RIFFs have a name), the name is blank. */
348 entry = GetDirectoryEntry( i );
349 if ( entry.parent != RIFF_NO_PARENT )
351 parent = GetDirectoryEntry( entry.parent );
352 list_name = parent.name;
356 list_name = make_fourcc( " " );
358 if ( entry.name != 0 )
360 entry_name = entry.name;
364 entry_name = make_fourcc( " " );
367 /* Print out the ascii representation of type and name, as well as
368 length and file offset. */
370 cout << hex << setfill( '0' ) << "type: "
371 << ((char *)&entry.type)[0]
372 << ((char *)&entry.type)[1]
373 << ((char *)&entry.type)[2]
374 << ((char *)&entry.type)[3]
376 << ((char *)&entry_name)[0]
377 << ((char *)&entry_name)[1]
378 << ((char *)&entry_name)[2]
379 << ((char *)&entry_name)[3]
380 << " length: 0x" << setw( 12 ) << entry.length
381 << " offset: 0x" << setw( 12 ) << entry.offset
383 << ((char *)&list_name)[0]
384 << ((char *)&list_name)[1]
385 << ((char *)&list_name)[2]
386 << ((char *)&list_name)[3] << dec << endl;
388 /* print the content itself */
390 PrintDirectoryEntryData( entry );
394 /** prints the contents of the entry
396 Prints a readable representation of the contents of an index.
397 Override this to print out any objects you store in the RIFF file.
399 \param entry the entry to print */
401 void RIFFFile::PrintDirectoryEntryData( const RIFFDirEntry &entry ) const
405 /** prints the contents of the whole directory
407 Prints a readable representation of the contents of an index.
408 Override this to print out any objects you store in the RIFF file.
410 \param entry the entry to print */
412 void RIFFFile::PrintDirectory() const
415 int count = directory.size();
417 for ( i = 0; i < count; ++i )
418 PrintDirectoryEntry( i );
424 finds the index of a given directory entry type
426 \todo inefficient if the directory has lots of items
427 \param type the type of the entry to find
428 \param n the zero-based instance of type to locate
429 \return the index of the found object in the directory, or -1 if not found */
431 int RIFFFile::FindDirectoryEntry ( FOURCC type, int n ) const
434 int count = directory.size();
436 for ( i = 0; i < count; ++i )
437 if ( directory[ i ].type == type )
448 /** Reads all items that are contained in one list
450 Read in one chunk and add it to the directory. If the chunk
451 happens to be of type LIST, then call ParseList recursively for
454 \param parent The id of the item to process
457 void RIFFFile::ParseChunk( int parent )
463 /* Check whether it is a LIST. If so, let ParseList deal with it */
465 fail_if( read( fd, &type, sizeof( type ) ) != sizeof( type ));
466 if ( type == make_fourcc( "LIST" ) )
468 typesize = (int) -sizeof( type );
469 fail_if( lseek( fd, typesize, SEEK_CUR ) == ( off_t ) - 1 );
473 /* it is a normal chunk, create a new directory entry for it */
477 fail_neg( read( fd, &length, sizeof( length ) ) );
480 AddDirectoryEntry( type, 0, length, parent );
481 fail_if( lseek( fd, length, SEEK_CUR ) == ( off_t ) - 1 );
486 /** Reads all items that are contained in one list
488 \param parent The id of the list to process
492 void RIFFFile::ParseList( int parent )
501 /* Read in the chunk header (type and length). */
502 fail_neg( read( fd, &type, sizeof( type ) ) );
503 fail_neg( read( fd, &length, sizeof( length ) ) );
508 /* The contents of the list starts here. Obtain its offset. The list
509 name (4 bytes) is already part of the contents). */
511 pos = lseek( fd, 0, SEEK_CUR );
512 fail_if( pos == ( off_t ) - 1 );
513 fail_neg( read( fd, &name, sizeof( name ) ) );
515 /* Add an entry for this list. */
517 list = AddDirectoryEntry( type, name, sizeof( name ), parent );
519 /* Read in any chunks contained in this list. This list is the
520 parent for all chunks it contains. */
522 listEnd = pos + length;
523 while ( pos < listEnd )
526 pos = lseek( fd, 0, SEEK_CUR );
527 fail_if( pos == ( off_t ) - 1 );
532 /** Reads the directory structure of the whole RIFF file
536 void RIFFFile::ParseRIFF( void )
542 int container = AddDirectoryEntry( make_fourcc( "FILE" ), make_fourcc( "FILE" ), 0, RIFF_NO_PARENT );
544 pos = lseek( fd, 0, SEEK_SET );
545 fail_if( pos == -1 );
547 /* calculate file size from RIFF header instead from physical file. */
549 while ( ( read( fd, &type, sizeof( type ) ) > 0 ) &&
550 ( read( fd, &length, sizeof( length ) ) > 0 ) &&
551 ( type == make_fourcc( "RIFF" ) ) )
554 filesize += length + RIFF_HEADERSIZE;
556 fail_if( lseek( fd, pos, SEEK_SET ) == ( off_t ) - 1 );
557 ParseList( container );
558 pos = lseek( fd, 0, SEEK_CUR );
559 fail_if( pos == ( off_t ) - 1 );
564 /** Reads one item including its contents from the RIFF file
566 \param chunk_index The index of the item to write
567 \param data A pointer to the data
571 void RIFFFile::ReadChunk( int chunk_index, void *data, off_t data_len )
575 entry = GetDirectoryEntry( chunk_index );
576 pthread_mutex_lock( &file_mutex );
577 fail_if( lseek( fd, entry.offset, SEEK_SET ) == ( off_t ) - 1 );
578 fail_neg( read( fd, data, entry.length > data_len ? data_len : entry.length ) );
579 pthread_mutex_unlock( &file_mutex );
583 /** Writes one item including its contents to the RIFF file
585 \param chunk_index The index of the item to write
586 \param data A pointer to the data
590 void RIFFFile::WriteChunk( int chunk_index, const void *data )
594 entry = GetDirectoryEntry( chunk_index );
595 pthread_mutex_lock( &file_mutex );
596 fail_if( lseek( fd, entry.offset - RIFF_HEADERSIZE, SEEK_SET ) == ( off_t ) - 1 );
597 fail_neg( write( fd, &entry.type, sizeof( entry.type ) ) );
598 DWORD length = entry.length;
599 fail_neg( write( fd, &length, sizeof( length ) ) );
600 fail_neg( write( fd, data, entry.length ) );
601 pthread_mutex_unlock( &file_mutex );
603 /* Remember that this entry already has been written. */
605 directory[ chunk_index ].written = true;
609 /** Writes out the directory structure
611 For all items in the directory list that have not been written
612 yet, it seeks to the file position where that item should be
613 stored and writes the type and length field. If the item has a
614 name, it will also write the name field.
616 \note It does not write the contents of any item. Use WriteChunk to do that. */
618 void RIFFFile::WriteRIFF( void )
622 int count = directory.size();
624 /* Start at the second entry (RIFF), since the first entry (FILE)
625 is needed only for internal purposes and is not written to the
628 for ( i = 1; i < count; ++i )
631 /* Only deal with entries that havenĀ“t been written */
633 entry = GetDirectoryEntry( i );
634 if ( entry.written == false )
637 /* A chunk entry consist of its type and length, a list
638 entry has an additional name. Look up the entry, seek
639 to the start of the header, which is at the offset of
640 the data start minus the header size and write out the
643 fail_if( lseek( fd, entry.offset - RIFF_HEADERSIZE, SEEK_SET ) == ( off_t ) - 1 ) ;
644 fail_neg( write( fd, &entry.type, sizeof( entry.type ) ) );
645 DWORD length = entry.length;
646 fail_neg( write( fd, &length, sizeof( length ) ) );
648 /* If it has a name, it is a list. Write out the extra name
651 if ( entry.name != 0 )
653 fail_neg( write( fd, &entry.name, sizeof( entry.name ) ) );
656 /* Remember that this entry already has been written. */
658 directory[ i ].written = true;