1 /*
2 ===========================================================================
3 Copyright (C) 2005-2006 Tim Angus
4 
5 This file is part of Quake III Arena source code.
6 
7 Quake III Arena source code is free software; you can redistribute it
8 and/or modify it under the terms of the GNU General Public License as
9 published by the Free Software Foundation; either version 2 of the License,
10 or (at your option) any later version.
11 
12 Quake III Arena source code is distributed in the hope that it will be
13 useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 GNU General Public License for more details.
16 
17 You should have received a copy of the GNU General Public License
18 along with Quake III Arena source code; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20 ===========================================================================
21 */
22 
23 #include "client.h"
24 #include "snd_local.h"
25 
26 #define INDEX_FILE_EXTENSION ".index.dat"
27 
28 #define MAX_RIFF_CHUNKS 16
29 
30 typedef struct audioFormat_s
31 {
32   int rate;
33   int format;
34   int channels;
35   int bits;
36 
37   int sampleSize;
38   int totalBytes;
39 } audioFormat_t;
40 
41 typedef struct aviFileData_s
42 {
43   qboolean      fileOpen;
44   fileHandle_t  f;
45   char          fileName[ MAX_QPATH ];
46   int           fileSize;
47   int           moviOffset;
48   int           moviSize;
49 
50   fileHandle_t  idxF;
51   int           numIndices;
52 
53   int           frameRate;
54   int           framePeriod;
55   int           width, height;
56   int           numVideoFrames;
57   int           maxRecordSize;
58   qboolean      motionJpeg;
59 
60   qboolean      audio;
61   audioFormat_t a;
62   int           numAudioFrames;
63 
64   int           chunkStack[ MAX_RIFF_CHUNKS ];
65   int           chunkStackTop;
66 
67   byte          *cBuffer, *eBuffer;
68 } aviFileData_t;
69 
70 static aviFileData_t afd;
71 
72 #define MAX_AVI_BUFFER 2048
73 
74 static byte buffer[ MAX_AVI_BUFFER ];
75 static int  bufIndex;
76 
77 /*
78 ===============
79 SafeFS_Write
80 ===============
81 */
SafeFS_Write(const void * buffer,int len,fileHandle_t f)82 static ID_INLINE void SafeFS_Write( const void *buffer, int len, fileHandle_t f )
83 {
84   if( FS_Write( buffer, len, f ) < len )
85     Com_Error( ERR_DROP, "Failed to write avi file\n" );
86 }
87 
88 /*
89 ===============
90 WRITE_STRING
91 ===============
92 */
WRITE_STRING(const char * s)93 static ID_INLINE void WRITE_STRING( const char *s )
94 {
95   Com_Memcpy( &buffer[ bufIndex ], s, strlen( s ) );
96   bufIndex += strlen( s );
97 }
98 
99 /*
100 ===============
101 WRITE_4BYTES
102 ===============
103 */
WRITE_4BYTES(int x)104 static ID_INLINE void WRITE_4BYTES( int x )
105 {
106   buffer[ bufIndex + 0 ] = (byte)( ( x >>  0 ) & 0xFF );
107   buffer[ bufIndex + 1 ] = (byte)( ( x >>  8 ) & 0xFF );
108   buffer[ bufIndex + 2 ] = (byte)( ( x >> 16 ) & 0xFF );
109   buffer[ bufIndex + 3 ] = (byte)( ( x >> 24 ) & 0xFF );
110   bufIndex += 4;
111 }
112 
113 /*
114 ===============
115 WRITE_2BYTES
116 ===============
117 */
WRITE_2BYTES(int x)118 static ID_INLINE void WRITE_2BYTES( int x )
119 {
120   buffer[ bufIndex + 0 ] = (byte)( ( x >>  0 ) & 0xFF );
121   buffer[ bufIndex + 1 ] = (byte)( ( x >>  8 ) & 0xFF );
122   bufIndex += 2;
123 }
124 
125 /*
126 ===============
127 WRITE_1BYTES
128 ===============
129 */
WRITE_1BYTES(int x)130 static ID_INLINE void WRITE_1BYTES( int x )
131 {
132   buffer[ bufIndex ] = x;
133   bufIndex += 1;
134 }
135 
136 /*
137 ===============
138 START_CHUNK
139 ===============
140 */
START_CHUNK(const char * s)141 static ID_INLINE void START_CHUNK( const char *s )
142 {
143   if( afd.chunkStackTop == MAX_RIFF_CHUNKS )
144   {
145     Com_Error( ERR_DROP, "ERROR: Top of chunkstack breached\n" );
146   }
147 
148   afd.chunkStack[ afd.chunkStackTop ] = bufIndex;
149   afd.chunkStackTop++;
150   WRITE_STRING( s );
151   WRITE_4BYTES( 0 );
152 }
153 
154 /*
155 ===============
156 END_CHUNK
157 ===============
158 */
END_CHUNK(void)159 static ID_INLINE void END_CHUNK( void )
160 {
161   int endIndex = bufIndex;
162 
163   if( afd.chunkStackTop <= 0 )
164   {
165     Com_Error( ERR_DROP, "ERROR: Bottom of chunkstack breached\n" );
166   }
167 
168   afd.chunkStackTop--;
169   bufIndex = afd.chunkStack[ afd.chunkStackTop ];
170   bufIndex += 4;
171   WRITE_4BYTES( endIndex - bufIndex - 4 );
172   bufIndex = endIndex;
173   bufIndex = PAD( bufIndex, 2 );
174 }
175 
176 /*
177 ===============
178 CL_WriteAVIHeader
179 ===============
180 */
CL_WriteAVIHeader(void)181 void CL_WriteAVIHeader( void )
182 {
183   bufIndex = 0;
184   afd.chunkStackTop = 0;
185 
186   START_CHUNK( "RIFF" );
187   {
188     WRITE_STRING( "AVI " );
189     {
190       START_CHUNK( "LIST" );
191       {
192         WRITE_STRING( "hdrl" );
193         WRITE_STRING( "avih" );
194         WRITE_4BYTES( 56 );                     //"avih" "chunk" size
195         WRITE_4BYTES( afd.framePeriod );        //dwMicroSecPerFrame
196         WRITE_4BYTES( afd.maxRecordSize *
197             afd.frameRate );                    //dwMaxBytesPerSec
198         WRITE_4BYTES( 0 );                      //dwReserved1
199         WRITE_4BYTES( 0x110 );                  //dwFlags bits HAS_INDEX and IS_INTERLEAVED
200         WRITE_4BYTES( afd.numVideoFrames );     //dwTotalFrames
201         WRITE_4BYTES( 0 );                      //dwInitialFrame
202 
203         if( afd.audio )                         //dwStreams
204           WRITE_4BYTES( 2 );
205         else
206           WRITE_4BYTES( 1 );
207 
208         WRITE_4BYTES( afd.maxRecordSize );      //dwSuggestedBufferSize
209         WRITE_4BYTES( afd.width );              //dwWidth
210         WRITE_4BYTES( afd.height );             //dwHeight
211         WRITE_4BYTES( 0 );                      //dwReserved[ 0 ]
212         WRITE_4BYTES( 0 );                      //dwReserved[ 1 ]
213         WRITE_4BYTES( 0 );                      //dwReserved[ 2 ]
214         WRITE_4BYTES( 0 );                      //dwReserved[ 3 ]
215 
216         START_CHUNK( "LIST" );
217         {
218           WRITE_STRING( "strl" );
219           WRITE_STRING( "strh" );
220           WRITE_4BYTES( 56 );                   //"strh" "chunk" size
221           WRITE_STRING( "vids" );
222 
223           if( afd.motionJpeg )
224             WRITE_STRING( "MJPG" );
225           else
226             WRITE_4BYTES( 0 );                  // BI_RGB
227 
228           WRITE_4BYTES( 0 );                    //dwFlags
229           WRITE_4BYTES( 0 );                    //dwPriority
230           WRITE_4BYTES( 0 );                    //dwInitialFrame
231 
232           WRITE_4BYTES( 1 );                    //dwTimescale
233           WRITE_4BYTES( afd.frameRate );        //dwDataRate
234           WRITE_4BYTES( 0 );                    //dwStartTime
235           WRITE_4BYTES( afd.numVideoFrames );   //dwDataLength
236 
237           WRITE_4BYTES( afd.maxRecordSize );    //dwSuggestedBufferSize
238           WRITE_4BYTES( -1 );                   //dwQuality
239           WRITE_4BYTES( 0 );                    //dwSampleSize
240           WRITE_2BYTES( 0 );                    //rcFrame
241           WRITE_2BYTES( 0 );                    //rcFrame
242           WRITE_2BYTES( afd.width );            //rcFrame
243           WRITE_2BYTES( afd.height );           //rcFrame
244 
245           WRITE_STRING( "strf" );
246           WRITE_4BYTES( 40 );                   //"strf" "chunk" size
247           WRITE_4BYTES( 40 );                   //biSize
248           WRITE_4BYTES( afd.width );            //biWidth
249           WRITE_4BYTES( afd.height );           //biHeight
250           WRITE_2BYTES( 1 );                    //biPlanes
251           WRITE_2BYTES( 24 );                   //biBitCount
252 
253           if( afd.motionJpeg )                  //biCompression
254           {
255             WRITE_STRING( "MJPG" );
256             WRITE_4BYTES( afd.width *
257                 afd.height );                   //biSizeImage
258           }
259           else
260           {
261             WRITE_4BYTES( 0 );                  // BI_RGB
262             WRITE_4BYTES( afd.width *
263                 afd.height * 3 );               //biSizeImage
264           }
265 
266           WRITE_4BYTES( 0 );                    //biXPelsPetMeter
267           WRITE_4BYTES( 0 );                    //biYPelsPetMeter
268           WRITE_4BYTES( 0 );                    //biClrUsed
269           WRITE_4BYTES( 0 );                    //biClrImportant
270         }
271         END_CHUNK( );
272 
273         if( afd.audio )
274         {
275           START_CHUNK( "LIST" );
276           {
277             WRITE_STRING( "strl" );
278             WRITE_STRING( "strh" );
279             WRITE_4BYTES( 56 );                 //"strh" "chunk" size
280             WRITE_STRING( "auds" );
281             WRITE_4BYTES( 0 );                  //FCC
282             WRITE_4BYTES( 0 );                  //dwFlags
283             WRITE_4BYTES( 0 );                  //dwPriority
284             WRITE_4BYTES( 0 );                  //dwInitialFrame
285 
286             WRITE_4BYTES( afd.a.sampleSize );   //dwTimescale
287             WRITE_4BYTES( afd.a.sampleSize *
288                 afd.a.rate );                   //dwDataRate
289             WRITE_4BYTES( 0 );                  //dwStartTime
290             WRITE_4BYTES( afd.a.totalBytes /
291                 afd.a.sampleSize );             //dwDataLength
292 
293             WRITE_4BYTES( 0 );                  //dwSuggestedBufferSize
294             WRITE_4BYTES( -1 );                 //dwQuality
295             WRITE_4BYTES( afd.a.sampleSize );   //dwSampleSize
296             WRITE_2BYTES( 0 );                  //rcFrame
297             WRITE_2BYTES( 0 );                  //rcFrame
298             WRITE_2BYTES( 0 );                  //rcFrame
299             WRITE_2BYTES( 0 );                  //rcFrame
300 
301             WRITE_STRING( "strf" );
302             WRITE_4BYTES( 18 );                 //"strf" "chunk" size
303             WRITE_2BYTES( afd.a.format );       //wFormatTag
304             WRITE_2BYTES( afd.a.channels );     //nChannels
305             WRITE_4BYTES( afd.a.rate );         //nSamplesPerSec
306             WRITE_4BYTES( afd.a.sampleSize *
307                 afd.a.rate );                   //nAvgBytesPerSec
308             WRITE_2BYTES( afd.a.sampleSize );   //nBlockAlign
309             WRITE_2BYTES( afd.a.bits );         //wBitsPerSample
310             WRITE_2BYTES( 0 );                  //cbSize
311           }
312           END_CHUNK( );
313         }
314       }
315       END_CHUNK( );
316 
317       afd.moviOffset = bufIndex;
318 
319       START_CHUNK( "LIST" );
320       {
321         WRITE_STRING( "movi" );
322       }
323     }
324   }
325 }
326 
327 /*
328 ===============
329 CL_OpenAVIForWriting
330 
331 Creates an AVI file and gets it into a state where
332 writing the actual data can begin
333 ===============
334 */
CL_OpenAVIForWriting(const char * fileName)335 qboolean CL_OpenAVIForWriting( const char *fileName )
336 {
337   if( afd.fileOpen )
338     return qfalse;
339 
340   Com_Memset( &afd, 0, sizeof( aviFileData_t ) );
341 
342   // Don't start if a framerate has not been chosen
343   if( cl_aviFrameRate->integer <= 0 )
344   {
345     Com_Printf( S_COLOR_RED "cl_aviFrameRate must be >= 1\n" );
346     return qfalse;
347   }
348 
349   if( ( afd.f = FS_FOpenFileWrite( fileName ) ) <= 0 )
350     return qfalse;
351 
352   if( ( afd.idxF = FS_FOpenFileWrite(
353           va( "%s" INDEX_FILE_EXTENSION, fileName ) ) ) <= 0 )
354   {
355     FS_FCloseFile( afd.f );
356     return qfalse;
357   }
358 
359   Q_strncpyz( afd.fileName, fileName, MAX_QPATH );
360 
361   afd.frameRate = cl_aviFrameRate->integer;
362   afd.framePeriod = (int)( 1000000.0f / afd.frameRate );
363   afd.width = cls.glconfig.vidWidth;
364   afd.height = cls.glconfig.vidHeight;
365 
366   if( cl_aviMotionJpeg->integer )
367     afd.motionJpeg = qtrue;
368   else
369     afd.motionJpeg = qfalse;
370 
371   afd.cBuffer = Z_Malloc( afd.width * afd.height * 4 );
372   afd.eBuffer = Z_Malloc( afd.width * afd.height * 4 );
373 
374   afd.a.rate = dma.speed;
375   afd.a.format = WAV_FORMAT_PCM;
376   afd.a.channels = dma.channels;
377   afd.a.bits = dma.samplebits;
378   afd.a.sampleSize = ( afd.a.bits / 8 ) * afd.a.channels;
379 
380   if( afd.a.rate % afd.frameRate )
381   {
382     int suggestRate = afd.frameRate;
383 
384     while( ( afd.a.rate % suggestRate ) && suggestRate >= 1 )
385       suggestRate--;
386 
387     Com_Printf( S_COLOR_YELLOW "WARNING: cl_aviFrameRate is not a divisor "
388         "of the audio rate, suggest %d\n", suggestRate );
389   }
390 
391   if( !Cvar_VariableIntegerValue( "s_initsound" ) )
392   {
393     afd.audio = qfalse;
394   }
395   else if( Q_stricmp( Cvar_VariableString( "s_backend" ), "OpenAL" ) )
396   {
397     if( afd.a.bits != 16 || afd.a.channels != 2 )
398     {
399       Com_Printf( S_COLOR_YELLOW "WARNING: Audio format of %d bit/%d channels not supported",
400           afd.a.bits, afd.a.channels );
401       afd.audio = qfalse;
402     }
403     else
404       afd.audio = qtrue;
405   }
406   else
407   {
408     afd.audio = qfalse;
409     Com_Printf( S_COLOR_YELLOW "WARNING: Audio capture is not supported "
410         "with OpenAL. Set s_useOpenAL to 0 for audio capture\n" );
411   }
412 
413   // This doesn't write a real header, but allocates the
414   // correct amount of space at the beginning of the file
415   CL_WriteAVIHeader( );
416 
417   SafeFS_Write( buffer, bufIndex, afd.f );
418   afd.fileSize = bufIndex;
419 
420   bufIndex = 0;
421   START_CHUNK( "idx1" );
422   SafeFS_Write( buffer, bufIndex, afd.idxF );
423 
424   afd.moviSize = 4; // For the "movi"
425   afd.fileOpen = qtrue;
426 
427   return qtrue;
428 }
429 
430 /*
431 ===============
432 CL_CheckFileSize
433 ===============
434 */
CL_CheckFileSize(int bytesToAdd)435 static qboolean CL_CheckFileSize( int bytesToAdd )
436 {
437   unsigned int newFileSize;
438 
439   newFileSize =
440     afd.fileSize +                // Current file size
441     bytesToAdd +                  // What we want to add
442     ( afd.numIndices * 16 ) +     // The index
443     4;                            // The index size
444 
445   // I assume all the operating systems
446   // we target can handle a 2Gb file
447   if( newFileSize > INT_MAX )
448   {
449     // Close the current file...
450     CL_CloseAVI( );
451 
452     // ...And open a new one
453     CL_OpenAVIForWriting( va( "%s_", afd.fileName ) );
454 
455     return qtrue;
456   }
457 
458   return qfalse;
459 }
460 
461 /*
462 ===============
463 CL_WriteAVIVideoFrame
464 ===============
465 */
CL_WriteAVIVideoFrame(const byte * imageBuffer,int size)466 void CL_WriteAVIVideoFrame( const byte *imageBuffer, int size )
467 {
468   int   chunkOffset = afd.fileSize - afd.moviOffset - 8;
469   int   chunkSize = 8 + size;
470   int   paddingSize = PAD( size, 2 ) - size;
471   byte  padding[ 4 ] = { 0 };
472 
473   if( !afd.fileOpen )
474     return;
475 
476   // Chunk header + contents + padding
477   if( CL_CheckFileSize( 8 + size + 2 ) )
478     return;
479 
480   bufIndex = 0;
481   WRITE_STRING( "00dc" );
482   WRITE_4BYTES( size );
483 
484   SafeFS_Write( buffer, 8, afd.f );
485   SafeFS_Write( imageBuffer, size, afd.f );
486   SafeFS_Write( padding, paddingSize, afd.f );
487   afd.fileSize += ( chunkSize + paddingSize );
488 
489   afd.numVideoFrames++;
490   afd.moviSize += ( chunkSize + paddingSize );
491 
492   if( size > afd.maxRecordSize )
493     afd.maxRecordSize = size;
494 
495   // Index
496   bufIndex = 0;
497   WRITE_STRING( "00dc" );           //dwIdentifier
498   WRITE_4BYTES( 0x00000010 );       //dwFlags (all frames are KeyFrames)
499   WRITE_4BYTES( chunkOffset );      //dwOffset
500   WRITE_4BYTES( size );             //dwLength
501   SafeFS_Write( buffer, 16, afd.idxF );
502 
503   afd.numIndices++;
504 }
505 
506 #define PCM_BUFFER_SIZE 44100
507 
508 /*
509 ===============
510 CL_WriteAVIAudioFrame
511 ===============
512 */
CL_WriteAVIAudioFrame(const byte * pcmBuffer,int size)513 void CL_WriteAVIAudioFrame( const byte *pcmBuffer, int size )
514 {
515   static byte pcmCaptureBuffer[ PCM_BUFFER_SIZE ] = { 0 };
516   static int  bytesInBuffer = 0;
517 
518   if( !afd.audio )
519     return;
520 
521   if( !afd.fileOpen )
522     return;
523 
524   // Chunk header + contents + padding
525   if( CL_CheckFileSize( 8 + bytesInBuffer + size + 2 ) )
526     return;
527 
528   if( bytesInBuffer + size > PCM_BUFFER_SIZE )
529   {
530     Com_Printf( S_COLOR_YELLOW
531         "WARNING: Audio capture buffer overflow -- truncating\n" );
532     size = PCM_BUFFER_SIZE - bytesInBuffer;
533   }
534 
535   Com_Memcpy( &pcmCaptureBuffer[ bytesInBuffer ], pcmBuffer, size );
536   bytesInBuffer += size;
537 
538   // Only write if we have a frame's worth of audio
539   if( bytesInBuffer >= (int)ceil( (float)afd.a.rate / (float)afd.frameRate ) *
540         afd.a.sampleSize )
541   {
542     int   chunkOffset = afd.fileSize - afd.moviOffset - 8;
543     int   chunkSize = 8 + bytesInBuffer;
544     int   paddingSize = PAD( bytesInBuffer, 2 ) - bytesInBuffer;
545     byte  padding[ 4 ] = { 0 };
546 
547     bufIndex = 0;
548     WRITE_STRING( "01wb" );
549     WRITE_4BYTES( bytesInBuffer );
550 
551     SafeFS_Write( buffer, 8, afd.f );
552     SafeFS_Write( pcmCaptureBuffer, bytesInBuffer, afd.f );
553     SafeFS_Write( padding, paddingSize, afd.f );
554     afd.fileSize += ( chunkSize + paddingSize );
555 
556     afd.numAudioFrames++;
557     afd.moviSize += ( chunkSize + paddingSize );
558     afd.a.totalBytes =+ bytesInBuffer;
559 
560     // Index
561     bufIndex = 0;
562     WRITE_STRING( "01wb" );           //dwIdentifier
563     WRITE_4BYTES( 0 );                //dwFlags
564     WRITE_4BYTES( chunkOffset );      //dwOffset
565     WRITE_4BYTES( bytesInBuffer );    //dwLength
566     SafeFS_Write( buffer, 16, afd.idxF );
567 
568     afd.numIndices++;
569 
570     bytesInBuffer = 0;
571   }
572 }
573 
574 /*
575 ===============
576 CL_TakeVideoFrame
577 ===============
578 */
CL_TakeVideoFrame(void)579 void CL_TakeVideoFrame( void )
580 {
581   // AVI file isn't open
582   if( !afd.fileOpen )
583     return;
584 
585   //re.TakeVideoFrame( afd.width, afd.height, afd.cBuffer, afd.eBuffer, afd.motionJpeg );
586 }
587 
588 /*
589 ===============
590 CL_CloseAVI
591 
592 Closes the AVI file and writes an index chunk
593 ===============
594 */
CL_CloseAVI(void)595 qboolean CL_CloseAVI( void )
596 {
597   int indexRemainder;
598   int indexSize = afd.numIndices * 16;
599   const char *idxFileName = va( "%s" INDEX_FILE_EXTENSION, afd.fileName );
600 
601   // AVI file isn't open
602   if( !afd.fileOpen )
603     return qfalse;
604 
605   afd.fileOpen = qfalse;
606 
607   FS_Seek( afd.idxF, 4, FS_SEEK_SET );
608   bufIndex = 0;
609   WRITE_4BYTES( indexSize );
610   SafeFS_Write( buffer, bufIndex, afd.idxF );
611   FS_FCloseFile( afd.idxF );
612 
613   // Write index
614 
615   // Open the temp index file
616   if( ( indexSize = FS_FOpenFileRead( idxFileName,
617           &afd.idxF, qtrue ) ) <= 0 )
618   {
619     FS_FCloseFile( afd.f );
620     return qfalse;
621   }
622 
623   indexRemainder = indexSize;
624 
625   // Append index to end of avi file
626   while( indexRemainder > MAX_AVI_BUFFER )
627   {
628     FS_Read( buffer, MAX_AVI_BUFFER, afd.idxF );
629     SafeFS_Write( buffer, MAX_AVI_BUFFER, afd.f );
630     afd.fileSize += MAX_AVI_BUFFER;
631     indexRemainder -= MAX_AVI_BUFFER;
632   }
633   FS_Read( buffer, indexRemainder, afd.idxF );
634   SafeFS_Write( buffer, indexRemainder, afd.f );
635   afd.fileSize += indexRemainder;
636   FS_FCloseFile( afd.idxF );
637 
638   // Remove temp index file
639   FS_HomeRemove( idxFileName );
640 
641   // Write the real header
642   FS_Seek( afd.f, 0, FS_SEEK_SET );
643   CL_WriteAVIHeader( );
644 
645   bufIndex = 4;
646   WRITE_4BYTES( afd.fileSize - 8 ); // "RIFF" size
647 
648   bufIndex = afd.moviOffset + 4;    // Skip "LIST"
649   WRITE_4BYTES( afd.moviSize );
650 
651   SafeFS_Write( buffer, bufIndex, afd.f );
652 
653   Z_Free( afd.cBuffer );
654   Z_Free( afd.eBuffer );
655   FS_FCloseFile( afd.f );
656 
657   Com_Printf( "Wrote %d:%d frames to %s\n", afd.numVideoFrames, afd.numAudioFrames, afd.fileName );
658 
659   return qtrue;
660 }
661 
662 /*
663 ===============
664 CL_VideoRecording
665 ===============
666 */
CL_VideoRecording(void)667 qboolean CL_VideoRecording( void )
668 {
669   return afd.fileOpen;
670 }
671