/*
  Hatari - avi_record.c

  This file is distributed under the GNU General Public License, version 2
  or at your option any later version. Read the file gpl.txt for details.

  AVI File recording

  This allows Hatari to record a video file, with both video and audio
  streams, at full frame rate.
  
  Video frames are saved using the current video frequency of the emulated
  machine (50 Hz, 60 Hz, 70 Hz, ...). Frames can be stored using different
  codecs. So far, supported codecs are :
   - BMP : uncompressed RGB images. Very fast to save, very few cpu needed
     but requires a lot of disk bandwidth and a lot of space.
   - PNG : compressed RBG images. Depending on the compression level, this
     can require more cpu and could slow down Hatari. As compressed images
     are much smaller than BMP images, this will require less space on disk
     and much less disk bandwidth. Compression levels 3 or 4 give good
     tradeoff between cpu usage and file size and should not slow down Hatari
     with recent computers.

  PNG compression will often give a x20 ratio when compared to BMP and should
  be used if you have a powerful enough cpu.

  Sound is saved as 16 bits pcm stereo, using the current Hatari sound output
  frequency. For best accuracy, sound frequency should be a multiple of the
  video frequency ; this means 44.1 kHz is the best choice for 50/60 Hz video.

  The AVI file is divided into multiple chunks. Hatari will save one video stream
  and one audio stream, so the overall structure of the file is the following :

  RIFF avi
      LIST
	hdrl
	  avih
	  LIST
	    strl
	      strh (vids)
	      strf
	  LIST
	    strl
	      strh (auds)
	      strf
      LIST
	INFO
      LIST
	movi
	  00db
	  01wb
	  ...
      idx1
*/

const char AVIRecord_fileid[] = "Hatari avi_record.c : " __DATE__ " " __TIME__;

#include <SDL.h>
#include <SDL_endian.h>

#include "main.h"
#include "version.h"
#include "audio.h"
#include "configuration.h"
#include "log.h"
#include "screen.h"
#include "screenSnapShot.h"
#include "sound.h"
#include "statusbar.h"
#include "avi_record.h"

/* after above that brings in config.h */
#if HAVE_LIBPNG
#include <png.h>
#endif

#include "pixel_convert.h"				/* inline functions */



typedef struct
{
	Uint8			ChunkName[4];		/* '00db', '01wb', 'idx1' */
	Uint8			ChunkSize[4];
} AVI_CHUNK;


typedef struct
{
	Uint8			identifier[4];		/* '00db', '01wb', 'idx1' */
	Uint8			flags[4];
	Uint8			offset[4];
	Uint8			length[4];
} AVI_CHUNK_INDEX;


typedef struct
{
	Uint8			ChunkName[4];		/* 'strh' */
	Uint8			ChunkSize[4];

	Uint8			stream_type[4];		/* 'vids' or 'auds' */
	Uint8			stream_handler[4];
	Uint8			flags[4];
	Uint8			priority[2];
	Uint8			language[2];
	Uint8			initial_frames[4];
	Uint8			time_scale[4];
	Uint8			data_rate[4];
	Uint8			start_time[4];
	Uint8			data_length[4];
	Uint8			buffer_size[4];
	Uint8			quality[4];
	Uint8			sample_size[4];
	Uint8			dest_left[2];
	Uint8			dest_top[2];
	Uint8			dest_right[2];
	Uint8			dest_bottom[2];
} AVI_STREAM_HEADER;


typedef struct
{
	Uint8			ChunkName[4];		/* 'strf' */
	Uint8			ChunkSize[4];

	Uint8			size[4];
	Uint8			width[4];
	Uint8			height[4];
	Uint8			planes[2];
	Uint8			bit_count[2];
	Uint8			compression[4];
	Uint8			size_image[4];
	Uint8			xpels_meter[4];
	Uint8			ypels_meter[4];
	Uint8			clr_used[4];
	Uint8			clr_important[4];
} AVI_STREAM_FORMAT_VIDS;

typedef struct
{
	Uint8			ChunkName[4];		/* 'LIST' */
	Uint8			ChunkSize[4];

	Uint8			Name[4];		/* 'strl' */
	AVI_STREAM_HEADER	Header;			/* 'strh' */
	AVI_STREAM_FORMAT_VIDS	Format;			/* 'strf' */
} AVI_STREAM_LIST_VIDS;


typedef struct
{
	Uint8			ChunkName[4];		/* 'strf' */
	Uint8			ChunkSize[4];

	Uint8			codec[2];
	Uint8			channels[2];
	Uint8			sample_rate[4];
	Uint8			bit_rate[4];
	Uint8			block_align[2];
	Uint8			bits_per_sample[2];
	Uint8			ext_size[2];
} AVI_STREAM_FORMAT_AUDS;

typedef struct
{
	Uint8			ChunkName[4];		/* 'LIST' */
	Uint8			ChunkSize[4];

	Uint8			Name[4];		/* 'strl' */
	AVI_STREAM_HEADER	Header;			/* 'strh' */
	AVI_STREAM_FORMAT_AUDS	Format;			/* 'strf' */
} AVI_STREAM_LIST_AUDS;


typedef struct {
	Uint8			ChunkName[4];		/* 'avih' */
	Uint8			ChunkSize[4];

	Uint8			microsec_per_frame[4];
	Uint8			max_bytes_per_second[4];
	Uint8			padding_granularity[4];
	Uint8			flags[4];
	Uint8			total_frames[4];
	Uint8			init_frame[4];
	Uint8			nb_streams[4];
	Uint8			buffer_size[4];
	Uint8			width[4];
	Uint8			height[4];
	Uint8			scale[4];
	Uint8			rate[4];
	Uint8			start[4];
	Uint8			length[4];
} AVI_STREAM_AVIH;

