|
|
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 good ! 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 structure 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; /* Fps << 24 */ ! 277: int Fps_scale; /* 1 << 24 */ ! 278: ! 279: int AudioCodec; ! 280: int AudioFreq; ! 281: ! 282: /* Internal data used by the avi recorder */ ! 283: int Width; ! 284: int Height; ! 285: int BitCount; ! 286: FILE *FileOut; /* file to write to */ ! 287: int TotalVideoFrames; /* number of recorded video frames */ ! 288: int TotalAudioSamples; /* number of recorded audio samples */ ! 289: long MoviChunkPosStart; /* as returned by ftell() */ ! 290: long MoviChunkPosEnd; /* as returned by ftell() */ ! 291: } RECORD_AVI_PARAMS; ! 292: ! 293: ! 294: ! 295: bool bRecordingAvi = false; ! 296: ! 297: static RECORD_AVI_PARAMS AviParams; ! 298: static AVI_FILE_HEADER AviFileHeader; ! 299: ! 300: ! 301: static void Avi_StoreU16 ( Uint8 *p , Uint16 val ); ! 302: static void Avi_StoreU32 ( Uint8 *p , Uint32 val ); ! 303: static void Avi_Store4cc ( Uint8 *p , const char *text ); ! 304: static Uint32 Avi_ReadU32 ( Uint8 *p ); ! 305: ! 306: static int Avi_GetBmpSize ( int Width , int Height , int BitCount ); ! 307: ! 308: static bool Avi_RecordVideoStream_BMP ( RECORD_AVI_PARAMS *pAviParams ); ! 309: #if HAVE_LIBPNG ! 310: static bool Avi_RecordVideoStream_PNG ( RECORD_AVI_PARAMS *pAviParams ); ! 311: #endif ! 312: static bool Avi_RecordAudioStream_PCM ( RECORD_AVI_PARAMS *pAviParams , Sint16 pSamples[][2], int SampleIndex, int SampleLength ); ! 313: ! 314: static void Avi_BuildFileHeader ( RECORD_AVI_PARAMS *pAviParams , AVI_FILE_HEADER *pAviFileHeader ); ! 315: static bool Avi_BuildIndex ( RECORD_AVI_PARAMS *pAviParams ); ! 316: ! 317: static bool Avi_StartRecording_WithParams ( RECORD_AVI_PARAMS *pAviParams , char *AviFileName ); ! 318: static bool Avi_StopRecording_WithParams ( RECORD_AVI_PARAMS *pAviParams ); ! 319: ! 320: ! 321: ! 322: ! 323: static void Avi_StoreU16 ( Uint8 *p , Uint16 val ) ! 324: { ! 325: *p++ = val & 0xff; ! 326: val >>= 8; ! 327: *p = val & 0xff; ! 328: } ! 329: ! 330: ! 331: static void Avi_StoreU32 ( Uint8 *p , Uint32 val ) ! 332: { ! 333: *p++ = val & 0xff; ! 334: val >>= 8; ! 335: *p++ = val & 0xff; ! 336: val >>= 8; ! 337: *p++ = val & 0xff; ! 338: val >>= 8; ! 339: *p = val & 0xff; ! 340: } ! 341: ! 342: ! 343: static void Avi_Store4cc ( Uint8 *p , const char *text ) ! 344: { ! 345: memcpy ( p , text , 4 ); ! 346: } ! 347: ! 348: ! 349: static Uint32 Avi_ReadU32 ( Uint8 *p ) ! 350: { ! 351: return (p[3]<<24) + (p[2]<<16) + (p[1]<<8) +p[0]; ! 352: } ! 353: ! 354: ! 355: static int Avi_GetBmpSize ( int Width , int Height , int BitCount ) ! 356: { ! 357: return ( Width * Height * BitCount / 8 ); /* bytes in one video frame */ ! 358: } ! 359: ! 360: ! 361: ! 362: static bool Avi_RecordVideoStream_BMP ( RECORD_AVI_PARAMS *pAviParams ) ! 363: { ! 364: AVI_CHUNK Chunk; ! 365: int SizeImage; ! 366: Uint8 LineBuf[ 3 * pAviParams->Width ]; /* temp buffer to convert to 24-bit BGR format */ ! 367: Uint8 *pBitmapIn , *pBitmapOut; ! 368: int y; ! 369: int NeedLock; ! 370: ! 371: SizeImage = Avi_GetBmpSize ( pAviParams->Width , pAviParams->Height , pAviParams->BitCount ); ! 372: ! 373: /* Write the video frame header */ ! 374: Avi_Store4cc ( Chunk.ChunkName , "00db" ); /* stream 0, uncompressed DIB bytes */ ! 375: Avi_StoreU32 ( Chunk.ChunkSize , SizeImage ); /* max size of RGB image */ ! 376: if ( fwrite ( &Chunk , sizeof ( Chunk ) , 1 , pAviParams->FileOut ) != 1 ) ! 377: { ! 378: perror ( "Avi_RecordVideoStream_BMP" ); ! 379: Log_AlertDlg ( LOG_ERROR, "AVI recording : failed to write bmp frame header" ); ! 380: return false; ! 381: } ! 382: ! 383: ! 384: /* Write the video frame data */ ! 385: NeedLock = SDL_MUSTLOCK( pAviParams->Surface ); ! 386: ! 387: /* Points to the top left pixel after cropping borders */ ! 388: /* For BMP format, frame is stored from bottom to top (origin is in bottom left corner) */ ! 389: /* and bytes are in BGR order (not RGB) */ ! 390: pBitmapIn = (Uint8 *)pAviParams->Surface->pixels ! 391: + pAviParams->Surface->pitch * ( pAviParams->CropTop + pAviParams->Height ) ! 392: + pAviParams->CropLeft * pAviParams->Surface->format->BytesPerPixel; ! 393: ! 394: for ( y=0 ; y<pAviParams->Height ; y++ ) ! 395: { ! 396: if ( NeedLock ) ! 397: SDL_LockSurface ( pAviParams->Surface ); ! 398: ! 399: pBitmapOut = LineBuf; ! 400: switch ( pAviParams->Surface->format->BytesPerPixel ) { ! 401: case 1 : PixelConvert_8to24Bits_BGR(LineBuf, pBitmapIn, pAviParams->Width, pAviParams->Surface->format->palette->colors); ! 402: break; ! 403: case 2 : PixelConvert_16to24Bits_BGR(LineBuf, (Uint16 *)pBitmapIn, pAviParams->Width, pAviParams->Surface->format); ! 404: break; ! 405: case 3 : PixelConvert_24to24Bits_BGR(LineBuf, pBitmapIn, pAviParams->Width); ! 406: break; ! 407: case 4 : PixelConvert_32to24Bits_BGR(LineBuf, (Uint32 *)pBitmapIn, pAviParams->Width, pAviParams->Surface->format); ! 408: break; ! 409: } ! 410: ! 411: if ( NeedLock ) ! 412: SDL_UnlockSurface ( pAviParams->Surface ); ! 413: ! 414: if ( (int)fwrite ( pBitmapOut , 1 , pAviParams->Width*3 , pAviParams->FileOut ) != pAviParams->Width*3 ) ! 415: { ! 416: perror ( "Avi_RecordVideoStream_BMP" ); ! 417: Log_AlertDlg ( LOG_ERROR, "AVI recording : failed to write bmp video frame" ); ! 418: return false; ! 419: } ! 420: ! 421: pBitmapIn -= pAviParams->Surface->pitch; /* go from bottom to top */ ! 422: } ! 423: ! 424: return true; ! 425: } ! 426: ! 427: ! 428: ! 429: #if HAVE_LIBPNG ! 430: static bool Avi_RecordVideoStream_PNG ( RECORD_AVI_PARAMS *pAviParams ) ! 431: { ! 432: AVI_CHUNK Chunk; ! 433: int SizeImage; ! 434: long ChunkPos; ! 435: Uint8 TempSize[4]; ! 436: ! 437: ! 438: /* Write the video frame header */ ! 439: ChunkPos = ftell ( pAviParams->FileOut ); ! 440: Avi_Store4cc ( Chunk.ChunkName , "00dc" ); /* stream 0, compressed DIB bytes */ ! 441: Avi_StoreU32 ( Chunk.ChunkSize , 0 ); /* size of PNG image (-> completed later) */ ! 442: if ( fwrite ( &Chunk , sizeof ( Chunk ) , 1 , pAviParams->FileOut ) != 1 ) ! 443: goto png_error; ! 444: ! 445: /* Write the video frame data */ ! 446: SizeImage = ScreenSnapShot_SavePNG_ToFile ( pAviParams->Surface , pAviParams->FileOut , ! 447: pAviParams->VideoCodecCompressionLevel , PNG_FILTER_NONE , ! 448: pAviParams->CropLeft , pAviParams->CropRight , pAviParams->CropTop , pAviParams->CropBottom ); ! 449: if ( SizeImage <= 0 ) ! 450: goto png_error; ! 451: if ( SizeImage & 1 ) ! 452: { ! 453: SizeImage++; /* add an extra '\0' byte to get an even size */ ! 454: fputc ( '\0' , pAviParams->FileOut ); /* next chunk must be aligned on 16 bits boundary */ ! 455: } ! 456: ! 457: /* Update the size of the video chunk */ ! 458: Avi_StoreU32 ( TempSize , SizeImage ); ! 459: if ( fseek ( pAviParams->FileOut , ChunkPos+4 , SEEK_SET ) != 0 ) ! 460: goto png_error; ! 461: if ( fwrite ( TempSize , sizeof ( TempSize ) , 1 , pAviParams->FileOut ) != 1 ) ! 462: goto png_error; ! 463: ! 464: /* Go to the end of the video frame data */ ! 465: if ( fseek ( pAviParams->FileOut , 0 , SEEK_END ) != 0 ) ! 466: goto png_error; ! 467: return true; ! 468: ! 469: png_error: ! 470: perror ( "Avi_RecordVideoStream_PNG" ); ! 471: Log_AlertDlg ( LOG_ERROR, "AVI recording : failed to write png frame" ); ! 472: return false; ! 473: } ! 474: #endif /* HAVE_LIBPNG */ ! 475: ! 476: ! 477: ! 478: bool Avi_RecordVideoStream ( void ) ! 479: { ! 480: if ( AviParams.VideoCodec == AVI_RECORD_VIDEO_CODEC_BMP ) ! 481: { ! 482: if ( Avi_RecordVideoStream_BMP ( &AviParams ) == false ) ! 483: { ! 484: return false; ! 485: } ! 486: } ! 487: #if HAVE_LIBPNG ! 488: else if ( AviParams.VideoCodec == AVI_RECORD_VIDEO_CODEC_PNG ) ! 489: { ! 490: if ( Avi_RecordVideoStream_PNG ( &AviParams ) == false ) ! 491: { ! 492: return false; ! 493: } ! 494: } ! 495: #endif ! 496: else ! 497: { ! 498: return false; ! 499: } ! 500: ! 501: if (++AviParams.TotalVideoFrames % ( AviParams.Fps / AviParams.Fps_scale ) == 0) ! 502: { ! 503: int secs = AviParams.TotalVideoFrames / ( AviParams.Fps / AviParams.Fps_scale ); ! 504: char str[6] = "00:00"; ! 505: str[0] = '0' + (secs / 60) / 10; ! 506: str[1] = '0' + (secs / 60) % 10; ! 507: str[3] = '0' + (secs % 60) / 10; ! 508: str[4] = '0' + (secs % 60) % 10; ! 509: Main_SetTitle(str); ! 510: } ! 511: return true; ! 512: } ! 513: ! 514: ! 515: ! 516: static bool Avi_RecordAudioStream_PCM ( RECORD_AVI_PARAMS *pAviParams , Sint16 pSamples[][2] , int SampleIndex , int SampleLength ) ! 517: { ! 518: AVI_CHUNK Chunk; ! 519: Sint16 sample[2]; ! 520: int i; ! 521: ! 522: /* Write the audio frame header */ ! 523: Avi_Store4cc ( Chunk.ChunkName , "01wb" ); /* stream 1, wave bytes */ ! 524: Avi_StoreU32 ( Chunk.ChunkSize , SampleLength * 4 ); /* 16 bits, stereo -> 4 bytes */ ! 525: if ( fwrite ( &Chunk , sizeof ( Chunk ) , 1 , pAviParams->FileOut ) != 1 ) ! 526: { ! 527: perror ( "Avi_RecordAudioStream_PCM" ); ! 528: Log_AlertDlg ( LOG_ERROR, "AVI recording : failed to write pcm frame header" ); ! 529: return false; ! 530: } ! 531: ! 532: /* Write the audio frame data */ ! 533: for ( i = 0 ; i < SampleLength; i++ ) ! 534: { ! 535: /* Convert sample to little endian */ ! 536: sample[0] = SDL_SwapLE16 ( pSamples[ (SampleIndex+i) % MIXBUFFER_SIZE ][0]); ! 537: sample[1] = SDL_SwapLE16 ( pSamples[ (SampleIndex+i) % MIXBUFFER_SIZE ][1]); ! 538: /* And store */ ! 539: if ( fwrite ( &sample , sizeof ( sample ) , 1 , pAviParams->FileOut ) != 1 ) ! 540: { ! 541: perror ( "Avi_RecordAudioStream_PCM" ); ! 542: Log_AlertDlg ( LOG_ERROR, "AVI recording : failed to write pcm frame" ); ! 543: return false; ! 544: } ! 545: } ! 546: ! 547: return true; ! 548: } ! 549: ! 550: ! 551: ! 552: bool Avi_RecordAudioStream ( Sint16 pSamples[][2] , int SampleIndex , int SampleLength ) ! 553: { ! 554: if ( AviParams.AudioCodec == AVI_RECORD_AUDIO_CODEC_PCM ) ! 555: { ! 556: if ( Avi_RecordAudioStream_PCM ( &AviParams , pSamples , SampleIndex , SampleLength ) == false ) ! 557: { ! 558: return false; ! 559: } ! 560: } ! 561: else ! 562: { ! 563: return false; ! 564: } ! 565: ! 566: AviParams.TotalAudioSamples += SampleLength; ! 567: return true; ! 568: } ! 569: ! 570: ! 571: ! 572: ! 573: static void Avi_BuildFileHeader ( RECORD_AVI_PARAMS *pAviParams , AVI_FILE_HEADER *pAviFileHeader ) ! 574: { ! 575: int Width , Height , BitCount , Fps , Fps_scale , SizeImage; ! 576: int AudioFreq; ! 577: ! 578: memset ( pAviFileHeader , 0 , sizeof ( *pAviFileHeader ) ); ! 579: ! 580: Width = pAviParams->Width; ! 581: Height =pAviParams->Height; ! 582: BitCount = pAviParams->BitCount; ! 583: Fps = pAviParams->Fps; ! 584: Fps_scale = pAviParams->Fps_scale; ! 585: AudioFreq = pAviParams->AudioFreq; ! 586: ! 587: SizeImage = 0; ! 588: if ( pAviParams->VideoCodec == AVI_RECORD_VIDEO_CODEC_BMP ) ! 589: SizeImage = Avi_GetBmpSize ( Width , Height , BitCount ); /* size of a BMP image */ ! 590: else if ( pAviParams->VideoCodec == AVI_RECORD_VIDEO_CODEC_PNG ) ! 591: SizeImage = Avi_GetBmpSize ( Width , Height , BitCount ); /* max size of a PNG image */ ! 592: ! 593: ! 594: /* RIFF / AVI headers */ ! 595: Avi_Store4cc ( pAviFileHeader->RiffHeader.signature , "RIFF" ); ! 596: Avi_StoreU32 ( pAviFileHeader->RiffHeader.filesize , 0 ); /* total file size (-> completed later) */ ! 597: Avi_Store4cc ( pAviFileHeader->RiffHeader.type , "AVI " ); ! 598: ! 599: Avi_Store4cc ( pAviFileHeader->AviHeader.ChunkName , "LIST" ); ! 600: Avi_StoreU32 ( pAviFileHeader->AviHeader.ChunkSize , sizeof ( AVI_STREAM_LIST_AVIH ) ! 601: + sizeof ( AVI_STREAM_LIST_VIDS ) + sizeof ( AVI_STREAM_LIST_AUDS ) - 8 ); ! 602: Avi_Store4cc ( pAviFileHeader->AviHeader.Name , "hdrl" ); ! 603: ! 604: Avi_Store4cc ( pAviFileHeader->AviHeader.Header.ChunkName , "avih" ); ! 605: Avi_StoreU32 ( pAviFileHeader->AviHeader.Header.ChunkSize , sizeof ( AVI_STREAM_AVIH ) - 8 ); ! 606: Avi_StoreU32 ( pAviFileHeader->AviHeader.Header.microsec_per_frame , (Uint32)( ( 1000000 * (Sint64)Fps_scale ) / Fps ) ); ! 607: Avi_StoreU32 ( pAviFileHeader->AviHeader.Header.max_bytes_per_second , (Uint32)( ( (Sint64)SizeImage * Fps ) / Fps_scale + AudioFreq * 4 ) ); ! 608: Avi_StoreU32 ( pAviFileHeader->AviHeader.Header.padding_granularity , 0 ); ! 609: Avi_StoreU32 ( pAviFileHeader->AviHeader.Header.flags , AVIF_HASINDEX | AVIF_ISINTERLEAVED | AVIF_TRUSTCKTYPE ); ! 610: Avi_StoreU32 ( pAviFileHeader->AviHeader.Header.total_frames , 0 ); /* number of video frames (-> completed later) */ ! 611: Avi_StoreU32 ( pAviFileHeader->AviHeader.Header.init_frame , 0 ); ! 612: Avi_StoreU32 ( pAviFileHeader->AviHeader.Header.nb_streams , 2 ); /* 1 video and 1 audio */ ! 613: Avi_StoreU32 ( pAviFileHeader->AviHeader.Header.buffer_size , SizeImage ); ! 614: Avi_StoreU32 ( pAviFileHeader->AviHeader.Header.width , Width ); ! 615: Avi_StoreU32 ( pAviFileHeader->AviHeader.Header.height , Height ); ! 616: Avi_StoreU32 ( pAviFileHeader->AviHeader.Header.scale , 0 ); /* reserved */ ! 617: Avi_StoreU32 ( pAviFileHeader->AviHeader.Header.rate , 0 ); /* reserved */ ! 618: Avi_StoreU32 ( pAviFileHeader->AviHeader.Header.start , 0 ); /* reserved */ ! 619: Avi_StoreU32 ( pAviFileHeader->AviHeader.Header.length , 0 ); /* reserved */ ! 620: ! 621: ! 622: /* Video Stream */ ! 623: Avi_Store4cc ( pAviFileHeader->VideoStream.ChunkName , "LIST" ); ! 624: Avi_StoreU32 ( pAviFileHeader->VideoStream.ChunkSize , sizeof ( AVI_STREAM_LIST_VIDS ) - 8 ); ! 625: Avi_Store4cc ( pAviFileHeader->VideoStream.Name , "strl" ); ! 626: ! 627: Avi_Store4cc ( pAviFileHeader->VideoStream.Header.ChunkName , "strh" ); ! 628: Avi_StoreU32 ( pAviFileHeader->VideoStream.Header.ChunkSize , sizeof ( AVI_STREAM_HEADER ) - 8 ); ! 629: Avi_Store4cc ( pAviFileHeader->VideoStream.Header.stream_type , "vids" ); ! 630: if ( pAviParams->VideoCodec == AVI_RECORD_VIDEO_CODEC_BMP ) ! 631: Avi_StoreU32 ( pAviFileHeader->VideoStream.Header.stream_handler , VIDEO_STREAM_RGB ); ! 632: else if ( pAviParams->VideoCodec == AVI_RECORD_VIDEO_CODEC_PNG ) ! 633: Avi_Store4cc ( pAviFileHeader->VideoStream.Header.stream_handler , VIDEO_STREAM_PNG ); ! 634: Avi_StoreU32 ( pAviFileHeader->VideoStream.Header.flags , 0 ); ! 635: Avi_StoreU16 ( pAviFileHeader->VideoStream.Header.priority , 0 ); ! 636: Avi_StoreU16 ( pAviFileHeader->VideoStream.Header.language , 0 ); ! 637: Avi_StoreU32 ( pAviFileHeader->VideoStream.Header.initial_frames , 0 ); ! 638: Avi_StoreU32 ( pAviFileHeader->VideoStream.Header.time_scale , Fps_scale ); ! 639: Avi_StoreU32 ( pAviFileHeader->VideoStream.Header.data_rate , Fps ); ! 640: Avi_StoreU32 ( pAviFileHeader->VideoStream.Header.start_time , 0 ); ! 641: Avi_StoreU32 ( pAviFileHeader->VideoStream.Header.data_length , 0 ); /* number of video frames (-> completed later) */ ! 642: Avi_StoreU32 ( pAviFileHeader->VideoStream.Header.buffer_size , SizeImage ); /* size of an uncompressed frame */ ! 643: Avi_StoreU32 ( pAviFileHeader->VideoStream.Header.quality , -1 ); /* use default quality */ ! 644: Avi_StoreU32 ( pAviFileHeader->VideoStream.Header.sample_size , 0 ); /* 0 for video */ ! 645: Avi_StoreU16 ( pAviFileHeader->VideoStream.Header.dest_left , 0 ); ! 646: Avi_StoreU16 ( pAviFileHeader->VideoStream.Header.dest_top , 0 ); ! 647: Avi_StoreU16 ( pAviFileHeader->VideoStream.Header.dest_right , Width ); ! 648: Avi_StoreU16 ( pAviFileHeader->VideoStream.Header.dest_bottom , Height ); ! 649: ! 650: Avi_Store4cc ( pAviFileHeader->VideoStream.Format.ChunkName , "strf" ); ! 651: Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.ChunkSize , sizeof ( AVI_STREAM_FORMAT_VIDS ) - 8 ); ! 652: if ( pAviParams->VideoCodec == AVI_RECORD_VIDEO_CODEC_BMP ) ! 653: { ! 654: Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.size , sizeof ( AVI_STREAM_FORMAT_VIDS ) - 8 ); ! 655: Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.width , Width ); ! 656: Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.height , Height ); ! 657: Avi_StoreU16 ( pAviFileHeader->VideoStream.Format.planes , 1 ); /* always 1 */ ! 658: Avi_StoreU16 ( pAviFileHeader->VideoStream.Format.bit_count , BitCount ); ! 659: Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.compression , VIDEO_STREAM_RGB ); ! 660: Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.size_image , SizeImage ); ! 661: Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.xpels_meter , 0 ); ! 662: Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.ypels_meter , 0 ); ! 663: Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.clr_used , 0 ); /* no color map */ ! 664: Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.clr_important , 0 ); /* no color map */ ! 665: } ! 666: else if ( pAviParams->VideoCodec == AVI_RECORD_VIDEO_CODEC_PNG ) ! 667: { ! 668: Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.size , sizeof ( AVI_STREAM_FORMAT_VIDS ) - 8 ); ! 669: Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.width , Width ); ! 670: Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.height , Height ); ! 671: Avi_StoreU16 ( pAviFileHeader->VideoStream.Format.planes , 1 ); /* always 1 */ ! 672: Avi_StoreU16 ( pAviFileHeader->VideoStream.Format.bit_count , BitCount ); ! 673: Avi_Store4cc ( pAviFileHeader->VideoStream.Format.compression , VIDEO_STREAM_PNG ); ! 674: Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.size_image , SizeImage ); /* max size if uncompressed */ ! 675: Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.xpels_meter , 0 ); ! 676: Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.ypels_meter , 0 ); ! 677: Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.clr_used , 0 ); /* no color map */ ! 678: Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.clr_important , 0 ); /* no color map */ ! 679: } ! 680: ! 681: ! 682: /* Audio Stream */ ! 683: Avi_Store4cc ( pAviFileHeader->AudioStream.ChunkName , "LIST" ); ! 684: Avi_StoreU32 ( pAviFileHeader->AudioStream.ChunkSize , sizeof ( AVI_STREAM_LIST_AUDS ) - 8 ); ! 685: Avi_Store4cc ( pAviFileHeader->AudioStream.Name , "strl" ); ! 686: ! 687: Avi_Store4cc ( pAviFileHeader->AudioStream.Header.ChunkName , "strh" ); ! 688: Avi_StoreU32 ( pAviFileHeader->AudioStream.Header.ChunkSize , sizeof ( AVI_STREAM_HEADER ) - 8 ); ! 689: Avi_Store4cc ( pAviFileHeader->AudioStream.Header.stream_type , "auds" ); ! 690: Avi_StoreU32 ( pAviFileHeader->AudioStream.Header.stream_handler , 0 ); /* not used (or could be 1 for pcm ?) */ ! 691: Avi_StoreU32 ( pAviFileHeader->AudioStream.Header.flags , 0 ); ! 692: Avi_StoreU16 ( pAviFileHeader->AudioStream.Header.priority , 0 ); ! 693: Avi_StoreU16 ( pAviFileHeader->AudioStream.Header.language , 0 ); ! 694: Avi_StoreU32 ( pAviFileHeader->AudioStream.Header.initial_frames , 0 ); /* should be 1 in interleaved ? */ ! 695: Avi_StoreU32 ( pAviFileHeader->AudioStream.Header.time_scale , 1 ); ! 696: Avi_StoreU32 ( pAviFileHeader->AudioStream.Header.data_rate , AudioFreq ); ! 697: Avi_StoreU32 ( pAviFileHeader->AudioStream.Header.start_time , 0 ); ! 698: Avi_StoreU32 ( pAviFileHeader->AudioStream.Header.data_length , 0 ); /* number of audio samples (-> completed later) */ ! 699: Avi_StoreU32 ( pAviFileHeader->AudioStream.Header.buffer_size , AudioFreq * 4 / 50 ); /* min VBL freq is 50 Hz */ ! 700: Avi_StoreU32 ( pAviFileHeader->AudioStream.Header.quality , -1 ); /* use default quality */ ! 701: Avi_StoreU32 ( pAviFileHeader->AudioStream.Header.sample_size , 4 ); /* 2 bytes, stereo */ ! 702: Avi_StoreU16 ( pAviFileHeader->AudioStream.Header.dest_left , 0 ); ! 703: Avi_StoreU16 ( pAviFileHeader->AudioStream.Header.dest_top , 0 ); ! 704: Avi_StoreU16 ( pAviFileHeader->AudioStream.Header.dest_right , 0 ); ! 705: Avi_StoreU16 ( pAviFileHeader->AudioStream.Header.dest_bottom , 0 ); ! 706: ! 707: Avi_Store4cc ( pAviFileHeader->AudioStream.Format.ChunkName , "strf" ); ! 708: Avi_StoreU32 ( pAviFileHeader->AudioStream.Format.ChunkSize , sizeof ( AVI_STREAM_FORMAT_AUDS ) - 8 ); ! 709: if ( pAviParams->AudioCodec == AVI_RECORD_AUDIO_CODEC_PCM ) /* 16 bits stereo pcm */ ! 710: { ! 711: Avi_StoreU16 ( pAviFileHeader->AudioStream.Format.codec , AUDIO_STREAM_WAVE_FORMAT_PCM ); /* 0x0001 */ ! 712: Avi_StoreU16 ( pAviFileHeader->AudioStream.Format.channels , 2 ); ! 713: Avi_StoreU32 ( pAviFileHeader->AudioStream.Format.sample_rate , AudioFreq ); ! 714: Avi_StoreU32 ( pAviFileHeader->AudioStream.Format.bit_rate , AudioFreq * 2 * 2 ); /* 2 channels * 2 bytes */ ! 715: Avi_StoreU16 ( pAviFileHeader->AudioStream.Format.block_align , 4 ); ! 716: Avi_StoreU16 ( pAviFileHeader->AudioStream.Format.bits_per_sample , 16 ); ! 717: Avi_StoreU16 ( pAviFileHeader->AudioStream.Format.ext_size , 0 ); ! 718: } ! 719: } ! 720: ! 721: ! 722: static bool Avi_BuildIndex ( RECORD_AVI_PARAMS *pAviParams ) ! 723: { ! 724: AVI_CHUNK Chunk; ! 725: long IndexChunkPosStart; ! 726: long Pos , PosWrite; ! 727: Uint8 TempSize[4]; ! 728: AVI_CHUNK_INDEX ChunkIndex; ! 729: Uint32 Size; ! 730: ! 731: fseek ( pAviParams->FileOut , 0 , SEEK_END ); /* go to the end of the file */ ! 732: ! 733: /* Write the 'idx1' chunk header */ ! 734: IndexChunkPosStart = ftell ( pAviParams->FileOut ); ! 735: Avi_Store4cc ( Chunk.ChunkName , "idx1" ); /* stream 0, uncompressed DIB bytes */ ! 736: Avi_StoreU32 ( Chunk.ChunkSize , 0 ); /* index size (-> completed later) */ ! 737: if ( fwrite ( &Chunk , sizeof ( Chunk ) , 1 , pAviParams->FileOut ) != 1 ) ! 738: goto index_error; ! 739: PosWrite = ftell ( pAviParams->FileOut ); /* position to start writing indexes */ ! 740: ! 741: /* Go to the first data chunk in the 'movi' chunk */ ! 742: fseek ( pAviParams->FileOut , pAviParams->MoviChunkPosStart + sizeof ( AVI_STREAM_LIST_MOVI ) , SEEK_SET ); ! 743: Pos = ftell ( pAviParams->FileOut ); ! 744: ! 745: /* Build the index : we seek/read one data chunk and seek/write the */ ! 746: /* corresponding infos at the end of the index, until we reach the */ ! 747: /* end of the 'movi' chunk. */ ! 748: while ( Pos < pAviParams->MoviChunkPosEnd ) ! 749: { ! 750: /* Read the header for this data chunk: ChunkName and ChunkSize */ ! 751: if (fread(&Chunk, sizeof(Chunk), 1, pAviParams->FileOut) != 1) ! 752: goto index_error; ! 753: Size = Avi_ReadU32 ( Chunk.ChunkSize ); ! 754: ! 755: /* Write the index infos for this chunk */ ! 756: fseek ( pAviParams->FileOut , PosWrite , SEEK_SET ); ! 757: Avi_Store4cc ( ChunkIndex.identifier , (char *)Chunk.ChunkName ); /* 00dc, 00db, 01wb, ... */ ! 758: Avi_StoreU32 ( ChunkIndex.flags , AVIIF_KEYFRAME ); /* AVIIF_KEYFRAME */ ! 759: Avi_StoreU32 ( ChunkIndex.offset , Pos - pAviParams->MoviChunkPosStart - 8 ); /* pos relative to 'movi' */ ! 760: Avi_StoreU32 ( ChunkIndex.length , Size ); ! 761: if (fwrite ( &ChunkIndex , sizeof ( ChunkIndex ) , 1 , pAviParams->FileOut ) != 1) ! 762: goto index_error; ! 763: PosWrite = ftell ( pAviParams->FileOut ); /* position for the next index */ ! 764: ! 765: /* Go to the next data chunk in the 'movi' chunk */ ! 766: Pos = Pos + sizeof ( Chunk ) + Size; /* position of the next data chunk */ ! 767: fseek ( pAviParams->FileOut , Pos , SEEK_SET ); ! 768: } ! 769: ! 770: /* Update the size of the 'idx1' chunk */ ! 771: Avi_StoreU32 ( TempSize , PosWrite - IndexChunkPosStart - 8 ); ! 772: if ( fseek ( pAviParams->FileOut , IndexChunkPosStart+4 , SEEK_SET ) != 0 ) ! 773: goto index_error; ! 774: if ( fwrite ( TempSize , sizeof ( TempSize ) , 1 , pAviParams->FileOut ) != 1 ) ! 775: goto index_error; ! 776: return true; ! 777: ! 778: index_error: ! 779: perror ( "Avi_BuildIndex" ); ! 780: Log_AlertDlg ( LOG_ERROR, "AVI recording : failed to create index header" ); ! 781: return false; ! 782: ! 783: } ! 784: ! 785: ! 786: static bool Avi_StartRecording_WithParams ( RECORD_AVI_PARAMS *pAviParams , char *AviFileName ) ! 787: { ! 788: AVI_STREAM_LIST_INFO ListInfo; ! 789: char InfoString[ 100 ]; ! 790: int Len , Len_rounded; ! 791: AVI_STREAM_LIST_MOVI ListMovi; ! 792: ! 793: ! 794: if ( bRecordingAvi == true ) /* already recording ? */ ! 795: return false; ! 796: ! 797: /* Compute some video parameters */ ! 798: pAviParams->Width = pAviParams->Surface->w - pAviParams->CropLeft - pAviParams->CropRight; ! 799: pAviParams->Height = pAviParams->Surface->h - pAviParams->CropTop - pAviParams->CropBottom; ! 800: pAviParams->BitCount = 24; ! 801: ! 802: #if !HAVE_LIBPNG ! 803: if ( pAviParams->VideoCodec == AVI_RECORD_VIDEO_CODEC_PNG ) ! 804: { ! 805: perror ( "AviStartRecording" ); ! 806: Log_AlertDlg ( LOG_ERROR, "AVI recording : Hatari was not built with libpng support" ); ! 807: return false; ! 808: } ! 809: #endif ! 810: ! 811: /* Open the file */ ! 812: pAviParams->FileOut = fopen ( AviFileName , "wb+" ); ! 813: if ( !pAviParams->FileOut ) ! 814: { ! 815: perror ( "AviStartRecording" ); ! 816: Log_AlertDlg ( LOG_ERROR, "AVI recording : failed to open file" ); ! 817: return false; ! 818: } ! 819: ! 820: /* Build the AVI header */ ! 821: Avi_BuildFileHeader ( pAviParams , &AviFileHeader ); ! 822: ! 823: /* Write the AVI header */ ! 824: if ( fwrite ( &AviFileHeader , sizeof ( AviFileHeader ) , 1 , pAviParams->FileOut ) != 1 ) ! 825: { ! 826: perror ( "AviStartRecording" ); ! 827: Log_AlertDlg ( LOG_ERROR, "AVI recording : failed to write avi header" ); ! 828: return false; ! 829: } ! 830: ! 831: /* Write the INFO header */ ! 832: memset ( InfoString , 0 , sizeof ( InfoString ) ); ! 833: Len = snprintf ( InfoString , sizeof ( InfoString ) , "%s - the Atari ST, STE, TT and Falcon emulator" , PROG_NAME ) + 1; ! 834: Len_rounded = Len + ( Len % 2 == 0 ? 0 : 1 ); /* round Len to the next multiple of 2 */ ! 835: Avi_Store4cc ( ListInfo.ChunkName , "LIST" ); ! 836: Avi_StoreU32 ( ListInfo.ChunkSize , sizeof ( AVI_STREAM_LIST_INFO ) - 8 + Len_rounded ); ! 837: Avi_Store4cc ( ListInfo.Name , "INFO" ); ! 838: Avi_Store4cc ( ListInfo.Info.ChunkName , "ISFT" ); ! 839: Avi_StoreU32 ( ListInfo.Info.ChunkSize , Len ); ! 840: if ( fwrite ( &ListInfo , sizeof ( ListInfo ) , 1 , pAviParams->FileOut ) != 1 ) ! 841: { ! 842: perror ( "AviStartRecording" ); ! 843: Log_AlertDlg ( LOG_ERROR, "AVI recording : failed to write info header" ); ! 844: return false; ! 845: } ! 846: /* Write the info string + '\0' and write an optionnal extra '\0' byte to get a total multiple of 2 */ ! 847: if ( fwrite ( InfoString , Len_rounded , 1 , pAviParams->FileOut ) != 1 ) ! 848: { ! 849: perror ( "AviStartRecording" ); ! 850: Log_AlertDlg ( LOG_ERROR, "AVI recording : failed to write info header" ); ! 851: return false; ! 852: } ! 853: ! 854: /* Write the MOVI header */ ! 855: Avi_Store4cc ( ListMovi.ChunkName , "LIST" ); ! 856: Avi_StoreU32 ( ListMovi.ChunkSize , 0 ); /* completed when recording stops */ ! 857: Avi_Store4cc ( ListMovi.Name , "movi" ); ! 858: pAviParams->MoviChunkPosStart = ftell ( pAviParams->FileOut ); ! 859: if ( fwrite ( &ListMovi , sizeof ( ListMovi ) , 1 , pAviParams->FileOut ) != 1 ) ! 860: { ! 861: perror ( "AviStartRecording" ); ! 862: Log_AlertDlg ( LOG_ERROR, "AVI recording : failed to write movi header" ); ! 863: return false; ! 864: } ! 865: ! 866: ! 867: /* We're ok to record */ ! 868: Log_AlertDlg ( LOG_INFO, "AVI recording has been started"); ! 869: bRecordingAvi = true; ! 870: ! 871: return true; ! 872: } ! 873: ! 874: ! 875: ! 876: static bool Avi_StopRecording_WithParams ( RECORD_AVI_PARAMS *pAviParams ) ! 877: { ! 878: long FileSize; ! 879: Uint8 TempSize[4]; ! 880: ! 881: ! 882: if ( bRecordingAvi == false ) /* no recording ? */ ! 883: return true; ! 884: ! 885: /* Update the size of the 'movi' chunk */ ! 886: fseek ( pAviParams->FileOut , 0 , SEEK_END ); /* go to the end of the 'movi' chunk */ ! 887: pAviParams->MoviChunkPosEnd = ftell ( pAviParams->FileOut ); ! 888: Avi_StoreU32 ( TempSize , pAviParams->MoviChunkPosEnd - pAviParams->MoviChunkPosStart - 8 ); ! 889: ! 890: if ( fseek ( pAviParams->FileOut , pAviParams->MoviChunkPosStart+4 , SEEK_SET ) != 0 ) ! 891: { ! 892: perror ( "AviStopRecording" ); ! 893: Log_AlertDlg ( LOG_ERROR, "AVI recording : failed to update movi header" ); ! 894: return false; ! 895: } ! 896: if ( fwrite ( TempSize , sizeof ( TempSize ) , 1 , pAviParams->FileOut ) != 1 ) ! 897: { ! 898: perror ( "AviStopRecording" ); ! 899: Log_AlertDlg ( LOG_ERROR, "AVI recording : failed to update movi header" ); ! 900: return false; ! 901: } ! 902: ! 903: /* Build the index chunk */ ! 904: if ( ! Avi_BuildIndex ( pAviParams ) ) ! 905: { ! 906: perror ( "AviStopRecording" ); ! 907: Log_AlertDlg ( LOG_ERROR, "AVI recording : failed to build index" ); ! 908: return false; ! 909: } ! 910: ! 911: /* Update the avi header (file size, number of output frames, ...) */ ! 912: fseek ( pAviParams->FileOut , 0 , SEEK_END ); /* go to the end of the file */ ! 913: FileSize = ftell ( pAviParams->FileOut ); ! 914: ! 915: Avi_StoreU32 ( AviFileHeader.RiffHeader.filesize , FileSize - 8 ); /* 32 bits, limited to 4GB */ ! 916: Avi_StoreU32 ( AviFileHeader.AviHeader.Header.total_frames , pAviParams->TotalVideoFrames ); /* number of video frames */ ! 917: Avi_StoreU32 ( AviFileHeader.VideoStream.Header.data_length , pAviParams->TotalVideoFrames ); /* number of video frames */ ! 918: Avi_StoreU32 ( AviFileHeader.AudioStream.Header.data_length , pAviParams->TotalAudioSamples ); /* number of audio samples */ ! 919: ! 920: if ( fseek ( pAviParams->FileOut , 0 , SEEK_SET ) != 0 ) ! 921: { ! 922: perror ( "AviStopRecording" ); ! 923: Log_AlertDlg ( LOG_ERROR, "AVI recording : failed to update avi header" ); ! 924: return false; ! 925: } ! 926: if ( fwrite ( &AviFileHeader , sizeof ( AviFileHeader ) , 1 , pAviParams->FileOut ) != 1 ) ! 927: { ! 928: perror ( "AviStopRecording" ); ! 929: Log_AlertDlg ( LOG_ERROR, "AVI recording : failed to update avi header" ); ! 930: return false; ! 931: } ! 932: ! 933: ! 934: /* Close the file */ ! 935: fclose ( pAviParams->FileOut ); ! 936: ! 937: Log_AlertDlg ( LOG_INFO, "AVI recording has been stopped"); ! 938: bRecordingAvi = false; ! 939: ! 940: return true; ! 941: } ! 942: ! 943: ! 944: ! 945: ! 946: /*-----------------------------------------------------------------------*/ ! 947: /** ! 948: * Are we recording an AVI ? ! 949: */ ! 950: bool Avi_AreWeRecording ( void ) ! 951: { ! 952: return bRecordingAvi; ! 953: } ! 954: ! 955: ! 956: ! 957: bool Avi_StartRecording ( char *FileName , bool CropGui , Uint32 Fps , Uint32 Fps_scale , int VideoCodec ) ! 958: { ! 959: memset ( &AviParams , 0 , sizeof ( AviParams ) ); ! 960: ! 961: AviParams.VideoCodec = VideoCodec; ! 962: AviParams.VideoCodecCompressionLevel = 9; /* png compression level */ ! 963: AviParams.AudioCodec = AVI_RECORD_AUDIO_CODEC_PCM; ! 964: AviParams.AudioFreq = ConfigureParams.Sound.nPlaybackFreq; ! 965: AviParams.Surface = sdlscrn; ! 966: ! 967: /* Some video players (quicktime, ...) don't support a value of Fps_scale */ ! 968: /* above 100000. So we decrease the precision from << 24 to << 16 for Fps and Fps_scale */ ! 969: AviParams.Fps = Fps >> 8; /* refresh rate << 16 */ ! 970: AviParams.Fps_scale = Fps_scale >> 8; /* 1 << 16 */ ! 971: ! 972: if ( !CropGui ) /* Keep gui's status bar */ ! 973: { ! 974: AviParams.CropLeft = 0; ! 975: AviParams.CropRight = 0; ! 976: AviParams.CropTop = 0; ! 977: AviParams.CropBottom = 0; ! 978: } ! 979: else /* Record only the content of the Atari's screen */ ! 980: { ! 981: AviParams.CropLeft = 0; ! 982: AviParams.CropRight = 0; ! 983: AviParams.CropTop = 0; ! 984: AviParams.CropBottom = Statusbar_GetHeight(); ! 985: } ! 986: ! 987: if (Avi_StartRecording_WithParams ( &AviParams , FileName )) ! 988: { ! 989: Main_SetTitle("00:00"); ! 990: return true; ! 991: } ! 992: return false; ! 993: } ! 994: ! 995: ! 996: bool Avi_StopRecording ( void ) ! 997: { ! 998: if (Avi_StopRecording_WithParams ( &AviParams )) ! 999: { ! 1000: Main_SetTitle(NULL); ! 1001: return true; ! 1002: } ! 1003: return false; ! 1004: } ! 1005: ! 1006:
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.