//
// Raw mjpeg avi writer
//
// This takes a stream of TBitmap images, compresses them to jpeg and saves them into an mjpeg .avi file - on the fly
//
// no API calls here thank you very much :)
//
// written by C.Moss using Delphi 6  ....  30th March 2003
// tab setting 3'
//
// note, they seem to recommend you add regular JUNK chunks at around every 20Kbytes - something to do with when reading it off CD ??? (beats me)
// This is done for you in the routine that saves the jpeg into the avi ... you could always comment it out it if you wish 
//
// ALL chunks in the AVI must start on a 2-byte alignment
//
// ALL images MUST be an even width AND even height size - and ALL the same size, standard AVI do not support changes in image width/height size
//
// note: unless you use the extended avi chunks your file must be limited to the 2GByte size.
//       The extended format is just basically more RIFF chunks than just the normal one - you'll find info on the net about this
//
// Prolly best to make this an object but couldn't be arsed
//
// Freeware, do as you see fit with this code

unit TBitmap_to_MJPEG_AVI;

interface

uses Windows, {WinTypes, Dialogs,} SysUtils, Classes, JPEG, Graphics;

const	// avih flags
			AVIF_HASINDEX								= $00000010;
			AVIF_MUSTUSEINDEX							= $00000020;
			AVIF_ISINTERLEAVED						= $00000100;
			AVIF_TRUSTCKTYPE							= $00000800;
			AVIF_WASCAPTUREFILE						= $00010000;
			AVIF_COPYRIGHTED							= $00020000;

			// strh flags
			AVISF_DISABLED								= $00000001;
			AVISF_VIDEO_PALCHANGES					= $00010000;

			// idx1 flags
			AVIIF_LIST									= $00000001;
			AVIIF_KEYFRAME								= $00000010;
			AVIIF_FIRSTPART							= $00000020;
			AVIIF_LASTPART								= $00000040;
			AVIIF_MIDPART								= $00000060;
			AVIIF_NOTIME								= $00000100;
			AVIIF_COMPUSE								= $0fff0000;

			// audio format tag
			WAVE_FORMAT_UNKNOWN						=	$0000;	// Microsoft Corporation
			WAVE_FORMAT_PCM							=	$0001;	// Microsoft Corporation
			WAVE_FORMAT_ADPCM							=	$0002;	// Microsoft Corporation
			WAVE_FORMAT_IBM_CVSD						=	$0005;	// IBM Corporation
			WAVE_FORMAT_ALAW							=	$0006;	// Microsoft Corporation
			WAVE_FORMAT_MULAW							=	$0007;	// Microsoft Corporation
			WAVE_FORMAT_OKI_ADPCM					=	$0010;	// OKI
			WAVE_FORMAT_DVI_ADPCM					=	$0011;	// Intel Corporation
			WAVE_FORMAT_IMA_ADPCM					=	$0011;	// Intel Corporation
			WAVE_FORMAT_MEDIASPACE_ADPCM			=	$0012;	// Videologic
			WAVE_FORMAT_SIERRA_ADPCM				=	$0013;	// Sierra Semiconductor Corp
			WAVE_FORMAT_G723_ADPCM					=	$0014;	// Antex Electronics Corporation
			WAVE_FORMAT_DIGISTD						=	$0015;	// DSP Solutions, Inc.
			WAVE_FORMAT_DIGIFIX						=	$0016;	// DSP Solutions, Inc.
			WAVE_FORMAT_DIALOGIC_OKI_ADPCM		=	$0017;	// Dialogic Corporation
			WAVE_FORMAT_YAMAHA_ADPCM				=	$0020;	// Yamaha Corporation of America
			WAVE_FORMAT_SONARC						=	$0021;	// Speech Compression
			WAVE_FORMAT_DSPGROUP_TRUESPEECH		=	$0022;	// DSP Group, Inc
			WAVE_FORMAT_ECHOSC1						=	$0023;	// Echo Speech Corporation
			WAVE_FORMAT_AUDIOFILE_AF36				=	$0024;	//
			WAVE_FORMAT_APTX							=	$0025;	// Audio Processing Technology
			WAVE_FORMAT_AUDIOFILE_AF10				=	$0026;	//
			WAVE_FORMAT_DOLBY_AC2					=	$0030;	// Dolby Laboratories
			WAVE_FORMAT_GSM610						=	$0031;	// Microsoft Corporation
			WAVE_FORMAT_MSN							=	$0032;	// Microsoft Corporation
			WAVE_FORMAT_ANTEX_ADPCME				=	$0033;	// Antex Electronics Corporation
			WAVE_FORMAT_CONTROL_RES_VQLPC			=	$0034;	// Control Resources Limited
			WAVE_FORMAT_DIGIREAL						=	$0035;	// DSP Solutions, Inc.
			WAVE_FORMAT_DIGIADPCM					=	$0036;	// DSP Solutions, Inc.
			WAVE_FORMAT_CONTROL_RES_CR10			=	$0037;	// Control Resources Limited
			WAVE_FORMAT_NMS_VBXADPCM				=	$0038;	// Natural MicroSystems
			WAVE_FORMAT_CS_IMAADPCM					=	$0039;	// Crystal Semiconductor IMA ADPCM
			WAVE_FORMAT_G721_ADPCM					=	$0040;	// Antex Electronics Corporation
			WAVE_FORMAT_MPEG							=	$0050;	// Microsoft Corporation
			WAVE_FORMAT_MPEG3							=	$0055;	// Microsoft Corporation
			WAVE_FORMAT_CELP48						=	$0070;	// Lernout & Hauspie
			WAVE_FORMAT_SBC8							=	$0071;	// Lernout & Hauspie
			WAVE_FORMAT_SBC12							=	$0072;	// Lernout & Hauspie
			WAVE_FORMAT_SBC16							=	$0073;	// Lernout & Hauspie
			WAVE_FORMAT_VOXRT							=	$0074;	// Voxware Audio
			WAVE_FORMAT_VOXAC							=	$0075;	// Voxware Audio
			WAVE_FORMAT_CREATIVE_ADPCM				=	$0200;	// Creative Labs, Inc
			WAVE_FORMAT_CREATIVE_FASTSPEECH8		=	$0202;	// Creative Labs, Inc
			WAVE_FORMAT_CREATIVE_FASTSPEECH10	=	$0203;	// Creative Labs, Inc
			WAVE_FORMAT_FM_TOWNS_SND				=	$0300;	// Fujitsu Corp.
			WAVE_FORMAT_OLIGSM						=	$1000;	// Ing C. Olivetti & C., S.p.A.
			WAVE_FORMAT_OLIADPCM						=	$1001;	// Ing C. Olivetti & C., S.p.A.
			WAVE_FORMAT_OLICELP						=	$1002;	// Ing C. Olivetti & C., S.p.A.
			WAVE_FORMAT_OLISBC						=	$1003;	// Ing C. Olivetti & C., S.p.A.
			WAVE_FORMAT_OLIOPR						=	$1004;	// Ing C. Olivetti & C., S.p.A.