typedef struct
{
	Uint8			ChunkName[4];		/* 'LIST' */
	Uint8			ChunkSize[4];

	Uint8			Name[4];		/* 'hdrl' */
	AVI_STREAM_AVIH		Header;
} AVI_STREAM_LIST_AVIH;


typedef struct {
	Uint8			ChunkName[4];		/* 'ISFT' (software used) */
	Uint8			ChunkSize[4];

//	Uint8			Text[2];		/* Text's size should be multiple of 2 (including '\0') */
} AVI_STREAM_INFO;

typedef struct
{
	Uint8			ChunkName[4];		/* 'LIST' */
	Uint8			ChunkSize[4];

	Uint8			Name[4];		/* 'INFO' */
	AVI_STREAM_INFO		Info;
} AVI_STREAM_LIST_INFO;


typedef struct
{
	Uint8			ChunkName[4];		/* 'LIST' */
	Uint8			ChunkSize[4];

	Uint8			Name[4];		/* 'movi' */
} AVI_STREAM_LIST_MOVI;


typedef struct
{
  Uint8		signature[4];				/* 'RIFF' */
  Uint8		filesize[4];
  Uint8		type[4];				/* 'AVI ' */
  
} RIFF_HEADER;


typedef struct {
  RIFF_HEADER			RiffHeader;

  AVI_STREAM_LIST_AVIH		AviHeader;
  
  AVI_STREAM_LIST_VIDS		VideoStream;
  AVI_STREAM_LIST_AUDS		AudioStream;
  
} AVI_FILE_HEADER;



#define	AUDIO_STREAM_WAVE_FORMAT_PCM		0x0001

#define	VIDEO_STREAM_RGB			0x00000000			/* fourcc for BMP video frames */
#define	VIDEO_STREAM_PNG			"MPNG"				/* fourcc for PNG video frames */

#define	AVIF_HASINDEX				0x00000010			/* index at the end of the file */
#define	AVIF_ISINTERLEAVED			0x00000100			/* data are interleaved */
#define	AVIF_TRUSTCKTYPE			0x00000800			/* trust chunk type */

#define	AVIIF_KEYFRAME				0x00000010			/* frame is a keyframe */


typedef struct {
  /* Input params to start recording */
  int		VideoCodec;
  int		VideoCodecCompressionLevel;					/* 0-9 for png compression */

  SDL_Surface	*Surface;

  int		CropLeft;
  int		CropRight;
  int		CropTop;
  int		CropBottom;

  int		Fps;					/* Fps << 24 */
  int		Fps_scale;				/* 1 << 24 */

  int		AudioCodec;
  int		AudioFreq;

  /* Internal data used by the avi recorder */
  int		Width;
  int		Height;
  int		BitCount;
  FILE		*FileOut;				/* file to write to */
  int		TotalVideoFrames;			/* number of recorded video frames */
  int		TotalAudioSamples;			/* number of recorded audio samples */
  long		MoviChunkPosStart;			/* as returned by ftell() */
  long		MoviChunkPosEnd;			/* as returned by ftell() */
} RECORD_AVI_PARAMS;



bool		bRecordingAvi = false;


static RECORD_AVI_PARAMS	AviParams;
static AVI_FILE_HEADER		AviFileHeader;


static void	Avi_StoreU16 ( Uint8 *p , Uint16 val );
static void	Avi_StoreU32 ( Uint8 *p , Uint32 val );
static void	Avi_Store4cc ( Uint8 *p , const char *text );
static Uint32	Avi_ReadU32 ( Uint8 *p );

static int	Avi_GetBmpSize ( int Width , int Height , int BitCount );

static bool	Avi_RecordVideoStream_BMP ( RECORD_AVI_PARAMS *pAviParams );
#if HAVE_LIBPNG
static bool	Avi_RecordVideoStream_PNG ( RECORD_AVI_PARAMS *pAviParams );
#endif
static bool	Avi_RecordAudioStream_PCM ( RECORD_AVI_PARAMS *pAviParams , Sint16 pSamples[][2], int SampleIndex, int SampleLength );

static void	Avi_BuildFileHeader ( RECORD_AVI_PARAMS *pAviParams , AVI_FILE_HEADER *pAviFileHeader );
static bool	Avi_BuildIndex ( RECORD_AVI_PARAMS *pAviParams );

static bool	Avi_StartRecording_WithParams ( RECORD_AVI_PARAMS *pAviParams , char *AviFileName );
static bool	Avi_StopRecording_WithParams ( RECORD_AVI_PARAMS *pAviParams );




static void	Avi_StoreU16 ( Uint8 *p , Uint16 val )
{
	*p++ = val & 0xff;
	val >>= 8;
	*p = val & 0xff;
}


static void	Avi_StoreU32 ( Uint8 *p , Uint32 val )
{
	*p++ = val & 0xff;
	val >>= 8;
	*p++ = val & 0xff;
	val >>= 8;
	*p++ = val & 0xff;
	val >>= 8;
	*p = val & 0xff;
}


static void	Avi_Store4cc ( Uint8 *p , const char *text )
{
	memcpy ( p , text , 4 );
}


static Uint32	Avi_ReadU32 ( Uint8 *p )
{
	return (p[3]<<24) + (p[2]<<16) + (p[1]<<8) +p[0];
}


static int	Avi_GetBmpSize ( int Width , int Height , int BitCount )
{
	return ( Width * Height * BitCount / 8 );						/* bytes in one video frame */
}



