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