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.
30 #endif /* __FreeBSD__ */
51 /** make a 32 bit "string-id"
53 \param s a pointer to 4 chars
54 \return the 32 bit "string id"
55 \bugs It is not checked whether we really have 4 characters
57 Some compilers understand constants like int id = 'ABCD'; but I
58 could not get it working on the gcc compiler so I had to use this
59 workaround. We can now use id = make_fourcc("ABCD") instead. */
61 FOURCC make_fourcc( const char *s )
66 return *( ( FOURCC* ) s );
70 RIFFDirEntry::RIFFDirEntry()
74 RIFFDirEntry::RIFFDirEntry ( FOURCC t, FOURCC n, int l, int o, int p ) : type( t ), name( n ), length( l ), offset( o ), parent( p ), written( 0 )
78 /** Creates the object without an output file.
82 RIFFFile::RIFFFile() : fd( -1 )
84 pthread_mutex_init( &file_mutex, NULL );
90 Duplicate the file descriptor
93 RIFFFile::RIFFFile( const RIFFFile& riff ) : fd( -1 )
99 directory = riff.directory;
103 /** Destroys the object.
105 If it has an associated opened file, close it. */
107 RIFFFile::~RIFFFile()
110 pthread_mutex_destroy( &file_mutex );
114 RIFFFile& RIFFFile::operator=( const RIFFFile& riff )
123 directory = riff.directory;
129 /** Creates or truncates the file.
131 \param s the filename
134 bool RIFFFile::Create( const char *s )
136 fd = open( s, O_RDWR | O_NONBLOCK | O_CREAT | O_TRUNC, 00644 );
145 /** Opens the file read only.
147 \param s the filename
150 bool RIFFFile::Open( const char *s )
152 fd = open( s, O_RDONLY | O_NONBLOCK );
161 /** Destroys the object.
163 If it has an associated opened file, close it. */
165 void RIFFFile::Close()
175 /** Adds an entry to the list of containers.
177 \param type the type of this entry
179 \param length the length of the data in the container
180 \param list the container in which this object is contained.
181 \return the ID of the newly created entry
183 The topmost object is not contained in any other container. Use
184 the special ID RIFF_NO_PARENT to create the topmost object. */
186 int RIFFFile::AddDirectoryEntry( FOURCC type, FOURCC name, off_t length, int list )
188 /* Put all parameters in an RIFFDirEntry object. The offset is
189 currently unknown. */
191 RIFFDirEntry entry( type, name, length, 0 /* offset */, list );
193 /* If the new chunk is in a list, then get the offset and size
194 of that list. The offset of this chunk is the end of the list
195 (parent_offset + parent_length) plus the size of the chunk
198 if ( list != RIFF_NO_PARENT )
200 RIFFDirEntry parent = GetDirectoryEntry( list );
201 entry.offset = parent.offset + parent.length + RIFF_HEADERSIZE;
204 /* The list which this new chunk is a member of has now increased in
205 size. Get that directory entry and bump up its length by the size
206 of the chunk. Since that list may also be contained in another
207 list, walk up to the top of the tree. */
209 while ( list != RIFF_NO_PARENT )
211 RIFFDirEntry parent = GetDirectoryEntry( list );
212 parent.length += RIFF_HEADERSIZE + length;
213 SetDirectoryEntry( list, parent );
214 list = parent.parent;
217 directory.insert( directory.end(), entry );
219 return directory.size() - 1;
223 /** Modifies an entry.
225 \param i the ID of the entry which is to modify
226 \param type the type of this entry
228 \param length the length of the data in the container
229 \param list the container in which this object is contained.
230 \note Do not change length, offset, or the parent container.
231 \note Do not change an empty name ("") to a name and vice versa */
233 void RIFFFile::SetDirectoryEntry( int i, FOURCC type, FOURCC name, off_t length, off_t offset, int list )
235 RIFFDirEntry entry( type, name, length, offset, list );
237 assert( i >= 0 && i < ( int ) directory.size() );
239 directory[ i ] = entry;
243 /** Modifies an entry.
245 The entry.written flag is set to false because the contents has been modified
247 \param i the ID of the entry which is to modify
248 \param entry the new entry
249 \note Do not change length, offset, or the parent container.
250 \note Do not change an empty name ("") to a name and vice versa */
252 void RIFFFile::SetDirectoryEntry( int i, RIFFDirEntry &entry )
254 assert( i >= 0 && i < ( int ) directory.size() );
256 entry.written = false;
257 directory[ i ] = entry;
261 /** Retrieves an entry.
263 Gets the most important member variables.
265 \param i the ID of the entry to retrieve
272 void RIFFFile::GetDirectoryEntry( int i, FOURCC &type, FOURCC &name, off_t &length, off_t &offset, int &list ) const
276 assert( i >= 0 && i < ( int ) directory.size() );
278 entry = directory[ i ];
281 length = entry.length;
282 offset = entry.offset;
287 /** Retrieves an entry.
289 Gets the whole RIFFDirEntry object.
291 \param i the ID of the entry to retrieve
294 RIFFDirEntry RIFFFile::GetDirectoryEntry( int i ) const
296 assert( i >= 0 && i < ( int ) directory.size() );
298 return directory[ i ];
302 /** Calculates the total size of the file
304 \return the size the file in bytes
307 off_t RIFFFile::GetFileSize( void ) const
310 /* If we have at least one entry, return the length field
311 of the FILE entry, which is the length of its contents,
312 which is the actual size of whatever is currently in the
313 AVI directory structure.
315 Note that the first entry does not belong to the AVI
318 If we don't have any entry, the file size is zero. */
320 if ( directory.size() > 0 )
321 return directory[ 0 ].length;
327 /** prints the attributes of the entry
329 \param i the ID of the entry to print
332 void RIFFFile::PrintDirectoryEntry ( int i ) const
339 /* Get all attributes of the chunk object. If it is contained
340 in a list, get the name of the list too (otherwise the name of
341 the list is blank). If the chunk object doesnĀ“t have a name (only
342 LISTs and RIFFs have a name), the name is blank. */
344 entry = GetDirectoryEntry( i );
345 if ( entry.parent != RIFF_NO_PARENT )
347 parent = GetDirectoryEntry( entry.parent );
348 list_name = parent.name;
352 list_name = make_fourcc( " " );
354 if ( entry.name != 0 )
356 entry_name = entry.name;
360 entry_name = make_fourcc( " " );
363 /* Print out the ascii representation of type and name, as well as
364 length and file offset. */
366 cout << hex << setfill( '0' ) << "type: "
367 << ((char *)&entry.type)[0]
368 << ((char *)&entry.type)[1]
369 << ((char *)&entry.type)[2]
370 << ((char *)&entry.type)[3]
372 << ((char *)&entry_name)[0]
373 << ((char *)&entry_name)[1]
374 << ((char *)&entry_name)[2]
375 << ((char *)&entry_name)[3]
376 << " length: 0x" << setw( 12 ) << entry.length
377 << " offset: 0x" << setw( 12 ) << entry.offset
379 << ((char *)&list_name)[0]
380 << ((char *)&list_name)[1]
381 << ((char *)&list_name)[2]
382 << ((char *)&list_name)[3] << dec << endl;
384 /* print the content itself */
386 PrintDirectoryEntryData( entry );
390 /** prints the contents of the entry
392 Prints a readable representation of the contents of an index.
393 Override this to print out any objects you store in the RIFF file.
395 \param entry the entry to print */
397 void RIFFFile::PrintDirectoryEntryData( const RIFFDirEntry &entry ) const
401 /** prints the contents of the whole directory
403 Prints a readable representation of the contents of an index.
404 Override this to print out any objects you store in the RIFF file.
406 \param entry the entry to print */
408 void RIFFFile::PrintDirectory() const
411 int count = directory.size();
413 for ( i = 0; i < count; ++i )
414 PrintDirectoryEntry( i );
420 finds the index of a given directory entry type
422 \todo inefficient if the directory has lots of items
423 \param type the type of the entry to find
424 \param n the zero-based instance of type to locate
425 \return the index of the found object in the directory, or -1 if not found */
427 int RIFFFile::FindDirectoryEntry ( FOURCC type, int n ) const
430 int count = directory.size();
432 for ( i = 0; i < count; ++i )
433 if ( directory[ i ].type == type )
444 /** Reads all items that are contained in one list
446 Read in one chunk and add it to the directory. If the chunk
447 happens to be of type LIST, then call ParseList recursively for
450 \param parent The id of the item to process
453 void RIFFFile::ParseChunk( int parent )
459 /* Check whether it is a LIST. If so, let ParseList deal with it */
461 fail_if( read( fd, &type, sizeof( type ) ) != sizeof( type ));
462 if ( type == make_fourcc( "LIST" ) )
464 typesize = (int) -sizeof( type );
465 fail_if( lseek( fd, typesize, SEEK_CUR ) == ( off_t ) - 1 );
469 /* it is a normal chunk, create a new directory entry for it */
473 fail_neg( read( fd, &length, sizeof( length ) ) );
476 AddDirectoryEntry( type, 0, length, parent );
477 fail_if( lseek( fd, length, SEEK_CUR ) == ( off_t ) - 1 );
482 /** Reads all items that are contained in one list
484 \param parent The id of the list to process
488 void RIFFFile::ParseList( int parent )
497 /* Read in the chunk header (type and length). */
498 fail_neg( read( fd, &type, sizeof( type ) ) );
499 fail_neg( read( fd, &length, sizeof( length ) ) );
504 /* The contents of the list starts here. Obtain its offset. The list
505 name (4 bytes) is already part of the contents). */
507 pos = lseek( fd, 0, SEEK_CUR );
508 fail_if( pos == ( off_t ) - 1 );
509 fail_neg( read( fd, &name, sizeof( name ) ) );
511 /* Add an entry for this list. */
513 list = AddDirectoryEntry( type, name, sizeof( name ), parent );
515 /* Read in any chunks contained in this list. This list is the
516 parent for all chunks it contains. */
518 listEnd = pos + length;
519 while ( pos < listEnd )
522 pos = lseek( fd, 0, SEEK_CUR );
523 fail_if( pos == ( off_t ) - 1 );
528 /** Reads the directory structure of the whole RIFF file
532 void RIFFFile::ParseRIFF( void )
538 int container = AddDirectoryEntry( make_fourcc( "FILE" ), make_fourcc( "FILE" ), 0, RIFF_NO_PARENT );
540 pos = lseek( fd, 0, SEEK_SET );
542 /* calculate file size from RIFF header instead from physical file. */
544 while ( ( read( fd, &type, sizeof( type ) ) > 0 ) &&
545 ( read( fd, &length, sizeof( length ) ) > 0 ) &&
546 ( type == make_fourcc( "RIFF" ) ) )
549 filesize += length + RIFF_HEADERSIZE;
551 fail_if( lseek( fd, pos, SEEK_SET ) == ( off_t ) - 1 );
552 ParseList( container );
553 pos = lseek( fd, 0, SEEK_CUR );
554 fail_if( pos == ( off_t ) - 1 );
559 /** Reads one item including its contents from the RIFF file
561 \param chunk_index The index of the item to write
562 \param data A pointer to the data
566 void RIFFFile::ReadChunk( int chunk_index, void *data, off_t data_len )
570 entry = GetDirectoryEntry( chunk_index );
571 pthread_mutex_lock( &file_mutex );
572 fail_if( lseek( fd, entry.offset, SEEK_SET ) == ( off_t ) - 1 );
573 fail_neg( read( fd, data, entry.length > data_len ? data_len : entry.length ) );
574 pthread_mutex_unlock( &file_mutex );
578 /** Writes one item including its contents to the RIFF file
580 \param chunk_index The index of the item to write
581 \param data A pointer to the data
585 void RIFFFile::WriteChunk( int chunk_index, const void *data )
589 entry = GetDirectoryEntry( chunk_index );
590 pthread_mutex_lock( &file_mutex );
591 fail_if( lseek( fd, entry.offset - RIFF_HEADERSIZE, SEEK_SET ) == ( off_t ) - 1 );
592 fail_neg( write( fd, &entry.type, sizeof( entry.type ) ) );
593 DWORD length = entry.length;
594 fail_neg( write( fd, &length, sizeof( length ) ) );
595 fail_neg( write( fd, data, entry.length ) );
596 pthread_mutex_unlock( &file_mutex );
598 /* Remember that this entry already has been written. */
600 directory[ chunk_index ].written = true;
604 /** Writes out the directory structure
606 For all items in the directory list that have not been written
607 yet, it seeks to the file position where that item should be
608 stored and writes the type and length field. If the item has a
609 name, it will also write the name field.
611 \note It does not write the contents of any item. Use WriteChunk to do that. */
613 void RIFFFile::WriteRIFF( void )
617 int count = directory.size();
619 /* Start at the second entry (RIFF), since the first entry (FILE)
620 is needed only for internal purposes and is not written to the
623 for ( i = 1; i < count; ++i )
626 /* Only deal with entries that havenĀ“t been written */
628 entry = GetDirectoryEntry( i );
629 if ( entry.written == false )
632 /* A chunk entry consist of its type and length, a list
633 entry has an additional name. Look up the entry, seek
634 to the start of the header, which is at the offset of
635 the data start minus the header size and write out the
638 fail_if( lseek( fd, entry.offset - RIFF_HEADERSIZE, SEEK_SET ) == ( off_t ) - 1 ) ;
639 fail_neg( write( fd, &entry.type, sizeof( entry.type ) ) );
640 DWORD length = entry.length;
641 fail_neg( write( fd, &length, sizeof( length ) ) );
643 /* If it has a name, it is a list. Write out the extra name
646 if ( entry.name != 0 )
648 fail_neg( write( fd, &entry.name, sizeof( entry.name ) ) );
651 /* Remember that this entry already has been written. */
653 directory[ i ].written = true;