|
|
1.1 root 1: /*
2: Hatari - avi_record.c
3:
4: This file is distributed under the GNU Public License, version 2 or at
5: your option any later version. Read the file gpl.txt for details.
6:
7: AVI File recording
8:
9: This allows Hatari to record a video file, with both video and audio
10: streams, at full frame rate.
11:
12: Video frames are saved using the current video frequency of the emulated
13: machine (50 Hz, 60 Hz, 70 Hz, ...). Frames can be stored using different
14: codecs. So far, supported codecs are :
15: - BMP : uncompressed RGB images. Very fast to save, very few cpu needed
16: but requires a lot of disk bandwidth and a lot of space.
17: - PNG : compressed RBG images. Depending on the compression level, this
18: can require more cpu and could slow down Hatari. As compressed images
19: are much smaller than BMP images, this will require less space on disk
20: and much less disk bandwidth. Compression levels 3 or 4 give could
21: tradeoff between cpu usage and file size and should not slow down Hatari
22: with recent computers.
23:
24: PNG compression will often give a x20 ratio when compared to BMP and should
25: be used if you have a powerful enough cpu.
26:
27: Sound is saved as 16 bits pcm stereo, using the current Hatari sound output
28: frequency. For best accuracy, sound frequency should be a multiple of the
29: video frequency ; this means 44.1 kHz is the best choice for 50/60 Hz video.
30:
31: The AVI file is divided into multiple chunks. Hatari will save one video stream
32: and one audio stream, so the overall strucutre of the file is the following :
33:
34: RIFF avi
35: LIST
36: hdrl
37: avih
38: LIST
39: strl
40: strh (vids)
41: strf
42: LIST
43: strl
44: strh (auds)
45: strf
46: LIST
47: INFO
48: LIST
49: movi
50: 00db
51: 01wb
52: ...
53: idx1
54: */
55:
56: const char AVIRecord_fileid[] = "Hatari avi_record.c : " __DATE__ " " __TIME__;
57:
58: #include <SDL.h>
59: #include <SDL_endian.h>
60:
61: #include "main.h"
62: #include "audio.h"
63: #include "configuration.h"
64: #include "log.h"
65: #include "screen.h"
66: #include "screenSnapShot.h"
67: #include "sound.h"
68: #include "statusbar.h"
69: #include "avi_record.h"
70:
71: /* after above that brings in config.h */
72: #if HAVE_LIBPNG
73: #include <png.h>
74: #endif
75:
76: #include "pixel_convert.h" /* inline functions */
77:
78:
79:
80: typedef struct
81: {
82: Uint8 ChunkName[4]; /* '00db', '01wb', 'idx1' */
83: Uint8 ChunkSize[4];
84: } AVI_CHUNK;
85:
86:
87: typedef struct
88: {
89: Uint8 identifier[4]; /* '00db', '01wb', 'idx1' */
90: Uint8 flags[4];
91: Uint8 offset[4];
92: Uint8 length[4];
93: } AVI_CHUNK_INDEX;
94:
95:
96: typedef struct
97: {
98: Uint8 ChunkName[4]; /* 'strh' */
99: Uint8 ChunkSize[4];
100:
101: Uint8 stream_type[4]; /* 'vids' or 'auds' */
102: Uint8 stream_handler[4];
103: Uint8 flags[4];
104: Uint8 priority[2];
105: Uint8 language[2];
106: Uint8 initial_frames[4];
107: Uint8 time_scale[4];
108: Uint8 data_rate[4];
109: Uint8 start_time[4];
110: Uint8 data_length[4];
111: Uint8 buffer_size[4];
112: Uint8 quality[4];
113: Uint8 sample_size[4];
114: Uint8 dest_left[2];
115: Uint8 dest_top[2];
116: Uint8 dest_right[2];
117: Uint8 dest_bottom[2];
118: } AVI_STREAM_HEADER;
119:
120:
121: typedef struct
122: {
123: Uint8 ChunkName[4]; /* 'strf' */
124: Uint8 ChunkSize[4];
125:
126: Uint8 size[4];
127: Uint8 width[4];
128: Uint8 height[4];
129: Uint8 planes[2];
130: Uint8 bit_count[2];
131: Uint8 compression[4];
132: Uint8 size_image[4];
133: Uint8 xpels_meter[4];
134: Uint8 ypels_meter[4];
135: Uint8 clr_used[4];
136: Uint8 clr_important[4];
137: } AVI_STREAM_FORMAT_VIDS;
138:
139: typedef struct
140: {
141: Uint8 ChunkName[4]; /* 'LIST' */
142: Uint8 ChunkSize[4];
143:
144: Uint8 Name[4]; /* 'strl' */
145: AVI_STREAM_HEADER Header; /* 'strh' */
146: AVI_STREAM_FORMAT_VIDS Format; /* 'strf' */
147: } AVI_STREAM_LIST_VIDS;
148:
149:
150: typedef struct
151: {
152: Uint8 ChunkName[4]; /* 'strf' */
153: Uint8 ChunkSize[4];
154:
155: Uint8 codec[2];
156: Uint8 channels[2];
157: Uint8 sample_rate[4];
158: Uint8 bit_rate[4];
159: Uint8 block_align[2];
160: Uint8 bits_per_sample[2];
161: Uint8 ext_size[2];
162: } AVI_STREAM_FORMAT_AUDS;
163:
164: typedef struct
165: {
166: Uint8 ChunkName[4]; /* 'LIST' */
167: Uint8 ChunkSize[4];
168:
169: Uint8 Name[4]; /* 'strl' */
170: AVI_STREAM_HEADER Header; /* 'strh' */
171: AVI_STREAM_FORMAT_AUDS Format; /* 'strf' */
172: } AVI_STREAM_LIST_AUDS;
173:
174:
175: typedef struct {
176: Uint8 ChunkName[4]; /* 'avih' */
177: Uint8 ChunkSize[4];
178:
179: Uint8 microsec_per_frame[4];
180: Uint8 max_bytes_per_second[4];
181: Uint8 padding_granularity[4];
182: Uint8 flags[4];
183: Uint8 total_frames[4];
184: Uint8 init_frame[4];
185: Uint8 nb_streams[4];
186: Uint8 buffer_size[4];
187: Uint8 width[4];
188: Uint8 height[4];
189: Uint8 scale[4];
190: Uint8 rate[4];
191: Uint8 start[4];
192: Uint8 length[4];
193: } AVI_STREAM_AVIH;
194:
195: typedef struct
196: {
197: Uint8 ChunkName[4]; /* 'LIST' */
198: Uint8 ChunkSize[4];
199:
200: Uint8 Name[4]; /* 'hdrl' */
201: AVI_STREAM_AVIH Header;
202: } AVI_STREAM_LIST_AVIH;
203:
204:
205: typedef struct {
206: Uint8 ChunkName[4]; /* 'ISFT' (software used) */
207: Uint8 ChunkSize[4];
208:
209: // Uint8 Text[2]; /* Text's size should be multiple of 2 (including '\0') */
210: } AVI_STREAM_INFO;
211:
212: typedef struct
213: {
214: Uint8 ChunkName[4]; /* 'LIST' */
215: Uint8 ChunkSize[4];
216:
217: Uint8 Name[4]; /* 'INFO' */
218: AVI_STREAM_INFO Info;
219: } AVI_STREAM_LIST_INFO;
220:
221:
222: typedef struct
223: {
224: Uint8 ChunkName[4]; /* 'LIST' */
225: Uint8 ChunkSize[4];
226:
227: Uint8 Name[4]; /* 'movi' */
228: } AVI_STREAM_LIST_MOVI;
229:
230:
231: typedef struct
232: {
233: Uint8 signature[4]; /* 'RIFF' */
234: Uint8 filesize[4];
235: Uint8 type[4]; /* 'AVI ' */
236:
237: } RIFF_HEADER;
238:
239:
240: typedef struct {
241: RIFF_HEADER RiffHeader;
242:
243: AVI_STREAM_LIST_AVIH AviHeader;
244:
245: AVI_STREAM_LIST_VIDS VideoStream;
246: AVI_STREAM_LIST_AUDS AudioStream;
247:
248: } AVI_FILE_HEADER;
249:
250:
251:
252: #define AUDIO_STREAM_WAVE_FORMAT_PCM 0x0001
253:
254: #define VIDEO_STREAM_RGB 0x00000000 /* fourcc for BMP video frames */
255: #define VIDEO_STREAM_PNG "MPNG" /* fourcc for PNG video frames */
256:
257: #define AVIF_HASINDEX 0x00000010 /* index at the end of the file */
258: #define AVIF_ISINTERLEAVED 0x00000100 /* data are interleaved */
259: #define AVIF_TRUSTCKTYPE 0x00000800 /* trust chunk type */
260:
261: #define AVIIF_KEYFRAME 0x00000010 /* frame is a keyframe */
262:
263:
264: typedef struct {
265: /* Input params to start recording */
266: int VideoCodec;
267: int VideoCodecCompressionLevel; /* 0-9 for png compression */
268:
269: SDL_Surface *Surface;
270:
271: int CropLeft;
272: int CropRight;
273: int CropTop;
274: int CropBottom;
275:
276: int Fps;
277:
278: int AudioCodec;
279: int AudioFreq;
280:
281: /* Internal data used by the avi recorder */
282: int Width;
283: int Height;
284: int BitCount;
285: FILE *FileOut; /* file to write to */
286: int TotalVideoFrames; /* number of recorded video frames */
287: int TotalAudioSamples; /* number of recorded audio samples */
288: long MoviChunkPosStart; /* as returned by ftell() */
289: long MoviChunkPosEnd; /* as returned by ftell() */
290: } RECORD_AVI_PARAMS;
291:
292:
293:
294: bool bRecordingAvi = false;
295: #if HAVE_LIBPNG
296: int AviRecordDefaultVcodec = AVI_RECORD_VIDEO_CODEC_PNG;
297: #else
298: int AviRecordDefaultVcodec = AVI_RECORD_VIDEO_CODEC_BMP;
299: #endif
300: bool AviRecordDefaultCrop = true;
301: int AviRecordDefaultFps = 50;
302: char AviRecordFile[FILENAME_MAX] = "hatari.avi";
303:
304:
305: static RECORD_AVI_PARAMS AviParams;
306: static AVI_FILE_HEADER AviFileHeader;
307:
308:
309: static void Avi_StoreU16 ( Uint8 *p , Uint16 val );
310: static void Avi_StoreU32 ( Uint8 *p , Uint32 val );
311: static void Avi_Store4cc ( Uint8 *p , const char *text );
312: static Uint32 Avi_ReadU32 ( Uint8 *p );
313:
314: static int Avi_GetBmpSize ( int Width , int Height , int BitCount );
315:
316: static bool Avi_RecordVideoStream_BMP ( RECORD_AVI_PARAMS *pAviParams );
317: #if HAVE_LIBPNG
318: static bool Avi_RecordVideoStream_PNG ( RECORD_AVI_PARAMS *pAviParams );
319: #endif
320: static bool Avi_RecordAudioStream_PCM ( RECORD_AVI_PARAMS *pAviParams , Sint16 pSamples[][2], int SampleIndex, int SampleLength );
321:
322: static void Avi_BuildFileHeader ( RECORD_AVI_PARAMS *pAviParams , AVI_FILE_HEADER *pAviFileHeader );
323: static bool Avi_BuildIndex ( RECORD_AVI_PARAMS *pAviParams );
324:
325: static bool Avi_StartRecording_WithParams ( RECORD_AVI_PARAMS *pAviParams , char *AviFileName );
326: static bool Avi_StopRecording_WithParams ( RECORD_AVI_PARAMS *pAviParams );
327:
328:
329:
330:
331: static void Avi_StoreU16 ( Uint8 *p , Uint16 val )
332: {
333: *p++ = val & 0xff;
334: val >>= 8;
335: *p = val & 0xff;
336: }
337:
338:
339: static void Avi_StoreU32 ( Uint8 *p , Uint32 val )
340: {
341: *p++ = val & 0xff;
342: val >>= 8;
343: *p++ = val & 0xff;
344: val >>= 8;
345: *p++ = val & 0xff;
346: val >>= 8;
347: *p = val & 0xff;
348: }
349:
350:
351: static void Avi_Store4cc ( Uint8 *p , const char *text )
352: {
353: memcpy ( p , text , 4 );
354: }
355:
356:
357: static Uint32 Avi_ReadU32 ( Uint8 *p )
358: {
359: return (p[3]<<24) + (p[2]<<16) + (p[1]<<8) +p[0];
360: }
361:
362:
363: static int Avi_GetBmpSize ( int Width , int Height , int BitCount )
364: {
365: return ( Width * Height * BitCount / 8 ); /* bytes in one video frame */
366: }
367:
368:
369:
370: static bool Avi_RecordVideoStream_BMP ( RECORD_AVI_PARAMS *pAviParams )
371: {
372: AVI_CHUNK Chunk;
373: int SizeImage;
374: Uint8 LineBuf[ 3 * pAviParams->Width ]; /* temp buffer to convert to 24-bit BGR format */
375: Uint8 *pBitmapIn , *pBitmapOut;
376: int y;
377: int NeedLock;
378:
379: SizeImage = Avi_GetBmpSize ( pAviParams->Width , pAviParams->Height , pAviParams->BitCount );
380:
381: /* Write the video frame header */
382: Avi_Store4cc ( Chunk.ChunkName , "00db" ); /* stream 0, uncompressed DIB bytes */
383: Avi_StoreU32 ( Chunk.ChunkSize , SizeImage ); /* max size of RGB image */
384: if ( fwrite ( &Chunk , sizeof ( Chunk ) , 1 , pAviParams->FileOut ) != 1 )
385: {
386: perror ( "Avi_RecordVideoStream_BMP" );
387: Log_AlertDlg ( LOG_ERROR, "AVI recording : failed to write bmp frame header" );
388: return false;
389: }
390:
391:
392: /* Write the video frame data */
393: NeedLock = SDL_MUSTLOCK( pAviParams->Surface );
394:
395: /* Points to the top left pixel after cropping borders */
396: /* For BMP format, frame is stored from bottom to top (origin is in bottom left corner) */
397: /* and bytes are in BGR order (not RGB) */
398: pBitmapIn = (Uint8 *)pAviParams->Surface->pixels
399: + pAviParams->Surface->pitch * ( pAviParams->CropTop + pAviParams->Height )
400: + pAviParams->CropLeft * pAviParams->Surface->format->BytesPerPixel;
401:
402: for ( y=0 ; y<pAviParams->Height ; y++ )
403: {
404: if ( NeedLock )
405: SDL_LockSurface ( pAviParams->Surface );
406:
407: pBitmapOut = LineBuf;
408: switch ( pAviParams->Surface->format->BytesPerPixel ) {
409: case 1 : PixelConvert_8to24Bits_BGR(LineBuf, pBitmapIn, pAviParams->Width, pAviParams->Surface->format->palette->colors);
410: break;
411: case 2 : PixelConvert_16to24Bits_BGR(LineBuf, (Uint16 *)pBitmapIn, pAviParams->Width, pAviParams->Surface->format);
412: break;
413: case 3 : PixelConvert_24to24Bits_BGR(LineBuf, pBitmapIn, pAviParams->Width);
414: break;
415: case 4 : PixelConvert_32to24Bits_BGR(LineBuf, (Uint32 *)pBitmapIn, pAviParams->Width, pAviParams->Surface->format);
416: break;
417: }
418:
419: if ( NeedLock )
420: SDL_UnlockSurface ( pAviParams->Surface );
421:
422: if ( (int)fwrite ( pBitmapOut , 1 , pAviParams->Width*3 , pAviParams->FileOut ) != pAviParams->Width*3 )
423: {
424: perror ( "Avi_RecordVideoStream_BMP" );
425: Log_AlertDlg ( LOG_ERROR, "AVI recording : failed to write bmp video frame" );
426: return false;
427: }
428:
429: pBitmapIn -= pAviParams->Surface->pitch; /* go from bottom to top */
430: }
431:
432: return true;
433: }
434:
435:
436:
437: #if HAVE_LIBPNG
438: static bool Avi_RecordVideoStream_PNG ( RECORD_AVI_PARAMS *pAviParams )
439: {
440: AVI_CHUNK Chunk;
441: int SizeImage;
442: long ChunkPos;
443: Uint8 TempSize[4];
444:
445:
446: /* Write the video frame header */
447: ChunkPos = ftell ( pAviParams->FileOut );
448: Avi_Store4cc ( Chunk.ChunkName , "00dc" ); /* stream 0, compressed DIB bytes */
449: Avi_StoreU32 ( Chunk.ChunkSize , 0 ); /* size of PNG image (-> completed later) */
450: if ( fwrite ( &Chunk , sizeof ( Chunk ) , 1 , pAviParams->FileOut ) != 1 )
451: {
452: perror ( "Avi_RecordVideoStream_PNG" );
453: Log_AlertDlg ( LOG_ERROR, "AVI recording : failed to write png frame header" );
454: return false;
455: }
456:
457:
458: /* Write the video frame data */
459: SizeImage = ScreenSnapShot_SavePNG_ToFile ( pAviParams->Surface , pAviParams->FileOut ,
460: pAviParams->VideoCodecCompressionLevel , PNG_FILTER_NONE ,
461: pAviParams->CropLeft , pAviParams->CropRight , pAviParams->CropTop , pAviParams->CropBottom );
462: if ( SizeImage <= 0 )
463: {
464: perror ( "Avi_RecordVideoStream_PNG" );
465: Log_AlertDlg ( LOG_ERROR, "AVI recording : failed to write png video frame" );
466: return false;
467: }
468: if ( SizeImage & 1 )
469: {
470: SizeImage++; /* add an extra '\0' byte to get an even size */
471: fputc ( '\0' , pAviParams->FileOut ); /* next chunk must be aligned on 16 bits boundary */
472: }
473:
474: /* Update the size of the video chunk */
475: Avi_StoreU32 ( TempSize , SizeImage );
476: if ( fseek ( pAviParams->FileOut , ChunkPos+4 , SEEK_SET ) != 0 )
477: {
478: perror ( "Avi_RecordVideoStream_PNG" );
479: Log_AlertDlg ( LOG_ERROR, "AVI recording : failed to update png frame header" );
480: return false;
481: }
482: if ( fwrite ( TempSize , sizeof ( TempSize ) , 1 , pAviParams->FileOut ) != 1 )
483: {
484: perror ( "Avi_RecordVideoStream_PNG" );
485: Log_AlertDlg ( LOG_ERROR, "AVI recording : failed to update png frame header" );
486: return false;
487: }
488:
489:
490: /* Go to the end of the video frame data */
491: if ( fseek ( pAviParams->FileOut , 0 , SEEK_END ) != 0 )
492: {
493: perror ( "Avi_RecordVideoStream_PNG" );
494: Log_AlertDlg ( LOG_ERROR, "AVI recording : failed to seek png video frame" );
495: return false;
496: }
497:
498: return true;
499: }
500: #endif /* HAVE_LIBPNG */
501:
502:
503:
504: bool Avi_RecordVideoStream ( void )
505: {
506: if ( AviParams.VideoCodec == AVI_RECORD_VIDEO_CODEC_BMP )
507: {
508: if ( Avi_RecordVideoStream_BMP ( &AviParams ) == false )
509: {
510: return false;
511: }
512: }
513: #if HAVE_LIBPNG
514: else if ( AviParams.VideoCodec == AVI_RECORD_VIDEO_CODEC_PNG )
515: {
516: if ( Avi_RecordVideoStream_PNG ( &AviParams ) == false )
517: {
518: return false;
519: }
520: }
521: #endif
522: else
523: {
524: return false;
525: }
526:
527: AviParams.TotalVideoFrames++;
528: return true;
529: }
530:
531:
532:
533: static bool Avi_RecordAudioStream_PCM ( RECORD_AVI_PARAMS *pAviParams , Sint16 pSamples[][2] , int SampleIndex , int SampleLength )
534: {
535: AVI_CHUNK Chunk;
536: Sint16 sample[2];
537: int i;
538:
539: /* Write the audio frame header */
540: Avi_Store4cc ( Chunk.ChunkName , "01wb" ); /* stream 1, wave bytes */
541: Avi_StoreU32 ( Chunk.ChunkSize , SampleLength * 4 ); /* 16 bits, stereo -> 4 bytes */
542: if ( fwrite ( &Chunk , sizeof ( Chunk ) , 1 , pAviParams->FileOut ) != 1 )
543: {
544: perror ( "Avi_RecordAudioStream_PCM" );
545: Log_AlertDlg ( LOG_ERROR, "AVI recording : failed to write pcm frame header" );
546: return false;
547: }
548:
549: /* Write the audio frame data */
550: for ( i = 0 ; i < SampleLength; i++ )
551: {
552: /* Convert sample to little endian */
553: sample[0] = SDL_SwapLE16 ( pSamples[ (SampleIndex+i) % MIXBUFFER_SIZE ][0]);
554: sample[1] = SDL_SwapLE16 ( pSamples[ (SampleIndex+i) % MIXBUFFER_SIZE ][1]);
555: /* And store */
556: if ( fwrite ( &sample , sizeof ( sample ) , 1 , pAviParams->FileOut ) != 1 )
557: {
558: perror ( "Avi_RecordAudioStream_PCM" );
559: Log_AlertDlg ( LOG_ERROR, "AVI recording : failed to write pcm frame" );
560: return false;
561: }
562: }
563:
564: return true;
565: }
566:
567:
568:
569: bool Avi_RecordAudioStream ( Sint16 pSamples[][2] , int SampleIndex , int SampleLength )
570: {
571: if ( AviParams.AudioCodec == AVI_RECORD_AUDIO_CODEC_PCM )
572: {
573: if ( Avi_RecordAudioStream_PCM ( &AviParams , pSamples , SampleIndex , SampleLength ) == false )
574: {
575: return false;
576: }
577: }
578: else
579: {
580: return false;
581: }
582:
583: AviParams.TotalAudioSamples += SampleLength;
584: return true;
585: }
586:
587:
588:
589:
590: static void Avi_BuildFileHeader ( RECORD_AVI_PARAMS *pAviParams , AVI_FILE_HEADER *pAviFileHeader )
591: {
592: int Width , Height , BitCount , Fps , SizeImage;
593: int AudioFreq;
594:
595: memset ( pAviFileHeader , 0 , sizeof ( *pAviFileHeader ) );
596:
597: Width = pAviParams->Width;
598: Height =pAviParams->Height;
599: BitCount = pAviParams->BitCount;
600: Fps = pAviParams->Fps;
601: AudioFreq = pAviParams->AudioFreq;
602:
603: SizeImage = 0;
604: if ( pAviParams->VideoCodec == AVI_RECORD_VIDEO_CODEC_BMP )
605: SizeImage = Avi_GetBmpSize ( Width , Height , BitCount ); /* size of a BMP image */
606: else if ( pAviParams->VideoCodec == AVI_RECORD_VIDEO_CODEC_PNG )
607: SizeImage = Avi_GetBmpSize ( Width , Height , BitCount ); /* max size of a PNG image */
608:
609:
610: /* RIFF / AVI headers */
611: Avi_Store4cc ( pAviFileHeader->RiffHeader.signature , "RIFF" );
612: Avi_StoreU32 ( pAviFileHeader->RiffHeader.filesize , 0 ); /* total file size (-> completed later) */
613: Avi_Store4cc ( pAviFileHeader->RiffHeader.type , "AVI " );
614:
615: Avi_Store4cc ( pAviFileHeader->AviHeader.ChunkName , "LIST" );
616: Avi_StoreU32 ( pAviFileHeader->AviHeader.ChunkSize , sizeof ( AVI_STREAM_LIST_AVIH )
617: + sizeof ( AVI_STREAM_LIST_VIDS ) + sizeof ( AVI_STREAM_LIST_AUDS ) - 8 );
618: Avi_Store4cc ( pAviFileHeader->AviHeader.Name , "hdrl" );
619:
620: Avi_Store4cc ( pAviFileHeader->AviHeader.Header.ChunkName , "avih" );
621: Avi_StoreU32 ( pAviFileHeader->AviHeader.Header.ChunkSize , sizeof ( AVI_STREAM_AVIH ) - 8 );
622: Avi_StoreU32 ( pAviFileHeader->AviHeader.Header.microsec_per_frame , 1000000 / Fps );
623: Avi_StoreU32 ( pAviFileHeader->AviHeader.Header.max_bytes_per_second , SizeImage * Fps + AudioFreq * 4 );
624: Avi_StoreU32 ( pAviFileHeader->AviHeader.Header.padding_granularity , 0 );
625: Avi_StoreU32 ( pAviFileHeader->AviHeader.Header.flags , AVIF_HASINDEX | AVIF_ISINTERLEAVED | AVIF_TRUSTCKTYPE );
626: Avi_StoreU32 ( pAviFileHeader->AviHeader.Header.total_frames , 0 ); /* number of video frames (-> completed later) */
627: Avi_StoreU32 ( pAviFileHeader->AviHeader.Header.init_frame , 0 );
628: Avi_StoreU32 ( pAviFileHeader->AviHeader.Header.nb_streams , 2 ); /* 1 video and 1 audio */
629: Avi_StoreU32 ( pAviFileHeader->AviHeader.Header.buffer_size , SizeImage );
630: Avi_StoreU32 ( pAviFileHeader->AviHeader.Header.width , Width );
631: Avi_StoreU32 ( pAviFileHeader->AviHeader.Header.height , Height );
632: Avi_StoreU32 ( pAviFileHeader->AviHeader.Header.scale , 0 ); /* reserved */
633: Avi_StoreU32 ( pAviFileHeader->AviHeader.Header.rate , 0 ); /* reserved */
634: Avi_StoreU32 ( pAviFileHeader->AviHeader.Header.start , 0 ); /* reserved */
635: Avi_StoreU32 ( pAviFileHeader->AviHeader.Header.length , 0 ); /* reserved */
636:
637:
638: /* Video Stream */
639: Avi_Store4cc ( pAviFileHeader->VideoStream.ChunkName , "LIST" );
640: Avi_StoreU32 ( pAviFileHeader->VideoStream.ChunkSize , sizeof ( AVI_STREAM_LIST_VIDS ) - 8 );
641: Avi_Store4cc ( pAviFileHeader->VideoStream.Name , "strl" );
642:
643: Avi_Store4cc ( pAviFileHeader->VideoStream.Header.ChunkName , "strh" );
644: Avi_StoreU32 ( pAviFileHeader->VideoStream.Header.ChunkSize , sizeof ( AVI_STREAM_HEADER ) - 8 );
645: Avi_Store4cc ( pAviFileHeader->VideoStream.Header.stream_type , "vids" );
646: if ( pAviParams->VideoCodec == AVI_RECORD_VIDEO_CODEC_BMP )
647: Avi_StoreU32 ( pAviFileHeader->VideoStream.Header.stream_handler , VIDEO_STREAM_RGB );
648: else if ( pAviParams->VideoCodec == AVI_RECORD_VIDEO_CODEC_PNG )
649: Avi_Store4cc ( pAviFileHeader->VideoStream.Header.stream_handler , VIDEO_STREAM_PNG );
650: Avi_StoreU32 ( pAviFileHeader->VideoStream.Header.flags , 0 );
651: Avi_StoreU16 ( pAviFileHeader->VideoStream.Header.priority , 0 );
652: Avi_StoreU16 ( pAviFileHeader->VideoStream.Header.language , 0 );
653: Avi_StoreU32 ( pAviFileHeader->VideoStream.Header.initial_frames , 0 );
654: Avi_StoreU32 ( pAviFileHeader->VideoStream.Header.time_scale , 1 );
655: Avi_StoreU32 ( pAviFileHeader->VideoStream.Header.data_rate , Fps );
656: Avi_StoreU32 ( pAviFileHeader->VideoStream.Header.start_time , 0 );
657: Avi_StoreU32 ( pAviFileHeader->VideoStream.Header.data_length , 0 ); /* number of video frames (-> completed later) */
658: Avi_StoreU32 ( pAviFileHeader->VideoStream.Header.buffer_size , SizeImage ); /* size of an uncompressed frame */
659: Avi_StoreU32 ( pAviFileHeader->VideoStream.Header.quality , -1 ); /* use default quality */
660: Avi_StoreU32 ( pAviFileHeader->VideoStream.Header.sample_size , 0 ); /* 0 for video */
661: Avi_StoreU16 ( pAviFileHeader->VideoStream.Header.dest_left , 0 );
662: Avi_StoreU16 ( pAviFileHeader->VideoStream.Header.dest_top , 0 );
663: Avi_StoreU16 ( pAviFileHeader->VideoStream.Header.dest_right , Width );
664: Avi_StoreU16 ( pAviFileHeader->VideoStream.Header.dest_bottom , Height );
665:
666: Avi_Store4cc ( pAviFileHeader->VideoStream.Format.ChunkName , "strf" );
667: Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.ChunkSize , sizeof ( AVI_STREAM_FORMAT_VIDS ) - 8 );
668: if ( pAviParams->VideoCodec == AVI_RECORD_VIDEO_CODEC_BMP )
669: {
670: Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.size , sizeof ( AVI_STREAM_FORMAT_VIDS ) - 8 );
671: Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.width , Width );
672: Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.height , Height );
673: Avi_StoreU16 ( pAviFileHeader->VideoStream.Format.planes , 1 ); /* always 1 */
674: Avi_StoreU16 ( pAviFileHeader->VideoStream.Format.bit_count , BitCount );
675: Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.compression , VIDEO_STREAM_RGB );
676: Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.size_image , SizeImage );
677: Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.xpels_meter , 0 );
678: Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.ypels_meter , 0 );
679: Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.clr_used , 0 ); /* no color map */
680: Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.clr_important , 0 ); /* no color map */
681: }
682: else if ( pAviParams->VideoCodec == AVI_RECORD_VIDEO_CODEC_PNG )
683: {
684: Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.size , sizeof ( AVI_STREAM_FORMAT_VIDS ) - 8 );
685: Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.width , Width );
686: Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.height , Height );
687: Avi_StoreU16 ( pAviFileHeader->VideoStream.Format.planes , 1 ); /* always 1 */
688: Avi_StoreU16 ( pAviFileHeader->VideoStream.Format.bit_count , BitCount );
689: Avi_Store4cc ( pAviFileHeader->VideoStream.Format.compression , VIDEO_STREAM_PNG );
690: Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.size_image , SizeImage ); /* max size if uncompressed */
691: Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.xpels_meter , 0 );
692: Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.ypels_meter , 0 );
693: Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.clr_used , 0 ); /* no color map */
694: Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.clr_important , 0 ); /* no color map */
695: }
696:
697:
698: /* Audio Stream */
699: Avi_Store4cc ( pAviFileHeader->AudioStream.ChunkName , "LIST" );
700: Avi_StoreU32 ( pAviFileHeader->AudioStream.ChunkSize , sizeof ( AVI_STREAM_LIST_AUDS ) - 8 );
701: Avi_Store4cc ( pAviFileHeader->AudioStream.Name , "strl" );
702:
703: Avi_Store4cc ( pAviFileHeader->AudioStream.Header.ChunkName , "strh" );
704: Avi_StoreU32 ( pAviFileHeader->AudioStream.Header.ChunkSize , sizeof ( AVI_STREAM_HEADER ) - 8 );
705: Avi_Store4cc ( pAviFileHeader->AudioStream.Header.stream_type , "auds" );
706: Avi_StoreU32 ( pAviFileHeader->AudioStream.Header.stream_handler , 0 ); /* not used (or could be 1 for pcm ?) */
707: Avi_StoreU32 ( pAviFileHeader->AudioStream.Header.flags , 0 );
708: Avi_StoreU16 ( pAviFileHeader->AudioStream.Header.priority , 0 );
709: Avi_StoreU16 ( pAviFileHeader->AudioStream.Header.language , 0 );
710: Avi_StoreU32 ( pAviFileHeader->AudioStream.Header.initial_frames , 0 ); /* should be 1 in interleaved ? */
711: Avi_StoreU32 ( pAviFileHeader->AudioStream.Header.time_scale , 1 );
712: Avi_StoreU32 ( pAviFileHeader->AudioStream.Header.data_rate , AudioFreq );
713: Avi_StoreU32 ( pAviFileHeader->AudioStream.Header.start_time , 0 );
714: Avi_StoreU32 ( pAviFileHeader->AudioStream.Header.data_length , 0 ); /* number of audio samples (-> completed later) */
715: Avi_StoreU32 ( pAviFileHeader->AudioStream.Header.buffer_size , AudioFreq * 4 / 50 ); /* min VBL freq is 50 Hz */
716: Avi_StoreU32 ( pAviFileHeader->AudioStream.Header.quality , -1 ); /* use default quality */
717: Avi_StoreU32 ( pAviFileHeader->AudioStream.Header.sample_size , 4 ); /* 2 bytes, stereo */
718: Avi_StoreU16 ( pAviFileHeader->AudioStream.Header.dest_left , 0 );
719: Avi_StoreU16 ( pAviFileHeader->AudioStream.Header.dest_top , 0 );
720: Avi_StoreU16 ( pAviFileHeader->AudioStream.Header.dest_right , 0 );
721: Avi_StoreU16 ( pAviFileHeader->AudioStream.Header.dest_bottom , 0 );
722:
723: Avi_Store4cc ( pAviFileHeader->AudioStream.Format.ChunkName , "strf" );
724: Avi_StoreU32 ( pAviFileHeader->AudioStream.Format.ChunkSize , sizeof ( AVI_STREAM_FORMAT_AUDS ) - 8 );
725: if ( pAviParams->AudioCodec == AVI_RECORD_AUDIO_CODEC_PCM ) /* 16 bits stereo pcm */
726: {
727: Avi_StoreU16 ( pAviFileHeader->AudioStream.Format.codec , AUDIO_STREAM_WAVE_FORMAT_PCM ); /* 0x0001 */
728: Avi_StoreU16 ( pAviFileHeader->AudioStream.Format.channels , 2 );
729: Avi_StoreU32 ( pAviFileHeader->AudioStream.Format.sample_rate , AudioFreq );
730: Avi_StoreU32 ( pAviFileHeader->AudioStream.Format.bit_rate , AudioFreq * 2 * 2 ); /* 2 channels * 2 bytes */
731: Avi_StoreU16 ( pAviFileHeader->AudioStream.Format.block_align , 4 );
732: Avi_StoreU16 ( pAviFileHeader->AudioStream.Format.bits_per_sample , 16 );
733: Avi_StoreU16 ( pAviFileHeader->AudioStream.Format.ext_size , 0 );
734: }
735: }
736:
737:
738: static bool Avi_BuildIndex ( RECORD_AVI_PARAMS *pAviParams )
739: {
740: AVI_CHUNK Chunk;
741: long IndexChunkPosStart;
742: long Pos , PosWrite;
743: Uint8 TempSize[4];
744: AVI_CHUNK_INDEX ChunkIndex;
745: Uint32 Size;
746:
747: fseek ( pAviParams->FileOut , 0 , SEEK_END ); /* go to the end of the file */
748:
749: /* Write the 'idx1' chunk header */
750: IndexChunkPosStart = ftell ( pAviParams->FileOut );
751: Avi_Store4cc ( Chunk.ChunkName , "idx1" ); /* stream 0, uncompressed DIB bytes */
752: Avi_StoreU32 ( Chunk.ChunkSize , 0 ); /* index size (-> completed later) */
753: if ( fwrite ( &Chunk , sizeof ( Chunk ) , 1 , pAviParams->FileOut ) != 1 )
754: {
755: perror ( "Avi_BuildIndex" );
756: Log_AlertDlg ( LOG_ERROR, "AVI recording : failed to write index header" );
757: return false;
758: }
759: PosWrite = ftell ( pAviParams->FileOut ); /* position to start writing indexes */
760:
761: /* Go to the first data chunk in the 'movi' chunk */
762: fseek ( pAviParams->FileOut , pAviParams->MoviChunkPosStart + sizeof ( AVI_STREAM_LIST_MOVI ) , SEEK_SET );
763: Pos = ftell ( pAviParams->FileOut );
764:
765: /* Build the index : we seek/read one data chunk and seek/write the */
766: /* corresponding infos at the end of the index, until we reach the */
767: /* end of the 'movi' chunk. */
768: while ( Pos < pAviParams->MoviChunkPosEnd )
769: {
770: /* Read the header for this data chunk: ChunkName and ChunkSize */
771: if (fread(&Chunk, sizeof(Chunk), 1, pAviParams->FileOut) != 1)
772: {
773: perror("Avi_BuildIndex");
774: return false;
775: }
776: Size = Avi_ReadU32 ( Chunk.ChunkSize );
777:
778: /* Write the index infos for this chunk */
779: fseek ( pAviParams->FileOut , PosWrite , SEEK_SET );
780: Avi_Store4cc ( ChunkIndex.identifier , (char *)Chunk.ChunkName ); /* 00dc, 00db, 01wb, ... */
781: Avi_StoreU32 ( ChunkIndex.flags , AVIIF_KEYFRAME ); /* AVIIF_KEYFRAME */
782: Avi_StoreU32 ( ChunkIndex.offset , Pos - pAviParams->MoviChunkPosStart - 8 ); /* pos relative to 'movi' */
783: Avi_StoreU32 ( ChunkIndex.length , Size );
784: fwrite ( &ChunkIndex , sizeof ( ChunkIndex ) , 1 , pAviParams->FileOut );
785: PosWrite = ftell ( pAviParams->FileOut ); /* position for the next index */
786:
787: /* Go to the next data chunk in the 'movi' chunk */
788: Pos = Pos + sizeof ( Chunk ) + Size; /* position of the next data chunk */
789: fseek ( pAviParams->FileOut , Pos , SEEK_SET );
790: }
791:
792: /* Update the size of the 'idx1' chunk */
793: Avi_StoreU32 ( TempSize , PosWrite - IndexChunkPosStart - 8 );
794: if ( fseek ( pAviParams->FileOut , IndexChunkPosStart+4 , SEEK_SET ) != 0 )
795: {
796: perror ( "Avi_BuildIndex" );
797: Log_AlertDlg ( LOG_ERROR, "AVI recording : failed to update idx1 header" );
798: return false;
799: }
800: if ( fwrite ( TempSize , sizeof ( TempSize ) , 1 , pAviParams->FileOut ) != 1 )
801: {
802: perror ( "Avi_BuildIndex" );
803: Log_AlertDlg ( LOG_ERROR, "AVI recording : failed to update idx1 header" );
804: return false;
805: }
806:
807: return true;
808: }
809:
810:
811: static bool Avi_StartRecording_WithParams ( RECORD_AVI_PARAMS *pAviParams , char *AviFileName )
812: {
813: AVI_STREAM_LIST_INFO ListInfo;
814: char InfoString[ 100 ];
815: int Len , Len_rounded;
816: AVI_STREAM_LIST_MOVI ListMovi;
817:
818:
819: if ( bRecordingAvi == true ) /* already recording ? */
820: return false;
821:
822: /* Compute some video parameters */
823: pAviParams->Width = pAviParams->Surface->w - pAviParams->CropLeft - pAviParams->CropRight;
824: pAviParams->Height = pAviParams->Surface->h - pAviParams->CropTop - pAviParams->CropBottom;
825: pAviParams->BitCount = 24;
826:
827: #if !HAVE_LIBPNG
828: if ( pAviParams->VideoCodec == AVI_RECORD_VIDEO_CODEC_PNG )
829: {
830: perror ( "AviStartRecording" );
831: Log_AlertDlg ( LOG_ERROR, "AVI recording : Hatari was not built with libpng support" );
832: return false;
833: }
834: #endif
835:
836: /* Open the file */
837: pAviParams->FileOut = fopen ( AviFileName , "wb+" );
838: if ( !pAviParams->FileOut )
839: {
840: perror ( "AviStartRecording" );
841: Log_AlertDlg ( LOG_ERROR, "AVI recording : failed to open file" );
842: return false;
843: }
844:
845: /* Build the AVI header */
846: Avi_BuildFileHeader ( pAviParams , &AviFileHeader );
847:
848: /* Write the AVI header */
849: if ( fwrite ( &AviFileHeader , sizeof ( AviFileHeader ) , 1 , pAviParams->FileOut ) != 1 )
850: {
851: perror ( "AviStartRecording" );
852: Log_AlertDlg ( LOG_ERROR, "AVI recording : failed to write avi header" );
853: return false;
854: }
855:
856: /* Write the INFO header */
857: memset ( InfoString , 0 , sizeof ( InfoString ) );
858: Len = snprintf ( InfoString , sizeof ( InfoString ) , "%s - the Atari ST, STE, TT and Falcon emulator" , PROG_NAME ) + 1;
859: Len_rounded = Len + ( Len % 2 == 0 ? 0 : 1 ); /* round Len to the next multiple of 2 */
860: Avi_Store4cc ( ListInfo.ChunkName , "LIST" );
861: Avi_StoreU32 ( ListInfo.ChunkSize , sizeof ( AVI_STREAM_LIST_INFO ) - 8 + Len_rounded );
862: Avi_Store4cc ( ListInfo.Name , "INFO" );
863: Avi_Store4cc ( ListInfo.Info.ChunkName , "ISFT" );
864: Avi_StoreU32 ( ListInfo.Info.ChunkSize , Len );
865: if ( fwrite ( &ListInfo , sizeof ( ListInfo ) , 1 , pAviParams->FileOut ) != 1 )
866: {
867: perror ( "AviStartRecording" );
868: Log_AlertDlg ( LOG_ERROR, "AVI recording : failed to write info header" );
869: return false;
870: }
871: /* Write the info string + '\0' and write an optionnal extra '\0' byte to get a total multiple of 2 */
872: if ( fwrite ( InfoString , Len_rounded , 1 , pAviParams->FileOut ) != 1 )
873: {
874: perror ( "AviStartRecording" );
875: Log_AlertDlg ( LOG_ERROR, "AVI recording : failed to write info header" );
876: return false;
877: }
878:
879: /* Write the MOVI header */
880: Avi_Store4cc ( ListMovi.ChunkName , "LIST" );
881: Avi_StoreU32 ( ListMovi.ChunkSize , 0 ); /* completed when recording stops */
882: Avi_Store4cc ( ListMovi.Name , "movi" );
883: pAviParams->MoviChunkPosStart = ftell ( pAviParams->FileOut );
884: if ( fwrite ( &ListMovi , sizeof ( ListMovi ) , 1 , pAviParams->FileOut ) != 1 )
885: {
886: perror ( "AviStartRecording" );
887: Log_AlertDlg ( LOG_ERROR, "AVI recording : failed to write movi header" );
888: return false;
889: }
890:
891:
892: /* We're ok to record */
893: Log_AlertDlg ( LOG_INFO, "AVI recording has been started");
894: bRecordingAvi = true;
895:
896: return true;
897: }
898:
899:
900:
901: static bool Avi_StopRecording_WithParams ( RECORD_AVI_PARAMS *pAviParams )
902: {
903: long FileSize;
904: Uint8 TempSize[4];
905:
906:
907: if ( bRecordingAvi == false ) /* no recording ? */
908: return true;
909:
910: /* Update the size of the 'movi' chunk */
911: fseek ( pAviParams->FileOut , 0 , SEEK_END ); /* go to the end of the 'movi' chunk */
912: pAviParams->MoviChunkPosEnd = ftell ( pAviParams->FileOut );
913: Avi_StoreU32 ( TempSize , pAviParams->MoviChunkPosEnd - pAviParams->MoviChunkPosStart - 8 );
914:
915: if ( fseek ( pAviParams->FileOut , pAviParams->MoviChunkPosStart+4 , SEEK_SET ) != 0 )
916: {
917: perror ( "AviStopRecording" );
918: Log_AlertDlg ( LOG_ERROR, "AVI recording : failed to update movi header" );
919: return false;
920: }
921: if ( fwrite ( TempSize , sizeof ( TempSize ) , 1 , pAviParams->FileOut ) != 1 )
922: {
923: perror ( "AviStopRecording" );
924: Log_AlertDlg ( LOG_ERROR, "AVI recording : failed to update movi header" );
925: return false;
926: }
927:
928: /* Build the index chunk */
929: if ( ! Avi_BuildIndex ( pAviParams ) )
930: {
931: perror ( "AviStopRecording" );
932: Log_AlertDlg ( LOG_ERROR, "AVI recording : failed to build index" );
933: return false;
934: }
935:
936: /* Update the avi header (file size, number of output frames, ...) */
937: fseek ( pAviParams->FileOut , 0 , SEEK_END ); /* go to the end of the file */
938: FileSize = ftell ( pAviParams->FileOut );
939:
940: Avi_StoreU32 ( AviFileHeader.RiffHeader.filesize , FileSize - 8 ); /* 32 bits, limited to 4GB */
941: Avi_StoreU32 ( AviFileHeader.AviHeader.Header.total_frames , pAviParams->TotalVideoFrames ); /* number of video frames */
942: Avi_StoreU32 ( AviFileHeader.VideoStream.Header.data_length , pAviParams->TotalVideoFrames ); /* number of video frames */
943: Avi_StoreU32 ( AviFileHeader.AudioStream.Header.data_length , pAviParams->TotalAudioSamples ); /* number of audio samples */
944:
945: if ( fseek ( pAviParams->FileOut , 0 , SEEK_SET ) != 0 )
946: {
947: perror ( "AviStopRecording" );
948: Log_AlertDlg ( LOG_ERROR, "AVI recording : failed to update avi header" );
949: return false;
950: }
951: if ( fwrite ( &AviFileHeader , sizeof ( AviFileHeader ) , 1 , pAviParams->FileOut ) != 1 )
952: {
953: perror ( "AviStopRecording" );
954: Log_AlertDlg ( LOG_ERROR, "AVI recording : failed to update avi header" );
955: return false;
956: }
957:
958:
959: /* Close the file */
960: fclose ( pAviParams->FileOut );
961:
962: Log_AlertDlg ( LOG_INFO, "AVI recording has been stopped");
963: bRecordingAvi = false;
964:
965: return true;
966: }
967:
968:
969:
970:
971: /*-----------------------------------------------------------------------*/
972: /**
973: * Are we recording an AVI ?
974: */
975: bool Avi_AreWeRecording ( void )
976: {
977: return bRecordingAvi;
978: }
979:
980:
981:
982: bool Avi_StartRecording ( char *FileName , bool CropGui , int Fps , int VideoCodec )
983: {
984: memset ( &AviParams , 0 , sizeof ( AviParams ) );
985:
986: AviParams.VideoCodec = VideoCodec;
987: AviParams.VideoCodecCompressionLevel = 9; /* png compression level */
988: AviParams.AudioCodec = AVI_RECORD_AUDIO_CODEC_PCM;
989: AviParams.AudioFreq = ConfigureParams.Sound.nPlaybackFreq;
990: AviParams.Surface = sdlscrn;
991: AviParams.Fps = Fps;
992:
993: if ( !CropGui ) /* Keep gui's status bar */
994: {
995: AviParams.CropLeft = 0;
996: AviParams.CropRight = 0;
997: AviParams.CropTop = 0;
998: AviParams.CropBottom = 0;
999: }
1000: else /* Record only the content of the Atari's screen */
1001: {
1002: AviParams.CropLeft = 0;
1003: AviParams.CropRight = 0;
1004: AviParams.CropTop = 0;
1005: AviParams.CropBottom = Statusbar_GetHeight();
1006: }
1007:
1008:
1009: return Avi_StartRecording_WithParams ( &AviParams , FileName );
1010: }
1011:
1012:
1013: bool Avi_StopRecording ( void )
1014: {
1015: return Avi_StopRecording_WithParams ( &AviParams );
1016: }
1017:
1018:
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.