type		FOURCC					=	array[0..3] of char;

			TCHUNK					=	record
												fcc						: FOURCC;
												cb							: dword;
											end;

			TAVIH						=	record
//												fcc						: FOURCC;
//												cb							: dword;
												dwMicroSecPerFrame	: dword;
												dwMaxBytesPerSec		: dword;
												dwPaddingGranularity	: dword;
												dwFlags					: dword;
												dwTotalFrames			: dword;
												dwInitialFrames		: dword;
												dwStreams				: dword;	// 1 for just video, 2 for video and audio
												dwSuggestedBufferSize: dword;
												dwWidth					: dword;
												dwHeight					: dword;
												dwReserved				: array[0..3] of dword;
											end;

			TSTRH						=	record
//												fcc							: FOURCC;
//												cb								: dword;
												fccType						: FOURCC;
												fccHandler					: FOURCC;
												dwFlags						: dword;
												wPriority					: word;
												wLanguage					: word;
												dwInitialFrames			: dword;
												dwScale						: dword;
												dwRate						: dword;
												dwStart						: dword;
												dwLength						: dword;
												dwSuggestedBufferSize	: dword;
												dwQuality					: dword;
												dwSampleSize				: dword;
												rcFrame						:	record
																						Left	: integer;	// short int
																						Top	: integer;
																						Right	: integer;
																						Bottom: integer;
																					end;
											end;

			TPAL						=	record
												bFirstEntry				: byte;
												bNumEntries				: byte;
												wFlags					: short;
											end;

			TIDX1						=	packed record
												ckid						: FOURCC;
												dwFlags					: dword;
												dwChunkOffset			: dword;
												dwChunkLength			: dword;
											end;

			TIDX2						=	packed record
												fcc						: FOURCC;
												cb							: dword;
												wLongsPerEntry			: word;
												bIndexSubType			: byte;
												bIndexType				: byte;
												nEntriesInUse			: dword;
												dwChunkId				: FOURCC;
												dwReserved				: array[0..2] of dword;
											end;

			TWaveFormatEx			=	packed record									// audio format
												wFormatTag				: word;
												nChannels				: word;
												nSamplesPerSec			: dword;
												nAvgBytesPerSec		: dword;
												nBlockAlign				: word;
												wBitsPerSample			: word;
												cbSize					: word;
											end;

			TAVIHDR					=	packed record									// this is a basic ready made avi header (video only atm)
												riff						: TCHUNK;
												avi						: FOURCC;

												list1						: TCHUNK;

												hdrl						: FOURCC;
												
												avihhdr					: TCHUNK;
												avih						: TAVIH;
											end;

			TjpegHeader				=	packed record									// JPEG-JFIF header
												SOIMarker	: word;						// FFD8
												APP0Marker	: word;						// FFE0 ... JFIF header marker
												HeaderSize	: word;						// 0010
												id				: fourcc;					// 'JFIF'
												version		: word;						//
											end;

function GetLastErrorStr(error: dword): string;

function str_to_fourcc(Strg: string): fourcc;
function fourcc_to_str(fcc: FOURCC): string;

function CreateMJPEGfile(	Filename: string;
									Width,
									Height,
									MicroSecPerFrame_: dword;
									JunkStr: string;
									Quality: integer;
									ProgramID: string;
									ContentsDesc: string;
									Bitmap: TBitmap;
									AudioWaveFormat: pointer;
									AudioWaveFormatSize: integer): boolean;
function AddAudio(AudioData: pointer; AudioDataSize: dword): boolean;
function AddBitmap(Bitmap: TBitmap): boolean;
function AddJPEGFile(Filename: string): boolean;
function CloseMJPEGFile(DeleteIt: boolean): boolean;

implementation

{$I-}	// stop file errors popping up

//*******************************

function GetLastErrorStr(error: dword): string;
var lpMsgBuf: cardinal;
begin
	FormatMessage(	FORMAT_MESSAGE_ALLOCATE_BUFFER or FORMAT_MESSAGE_FROM_SYSTEM,
						nil,
						error,
						(SUBLANG_DEFAULT shl 10) or LANG_NEUTRAL, // Default language
						@lpMsgBuf,
						0,
						nil);
	result := '[' + inttostr(error) + '] ' + pchar(lpMsgBuf);
	LocalFree(lpMsgBuf);
end;

function str_to_fourcc(Strg: string): fourcc;
begin
	move(Strg[1], result, sizeof(fourcc));
end;

function fourcc_to_str(fcc: FOURCC): string;
begin
	result := fcc[0] + fcc[1] + fcc[2] + fcc[3];
end;

//******************************

const	MaxIndexes			=	1000000;
type		TIndexTable			=	packed record
											Offset	: dword;							// address in avi file of the jpeg image header
											Length	: dword;							// size of jpeg image data
											fcc		: fourcc;
										end;
			pIndexTable			=	packed array[0..MaxIndexes-1] of TIndexTable;
var		IndexTable			:	^pIndexTable = nil;
			Indexes				:	integer = 0;

			Opened				: boolean = false;								// true if we have the .avi file open
			MJPEGFilename		: string = '';
			mjpegfile			: file;
			StreamBytes			: dword = 0;
			TotalFrames			: dword = 0;
			MicroSecPerFrame	: dword = 0;
			jpegStream			: TMemoryStream = nil;
			JPG					: TJPEGImage = nil;

			avihdr				: TAVIHDR;
			vFormat				:	record							// video format
											list		: TCHUNK;
											strl		: FOURCC;
											strhhdr	: TCHUNK;
											strh		: TSTRH;
											strfhdr	: TCHUNK;
										end;
			aFormat				:	record							// audio format
											list		: TCHUNK;
											strl		: FOURCC;
											strhhdr	: TCHUNK;
											strh		: TSTRH;
											strfhdr	: TCHUNK;
										end;
			moviChunk			: TChunk;
			moviStart			: dword = 0;
			LastJunkChunkAddr	: dword;
			EndOfHeaderAddr	: dword;

			ImageBuffer			: pointer = nil;
			
//********************

function FreeIndexTable: boolean;
// free the memory the index table has used
var PrevErrorMode: Word;
begin
	result := false;

	if (IndexTable <> nil) then
	begin
		PrevErrorMode := SetErrorMode(SEM_FAILCRITICALERRORS);	// ignore critical errors
		try
			FreeMem(IndexTable);
		finally
			IndexTable := nil;
			Indexes := 0;
			SetErrorMode(PrevErrorMode);										// go back to previous error mode
		end;
	end
	else
		Indexes := 0;

	result := (IndexTable = nil);
end;