static bool	Avi_RecordVideoStream_BMP ( RECORD_AVI_PARAMS *pAviParams )
{
	AVI_CHUNK	Chunk;
	int		SizeImage;
	Uint8		LineBuf[ 3 * pAviParams->Width ];			/* temp buffer to convert to 24-bit BGR format */
	Uint8		*pBitmapIn , *pBitmapOut;
	int		y;
	int		NeedLock;
	
	SizeImage = Avi_GetBmpSize ( pAviParams->Width , pAviParams->Height , pAviParams->BitCount );

	/* Write the video frame header */
	Avi_Store4cc ( Chunk.ChunkName , "00db" );				/* stream 0, uncompressed DIB bytes */
	Avi_StoreU32 ( Chunk.ChunkSize , SizeImage );					/* max size of RGB image */
	if ( fwrite ( &Chunk , sizeof ( Chunk ) , 1 , pAviParams->FileOut ) != 1 )
	{
		perror ( "Avi_RecordVideoStream_BMP" );
		Log_AlertDlg ( LOG_ERROR, "AVI recording : failed to write bmp frame header" );
		return false;
	}


	/* Write the video frame data */
	NeedLock = SDL_MUSTLOCK( pAviParams->Surface );

	/* Points to the top left pixel after cropping borders */
	/* For BMP format, frame is stored from bottom to top (origin is in bottom left corner) */
	/* and bytes are in BGR order (not RGB) */
	pBitmapIn = (Uint8 *)pAviParams->Surface->pixels
			+ pAviParams->Surface->pitch * ( pAviParams->CropTop + pAviParams->Height )
			+ pAviParams->CropLeft * pAviParams->Surface->format->BytesPerPixel;

	for ( y=0 ; y<pAviParams->Height ; y++ )
	{
		if ( NeedLock )
			SDL_LockSurface ( pAviParams->Surface );

		pBitmapOut = LineBuf;
		switch ( pAviParams->Surface->format->BytesPerPixel ) {
			case 1 :	PixelConvert_8to24Bits_BGR(LineBuf, pBitmapIn, pAviParams->Width, pAviParams->Surface->format->palette->colors);
					break;
			case 2 :	PixelConvert_16to24Bits_BGR(LineBuf, (Uint16 *)pBitmapIn, pAviParams->Width, pAviParams->Surface->format);
					break;
			case 3 :	PixelConvert_24to24Bits_BGR(LineBuf, pBitmapIn, pAviParams->Width);
					break;
			case 4 :	PixelConvert_32to24Bits_BGR(LineBuf, (Uint32 *)pBitmapIn, pAviParams->Width, pAviParams->Surface->format);
					break;
		}

		if ( NeedLock )
			SDL_UnlockSurface ( pAviParams->Surface );

		if ( (int)fwrite ( pBitmapOut , 1 , pAviParams->Width*3 , pAviParams->FileOut ) != pAviParams->Width*3 )
		{
			perror ( "Avi_RecordVideoStream_BMP" );
			Log_AlertDlg ( LOG_ERROR, "AVI recording : failed to write bmp video frame" );
			return false;
		}

		pBitmapIn -= pAviParams->Surface->pitch;			/* go from bottom to top */
	}

	return true;
}



#if HAVE_LIBPNG
static bool	Avi_RecordVideoStream_PNG ( RECORD_AVI_PARAMS *pAviParams )
{
	AVI_CHUNK	Chunk;
	int		SizeImage;
	long		ChunkPos;
	Uint8	TempSize[4];
	

	/* Write the video frame header */
	ChunkPos = ftell ( pAviParams->FileOut );
	Avi_Store4cc ( Chunk.ChunkName , "00dc" );				/* stream 0, compressed DIB bytes */
	Avi_StoreU32 ( Chunk.ChunkSize , 0 );					/* size of PNG image (-> completed later) */
	if ( fwrite ( &Chunk , sizeof ( Chunk ) , 1 , pAviParams->FileOut ) != 1 )
		goto png_error;

	/* Write the video frame data */
	SizeImage = ScreenSnapShot_SavePNG_ToFile ( pAviParams->Surface , pAviParams->FileOut ,
		pAviParams->VideoCodecCompressionLevel , PNG_FILTER_NONE ,
		pAviParams->CropLeft , pAviParams->CropRight , pAviParams->CropTop , pAviParams->CropBottom );
	if ( SizeImage <= 0 )
		goto png_error;
	if ( SizeImage & 1 )
	{
		SizeImage++;							/* add an extra '\0' byte to get an even size */
		fputc ( '\0' , pAviParams->FileOut );				/* next chunk must be aligned on 16 bits boundary */
	}

	/* Update the size of the video chunk */
	Avi_StoreU32 ( TempSize , SizeImage );
	if ( fseek ( pAviParams->FileOut , ChunkPos+4 , SEEK_SET ) != 0 )
		goto png_error;
	if ( fwrite ( TempSize , sizeof ( TempSize ) , 1 , pAviParams->FileOut ) != 1 )
		goto png_error;

	/* Go to the end of the video frame data */
	if ( fseek ( pAviParams->FileOut , 0 , SEEK_END ) != 0 )
		goto png_error;
	return true;

png_error:
	perror ( "Avi_RecordVideoStream_PNG" );
	Log_AlertDlg ( LOG_ERROR, "AVI recording : failed to write png frame" );
	return false;
}
#endif  /* HAVE_LIBPNG */



