|
|
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.