function AllocateIndexTable(NumIndexes: integer): boolean;
// Get the memory the index table needs
var PrevErrorMode: Word;
begin
	result := false;

	if (not FreeIndexTable) then exit;

	PrevErrorMode := SetErrorMode(SEM_FAILCRITICALERRORS);	// ignore critical errors
	try
		GetMem(IndexTable, sizeof(TIndexTable) * NumIndexes);
	except
		IndexTable := nil;
		Indexes := 0;

		CloseMJPEGFile(true);
		FreeIndexTable;
//		ShowMessage('Could not allocate mem for the index table');
	end;
	SetErrorMode(PrevErrorMode);										// go back to previous error mode

	result := (IndexTable <> nil);
end;

function AddToIndexTable(Offset, Size: dword; fcc: fourcc): boolean;
// Add a new entry to the index table
// Offset if the image chunk address if the AVI file
// Size is the number of bytes to the image data
begin
	result := false;

	if (Indexes >= MaxIndexes) then
	begin	// we're full up :(
		CloseMJPEGFile(true);
		FreeIndexTable;
//		ShowMessage('Index table full up');
		exit;
	end;

	IndexTable[Indexes].Offset := Offset;
	IndexTable[Indexes].Length := Size;
	IndexTable[Indexes].fcc := fcc;
	inc(Indexes);

	result := true;
end;

//********************

function AddStringChunk(fcc: fourcc; Strg: string): boolean;
var	PrevErrorMode	: Word;
		err				: dword;
		BytesWrote, i	: integer;
		ior				: integer;
		Chunk				: TCHUNK;
		Buffer			: array[0..1023] of char;
begin
	result := false;

	if (length(Strg) >= sizeof(Buffer)) then Strg := trim(Copy(Strg, 1, sizeof(Buffer) - 1));

	FillChar(Buffer, sizeof(Buffer), 0);
	StrPCopy(Buffer, Strg);

	Chunk.fcc := fcc;
	Chunk.cb := StrLen(Buffer);

	BlockWrite(mjpegfile, Chunk, sizeof(Chunk), BytesWrote); ior := ioresult;
	if (ior <> 0) or (BytesWrote <> sizeof(Chunk)) then
	begin
		CloseFile(mjpegfile); ior := ioresult;
		DeleteFile(MJPEGFilename);
		FreeIndexTable;
//		ShowMessage('Could not write the ' + fourcc_to_str(fcc) + ' header');
		exit;
	end;

	i := (Chunk.cb + 1) and $fffffffe;	// must be 2-byte aligned
	
	BlockWrite(mjpegfile, Buffer, i, BytesWrote); ior := ioresult;
	if (ior <> 0) or (BytesWrote <> i) then
	begin
		CloseFile(mjpegfile); ior := ioresult;
		DeleteFile(MJPEGFilename);
		FreeIndexTable;
//		ShowMessage('Could not write the ' + fourcc_to_str(fcc) + ' data');
		exit;
	end;

	result := true;
end;

function AddJUNKChunk(Strg: string; MinimumSize: integer): boolean;
// adds a JUNK chunk :)
var	PrevErrorMode		: Word;
		err					: dword;
		BytesWrote, i, j	: integer;
		ior					: integer;
		Chunk					: TCHUNK;
		Buffer				: pchar;
		s						: string;
begin
	result := false;

	i := length(Strg) + 1;
	if (i < MinimumSize) then i := MinimumSize;
	if (i < 0) then
	begin
		result := true;
		exit;
	end;

	j := (i + 1) and $fffffffe;										// 2-byte align

	Buffer := nil;
	PrevErrorMode := SetErrorMode(SEM_FAILCRITICALERRORS);	// ignore critical errors
	try
		GetMem(Buffer, j);
	except
		err := GetLastError;
		s := GetLastErrorStr(err);
		Buffer := nil;
	end;
	SetErrorMode(PrevErrorMode);										// go back to previous error mode

	if (Buffer = nil) then
	begin
		result := true;													// don't bother stopping if it's just memory we can't get
		exit;
	end;

	Chunk.fcc := 'JUNK';
	Chunk.cb := i;

	BlockWrite(mjpegfile, Chunk, sizeof(Chunk), BytesWrote); ior := ioresult;
	if (ior <> 0) or (BytesWrote <> sizeof(Chunk)) then
	begin
		CloseFile(mjpegfile); ior := ioresult;
		DeleteFile(MJPEGFilename);
		FreeIndexTable;

		PrevErrorMode := SetErrorMode(SEM_FAILCRITICALERRORS);
		try
			FreeMem(Buffer);
		finally
			Buffer := nil;
			SetErrorMode(PrevErrorMode);
		end;

//		ShowMessage('Could not write the JUNK header');
		exit;
	end;

	fillchar(Buffer^, j, 0);
	StrPCopy(Buffer, Strg);

	BlockWrite(mjpegfile, Buffer^, j, BytesWrote); ior := ioresult;
	if (ior <> 0) or (BytesWrote <> j) then
	begin
		CloseFile(mjpegfile); ior := ioresult;
		DeleteFile(MJPEGFilename);
		FreeIndexTable;
//		ShowMessage('Could not write the JUNK data');
		exit;
	end;

	LastJunkChunkAddr := FilePos(mjpegFile); ior := ioresult;
	if (ior <> 0) then
	begin
		CloseFile(mjpegfile); ior := ioresult;
		DeleteFile(MJPEGFilename);
		FreeIndexTable;
//		ShowMessage('FilePos error - writing JUNK chunk');
		exit;
	end;

	result := true;
end;

function CreateMJPEGfile(	Filename: string;
									Width,
									Height,
									MicroSecPerFrame_: dword;
									JunkStr: string;
									Quality: integer;
									ProgramID: string;
									ContentsDesc: string;
									Bitmap: TBitmap;
									AudioWaveFormat: pointer;
									AudioWaveFormatSize: integer): boolean;
// call this to start with
var	PrevErrorMode			: Word;
		err						: dword;
		ior, i					: integer;
		BytesWrote				: integer;
		Chunk						: TCHUNK;
		s							: string;
		BitmapInfoHeaderSize	: dword;
		BitmapInfoHeader		: ^TBitmapInfoHeader;
		ImageSize				: dword;
		Image						: pointer;
