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()
71 RIFFDirEntry::RIFFDirEntry ( FOURCC t, FOURCC n, int l, int o, int p ) : type( t ), name( n ), length( l ), offset( o ), parent( p ), written( 0 )
75 /** Creates the object without an output file.
79 RIFFFile::RIFFFile() : fd( -1 )
81 pthread_mutex_init( &file_mutex, NULL );
87 Duplicate the file descriptor
90 RIFFFile::RIFFFile( const RIFFFile& riff ) : fd( -1 )
96 directory = riff.directory;
100 /** Destroys the object.
102 If it has an associated opened file, close it. */
104 RIFFFile::~RIFFFile()
107 pthread_mutex_destroy( &file_mutex );
111 RIFFFile& RIFFFile::operator=( const RIFFFile& riff )
120 directory = riff.directory;
126 /** Creates or truncates the file.
128 \param s the filename
131 bool RIFFFile::Create( const char *s )
133 fd = open( s, O_RDWR | O_NONBLOCK | O_CREAT | O_TRUNC, 00644 );
142 /** Opens the file read only.
144 \param s the filename
147 bool RIFFFile::Open( const char *s )
149 fd = open( s, O_RDONLY | O_NONBLOCK );
158 /** Destroys the object.
160 If it has an associated opened file, close it. */
162 void RIFFFile::Close()
172 /** Adds an entry to the list of containers.
174 \param type the type of this entry
176 \param length the length of the data in the container
177 \param list the container in which this object is contained.
178 \return the ID of the newly created entry
180 The topmost object is not contained in any other container. Use
181 the special ID RIFF_NO_PARENT to create the topmost object. */
183 int RIFFFile::AddDirectoryEntry( FOURCC type, FOURCC name, off_t length, int list )
185 /* Put all parameters in an RIFFDirEntry object. The offset is
186 currently unknown. */
188 RIFFDirEntry entry( type, name, length, 0 /* offset */, list );
190 /* If the new chunk is in a list, then get the offset and size
191 of that list. The offset of this chunk is the end of the list
192 (parent_offset + parent_length) plus the size of the chunk
195 if ( list != RIFF_NO_PARENT )
197 RIFFDirEntry parent = GetDirectoryEntry( list );
198 entry.offset = parent.offset + parent.length + RIFF_HEADERSIZE;
201 /* The list which this new chunk is a member of has now increased in
202 size. Get that directory entry and bump up its length by the size
203 of the chunk. Since that list may also be contained in another
204 list, walk up to the top of the tree. */
206 while ( list != RIFF_NO_PARENT )
208 RIFFDirEntry parent = GetDirectoryEntry( list );
209 parent.length += RIFF_HEADERSIZE + length;
210 SetDirectoryEntry( list, parent );
211 list = parent.parent;
214 directory.insert( directory.end(), entry );
216 return directory.size() - 1;
220 /** Modifies an entry.
222 \param i the ID of the entry which is to modify
223 \param type the type of this entry
225 \param length the length of the data in the container
226 \param list the container in which this object is contained.
227 \note Do not change length, offset, or the parent container.
228 \note Do not change an empty name ("") to a name and vice versa */
230 void RIFFFile::SetDirectoryEntry( int i, FOURCC type, FOURCC name, off_t length, off_t offset, int list )
232 RIFFDirEntry entry( type, name, length, offset, list );
234 assert( i >= 0 && i < ( int ) directory.size() );
236 directory[ i ] = entry;
240 /** Modifies an entry.
242 The entry.written flag is set to false because the contents has been modified
244 \param i the ID of the entry which is to modify
245 \param entry the new entry
246 \note Do not change length, offset, or the parent container.
247 \note Do not change an empty name ("") to a name and vice versa */
249 void RIFFFile::SetDirectoryEntry( int i, RIFFDirEntry &entry )
251 assert( i >= 0 && i < ( int ) directory.size() );
253 entry.written = false;
254 directory[ i ] = entry;
258 /** Retrieves an entry.
260 Gets the most important member variables.
262 \param i the ID of the entry to retrieve
269 void RIFFFile::GetDirectoryEntry( int i, FOURCC &type, FOURCC &name, off_t &length, off_t &offset, int &list ) const
273 assert( i >= 0 && i < ( int ) directory.size() );
275 entry = directory[ i ];
278 length = entry.length;
279 offset = entry.offset;
284 /** Retrieves an entry.
286 Gets the whole RIFFDirEntry object.
288 \param i the ID of the entry to retrieve
291 RIFFDirEntry RIFFFile::GetDirectoryEntry( int i ) const
293 assert( i >= 0 && i < ( int ) directory.size() );
295 return directory[ i ];
299 /** Calculates the total size of the file
301 \return the size the file in bytes
304 off_t RIFFFile::GetFileSize( void ) const
307 /* If we have at least one entry, return the length field
308 of the FILE entry, which is the length of its contents,
309 which is the actual size of whatever is currently in the
310 AVI directory structure.
312 Note that the first entry does not belong to the AVI
315 If we don't have any entry, the file size is zero. */
317 if ( directory.size() > 0 )
318 return directory[ 0 ].length;
324 /** prints the attributes of the entry
326 \param i the ID of the entry to print
329 void RIFFFile::PrintDirectoryEntry ( int i ) const
336 /* Get all attributes of the chunk object. If it is contained
337 in a list, get the name of the list too (otherwise the name of
338 the list is blank). If the chunk object doesnĀ“t have a name (only
339 LISTs and RIFFs have a name), the name is blank. */
341 entry = GetDirectoryEntry( i );
342 if ( entry.parent != RIFF_NO_PARENT )
344 parent = GetDirectoryEntry( entry.parent );
345 list_name = parent.name;
349 list_name = make_fourcc( " " );
351 if ( entry.name != 0 )
353 entry_name = entry.name;
357 entry_name = make_fourcc( " " );
360 /* Print out the ascii representation of type and name, as well as
361 length and file offset. */
363 cout << hex << setfill( '0' ) << "type: "
364 << ((char *)&entry.type)[0]
365 << ((char *)&entry.type)[1]
366 << ((char *)&entry.type)[2]
367 << ((char *)&entry.type)[3]
369 << ((char *)&entry_name)[0]
370 << ((char *)&entry_name)[1]
371 << ((char *)&entry_name)[2]
372 << ((char *)&entry_name)[3]
373 << " length: 0x" << setw( 12 ) << entry.length
374 << " offset: 0x" << setw( 12 ) << entry.offset
376 << ((char *)&list_name)[0]
377 << ((char *)&list_name)[1]
378 << ((char *)&list_name)[2]
379 << ((char *)&list_name)[3] << dec << endl;
381 /* print the content itself */
383 PrintDirectoryEntryData( entry );
387 /** prints the contents of the entry
389 Prints a readable representation of the contents of an index.
390 Override this to print out any objects you store in the RIFF file.
392 \param entry the entry to print */
394 void RIFFFile::PrintDirectoryEntryData( const RIFFDirEntry &entry ) const
398 /** prints the contents of the whole directory
400 Prints a readable representation of the contents of an index.
401 Override this to print out any objects you store in the RIFF file.
403 \param entry the entry to print */
405 void RIFFFile::PrintDirectory() const
408 int count = directory.size();
410 for ( i = 0; i < count; ++i )
411 PrintDirectoryEntry( i );
417 finds the index of a given directory entry type
419 \todo inefficient if the directory has lots of items
420 \param type the type of the entry to find
421 \param n the zero-based instance of type to locate
422 \return the index of the found object in the directory, or -1 if not found */
424 int RIFFFile::FindDirectoryEntry ( FOURCC type, int n ) const
427 int count = directory.size();
429 for ( i = 0; i < count; ++i )
430 if ( directory[ i ].type == type )
441 /** Reads all items that are contained in one list
443 Read in one chunk and add it to the directory. If the chunk
444 happens to be of type LIST, then call ParseList recursively for
447 \param parent The id of the item to process
450 void RIFFFile::ParseChunk( int parent )
456 /* Check whether it is a LIST. If so, let ParseList deal with it */
458 fail_if( read( fd, &type, sizeof( type ) ) != sizeof( type ));
459 if ( type == make_fourcc( "LIST" ) )
461 typesize = (int) -sizeof( type );
462 fail_if( lseek( fd, typesize, SEEK_CUR ) == ( off_t ) - 1 );
466 /* it is a normal chunk, create a new directory entry for it */
470 fail_neg( read( fd, &length, sizeof( length ) ) );
473 AddDirectoryEntry( type, 0, length, parent );
474 fail_if( lseek( fd, length, SEEK_CUR ) == ( off_t ) - 1 );
479 /** Reads all items that are contained in one list
481 \param parent The id of the list to process
485 void RIFFFile::ParseList( int parent )
494 /* Read in the chunk header (type and length). */
495 fail_neg( read( fd, &type, sizeof( type ) ) );
496 fail_neg( read( fd, &length, sizeof( length ) ) );
501 /* The contents of the list starts here. Obtain its offset. The list
502 name (4 bytes) is already part of the contents). */
504 pos = lseek( fd, 0, SEEK_CUR );
505 fail_if( pos == ( off_t ) - 1 );
506 fail_neg( read( fd, &name, sizeof( name ) ) );
508 /* Add an entry for this list. */
510 list = AddDirectoryEntry( type, name, sizeof( name ), parent );
512 /* Read in any chunks contained in this list. This list is the
513 parent for all chunks it contains. */
515 listEnd = pos + length;
516 while ( pos < listEnd )
519 pos = lseek( fd, 0, SEEK_CUR );
520 fail_if( pos == ( off_t ) - 1 );
525 /** Reads the directory structure of the whole RIFF file
529 void RIFFFile::ParseRIFF( void )
535 int container = AddDirectoryEntry( make_fourcc( "FILE" ), make_fourcc( "FILE" ), 0, RIFF_NO_PARENT );
537 pos = lseek( fd, 0, SEEK_SET );
539 /* calculate file size from RIFF header instead from physical file. */
541 while ( ( read( fd, &type, sizeof( type ) ) > 0 ) &&
542 ( read( fd, &length, sizeof( length ) ) > 0 ) &&
543 ( type == make_fourcc( "RIFF" ) ) )
546 filesize += length + RIFF_HEADERSIZE;
548 fail_if( lseek( fd, pos, SEEK_SET ) == ( off_t ) - 1 );
549 ParseList( container );
550 pos = lseek( fd, 0, SEEK_CUR );
551 fail_if( pos == ( off_t ) - 1 );
556 /** Reads one item including its contents from the RIFF file
558 \param chunk_index The index of the item to write
559 \param data A pointer to the data
563 void RIFFFile::ReadChunk( int chunk_index, void *data, off_t data_len )
567 entry = GetDirectoryEntry( chunk_index );
568 pthread_mutex_lock( &file_mutex );
569 fail_if( lseek( fd, entry.offset, SEEK_SET ) == ( off_t ) - 1 );
570 fail_neg( read( fd, data, entry.length > data_len ? data_len : entry.length ) );
571 pthread_mutex_unlock( &file_mutex );
575 /** Writes one item including its contents to the RIFF file
577 \param chunk_index The index of the item to write
578 \param data A pointer to the data
582 void RIFFFile::WriteChunk( int chunk_index, const void *data )
586 entry = GetDirectoryEntry( chunk_index );
587 pthread_mutex_lock( &file_mutex );
588 fail_if( lseek( fd, entry.offset - RIFF_HEADERSIZE, SEEK_SET ) == ( off_t ) - 1 );
589 fail_neg( write( fd, &entry.type, sizeof( entry.type ) ) );
590 DWORD length = entry.length;
591 fail_neg( write( fd, &length, sizeof( length ) ) );
592 fail_neg( write( fd, data, entry.length ) );
593 pthread_mutex_unlock( &file_mutex );
595 /* Remember that this entry already has been written. */
597 directory[ chunk_index ].written = true;
601 /** Writes out the directory structure
603 For all items in the directory list that have not been written
604 yet, it seeks to the file position where that item should be
605 stored and writes the type and length field. If the item has a
606 name, it will also write the name field.
608 \note It does not write the contents of any item. Use WriteChunk to do that. */
610 void RIFFFile::WriteRIFF( void )
614 int count = directory.size();
616 /* Start at the second entry (RIFF), since the first entry (FILE)
617 is needed only for internal purposes and is not written to the
620 for ( i = 1; i < count; ++i )
623 /* Only deal with entries that havenĀ“t been written */
625 entry = GetDirectoryEntry( i );
626 if ( entry.written == false )
629 /* A chunk entry consist of its type and length, a list
630 entry has an additional name. Look up the entry, seek
631 to the start of the header, which is at the offset of
632 the data start minus the header size and write out the
635 fail_if( lseek( fd, entry.offset - RIFF_HEADERSIZE, SEEK_SET ) == ( off_t ) - 1 ) ;
636 fail_neg( write( fd, &entry.type, sizeof( entry.type ) ) );
637 DWORD length = entry.length;
638 fail_neg( write( fd, &length, sizeof( length ) ) );
640 /* If it has a name, it is a list. Write out the extra name
643 if ( entry.name != 0 )
645 fail_neg( write( fd, &entry.name, sizeof( entry.name ) ) );
648 /* Remember that this entry already has been written. */
650 directory[ i ].written = true;