bool	Avi_RecordVideoStream ( void )
{
	if ( AviParams.VideoCodec == AVI_RECORD_VIDEO_CODEC_BMP )
	{
		if ( Avi_RecordVideoStream_BMP ( &AviParams ) == false )
		{
			return false;
		}
	}
#if HAVE_LIBPNG
	else if ( AviParams.VideoCodec == AVI_RECORD_VIDEO_CODEC_PNG )
	{
		if ( Avi_RecordVideoStream_PNG ( &AviParams ) == false )
		{
			return false;
		}
	}
#endif
	else
	{
		return false;
	}

	if (++AviParams.TotalVideoFrames % ( AviParams.Fps / AviParams.Fps_scale ) == 0)
	{
		int secs = AviParams.TotalVideoFrames / ( AviParams.Fps / AviParams.Fps_scale );
		char str[6] = "00:00";
		str[0] = '0' + (secs / 60) / 10;
		str[1] = '0' + (secs / 60) % 10;
		str[3] = '0' + (secs % 60) / 10;
		str[4] = '0' + (secs % 60) % 10;
		Main_SetTitle(str);
	}
	return true;
}



static bool	Avi_RecordAudioStream_PCM ( RECORD_AVI_PARAMS *pAviParams , Sint16 pSamples[][2] , int SampleIndex , int SampleLength )
{
	AVI_CHUNK	Chunk;
	Sint16		sample[2];
	int		i;

	/* Write the audio frame header */
	Avi_Store4cc ( Chunk.ChunkName , "01wb" );				/* stream 1, wave bytes */
	Avi_StoreU32 ( Chunk.ChunkSize , SampleLength * 4 );			/* 16 bits, stereo -> 4 bytes */
	if ( fwrite ( &Chunk , sizeof ( Chunk ) , 1 , pAviParams->FileOut ) != 1 )
	{
		perror ( "Avi_RecordAudioStream_PCM" );
		Log_AlertDlg ( LOG_ERROR, "AVI recording : failed to write pcm frame header" );
		return false;
	}

	/* Write the audio frame data */
	for ( i = 0 ; i < SampleLength; i++ )
	{
		/* Convert sample to little endian */
		sample[0] = SDL_SwapLE16 ( pSamples[ (SampleIndex+i) % MIXBUFFER_SIZE ][0]);
		sample[1] = SDL_SwapLE16 ( pSamples[ (SampleIndex+i) % MIXBUFFER_SIZE ][1]);
		/* And store */
		if ( fwrite ( &sample , sizeof ( sample ) , 1 , pAviParams->FileOut ) != 1 )
		{
			perror ( "Avi_RecordAudioStream_PCM" );
			Log_AlertDlg ( LOG_ERROR, "AVI recording : failed to write pcm frame" );
			return false;
		}
	}

	return true;
}



bool	Avi_RecordAudioStream ( Sint16 pSamples[][2] , int SampleIndex , int SampleLength )
{
	if ( AviParams.AudioCodec == AVI_RECORD_AUDIO_CODEC_PCM )
	{
		if ( Avi_RecordAudioStream_PCM ( &AviParams , pSamples , SampleIndex , SampleLength ) == false )
		{
			return false;
		}
	}
	else
	{
		return false;
	}

	AviParams.TotalAudioSamples += SampleLength;
	return true;
}