begin
	result := false;

	CloseMJPEGFile(false);	// just incase

	MJPEGFilename := Filename;
	StreamBytes := 0;
	TotalFrames := 0;
	Opened := false;
	MicroSecPerFrame := MicroSecPerFrame_;
	jpegStream := nil;
	JPG := nil;
	moviStart := 0;
	LastJUNKChunkAddr := 0;

	if (Bitmap <> nil) then
	begin
		if (Bitmap.Width <= 0) then exit;							// Doh !
		if (Bitmap.Height <= 0) then exit;
		if ((Bitmap.Width and 1) <> 0) then exit;					// what have I said ????
		if ((Bitmap.Height and 1) <> 0) then exit;				// ditto !
	end
	else
	begin
		if (Width <= 0) then exit;										// Doh !
		if (Height <= 0) then exit;
		if ((Width and 1) <> 0) then exit;							// uneven width
		if ((Height and 1) <> 0) then exit;							// uneven height
	end;

	if (not AllocateIndexTable(MaxIndexes)) then exit;			// get memory to hold the index table

	//****************
	// get memory to hold the image data

	PrevErrorMode := SetErrorMode(SEM_FAILCRITICALERRORS);	// ignore critical errors
	try
		if (Bitmap = nil) then
			GetMem(ImageBuffer, Width * Height * 3 * 2)
		else
			GetMem(ImageBuffer, Bitmap.Width * Bitmap.Height * 3 * 2);
	except
		err := GetLastError;
		s := GetLastErrorStr(err);
		ImageBuffer := nil;
	end;
	SetErrorMode(PrevErrorMode);										// go back to previous error mode

	if (ImageBuffer = nil) then
	begin
		CloseMJPEGFile(true);
		FreeIndexTable;
//		ShowMessage('Could not allocate mem for the image buffer: ' + s);
		exit;
	end;

	//****************

	FileMode := fmOpenReadWrite;
	AssignFile(mjpegfile, MJPEGFilename);
	ReWrite(mjpegfile, 1); ior := ioresult;
	if (ior <> 0) then
	begin
//		ShowMessage('The avi file could not be created');
		exit;
	end;

	// ********************
	// Build the header up

	fillchar(avihdr, sizeof(avihdr), 0);			// wipe it clean to start with

	// **

	avihdr.riff.fcc := 'RIFF';
	avihdr.riff.cb := 0;									// update this when finished .. the size of the entire file - 8

	// **

	avihdr.avi := 'AVI ';

	// **

	avihdr.list1.fcc := 'LIST';
	avihdr.list1.cb := 0;								// we'll update this in a bit

	avihdr.hdrl := 'hdrl';

	avihdr.avihhdr.fcc := 'avih';
	avihdr.avihhdr.cb := sizeof(avihdr.avih);
	avihdr.avih.dwMicroSecPerFrame := MicroSecPerFrame;
	avihdr.avih.dwMaxBytesPerSec := 0;				// update this when finished
	avihdr.avih.dwPaddingGranularity := 0;
	avihdr.avih.dwFlags := AVIF_WASCAPTUREFILE or AVIF_HASINDEX;
	avihdr.avih.dwTotalFrames := 0;					// update this when finished
	avihdr.avih.dwInitialFrames := 0;
	avihdr.avih.dwStreams := 1;						// 1 = video only, 2 = video and audio
	avihdr.avih.dwSuggestedBufferSize := 0;
	if (Bitmap = nil) then
	begin
		avihdr.avih.dwWidth := Width;
		avihdr.avih.dwHeight := Height;
	end
	else
	begin
		avihdr.avih.dwWidth := Bitmap.Width;
		avihdr.avih.dwHeight := Bitmap.Height;
	end;

	BlockWrite(mjpegfile, avihdr, sizeof(avihdr), BytesWrote); ior := ioresult;
	if (ior <> 0) or (BytesWrote <> sizeof(avihdr)) then
	begin
		CloseFile(mjpegfile); ior := ioresult;
		DeleteFile(MJPEGFilename);
//		ShowMessage('Could not write the avi header');
		exit;
	end;

	//*******************************
	// add the video format chunks

	vFormat.list.fcc := 'LIST';
	vFormat.list.cb := (sizeof(vFormat) - 8);

	vFormat.strl := 'strl';

	vFormat.strhhdr.fcc := 'strh';
	vFormat.strhhdr.cb := sizeof(vFormat.strh);
	vFormat.strh.fccType := 'vids';
	vFormat.strh.fccHandler := 'MJPG';
	vFormat.strh.dwFlags := 0;
	vFormat.strh.wPriority := 0;
	vFormat.strh.wLanguage := 0;
	vFormat.strh.dwInitialFrames := 0;
	vFormat.strh.dwScale := MicroSecPerFrame;
	vFormat.strh.dwRate := 1000000;
	vFormat.strh.dwStart := 0;
	vFormat.strh.dwLength := 0;								// update this when finished
	vFormat.strh.dwSuggestedBufferSize := 0;
	vFormat.strh.dwQuality := Quality * 100;				// 0 to 10000 .. can just leave this at '0' or '-1' if you wish
	vFormat.strh.dwSampleSize := 0;

	BitmapInfoHeaderSize := 0;
	BitmapInfoHeader := nil;
	ImageSize := 0;
	Image := nil;

	if (Bitmap = nil) then
	begin	// fill the BitmapInfoHeader in ourself - assume it's a 24-bit rgb bitmap
		BitmapInfoHeaderSize := sizeof(TBitmapInfoHeader);
		GetMem(BitmapInfoHeader, BitmapInfoHeaderSize);
		if (BitmapInfoHeader = nil) then
		begin
			CloseFile(mjpegfile); ior := ioresult;
			DeleteFile(MJPEGFilename);
//			ShowMessage('Could not write the avi header');
			exit;
		end;
		BitmapInfoHeader.biSize := sizeof(TBitmapInfoHeader);
		BitmapInfoHeader.biWidth := Width;
		BitmapInfoHeader.biHeight := Height;
		BitmapInfoHeader.biPlanes := 1;
		BitmapInfoHeader.biBitCount := 24;
		BitmapInfoHeader.biSizeImage := (Width * Height * 3 * BitmapInfoHeader.biPlanes);
		BitmapInfoHeader.biXPelsPerMeter := 0;
		BitmapInfoHeader.biYPelsPerMeter := 0;
		BitmapInfoHeader.biClrUsed := 0;
		BitmapInfoHeader.biClrImportant := 0;
	end
	else
	begin
		// create the TBitmapInfo
		GetDIBSizes(Bitmap.Handle, BitmapInfoHeaderSize, ImageSize);
		if (BitmapInfoHeaderSize = 0) or (ImageSize = 0) then
		begin
			CloseFile(mjpegfile); ior := ioresult;
			DeleteFile(MJPEGFilename);
//			ShowMessage('Could not write the avi header');
			exit;
		end;
		GetMem(BitmapInfoHeader, BitmapInfoHeaderSize);
		GetMem(Image, ImageSize);
		if (BitmapInfoHeader = nil) or (Image = nil) then
		begin
			CloseFile(mjpegfile); ior := ioresult;
			DeleteFile(MJPEGFilename);
			if (BitmapInfoHeader <> nil) then FreeMem(BitmapInfoHeader);
			if (Image <> nil) then FreeMem(Image);
