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.
46 /** make a 32 bit "string-id"
48 \param s a pointer to 4 chars
49 \return the 32 bit "string id"
50 \bugs It is not checked whether we really have 4 characters
52 Some compilers understand constants like int id = 'ABCD'; but I
53 could not get it working on the gcc compiler so I had to use this
54 workaround. We can now use id = make_fourcc("ABCD") instead. */
56 FOURCC make_fourcc( const char *s )
61 return *( ( FOURCC* ) s );
65 RIFFDirEntry::RIFFDirEntry()
76 RIFFDirEntry::RIFFDirEntry ( FOURCC t, FOURCC n, int l, int o, int p ) : type( t ), name( n ), length( l ), offset( o ), parent( p ), written( 0 )
80 /** Creates the object without an output file.
84 RIFFFile::RIFFFile() : fd( -1 )
86 pthread_mutex_init( &file_mutex, NULL );
92 Duplicate the file descriptor
95 RIFFFile::RIFFFile( const RIFFFile& riff ) : fd( -1 )
101 directory = riff.directory;
105 /** Destroys the object.
107 If it has an associated opened file, close it. */
109 RIFFFile::~RIFFFile()
112 pthread_mutex_destroy( &file_mutex );
116 RIFFFile& RIFFFile::operator=( const RIFFFile& riff )
125 directory = riff.directory;
131 /** Creates or truncates the file.
133 \param s the filename
136 bool RIFFFile::Create( const char *s )
138 fd = open( s, O_RDWR | O_NONBLOCK | O_CREAT | O_TRUNC, 00644 );
147 /** Opens the file read only.
149 \param s the filename
152 bool RIFFFile::Open( const char *s )
154 fd = open( s, O_RDONLY | O_NONBLOCK );
163 /** Destroys the object.
165 If it has an associated opened file, close it. */
167 void RIFFFile::Close()
177 /** Adds an entry to the list of containers.
179 \param type the type of this entry
181 \param length the length of the data in the container
182 \param list the container in which this object is contained.
183 \return the ID of the newly created entry
185 The topmost object is not contained in any other container. Use
186 the special ID RIFF_NO_PARENT to create the topmost object. */
188 int RIFFFile::AddDirectoryEntry( FOURCC type, FOURCC name, off_t length, int list )
190 /* Put all parameters in an RIFFDirEntry object. The offset is
191 currently unknown. */
193 RIFFDirEntry entry( type, name, length, 0 /* offset */, list );
195 /* If the new chunk is in a list, then get the offset and size
196 of that list. The offset of this chunk is the end of the list
197 (parent_offset + parent_length) plus the size of the chunk
200 if ( list != RIFF_NO_PARENT )
202 RIFFDirEntry parent = GetDirectoryEntry( list );
203 entry.offset = parent.offset + parent.length + RIFF_HEADERSIZE;
206 /* The list which this new chunk is a member of has now increased in
207 size. Get that directory entry and bump up its length by the size
208 of the chunk. Since that list may also be contained in another
209 list, walk up to the top of the tree. */
211 while ( list != RIFF_NO_PARENT )
213 RIFFDirEntry parent = GetDirectoryEntry( list );
214 parent.length += RIFF_HEADERSIZE + length;
215 SetDirectoryEntry( list, parent );
216 list = parent.parent;
219 directory.insert( directory.end(), entry );
221 return directory.size() - 1;
225 /** Modifies an entry.
227 \param i the ID of the entry which is to modify
228 \param type the type of this entry
230 \param length the length of the data in the container
231 \param list the container in which this object is contained.
232 \note Do not change length, offset, or the parent container.
233 \note Do not change an empty name ("") to a name and vice versa */
235 void RIFFFile::SetDirectoryEntry( int i, FOURCC type, FOURCC name, off_t length, off_t offset, int list )
237 RIFFDirEntry entry( type, name, length, offset, list );
239 assert( i >= 0 && i < ( int ) directory.size() );
241 directory[ i ] = entry;
245 /** Modifies an entry.
247 The entry.written flag is set to false because the contents has been modified
249 \param i the ID of the entry which is to modify
250 \param entry the new entry
251 \note Do not change length, offset, or the parent container.
252 \note Do not change an empty name ("") to a name and vice versa */
254 void RIFFFile::SetDirectoryEntry( int i, RIFFDirEntry &entry )
256 assert( i >= 0 && i < ( int ) directory.size() );
258 entry.written = false;
259 directory[ i ] = entry;
263 /** Retrieves an entry.
265 Gets the most important member variables.
267 \param i the ID of the entry to retrieve
274 void RIFFFile::GetDirectoryEntry( int i, FOURCC &type, FOURCC &name, off_t &length, off_t &offset, int &list ) const
278 assert( i >= 0 && i < ( int ) directory.size() );
280 entry = directory[ i ];
283 length = entry.length;
284 offset = entry.offset;
289 /** Retrieves an entry.
291 Gets the whole RIFFDirEntry object.
293 \param i the ID of the entry to retrieve
296 RIFFDirEntry RIFFFile::GetDirectoryEntry( int i ) const
298 assert( i >= 0 && i < ( int ) directory.size() );
300 return directory[ i ];
304 /** Calculates the total size of the file
306 \return the size the file in bytes
309 off_t RIFFFile::GetFileSize( void ) const
312 /* If we have at least one entry, return the length field
313 of the FILE entry, which is the length of its contents,
314 which is the actual size of whatever is currently in the
315 AVI directory structure.
317 Note that the first entry does not belong to the AVI
320 If we don't have any entry, the file size is zero. */
322 if ( directory.size() > 0 )
323 return directory[ 0 ].length;
329 /** prints the attributes of the entry
331 \param i the ID of the entry to print
334 void RIFFFile::PrintDirectoryEntry ( int i ) const
341 /* Get all attributes of the chunk object. If it is contained
342 in a list, get the name of the list too (otherwise the name of
343 the list is blank). If the chunk object doesnĀ“t have a name (only
344 LISTs and RIFFs have a name), the name is blank. */
346 entry = GetDirectoryEntry( i );
347 if ( entry.parent != RIFF_NO_PARENT )
349 parent = GetDirectoryEntry( entry.parent );
350 list_name = parent.name;
354 list_name = make_fourcc( " " );
356 if ( entry.name != 0 )
358 entry_name = entry.name;
362 entry_name = make_fourcc( " " );
365 /* Print out the ascii representation of type and name, as well as
366 length and file offset. */
368 cout << hex << setfill( '0' ) << "type: "
369 << ((char *)&entry.type)[0]
370 << ((char *)&entry.type)[1]
371 << ((char *)&entry.type)[2]
372 << ((char *)&entry.type)[3]
374 << ((char *)&entry_name)[0]
375 << ((char *)&entry_name)[1]
376 << ((char *)&entry_name)[2]
377 << ((char *)&entry_name)[3]
378 << " length: 0x" << setw( 12 ) << entry.length
379 << " offset: 0x" << setw( 12 ) << entry.offset
381 << ((char *)&list_name)[0]
382 << ((char *)&list_name)[1]
383 << ((char *)&list_name)[2]
384 << ((char *)&list_name)[3] << dec << endl;
386 /* print the content itself */
388 PrintDirectoryEntryData( entry );
392 /** prints the contents of the entry
394 Prints a readable representation of the contents of an index.
395 Override this to print out any objects you store in the RIFF file.
397 \param entry the entry to print */
399 void RIFFFile::PrintDirectoryEntryData( const RIFFDirEntry &entry ) const
403 /** prints the contents of the whole directory
405 Prints a readable representation of the contents of an index.
406 Override this to print out any objects you store in the RIFF file.
408 \param entry the entry to print */
410 void RIFFFile::PrintDirectory() const
413 int count = directory.size();
415 for ( i = 0; i < count; ++i )
416 PrintDirectoryEntry( i );
422 finds the index of a given directory entry type
424 \todo inefficient if the directory has lots of items
425 \param type the type of the entry to find
426 \param n the zero-based instance of type to locate
427 \return the index of the found object in the directory, or -1 if not found */
429 int RIFFFile::FindDirectoryEntry ( FOURCC type, int n ) const
432 int count = directory.size();
434 for ( i = 0; i < count; ++i )
435 if ( directory[ i ].type == type )
446 /** Reads all items that are contained in one list
448 Read in one chunk and add it to the directory. If the chunk
449 happens to be of type LIST, then call ParseList recursively for
452 \param parent The id of the item to process
455 void RIFFFile::ParseChunk( int parent )
461 /* Check whether it is a LIST. If so, let ParseList deal with it */
463 fail_if( read( fd, &type, sizeof( type ) ) != sizeof( type ));
464 if ( type == make_fourcc( "LIST" ) )
466 typesize = (int) -sizeof( type );
467 fail_if( lseek( fd, typesize, SEEK_CUR ) == ( off_t ) - 1 );
471 /* it is a normal chunk, create a new directory entry for it */
475 fail_neg( read( fd, &length, sizeof( length ) ) );
478 AddDirectoryEntry( type, 0, length, parent );
479 fail_if( lseek( fd, length, SEEK_CUR ) == ( off_t ) - 1 );
484 /** Reads all items that are contained in one list
486 \param parent The id of the list to process
490 void RIFFFile::ParseList( int parent )
499 /* Read in the chunk header (type and length). */
500 fail_neg( read( fd, &type, sizeof( type ) ) );
501 fail_neg( read( fd, &length, sizeof( length ) ) );
506 /* The contents of the list starts here. Obtain its offset. The list
507 name (4 bytes) is already part of the contents). */
509 pos = lseek( fd, 0, SEEK_CUR );
510 fail_if( pos == ( off_t ) - 1 );
511 fail_neg( read( fd, &name, sizeof( name ) ) );
513 /* Add an entry for this list. */
515 list = AddDirectoryEntry( type, name, sizeof( name ), parent );
517 /* Read in any chunks contained in this list. This list is the
518 parent for all chunks it contains. */
520 listEnd = pos + length;
521 while ( pos < listEnd )
524 pos = lseek( fd, 0, SEEK_CUR );
525 fail_if( pos == ( off_t ) - 1 );
530 /** Reads the directory structure of the whole RIFF file
534 void RIFFFile::ParseRIFF( void )
540 int container = AddDirectoryEntry( make_fourcc( "FILE" ), make_fourcc( "FILE" ), 0, RIFF_NO_PARENT );
542 pos = lseek( fd, 0, SEEK_SET );
543 fail_if( pos == -1 );
545 /* calculate file size from RIFF header instead from physical file. */
547 while ( ( read( fd, &type, sizeof( type ) ) > 0 ) &&
548 ( read( fd, &length, sizeof( length ) ) > 0 ) &&
549 ( type == make_fourcc( "RIFF" ) ) )
552 filesize += length + RIFF_HEADERSIZE;
554 fail_if( lseek( fd, pos, SEEK_SET ) == ( off_t ) - 1 );
555 ParseList( container );
556 pos = lseek( fd, 0, SEEK_CUR );
557 fail_if( pos == ( off_t ) - 1 );
562 /** Reads one item including its contents from the RIFF file
564 \param chunk_index The index of the item to write
565 \param data A pointer to the data
569 void RIFFFile::ReadChunk( int chunk_index, void *data, off_t data_len )
573 entry = GetDirectoryEntry( chunk_index );
574 pthread_mutex_lock( &file_mutex );
575 fail_if( lseek( fd, entry.offset, SEEK_SET ) == ( off_t ) - 1 );
576 fail_neg( read( fd, data, entry.length > data_len ? data_len : entry.length ) );
577 pthread_mutex_unlock( &file_mutex );
581 /** Writes one item including its contents to the RIFF file
583 \param chunk_index The index of the item to write
584 \param data A pointer to the data
588 void RIFFFile::WriteChunk( int chunk_index, const void *data )
592 entry = GetDirectoryEntry( chunk_index );
593 pthread_mutex_lock( &file_mutex );
594 fail_if( lseek( fd, entry.offset - RIFF_HEADERSIZE, SEEK_SET ) == ( off_t ) - 1 );
595 fail_neg( write( fd, &entry.type, sizeof( entry.type ) ) );
596 DWORD length = entry.length;
597 fail_neg( write( fd, &length, sizeof( length ) ) );
598 fail_neg( write( fd, data, entry.length ) );
599 pthread_mutex_unlock( &file_mutex );
601 /* Remember that this entry already has been written. */
603 directory[ chunk_index ].written = true;
607 /** Writes out the directory structure
609 For all items in the directory list that have not been written
610 yet, it seeks to the file position where that item should be
611 stored and writes the type and length field. If the item has a
612 name, it will also write the name field.
614 \note It does not write the contents of any item. Use WriteChunk to do that. */
616 void RIFFFile::WriteRIFF( void )
620 int count = directory.size();
622 /* Start at the second entry (RIFF), since the first entry (FILE)
623 is needed only for internal purposes and is not written to the
626 for ( i = 1; i < count; ++i )
629 /* Only deal with entries that havenĀ“t been written */
631 entry = GetDirectoryEntry( i );
632 if ( entry.written == false )
635 /* A chunk entry consist of its type and length, a list
636 entry has an additional name. Look up the entry, seek
637 to the start of the header, which is at the offset of
638 the data start minus the header size and write out the
641 fail_if( lseek( fd, entry.offset - RIFF_HEADERSIZE, SEEK_SET ) == ( off_t ) - 1 ) ;
642 fail_neg( write( fd, &entry.type, sizeof( entry.type ) ) );
643 DWORD length = entry.length;
644 fail_neg( write( fd, &length, sizeof( length ) ) );
646 /* If it has a name, it is a list. Write out the extra name
649 if ( entry.name != 0 )
651 fail_neg( write( fd, &entry.name, sizeof( entry.name ) ) );
654 /* Remember that this entry already has been written. */
656 directory[ i ].written = true;