static void	Avi_BuildFileHeader ( RECORD_AVI_PARAMS *pAviParams , AVI_FILE_HEADER *pAviFileHeader )
{
	int	Width , Height , BitCount , Fps , Fps_scale , SizeImage;
	int	AudioFreq;

	memset ( pAviFileHeader , 0 , sizeof ( *pAviFileHeader ) );

	Width = pAviParams->Width;
	Height =pAviParams->Height;
	BitCount = pAviParams->BitCount;
	Fps = pAviParams->Fps;
	Fps_scale = pAviParams->Fps_scale;
	AudioFreq = pAviParams->AudioFreq;

	SizeImage = 0;
	if ( pAviParams->VideoCodec == AVI_RECORD_VIDEO_CODEC_BMP )
		SizeImage = Avi_GetBmpSize ( Width , Height , BitCount );		/* size of a BMP image */
	else if ( pAviParams->VideoCodec == AVI_RECORD_VIDEO_CODEC_PNG )
		SizeImage = Avi_GetBmpSize ( Width , Height , BitCount );		/* max size of a PNG image */


	/* RIFF / AVI headers */
	Avi_Store4cc ( pAviFileHeader->RiffHeader.signature , "RIFF" );
	Avi_StoreU32 ( pAviFileHeader->RiffHeader.filesize , 0 );				/* total file size (-> completed later) */
	Avi_Store4cc ( pAviFileHeader->RiffHeader.type , "AVI " );

	Avi_Store4cc ( pAviFileHeader->AviHeader.ChunkName , "LIST" );
	Avi_StoreU32 ( pAviFileHeader->AviHeader.ChunkSize , sizeof ( AVI_STREAM_LIST_AVIH )
		+ sizeof ( AVI_STREAM_LIST_VIDS ) + sizeof ( AVI_STREAM_LIST_AUDS ) - 8 );
	Avi_Store4cc ( pAviFileHeader->AviHeader.Name , "hdrl" );

	Avi_Store4cc ( pAviFileHeader->AviHeader.Header.ChunkName , "avih" );
	Avi_StoreU32 ( pAviFileHeader->AviHeader.Header.ChunkSize , sizeof ( AVI_STREAM_AVIH ) - 8 );
	Avi_StoreU32 ( pAviFileHeader->AviHeader.Header.microsec_per_frame , (Uint32)( ( 1000000 * (Sint64)Fps_scale ) / Fps ) );
	Avi_StoreU32 ( pAviFileHeader->AviHeader.Header.max_bytes_per_second , (Uint32)( ( (Sint64)SizeImage * Fps ) / Fps_scale + AudioFreq * 4 ) );
	Avi_StoreU32 ( pAviFileHeader->AviHeader.Header.padding_granularity , 0 );
	Avi_StoreU32 ( pAviFileHeader->AviHeader.Header.flags , AVIF_HASINDEX | AVIF_ISINTERLEAVED | AVIF_TRUSTCKTYPE );
	Avi_StoreU32 ( pAviFileHeader->AviHeader.Header.total_frames , 0 );			/* number of video frames (-> completed later) */
	Avi_StoreU32 ( pAviFileHeader->AviHeader.Header.init_frame , 0 );
	Avi_StoreU32 ( pAviFileHeader->AviHeader.Header.nb_streams , 2 );			/* 1 video and 1 audio */
	Avi_StoreU32 ( pAviFileHeader->AviHeader.Header.buffer_size , SizeImage );
	Avi_StoreU32 ( pAviFileHeader->AviHeader.Header.width , Width );
	Avi_StoreU32 ( pAviFileHeader->AviHeader.Header.height , Height );
	Avi_StoreU32 ( pAviFileHeader->AviHeader.Header.scale , 0 );				/* reserved */
	Avi_StoreU32 ( pAviFileHeader->AviHeader.Header.rate , 0 );				/* reserved */
	Avi_StoreU32 ( pAviFileHeader->AviHeader.Header.start , 0 );				/* reserved */
	Avi_StoreU32 ( pAviFileHeader->AviHeader.Header.length , 0 );				/* reserved */


	/* Video Stream */
	Avi_Store4cc ( pAviFileHeader->VideoStream.ChunkName , "LIST" );
	Avi_StoreU32 ( pAviFileHeader->VideoStream.ChunkSize , sizeof ( AVI_STREAM_LIST_VIDS ) - 8 );
	Avi_Store4cc ( pAviFileHeader->VideoStream.Name , "strl" );

	Avi_Store4cc ( pAviFileHeader->VideoStream.Header.ChunkName , "strh" );
	Avi_StoreU32 ( pAviFileHeader->VideoStream.Header.ChunkSize , sizeof ( AVI_STREAM_HEADER ) - 8 );
	Avi_Store4cc ( pAviFileHeader->VideoStream.Header.stream_type , "vids" );
	if ( pAviParams->VideoCodec == AVI_RECORD_VIDEO_CODEC_BMP )
		Avi_StoreU32 ( pAviFileHeader->VideoStream.Header.stream_handler , VIDEO_STREAM_RGB );
	else if ( pAviParams->VideoCodec == AVI_RECORD_VIDEO_CODEC_PNG )
		Avi_Store4cc ( pAviFileHeader->VideoStream.Header.stream_handler , VIDEO_STREAM_PNG );
	Avi_StoreU32 ( pAviFileHeader->VideoStream.Header.flags , 0 );
	Avi_StoreU16 ( pAviFileHeader->VideoStream.Header.priority , 0 );
	Avi_StoreU16 ( pAviFileHeader->VideoStream.Header.language , 0 );
	Avi_StoreU32 ( pAviFileHeader->VideoStream.Header.initial_frames , 0 );
	Avi_StoreU32 ( pAviFileHeader->VideoStream.Header.time_scale , Fps_scale );
	Avi_StoreU32 ( pAviFileHeader->VideoStream.Header.data_rate , Fps );
	Avi_StoreU32 ( pAviFileHeader->VideoStream.Header.start_time , 0 );
	Avi_StoreU32 ( pAviFileHeader->VideoStream.Header.data_length , 0 );			/* number of video frames (-> completed later) */
	Avi_StoreU32 ( pAviFileHeader->VideoStream.Header.buffer_size , SizeImage );		/* size of an uncompressed frame */
	Avi_StoreU32 ( pAviFileHeader->VideoStream.Header.quality , -1 );			/* use default quality */
	Avi_StoreU32 ( pAviFileHeader->VideoStream.Header.sample_size , 0 );			/* 0 for video */
	Avi_StoreU16 ( pAviFileHeader->VideoStream.Header.dest_left , 0 );
	Avi_StoreU16 ( pAviFileHeader->VideoStream.Header.dest_top , 0 );
	Avi_StoreU16 ( pAviFileHeader->VideoStream.Header.dest_right , Width );
	Avi_StoreU16 ( pAviFileHeader->VideoStream.Header.dest_bottom , Height );

	Avi_Store4cc ( pAviFileHeader->VideoStream.Format.ChunkName , "strf" );
	Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.ChunkSize , sizeof ( AVI_STREAM_FORMAT_VIDS ) - 8 );
	if ( pAviParams->VideoCodec == AVI_RECORD_VIDEO_CODEC_BMP )
	{
		Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.size , sizeof ( AVI_STREAM_FORMAT_VIDS ) - 8 );
		Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.width , Width );
		Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.height , Height );
		Avi_StoreU16 ( pAviFileHeader->VideoStream.Format.planes , 1 );			/* always 1 */
		Avi_StoreU16 ( pAviFileHeader->VideoStream.Format.bit_count , BitCount );
		Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.compression , VIDEO_STREAM_RGB );
		Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.size_image , SizeImage );
		Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.xpels_meter , 0 );
		Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.ypels_meter , 0 );
		Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.clr_used , 0 );		/* no color map */
		Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.clr_important , 0 );		/* no color map */
	}
	else if ( pAviParams->VideoCodec == AVI_RECORD_VIDEO_CODEC_PNG )
	{
		Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.size , sizeof ( AVI_STREAM_FORMAT_VIDS ) - 8 );
		Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.width , Width );
		Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.height , Height );
		Avi_StoreU16 ( pAviFileHeader->VideoStream.Format.planes , 1 );			/* always 1 */
		Avi_StoreU16 ( pAviFileHeader->VideoStream.Format.bit_count , BitCount );
		Avi_Store4cc ( pAviFileHeader->VideoStream.Format.compression , VIDEO_STREAM_PNG );
		Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.size_image , SizeImage );	/* max size if uncompressed */
		Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.xpels_meter , 0 );
		Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.ypels_meter , 0 );
		Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.clr_used , 0 );		/* no color map */
		Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.clr_important , 0 );		/* no color map */
	}


	/* Audio Stream */
	Avi_Store4cc ( pAviFileHeader->AudioStream.ChunkName , "LIST" );
	Avi_StoreU32 ( pAviFileHeader->AudioStream.ChunkSize , sizeof ( AVI_STREAM_LIST_AUDS ) - 8 );
	Avi_Store4cc ( pAviFileHeader->AudioStream.Name , "strl" );

	Avi_Store4cc ( pAviFileHeader->AudioStream.Header.ChunkName , "strh" );
	Avi_StoreU32 ( pAviFileHeader->AudioStream.Header.ChunkSize , sizeof ( AVI_STREAM_HEADER ) - 8 );
	Avi_Store4cc ( pAviFileHeader->AudioStream.Header.stream_type , "auds" );
	Avi_StoreU32 ( pAviFileHeader->AudioStream.Header.stream_handler , 0 );			/* not used (or could be 1 for pcm ?) */
	Avi_StoreU32 ( pAviFileHeader->AudioStream.Header.flags , 0 );
	Avi_StoreU16 ( pAviFileHeader->AudioStream.Header.priority , 0 );
	Avi_StoreU16 ( pAviFileHeader->AudioStream.Header.language , 0 );
	Avi_StoreU32 ( pAviFileHeader->AudioStream.Header.initial_frames , 0 );			/* should be 1 in interleaved ? */
	Avi_StoreU32 ( pAviFileHeader->AudioStream.Header.time_scale , 1 );
	Avi_StoreU32 ( pAviFileHeader->AudioStream.Header.data_rate , AudioFreq );
	Avi_StoreU32 ( pAviFileHeader->AudioStream.Header.start_time , 0 );
	Avi_StoreU32 ( pAviFileHeader->AudioStream.Header.data_length , 0 );			/* number of audio samples (-> completed later) */
	Avi_StoreU32 ( pAviFileHeader->AudioStream.Header.buffer_size , AudioFreq * 4 / 50 );	/* min VBL freq is 50 Hz */
	Avi_StoreU32 ( pAviFileHeader->AudioStream.Header.quality , -1 );			/* use default quality */
	Avi_StoreU32 ( pAviFileHeader->AudioStream.Header.sample_size , 4 );			/* 2 bytes, stereo */
	Avi_StoreU16 ( pAviFileHeader->AudioStream.Header.dest_left , 0 );
	Avi_StoreU16 ( pAviFileHeader->AudioStream.Header.dest_top , 0 );
	Avi_StoreU16 ( pAviFileHeader->AudioStream.Header.dest_right , 0 );
	Avi_StoreU16 ( pAviFileHeader->AudioStream.Header.dest_bottom , 0 );

	Avi_Store4cc ( pAviFileHeader->AudioStream.Format.ChunkName , "strf" );
	Avi_StoreU32 ( pAviFileHeader->AudioStream.Format.ChunkSize , sizeof ( AVI_STREAM_FORMAT_AUDS ) - 8 );
	if ( pAviParams->AudioCodec == AVI_RECORD_AUDIO_CODEC_PCM )				/* 16 bits stereo pcm */
	{
		Avi_StoreU16 ( pAviFileHeader->AudioStream.Format.codec , AUDIO_STREAM_WAVE_FORMAT_PCM );	/* 0x0001 */
		Avi_StoreU16 ( pAviFileHeader->AudioStream.Format.channels , 2 );
		Avi_StoreU32 ( pAviFileHeader->AudioStream.Format.sample_rate , AudioFreq );
		Avi_StoreU32 ( pAviFileHeader->AudioStream.Format.bit_rate , AudioFreq * 2 * 2 );	/* 2 channels * 2 bytes */
		Avi_StoreU16 ( pAviFileHeader->AudioStream.Format.block_align , 4 );
		Avi_StoreU16 ( pAviFileHeader->AudioStream.Format.bits_per_sample , 16 );
		Avi_StoreU16 ( pAviFileHeader->AudioStream.Format.ext_size , 0 );
	}
}