//			ShowMessage('Could not write the avi header');
			exit;
		end;
		if (not GetDIB(Bitmap.Handle, Bitmap.Palette, BitmapInfoHeader^, Image^)) then
		begin
			if (BitmapInfoHeader <> nil) then FreeMem(BitmapInfoHeader);
			if (Image <> nil) then FreeMem(Image);
			CloseFile(mjpegfile); ior := ioresult;
			DeleteFile(MJPEGFilename);
//			MessageDlg('Error: Getting BitmapInfo', mtError, [mbOk], 0);
			exit;
		end;
	end;

	vFormat.list.cb := vFormat.list.cb + BitmapInfoHeaderSize;
	vFormat.strfhdr.fcc := 'strf';
	vFormat.strfhdr.cb := BitmapInfoHeaderSize;

	BlockWrite(mjpegfile, vFormat, sizeof(vFormat), BytesWrote); ior := ioresult;
	if (ior <> 0) or (BytesWrote <> sizeof(vFormat)) then
	begin
		CloseFile(mjpegfile); ior := ioresult;
		DeleteFile(MJPEGFilename);
		if (BitmapInfoHeader <> nil) then FreeMem(BitmapInfoHeader);
		if (Image <> nil) then FreeMem(Image);
//		ShowMessage('Could not write the avi header');
		exit;
	end;

	BitmapInfoHeader.biCompression := dword(str_to_fourcc('MJPG'));

	BlockWrite(mjpegfile, BitmapInfoHeader^, BitmapInfoHeaderSize, BytesWrote); ior := ioresult;
	if (ior <> 0) or (BytesWrote <> BitmapInfoHeaderSize) then
	begin
		CloseFile(mjpegfile); ior := ioresult;
		DeleteFile(MJPEGFilename);
		if (BitmapInfoHeader <> nil) then FreeMem(BitmapInfoHeader);
		if (Image <> nil) then FreeMem(Image);
//		ShowMessage('Could not write the avi header');
		exit;
	end;

	if (BitmapInfoHeader <> nil) then FreeMem(BitmapInfoHeader);
	if (Image <> nil) then FreeMem(Image);

	//*******************************
	// add the audio format chunks

	if (AudioWaveFormat <> nil) and (AudioWaveFormatSize > 0) then
	begin
		aFormat.list.fcc := 'LIST';
		aFormat.list.cb := sizeof(aFormat) - 8;

		aFormat.strl := 'strl';

		aFormat.strhhdr.fcc := 'strh';
		aFormat.strhhdr.cb := sizeof(aFormat.strh);
		aFormat.strh.fccType := 'auds';
		aFormat.strh.fccHandler := '    ';
		aFormat.strh.dwFlags := 0;
		aFormat.strh.wPriority := 0;
		aFormat.strh.wLanguage := 0;
		aFormat.strh.dwInitialFrames := 1;
		aFormat.strh.dwScale := 1152;								/// you'll have set this according
		aFormat.strh.dwRate := TWaveFormatEx(AudioWaveFormat^).nSamplesPerSec;
		aFormat.strh.dwStart := 0;
		aFormat.strh.dwLength := 0;								// update this when finished
		aFormat.strh.dwSuggestedBufferSize := 0;
		aFormat.strh.dwQuality := 0;
		aFormat.strh.dwSampleSize := 0;

//		AudioWaveFormat.wFormatTag := WAVE_FORMAT_MPEG3;		// mpeg3
//		AudioWaveFormat.nChannels := 2;								// stereo
//		AudioWaveFormat.nSamplesPerSec := 32000;
//		AudioWaveFormat.nAvgBytesPerSec := 11838;
//		AudioWaveFormat.nBlockAlign := 1152;
//		AudioWaveFormat.wBitsPerSample := 0;
//		AudioWaveFormat.cbSize := 0;									// no extra data

		aFormat.list.cb := aFormat.list.cb + AudioWaveFormatSize;
		aFormat.strfhdr.fcc := 'strf';
		aFormat.strfhdr.cb := AudioWaveFormatSize;

		BlockWrite(mjpegfile, aFormat, sizeof(aFormat), BytesWrote); ior := ioresult;
		if (ior <> 0) or (BytesWrote <> sizeof(aFormat)) then
		begin
			CloseFile(mjpegfile); ior := ioresult;
			DeleteFile(MJPEGFilename);
//			ShowMessage('Could not write the avi header');
			exit;
		end;

		BlockWrite(mjpegfile, AudioWaveFormat^, AudioWaveFormatSize, BytesWrote); ior := ioresult;
		if (ior <> 0) or (BytesWrote <> AudioWaveFormatSize) then
		begin
			CloseFile(mjpegfile); ior := ioresult;
			DeleteFile(MJPEGFilename);
//			ShowMessage('Could not write the avi header');
			exit;
		end;
	end;

	//****************
	// add some crap - this is not required btw

	// add the date and time
	if (not AddStringChunk('IDIT', FormatDateTime('ddd mmm dd hh:nn:ss yyyy', Now))) then exit;

	// add the program id
	if (length(ProgramID) > 0) then
		if (not AddStringChunk('ISFT', ProgramID)) then
			exit;

	// add a file contents description
	if (length(ContentsDesc) > 0) then
		if (not AddStringChunk('ISBJ', ContentsDesc)) then
			exit;

	// ...... their are a fair few more different chunks too you can add here
	
	//****************
	// update the header size

	EndOfHeaderAddr := FilePos(mjpegFile); ior := ioresult;
	if (ior <> 0) then
	begin
		CloseFile(mjpegfile); ior := ioresult;
		DeleteFile(MJPEGFilename);
//		ShowMessage('FilePos error');
		exit;
	end;

	avihdr.list1.cb := EndOFHeaderAddr - sizeof(avihdr.riff) - sizeof(avihdr.avi) - sizeof(avihdr.list1);

	//****************
	// add a junk section to the avi file
	// you can put anything at you like in the JUNK sections

	if (not AddJUNKChunk(JunkStr, 100)) then exit;

	// ***********************

	moviChunk.fcc := 'LIST';
	moviChunk.cb := 0;											// update this when finished

	BlockWrite(mjpegfile, moviChunk, sizeof(moviChunk), BytesWrote); ior := ioresult;
	if (ior <> 0) or (BytesWrote <> sizeof(moviChunk)) then
	begin
		CloseFile(mjpegfile); ior := ioresult;
		DeleteFile(MJPEGFilename);
//		ShowMessage('Could not write the movi header');
		exit;
	end;

	// ***********************
	// add the 'movi' string

	moviStart := FilePos(mjpegFile); ior := ioresult;	// remember where we are in the file
	if (ior <> 0) then
	begin
		CloseFile(mjpegfile); ior := ioresult;
		DeleteFile(MJPEGFilename);
