]> git.sesse.net Git - mlt/blob - src/modules/kino/riff.cc
fff59edb6c26299b92a41ff6dd754afa84a29002
[mlt] / src / modules / kino / riff.cc
1 /*
2 * riff.cc library for RIFF file format i/o
3 * Copyright (C) 2000 - 2002 Arne Schirmacher <arne@schirmacher.de>
4 *
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.
9 *
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.
14 *
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.
18 */
19
20 #include "config.h"
21
22 // C++ includes
23
24 #include <string> 
25 //#include <stdio.h>
26 #include <iostream>
27 #include <iomanip>
28
29 using std::cout;
30 using std::hex;
31 using std::dec;
32 using std::setw;
33 using std::setfill;
34 using std::endl;
35
36 // C includes
37
38 #include <fcntl.h>
39 #include <unistd.h>
40 #include <assert.h>
41
42 // local includes
43
44 #include "error.h"
45 #include "riff.h"
46
47
48 /** make a 32 bit "string-id"
49  
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
53  
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. */
57
58 FOURCC make_fourcc( const char *s )
59 {
60         if ( s[ 0 ] == 0 )
61                 return 0;
62         else
63                 return *( ( FOURCC* ) s );
64 }
65
66
67 RIFFDirEntry::RIFFDirEntry()
68 {
69         type = 0;
70         name = 0;
71         length = 0;
72         offset = 0;
73         parent = 0;
74         written = 0;
75 }
76
77
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 )
79 {}
80
81
82 /** Creates the object without an output file.
83  
84 */
85
86 RIFFFile::RIFFFile() : fd( -1 )
87 {
88         pthread_mutex_init( &file_mutex, NULL );
89 }
90
91
92 /* Copy constructor
93  
94    Duplicate the file descriptor
95 */
96
97 RIFFFile::RIFFFile( const RIFFFile& riff ) : fd( -1 )
98 {
99         if ( riff.fd != -1 )
100         {
101                 fd = dup( riff.fd );
102         }
103         directory = riff.directory;
104 }
105
106
107 /** Destroys the object.
108  
109     If it has an associated opened file, close it. */
110
111 RIFFFile::~RIFFFile()
112 {
113         Close();
114         pthread_mutex_destroy( &file_mutex );
115 }
116
117
118 RIFFFile& RIFFFile::operator=( const RIFFFile& riff )
119 {
120         if ( fd != riff.fd )
121         {
122                 Close();
123                 if ( riff.fd != -1 )
124                 {
125                         fd = dup( riff.fd );
126                 }
127                 directory = riff.directory;
128         }
129         return *this;
130 }
131
132
133 /** Creates or truncates the file.
134  
135     \param s the filename
136 */
137
138 bool RIFFFile::Create( const char *s )
139 {
140         fd = open( s, O_RDWR | O_NONBLOCK | O_CREAT | O_TRUNC, 00644 );
141
142         if ( fd == -1 )
143                 return false;
144         else
145                 return true;
146 }
147
148
149 /** Opens the file read only.
150  
151     \param s the filename
152 */
153
154 bool RIFFFile::Open( const char *s )
155 {
156         fd = open( s, O_RDONLY | O_NONBLOCK );
157
158         if ( fd == -1 )
159                 return false;
160         else
161                 return true;
162 }
163
164
165 /** Destroys the object.
166  
167     If it has an associated opened file, close it. */
168
169 void RIFFFile::Close()
170 {
171         if ( fd != -1 )
172         {
173                 close( fd );
174                 fd = -1;
175         }
176 }
177
178
179 /** Adds an entry to the list of containers.
180  
181     \param type the type of this entry
182     \param name the name
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
186  
187     The topmost object is not contained in any other container. Use
188     the special ID RIFF_NO_PARENT to create the topmost object. */
189
190 int RIFFFile::AddDirectoryEntry( FOURCC type, FOURCC name, off_t length, int list )
191 {
192         /* Put all parameters in an RIFFDirEntry object. The offset is
193            currently unknown. */
194
195         RIFFDirEntry entry( type, name, length, 0 /* offset */, list );
196
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
200            header. */
201
202         if ( list != RIFF_NO_PARENT )
203         {
204                 RIFFDirEntry parent = GetDirectoryEntry( list );
205                 entry.offset = parent.offset + parent.length + RIFF_HEADERSIZE;
206         }
207
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. */
212
213         while ( list != RIFF_NO_PARENT )
214         {
215                 RIFFDirEntry parent = GetDirectoryEntry( list );
216                 parent.length += RIFF_HEADERSIZE + length;
217                 SetDirectoryEntry( list, parent );
218                 list = parent.parent;
219         }
220
221         directory.insert( directory.end(), entry );
222
223         return directory.size() - 1;
224 }
225
226
227 /** Modifies an entry.
228  
229     \param i the ID of the entry which is to modify
230     \param type the type of this entry
231     \param name the name
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 */
236
237 void RIFFFile::SetDirectoryEntry( int i, FOURCC type, FOURCC name, off_t length, off_t offset, int list )
238 {
239         RIFFDirEntry entry( type, name, length, offset, list );
240
241         assert( i >= 0 && i < ( int ) directory.size() );
242
243         directory[ i ] = entry;
244 }
245
246
247 /** Modifies an entry.
248  
249     The entry.written flag is set to false because the contents has been modified
250  
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 */
255
256 void RIFFFile::SetDirectoryEntry( int i, RIFFDirEntry &entry )
257 {
258         assert( i >= 0 && i < ( int ) directory.size() );
259
260         entry.written = false;
261         directory[ i ] = entry;
262 }
263
264
265 /** Retrieves an entry.
266  
267     Gets the most important member variables.
268  
269     \param i the ID of the entry to retrieve
270     \param type
271     \param name
272     \param length
273     \param offset
274     \param list */
275
276 void RIFFFile::GetDirectoryEntry( int i, FOURCC &type, FOURCC &name, off_t &length, off_t &offset, int &list ) const
277 {
278         RIFFDirEntry entry;
279
280         assert( i >= 0 && i < ( int ) directory.size() );
281
282         entry = directory[ i ];
283         type = entry.type;
284         name = entry.name;
285         length = entry.length;
286         offset = entry.offset;
287         list = entry.parent;
288 }
289
290
291 /** Retrieves an entry.
292  
293     Gets the whole RIFFDirEntry object.
294  
295     \param i the ID of the entry to retrieve
296     \return the entry */
297
298 RIFFDirEntry RIFFFile::GetDirectoryEntry( int i ) const
299 {
300         assert( i >= 0 && i < ( int ) directory.size() );
301
302         return directory[ i ];
303 }
304
305
306 /** Calculates the total size of the file
307  
308     \return the size the file in bytes
309 */
310
311 off_t RIFFFile::GetFileSize( void ) const
312 {
313
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. 
318
319            Note that the first entry does not belong to the AVI
320            file.
321
322            If we don't have any entry, the file size is zero. */
323
324         if ( directory.size() > 0 )
325                 return directory[ 0 ].length;
326         else
327                 return 0;
328 }
329
330
331 /** prints the attributes of the entry
332  
333     \param i the ID of the entry to print
334 */
335
336 void RIFFFile::PrintDirectoryEntry ( int i ) const
337 {
338         RIFFDirEntry entry;
339         RIFFDirEntry parent;
340         FOURCC entry_name;
341         FOURCC list_name;
342
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. */
347
348         entry = GetDirectoryEntry( i );
349         if ( entry.parent != RIFF_NO_PARENT )
350         {
351                 parent = GetDirectoryEntry( entry.parent );
352                 list_name = parent.name;
353         }
354         else
355         {
356                 list_name = make_fourcc( "    " );
357         }
358         if ( entry.name != 0 )
359         {
360                 entry_name = entry.name;
361         }
362         else
363         {
364                 entry_name = make_fourcc( "    " );
365         }
366
367         /* Print out the ascii representation of type and name, as well as
368            length and file offset. */
369
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]
375         << " name: "
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
382         << " list: "
383         << ((char *)&list_name)[0]
384         << ((char *)&list_name)[1]
385         << ((char *)&list_name)[2]
386         << ((char *)&list_name)[3] << dec << endl;
387
388         /* print the content itself */
389
390         PrintDirectoryEntryData( entry );
391 }
392
393
394 /** prints the contents of the entry
395  
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.
398  
399     \param entry the entry to print */
400
401 void RIFFFile::PrintDirectoryEntryData( const RIFFDirEntry &entry ) const
402         {}
403
404
405 /** prints the contents of the whole directory
406  
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.
409  
410     \param entry the entry to print */
411
412 void RIFFFile::PrintDirectory() const
413 {
414         int i;
415         int count = directory.size();
416
417         for ( i = 0; i < count; ++i )
418                 PrintDirectoryEntry( i );
419 }
420
421
422 /** finds the index
423  
424     finds the index of a given directory entry type 
425  
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 */
430
431 int RIFFFile::FindDirectoryEntry ( FOURCC type, int n ) const
432 {
433         int i, j = 0;
434         int count = directory.size();
435
436         for ( i = 0; i < count; ++i )
437                 if ( directory[ i ].type == type )
438                 {
439                         if ( j == n )
440                                 return i;
441                         j++;
442                 }
443
444         return -1;
445 }
446
447
448 /** Reads all items that are contained in one list
449  
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
452     it.
453  
454     \param parent The id of the item to process
455 */
456
457 void RIFFFile::ParseChunk( int parent )
458 {
459         FOURCC type;
460         DWORD length;
461         int typesize;
462
463         /* Check whether it is a LIST. If so, let ParseList deal with it */
464
465         fail_if( read( fd, &type, sizeof( type ) ) != sizeof( type ));
466         if ( type == make_fourcc( "LIST" ) )
467         {
468                 typesize = (int) -sizeof( type );
469                 fail_if( lseek( fd, typesize, SEEK_CUR ) == ( off_t ) - 1 );
470                 ParseList( parent );
471         }
472
473         /* it is a normal chunk, create a new directory entry for it */
474
475         else
476         {
477                 fail_neg( read( fd, &length, sizeof( length ) ) );
478                 if ( length & 1 )
479                         length++;
480                 AddDirectoryEntry( type, 0, length, parent );
481                 fail_if( lseek( fd, length, SEEK_CUR ) == ( off_t ) - 1 );
482         }
483 }
484
485
486 /** Reads all items that are contained in one list
487  
488     \param parent The id of the list to process
489  
490 */
491
492 void RIFFFile::ParseList( int parent )
493 {
494         FOURCC type;
495         FOURCC name;
496         int list;
497         DWORD length;
498         off_t pos;
499         off_t   listEnd;
500
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 ) ) );
504
505         if ( length & 1 )
506                 length++;
507
508         /* The contents of the list starts here. Obtain its offset. The list
509            name (4 bytes) is already part of the contents). */
510
511         pos = lseek( fd, 0, SEEK_CUR );
512         fail_if( pos == ( off_t ) - 1 );
513         fail_neg( read( fd, &name, sizeof( name ) ) );
514
515         /* Add an entry for this list. */
516
517         list = AddDirectoryEntry( type, name, sizeof( name ), parent );
518
519         /* Read in any chunks contained in this list. This list is the
520            parent for all chunks it contains. */
521
522         listEnd = pos + length;
523         while ( pos < listEnd )
524         {
525                 ParseChunk( list );
526                 pos = lseek( fd, 0, SEEK_CUR );
527                 fail_if( pos == ( off_t ) - 1 );
528         }
529 }
530
531
532 /** Reads the directory structure of the whole RIFF file
533  
534 */
535
536 void RIFFFile::ParseRIFF( void )
537 {
538         FOURCC type;
539         DWORD length;
540         off_t filesize = 0;
541         off_t pos;
542         int container = AddDirectoryEntry( make_fourcc( "FILE" ), make_fourcc( "FILE" ), 0, RIFF_NO_PARENT );
543
544         pos = lseek( fd, 0, SEEK_SET );
545         fail_if( pos == -1 );
546
547         /* calculate file size from RIFF header instead from physical file. */
548
549         while ( ( read( fd, &type, sizeof( type ) ) > 0 ) &&
550                 ( read( fd, &length, sizeof( length ) ) > 0 ) &&
551                 ( type == make_fourcc( "RIFF" ) ) )
552         {
553
554                 filesize += length + RIFF_HEADERSIZE;
555
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 );
560         }
561 }
562
563
564 /** Reads one item including its contents from the RIFF file
565  
566     \param chunk_index The index of the item to write
567     \param data A pointer to the data
568  
569 */
570
571 void RIFFFile::ReadChunk( int chunk_index, void *data, off_t data_len )
572 {
573         RIFFDirEntry entry;
574
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 );
580 }
581
582
583 /** Writes one item including its contents to the RIFF file
584  
585     \param chunk_index The index of the item to write
586     \param data A pointer to the data
587  
588 */
589
590 void RIFFFile::WriteChunk( int chunk_index, const void *data )
591 {
592         RIFFDirEntry entry;
593
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 );
602
603         /* Remember that this entry already has been written. */
604
605         directory[ chunk_index ].written = true;
606 }
607
608
609 /** Writes out the directory structure
610  
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.
615  
616     \note It does not write the contents of any item. Use WriteChunk to do that. */
617
618 void RIFFFile::WriteRIFF( void )
619 {
620         int i;
621         RIFFDirEntry entry;
622         int count = directory.size();
623
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
626            file. */
627
628         for ( i = 1; i < count; ++i )
629         {
630
631                 /* Only deal with entries that havenĀ“t been written */
632
633                 entry = GetDirectoryEntry( i );
634                 if ( entry.written == false )
635                 {
636
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
641                            items. */
642
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 ) ) );
647
648                         /* If it has a name, it is a list. Write out the extra name
649                            field. */
650
651                         if ( entry.name != 0 )
652                         {
653                                 fail_neg( write( fd, &entry.name, sizeof( entry.name ) ) );
654                         }
655
656                         /* Remember that this entry already has been written. */
657
658                         directory[ i ].written = true;
659                 }
660         }
661 }