static bool	Avi_BuildIndex ( RECORD_AVI_PARAMS *pAviParams )
{
	AVI_CHUNK	Chunk;
	long		IndexChunkPosStart;
	long		Pos , PosWrite;
	Uint8		TempSize[4];
	AVI_CHUNK_INDEX	ChunkIndex;
	Uint32		Size;

	if (fseek(pAviParams->FileOut, 0, SEEK_END) != 0)			/* go to the end of the file */
		goto index_error;

	/* Write the 'idx1' chunk header */
	IndexChunkPosStart = ftell ( pAviParams->FileOut );
	Avi_Store4cc ( Chunk.ChunkName , "idx1" );				/* stream 0, uncompressed DIB bytes */
	Avi_StoreU32 ( Chunk.ChunkSize , 0 );					/* index size (-> completed later) */
	if ( fwrite ( &Chunk , sizeof ( Chunk ) , 1 , pAviParams->FileOut ) != 1 )
		goto index_error;
	PosWrite = ftell ( pAviParams->FileOut );				/* position to start writing indexes */

	/* Go to the first data chunk in the 'movi' chunk */
	if (fseek(pAviParams->FileOut, pAviParams->MoviChunkPosStart + sizeof(AVI_STREAM_LIST_MOVI), SEEK_SET) != 0)
		goto index_error;
	Pos = ftell ( pAviParams->FileOut );

	/* Build the index : we seek/read one data chunk and seek/write the */
	/* corresponding infos at the end of the index, until we reach the */
	/* end of the 'movi' chunk. */
	while ( Pos < pAviParams->MoviChunkPosEnd )
	{
		/* Read the header for this data chunk: ChunkName and ChunkSize */
		if (fread(&Chunk, sizeof(Chunk), 1, pAviParams->FileOut) != 1)
			goto index_error;
		Size = Avi_ReadU32 ( Chunk.ChunkSize );

		/* Write the index infos for this chunk */
		if (fseek(pAviParams->FileOut, PosWrite, SEEK_SET) != 0)
			goto index_error;
		Avi_Store4cc ( ChunkIndex.identifier , (char *)Chunk.ChunkName );	/* 00dc, 00db, 01wb, ... */
		Avi_StoreU32 ( ChunkIndex.flags , AVIIF_KEYFRAME );		/* AVIIF_KEYFRAME */
		Avi_StoreU32 ( ChunkIndex.offset , Pos - pAviParams->MoviChunkPosStart - 8  );	/* pos relative to 'movi' */
		Avi_StoreU32 ( ChunkIndex.length , Size );
		if (fwrite ( &ChunkIndex , sizeof ( ChunkIndex ) , 1 , pAviParams->FileOut ) != 1)
			goto index_error;
		PosWrite = ftell ( pAviParams->FileOut );			/* position for the next index */

		/* Go to the next data chunk in the 'movi' chunk */
		Pos = Pos + sizeof ( Chunk ) + Size;				/* position of the next data chunk */
		if (fseek(pAviParams->FileOut, Pos, SEEK_SET) != 0)
			goto index_error;
	}

	/* Update the size of the 'idx1' chunk */
	Avi_StoreU32 ( TempSize , PosWrite - IndexChunkPosStart - 8 );
	if ( fseek ( pAviParams->FileOut , IndexChunkPosStart+4 , SEEK_SET ) != 0 )
		goto index_error;
	if ( fwrite ( TempSize , sizeof ( TempSize ) , 1 , pAviParams->FileOut ) != 1 )
		goto index_error;
	return true;

index_error:
	perror ( "Avi_BuildIndex" );
	Log_AlertDlg ( LOG_ERROR, "AVI recording : failed to create index header" );
	return false;
}