//		ShowMessage('FilePos error');
		exit;
	end;

	Chunk.fcc := 'movi';

	BlockWrite(mjpegfile, Chunk.fcc, sizeof(Chunk.fcc), BytesWrote); ior := ioresult;
	if (ior <> 0) or (BytesWrote <> sizeof(Chunk.fcc)) then
	begin
		CloseFile(mjpegfile); ior := ioresult;
		DeleteFile(MJPEGFilename);
//		ShowMessage('Could not write the movi header');
		exit;
	end;

	// ***********************
	// create a memory stream - this is used to save the jpeg data into before inserting into the avi file

	PrevErrorMode := SetErrorMode(SEM_FAILCRITICALERRORS);	// ignore critical errors
	try
		jpegStream := TMemoryStream.Create;
	except
		err := GetLastError;
		s := GetLastErrorStr(err);
		jpegStream := nil;
	end;
	SetErrorMode(PrevErrorMode);										// go back to previous error mode

	if (jpegStream = nil) then
	begin
		CloseFile(mjpegfile); ior := ioresult;
		DeleteFile(MJPEGFilename);
//		ShowMessage('Could not create the jpeg memory stream');
		exit;
	end;

	// ***********************
	// create a JPEG item - used to compress the TBitmap into a jpeg

	PrevErrorMode := SetErrorMode(SEM_FAILCRITICALERRORS);	// ignore critical errors
	try
		JPG := TJPEGImage.Create;
		JPG.CompressionQuality := Quality;
		JPG.ProgressiveEncoding := false;
		JPG.Performance := jpBestSpeed;
//		JPG.Performance := jpBestQuality;
	except
		err := GetLastError;
		s := GetLastErrorStr(err);
	end;
	SetErrorMode(PrevErrorMode);										// go back to previous error mode

	if (JPG = nil) then
	begin
		CloseFile(mjpegfile); ior := ioresult;
		DeleteFile(MJPEGFilename);
//		ShowMessage('Could not create the jpeg');
		exit;
	end;

	// ***********************

	Opened := true;
	result := true;
end;

function AddAudio(AudioData: pointer; AudioDataSize: dword): boolean;
// Add a block of audio data
var	PrevErrorMode	: Word;
		err				: dword;
		ior				: integer;
		BytesWrote		: integer;
		BytesRead		: integer;
		aBytes			: integer;
		i, j				: integer;
		Chunk				: TChunk;
begin
	result := false;

	if (not Opened) then exit;
	if (AudioData = nil) then exit;
	if (AudioDataSize = 0) then exit;
		
	//******************************
	// setup the audio chunk header

	Chunk.fcc := '01wb';
	Chunk.cb := AudioDataSize;

	//******************************
	// update our index

	i := FilePos(mjpegfile); ior := ioresult;
	if (ior <> 0) then
	begin
		CloseMJPEGFile(true);
		DeleteFile(MJPEGFilename);
		FreeIndexTable;
//		ShowMessage('FilePos error');
		exit;
	end;

	AddToIndexTable(i, Chunk.cb, Chunk.fcc);					// update the index table

	//******************************

	aBytes := 0;

	//******************************
	// write the audio chunk header

	BlockWrite(mjpegfile, Chunk, sizeof(Chunk), BytesWrote); ior := ioresult;
	if (ior <> 0) or (BytesWrote <> sizeof(Chunk)) then
	begin
		CloseMJPEGFile(true);
		DeleteFile(MJPEGFilename);
		FreeIndexTable;
//		ShowMessage('error writing the jpeg chunk header');
		exit;
	end;
	inc(aBytes, BytesWrote);

	//******************************
	// save the audio data into the AVI file

	BlockWrite(mjpegfile, AudioData^, Chunk.cb, BytesWrote); ior := ioresult;
	if (ior <> 0) or (BytesWrote <> Chunk.cb) then
	begin
		CloseMJPEGFile(true);
		DeleteFile(MJPEGFilename);
		FreeIndexTable;
//		ShowMessage('error writing the jpeg header');
		exit;
	end;
	inc(aBytes, BytesWrote);

	i := aBytes and $00000001;
	if (i > 0) then
	begin	// next chunk must be on 2-byte boundry .. pad it with zeros
		j := 0;
		BlockWrite(mjpegfile, j, i, BytesWrote); ior := ioresult;
		if (ior <> 0) or (BytesWrote <> i) then
		begin
			CloseMJPEGFile(true);
			DeleteFile(MJPEGFilename);
			FreeIndexTable;
//			ShowMessage('error writing 2-byte boundry padding');
			exit;
		end;
		inc(aBytes, BytesWrote);
	end;

	inc(StreamBytes, aBytes);
	inc(TotalFrames);

	//*************
	
	i := FilePos(mjpegfile); ior := ioresult;
	if (ior <> 0) then
	begin
		CloseMJPEGFile(true);
		DeleteFile(MJPEGFilename);
		FreeIndexTable;
//		ShowMessage('FilePos error');
		exit;
	end;

	//*************
	// add a JUNK chunk is need be

	if ((i - LastJUNKChunkAddr) > 20000) then
		if (not AddJUNKChunk('', 1)) then
			exit;

	//*************
	// check to see if we are near the file size limit

	if (i >= 2000000000) then
	begin	// yes we are ! ... either start a new file or use the extended format
		Windows.Beep(1500, 5);		
	end;

	//*************
	
	result := true;
end;

function AddBitmap(Bitmap: TBitmap): boolean;
// jpeg compress a TBitmap then add it to the avi file
var	PrevErrorMode	: Word;
		err				: dword;
		jpegHeader		: TjpegHeader;
		ior				: integer;
		BytesWrote		: integer;
		BytesRead		: integer;
		jBytes			: integer;
		i, j				: integer;
		Chunk				: TChunk;
begin
	result := false;

	if (not Opened) then exit;
	if (Bitmap = nil) then exit;
	if (JPG = nil) then exit;
	if (jpegStream = nil) then exit;

	//******************************
	// compress the bitmap to a jpeg

	jpegStream.Position := 0;
	jpegStream.SetSize(0);

	JPG.Assign(Bitmap);
	JPG.Compress;
	JPG.SaveToStream(jpegStream);

	//******************************
	// setup the jpeg chunk header

	Chunk.fcc := '00db';
	Chunk.cb := jpegStream.Position;		// size of the jpeg image data

	//******************************
	// update our index

	i := FilePos(mjpegfile); ior := ioresult;
	if (ior <> 0) then
	begin
		CloseMJPEGFile(true);
		DeleteFile(MJPEGFilename);
		FreeIndexTable;
//		ShowMessage('FilePos error');
		exit;
	end;

	AddToIndexTable(i, Chunk.cb, Chunk.fcc);					// update the index table

	//******************************

	jBytes := 0;

	//******************************
	// write the jpeg chunk header

	BlockWrite(mjpegfile, Chunk, sizeof(Chunk), BytesWrote); ior := ioresult;
	if (ior <> 0) or (BytesWrote <> sizeof(Chunk)) then
	begin
		CloseMJPEGFile(true);
		DeleteFile(MJPEGFilename);
		FreeIndexTable;
//		ShowMessage('error writing the jpeg chunk header');
		exit;
	end;
	inc(jBytes, BytesWrote);

	//******************************
	// save the jpeg image data into the AVI file

//	TjpegHeader(jpegStream.Memory^).id := 'AVI1';	// change the type

	BlockWrite(mjpegfile, jpegStream.Memory^, Chunk.cb, BytesWrote); ior := ioresult;
	if (ior <> 0) or (BytesWrote <> Chunk.cb) then
	begin
		CloseMJPEGFile(true);
		DeleteFile(MJPEGFilename);
		FreeIndexTable;
//		ShowMessage('error writing the jpeg header');
		exit;
	end;
	inc(jBytes, BytesWrote);

	i := jBytes and $00000001;
	if (i > 0) then
	begin	// next chunk must be on 2-byte boundry .. pad it with zeros
		j := 0;
		BlockWrite(mjpegfile, j, i, BytesWrote); ior := ioresult;
		if (ior <> 0) or (BytesWrote <> i) then
		begin
			CloseMJPEGFile(true);
			DeleteFile(MJPEGFilename);
			FreeIndexTable;
//			ShowMessage('error writing 2-byte boundry padding');
			exit;
		end;
		inc(jBytes, BytesWrote);
	end;

	inc(StreamBytes, jBytes);
	inc(TotalFrames);

	//*************
	
	i := FilePos(mjpegfile); ior := ioresult;
	if (ior <> 0) then
	begin
		CloseMJPEGFile(true);
		DeleteFile(MJPEGFilename);
		FreeIndexTable;
//		ShowMessage('FilePos error');
		exit;
	end;

	//*************
	// add a JUNK chunk is need be

	if ((i - LastJUNKChunkAddr) > 20000) then
		if (not AddJUNKChunk('', 1)) then
			exit;

	//*************
	// check to see if we are near the file size limit

	if (i >= 2000000000) then
	begin	// yes we are ! ... either start a new file or use the extended format
		Windows.Beep(1500, 5);		
	end;

	//*************
	
	result := true;
end;

function AddJPEGFile(Filename: string): boolean;
// add a jpeg file into the avi
var	PrevErrorMode	: Word;
		err				: dword;
		jpegHeader		: TjpegHeader;
		ior				: integer;
		BytesWrote		: integer;
		BytesRead		: integer;
		jBytes			: integer;
		i, j				: integer;
		Chunk				: TChunk;
		jpegFile			: file;
begin
	result := false;

	if (not Opened) then exit;
	if (ImageBuffer = nil) then exit;

	Filename := trim(Filename);
	if (not FileExists(Filename)) then exit;

	//*********************
	// open the jpeg file

	FileMode := fmOpenRead;
	AssignFile(jpegfile, Filename);
	Reset(jpegfile, 1); ior := ioresult;
	if (ior <> 0) then
	begin
//		ShowMessage('Could not open the jpeg file');
		exit;
	end;

	//******************************
	// get the size of the jpeg file

	i := FileSize(jpegfile); ior := ioresult;
	if (ior <> 0) then
	begin
		CloseFile(jpegfile); ior := ioresult;
//		ShowMessage('FileSize error');
		exit;
	end;

	//******************************
	// setup the jpeg chunk header

	Chunk.fcc := '00db';
	Chunk.cb := i;								// size of the jpeg file

	if (Chunk.cb = 0) then
	begin
		CloseFile(jpegfile); ior := ioresult;
		result := true;
		exit;
	end;

	//******************************
	// update our index table

	i := FilePos(mjpegfile); ior := ioresult;
	if (ior <> 0) then
	begin
		CloseMJPEGFile(true);
		DeleteFile(MJPEGFilename);
		FreeIndexTable;
//		ShowMessage('FilePos error');
		exit;
	end;

	if (not AddToIndexTable(i, Chunk.cb, Chunk.fcc)) then exit;

	//******************************

	jBytes := 0;

	//******************************
	// write the jpeg chunk header

	BlockWrite(mjpegfile, Chunk, sizeof(Chunk), BytesWrote); ior := ioresult;
	if (ior <> 0) or (BytesWrote <> sizeof(Chunk)) then
	begin
		CloseMJPEGFile(true);
		DeleteFile(MJPEGFilename);
		FreeIndexTable;
//		ShowMessage('error writing the jpeg chunk header');
		exit;
	end;
	inc(jBytes, BytesWrote);

	//******************************

	// read the jpeg file into the buffer
	BlockRead(jpegFile, ImageBuffer^, Chunk.cb, BytesRead); ior := ioresult;
	if (ior <> 0) or (BytesRead <> Chunk.cb) then
	begin
		CloseMJPEGFile(true);
		DeleteFile(MJPEGFilename);
		FreeIndexTable;
//		ShowMessage('error reading the jpeg file header');
		exit;
	end;

//	TjpegHeader(ImageBuffer^).id := 'AVI1';	// change the type

	// add the jpeg file into the AVI file
	BlockWrite(mjpegfile, ImageBuffer^, Chunk.cb, BytesWrote); ior := ioresult;
	if (ior <> 0) or (BytesWrote <> Chunk.cb) then
	begin
		CloseMJPEGFile(true);
		DeleteFile(MJPEGFilename);
		FreeIndexTable;
//		ShowMessage('error writing the jpeg header');
		exit;
	end;
	inc(jBytes, BytesWrote);

	CloseFile(jpegfile); ior := ioresult;

	i := jBytes and $00000001;
	if (i > 0) then
	begin	// next chunk must be on 2-byte boundry .. pad it with zeros
		j := 0;
		BlockWrite(mjpegfile, j, i, BytesWrote); ior := ioresult;
		if (ior <> 0) or (BytesWrote <> i) then
		begin
			CloseMJPEGFile(true);
			DeleteFile(MJPEGFilename);
			FreeIndexTable;
//			ShowMessage('error writing 2-byte boundry padding');
			exit;
		end;
		inc(jBytes, BytesWrote);
	end;

	inc(StreamBytes, jBytes);
	inc(TotalFrames);

	//*************

	i := FilePos(mjpegfile); ior := ioresult;
	if (ior <> 0) then
	begin
		CloseMJPEGFile(true);
		DeleteFile(MJPEGFilename);
		FreeIndexTable;
//		ShowMessage('FilePos error');
		exit;
	end;

	//*************
	// add a JUNK chunk is need be

	if ((i - LastJUNKChunkAddr) > 20000) then
		if (not AddJUNKChunk('', 1)) then
			exit;

	//*************
	// check to see if we are near the file size limit

	if (i >= 2000000000) then
	begin	// yes we are ! ... either start a new file or use the extended format
		Windows.Beep(1500, 5);		
	end;

	//*************
	
	result := true;