static bool	Avi_StartRecording_WithParams ( RECORD_AVI_PARAMS *pAviParams , char *AviFileName )
{
	AVI_STREAM_LIST_INFO	ListInfo;
	char			InfoString[ 100 ];
	int			Len , Len_rounded;
	AVI_STREAM_LIST_MOVI	ListMovi;


	if ( bRecordingAvi == true )						/* already recording ? */
		return false;

	/* Compute some video parameters */
	pAviParams->Width = pAviParams->Surface->w - pAviParams->CropLeft - pAviParams->CropRight;
	pAviParams->Height = pAviParams->Surface->h - pAviParams->CropTop - pAviParams->CropBottom;
	pAviParams->BitCount = 24;
	
#if !HAVE_LIBPNG
	if ( pAviParams->VideoCodec == AVI_RECORD_VIDEO_CODEC_PNG )
	{
		perror ( "AviStartRecording" );
		Log_AlertDlg ( LOG_ERROR, "AVI recording : Hatari was not built with libpng support" );
		return false;
	}
#endif

	/* Open the file */
	pAviParams->FileOut = fopen ( AviFileName , "wb+" );
	if ( !pAviParams->FileOut )
	{
		perror ( "AviStartRecording" );
		Log_AlertDlg ( LOG_ERROR, "AVI recording : failed to open file" );
		return false;
	}

	/* Build the AVI header */
	Avi_BuildFileHeader ( pAviParams , &AviFileHeader );
	
	/* Write the AVI header */
	if ( fwrite ( &AviFileHeader , sizeof ( AviFileHeader ) , 1 , pAviParams->FileOut ) != 1 )
	{
		perror ( "AviStartRecording" );
		Log_AlertDlg ( LOG_ERROR, "AVI recording : failed to write avi header" );
		return false;
	}

	/* Write the INFO header */
	memset ( InfoString , 0 , sizeof ( InfoString ) );
	Len = snprintf ( InfoString , sizeof ( InfoString ) , "%s - the Atari ST, STE, TT and Falcon emulator" , PROG_NAME ) + 1;
	Len_rounded = Len + ( Len % 2 == 0 ? 0 : 1 );				/* round Len to the next multiple of 2 */
	Avi_Store4cc ( ListInfo.ChunkName , "LIST" );
	Avi_StoreU32 ( ListInfo.ChunkSize , sizeof ( AVI_STREAM_LIST_INFO ) - 8 + Len_rounded );
	Avi_Store4cc ( ListInfo.Name , "INFO" );
	Avi_Store4cc ( ListInfo.Info.ChunkName , "ISFT" );
	Avi_StoreU32 ( ListInfo.Info.ChunkSize , Len );
	if ( fwrite ( &ListInfo , sizeof ( ListInfo ) , 1 , pAviParams->FileOut ) != 1 )
	{
		perror ( "AviStartRecording" );
		Log_AlertDlg ( LOG_ERROR, "AVI recording : failed to write info header" );
		return false;
	}
	/* Write the info string + '\0' and write an optional extra '\0' byte to get a total multiple of 2 */
	if ( fwrite ( InfoString , Len_rounded , 1 , pAviParams->FileOut ) != 1 )
	{
		perror ( "AviStartRecording" );
		Log_AlertDlg ( LOG_ERROR, "AVI recording : failed to write info header" );
		return false;
	}

	/* Write the MOVI header */
	Avi_Store4cc ( ListMovi.ChunkName , "LIST" );
	Avi_StoreU32 ( ListMovi.ChunkSize , 0 );					/* completed when recording stops */
	Avi_Store4cc ( ListMovi.Name , "movi" );
	pAviParams->MoviChunkPosStart = ftell ( pAviParams->FileOut );
	if ( fwrite ( &ListMovi , sizeof ( ListMovi ) , 1 , pAviParams->FileOut ) != 1 )
	{
		perror ( "AviStartRecording" );
		Log_AlertDlg ( LOG_ERROR, "AVI recording : failed to write movi header" );
		return false;
	}


	/* We're ok to record */
	Log_AlertDlg ( LOG_INFO, "AVI recording has been started");
	bRecordingAvi = true;

	return true;
}