end;

function CloseMJPEGFile(DeleteIt: boolean): boolean;
// call this when your done
var	PrevErrorMode	: Word;
		err				: dword;
		ior, i			: integer;
		BytesRead		: integer;
		BytesWrote		: integer;
		Chunk				: TChunk;
		Index				: TIDX1;
		IndexStart		: dword;
begin
	result := false;

	//*******************

	if (ImageBuffer <> nil) then
	begin
		PrevErrorMode := SetErrorMode(SEM_FAILCRITICALERRORS);	// ignore critical errors
		try
			FreeMem(ImageBuffer);
		finally
			ImageBuffer := nil;
			SetErrorMode(PrevErrorMode);									// go back to previous error mode
		end;
	end;

	if (jpegStream <> nil) then
	begin
		PrevErrorMode := SetErrorMode(SEM_FAILCRITICALERRORS);
		try
			jpegStream.Free;
		finally
			jpegStream := nil;
			SetErrorMode(PrevErrorMode);								
		end;
	end;

	if (JPG <> nil) then
	begin
		PrevErrorMode := SetErrorMode(SEM_FAILCRITICALERRORS);
		try
			JPG.Free;
		finally
			JPG := nil;
			SetErrorMode(PrevErrorMode);
		end;
	end;

	//*******************

	if (not Opened) then
	begin
		FreeIndexTable;
		exit;
	end;

	//*******************

	if (DeleteIt) then
	begin
		CloseFile(mjpegfile); ior := ioresult;
		DeleteFile(MJPEGFilename);
		FreeIndexTable;
		Opened := false;
		exit;
	end;

	//*******************
	// add the index

	IndexStart := FilePos(mjpegFile); ior := ioresult;	// remember the current file position
	if (ior <> 0) then
	begin
		DeleteFile(MJPEGFilename);
		FreeIndexTable;
		Opened := false;
//		ShowMessage('FilePos error');
		exit;
	end;

	if (Indexes > 0) and (IndexTable <> nil) then
	begin
		Chunk.fcc := 'idx1';
		Chunk.cb := sizeof(TIDX1) * Indexes;

		BlockWrite(mjpegfile, Chunk, sizeof(Chunk), BytesWrote); ior := ioresult;
		if (ior <> 0) or (BytesWrote <> sizeof(Chunk)) then
		begin
			CloseFile(mjpegfile); ior := ioresult;
			DeleteFile(MJPEGFilename);
			FreeIndexTable;
			Opened := false;
//			ShowMessage('error writing the index table header');
			exit;
		end;

		i := 0;
		while (i < Indexes) do
		begin
			Index.ckid := IndexTable[i].fcc;
			Index.dwFlags := AVIIF_KEYFRAME;
			Index.dwChunkOffset := IndexTable[i].Offset;
			Index.dwChunkLength := IndexTable[i].Length;

			BlockWrite(mjpegfile, Index, sizeof(Index), BytesWrote); ior := ioresult;
			if (ior <> 0) or (BytesWrote <> sizeof(Index)) then
			begin
				CloseFile(mjpegfile); ior := ioresult;
				DeleteFile(MJPEGFilename);
				FreeIndexTable;
				Opened := false;
//				ShowMessage('error writing an index table entry');
				exit;
			end;

			inc(i);
		end;
	end;

	//*******************
	// update the header

	if (TotalFrames > 0) and (MicroSecPerFrame > 0) then
	begin
		Seek(mjpegfile, 0); ior := ioresult;
		if (ior <> 0) then
		begin
			CloseFile(mjpegfile); ior := ioresult;
			DeleteFile(MJPEGFilename);
			FreeIndexTable;
			Opened := false;
//			ShowMessage('seek error');
			exit;
		end;

		i := FileSize(mjpegfile); ior := ioresult;			// get the entire file size
		if (ior <> 0) then
		begin
			CloseFile(mjpegfile); ior := ioresult;
			DeleteFile(MJPEGFilename);
			FreeIndexTable;
			Opened := false;
//			ShowMessage('FileSize error');
			exit;
		end;

		avihdr.riff.cb := i - sizeof(avihdr.riff); ior := ioresult;

		avihdr.avih.dwMaxBytesPerSec := round(((StreamBytes / TotalFrames) * vFormat.strh.dwRate) / MicroSecPerFrame);
		avihdr.avih.dwTotalFrames := TotalFrames;

		BlockWrite(mjpegfile, avihdr, sizeof(avihdr), BytesWrote); ior := ioresult;
		if (ior <> 0) or (BytesWrote <> sizeof(avihdr)) then
		begin
			CloseFile(mjpegfile); ior := ioresult;
			DeleteFile(MJPEGFilename);
			FreeIndexTable;
			Opened := false;
//			ShowMessage('error updating the main header');
			exit;
		end;

		// **************

		Seek(mjpegfile, sizeof(avihdr)); ior := ioresult;
		if (ior <> 0) then
		begin
			CloseFile(mjpegfile); ior := ioresult;
			DeleteFile(MJPEGFilename);
			FreeIndexTable;
			Opened := false;
//			ShowMessage('seek error');
			exit;
		end;

		vFormat.strh.dwLength := TotalFrames;

		BlockWrite(mjpegfile, vFormat, sizeof(vFormat), BytesWrote); ior := ioresult;
		if (ior <> 0) or (BytesWrote <> sizeof(vFormat)) then
		begin
			CloseFile(mjpegfile); ior := ioresult;
			DeleteFile(MJPEGFilename);
			FreeIndexTable;
			Opened := false;
//			ShowMessage('error updating the main header');
			exit;
		end;

		// **************

	end;

	//*******************
	// update the movi section

	moviChunk.cb := IndexStart - moviStart;

	Seek(mjpegfile,  moviStart - sizeof(moviChunk)); ior := ioresult;
	if (ior <> 0) then
	begin
		CloseFile(mjpegfile); ior := ioresult;
		DeleteFile(MJPEGFilename);
		FreeIndexTable;
		Opened := false;
//			ShowMessage('Seek error');
		exit;
	end;

	BlockWrite(mjpegfile, moviChunk, sizeof(moviChunk), BytesWrote); ior := ioresult;
	if (ior <> 0) or (BytesWrote <> sizeof(moviChunk)) then
	begin
		CloseFile(mjpegfile); ior := ioresult;
		DeleteFile(MJPEGFilename);
		FreeIndexTable;
		Opened := false;
//		ShowMessage('error updating the movi list');
		exit;
	end;

	//*******************

	CloseFile(mjpegfile); ior := ioresult;

	FreeIndexTable;
	
	Opened := false;
	result := true;
end;

//******************************

end.