static bool	Avi_StopRecording_WithParams ( RECORD_AVI_PARAMS *pAviParams )
{
	long	FileSize;
	Uint8	TempSize[4];


	if ( bRecordingAvi == false )						/* no recording ? */
		return true;

	/* Update the size of the 'movi' chunk */
	if (fseek(pAviParams->FileOut, 0, SEEK_END) != 0)			/* go to the end of the 'movi' chunk */
		goto stoprec_error;
	pAviParams->MoviChunkPosEnd = ftell ( pAviParams->FileOut );
	Avi_StoreU32 ( TempSize , pAviParams->MoviChunkPosEnd - pAviParams->MoviChunkPosStart - 8 );

	if ( fseek ( pAviParams->FileOut , pAviParams->MoviChunkPosStart+4 , SEEK_SET ) != 0 )
		goto stoprec_error;
	if ( fwrite ( TempSize , sizeof ( TempSize ) , 1 , pAviParams->FileOut ) != 1 )
		goto stoprec_error;

	/* Build the index chunk */
	if ( ! Avi_BuildIndex ( pAviParams ) )
	{
		perror ( "AviStopRecording" );
		Log_AlertDlg ( LOG_ERROR, "AVI recording : failed to build index" );
		return false;
	}

	/* Update the avi header (file size, number of output frames, ...) */
	if (fseek(pAviParams->FileOut, 0, SEEK_END) != 0)			/* go to the end of the file */
		goto stoprec_error;
	FileSize = ftell ( pAviParams->FileOut );

	Avi_StoreU32 ( AviFileHeader.RiffHeader.filesize , FileSize - 8 );	/* 32 bits, limited to 4GB */
	Avi_StoreU32 ( AviFileHeader.AviHeader.Header.total_frames , pAviParams->TotalVideoFrames );	/* number of video frames */
	Avi_StoreU32 ( AviFileHeader.VideoStream.Header.data_length , pAviParams->TotalVideoFrames );	/* number of video frames */
	Avi_StoreU32 ( AviFileHeader.AudioStream.Header.data_length , pAviParams->TotalAudioSamples );	/* number of audio samples */

	if ( fseek ( pAviParams->FileOut , 0 , SEEK_SET ) != 0 )
		goto stoprec_error;
	if ( fwrite ( &AviFileHeader , sizeof ( AviFileHeader ) , 1 , pAviParams->FileOut ) != 1 )
		goto stoprec_error;

	/* Close the file */
	fclose ( pAviParams->FileOut );

	Log_AlertDlg ( LOG_INFO, "AVI recording has been stopped");
	bRecordingAvi = false;

	return true;

stoprec_error:
	fclose (pAviParams->FileOut);
	perror("AviStopRecording");
	Log_AlertDlg(LOG_ERROR, "AVI recording : failed to update header");
	return false;
}


/*-----------------------------------------------------------------------*/
/**
 * Are we recording an AVI ?
 */
bool	Avi_AreWeRecording ( void )
{
        return bRecordingAvi;
}


/* PNG compression level, 0-9 */
static int compression_level = 9;

/**
 * Set recording level from given string
 * return true for valid, false for invalid value
 */
bool Avi_SetCompressionLevel(const char *str)
{
	char *end;
	long level = strtol(str, &end, 10);
	if (*end)
		return false;
	if (level < 0 || level > 9)
		return false;
	compression_level = level;
	return true;
}


bool	Avi_StartRecording ( char *FileName , bool CropGui , Uint32 Fps , Uint32 Fps_scale , int VideoCodec )
{
	memset ( &AviParams , 0 , sizeof ( AviParams ) );

	AviParams.VideoCodec = VideoCodec;
	AviParams.VideoCodecCompressionLevel = compression_level;
	AviParams.AudioCodec = AVI_RECORD_AUDIO_CODEC_PCM;
	AviParams.AudioFreq = ConfigureParams.Sound.nPlaybackFreq;
	AviParams.Surface = sdlscrn;

	/* Some video players (quicktime, ...) don't support a value of Fps_scale */
	/* above 100000. So we decrease the precision from << 24 to << 16 for Fps and Fps_scale */
	AviParams.Fps = Fps >> 8;			/* refresh rate << 16 */
	AviParams.Fps_scale = Fps_scale >> 8;		/* 1 << 16 */

	if ( !CropGui )					/* Keep gui's status bar */
	{
		AviParams.CropLeft = 0;
		AviParams.CropRight = 0;
		AviParams.CropTop = 0;
		AviParams.CropBottom = 0;
	}
	else						/* Record only the content of the Atari's screen */
	{
		AviParams.CropLeft = 0;
		AviParams.CropRight = 0;
		AviParams.CropTop = 0;
		AviParams.CropBottom = Statusbar_GetHeight();
	}
	
	
	if (Avi_StartRecording_WithParams ( &AviParams , FileName ))
	{
		Main_SetTitle("00:00");
		return true;
	}
	return false;
}


bool	Avi_StopRecording ( void )
{
	if (Avi_StopRecording_WithParams ( &AviParams ))
	{
		Main_SetTitle(NULL);
		return true;
	}
	return false;
}


