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

    aviio.c

    AVI movie format parsing helpers.

    Copyright Nicola Salmoria and the MAME Team.
    Visit http://mamedev.org for licensing and usage restrictions.

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

#include "aviio.h"


/***************************************************************************
    CONSTANTS
***************************************************************************/

#define FILETYPE_READ			1
#define FILETYPE_CREATE			2

#define MAX_RIFF_SIZE			(2UL * 1024 * 1024 * 1024 - 1024)	/* just under 2GB */
#define MAX_AVI_SIZE_IN_GB		(256)
#define FOUR_GB					((UINT64)1 << 32)

#define MAX_SOUND_CHANNELS		2
#define SOUND_BUFFER_MSEC		2000		/* microseconds of sond buffering */

#define CHUNKTYPE_RIFF			AVI_FOURCC('R','I','F','F')
#define CHUNKTYPE_LIST			AVI_FOURCC('L','I','S','T')
#define CHUNKTYPE_JUNK			AVI_FOURCC('J','U','N','K')
#define CHUNKTYPE_AVIH			AVI_FOURCC('a','v','i','h')
#define CHUNKTYPE_STRH			AVI_FOURCC('s','t','r','h')
#define CHUNKTYPE_STRF			AVI_FOURCC('s','t','r','f')
#define CHUNKTYPE_IDX1			AVI_FOURCC('i','d','x','1')
#define CHUNKTYPE_INDX			AVI_FOURCC('i','n','d','x')
#define CHUNKTYPE_XXDB			AVI_FOURCC(0x00,0x00,'d','b')
#define CHUNKTYPE_XXDC			AVI_FOURCC(0x00,0x00,'d','c')
#define CHUNKTYPE_XXWB			AVI_FOURCC(0x00,0x00,'w','b')
#define CHUNKTYPE_IXXX			AVI_FOURCC('i','x',0x00,0x00)
#define CHUNKTYPE_XX_MASK		AVI_FOURCC(0x00,0x00,0xff,0xff)

#define LISTTYPE_AVI			AVI_FOURCC('A','V','I',' ')
#define LISTTYPE_AVIX			AVI_FOURCC('A','V','I','X')
#define LISTTYPE_HDRL			AVI_FOURCC('h','d','r','l')
#define LISTTYPE_STRL			AVI_FOURCC('s','t','r','l')
#define LISTTYPE_MOVI			AVI_FOURCC('m','o','v','i')

#define STREAMTYPE_VIDS			AVI_FOURCC('v','i','d','s')
#define STREAMTYPE_AUDS			AVI_FOURCC('a','u','d','s')

#define HANDLER_DIB				AVI_FOURCC('D','I','B',' ')
#define HANDLER_HFYU			AVI_FOURCC('h','f','y','u')

/* main AVI header files */
#define AVIF_HASINDEX			0x00000010
#define AVIF_MUSTUSEINDEX		0x00000020
#define AVIF_ISINTERLEAVED		0x00000100
#define AVIF_COPYRIGHTED		0x00010000
#define AVIF_WASCAPTUREFILE		0x00020000

/* index definitions */
#define AVI_INDEX_OF_INDEXES	0x00
#define AVI_INDEX_OF_CHUNKS		0x01
#define AVI_INDEX_IS_DATA		0x80
#define AVI_INDEX_2FIELD		0x01

/* HuffYUV definitions */
#define HUFFYUV_PREDICT_LEFT	 0
#define HUFFYUV_PREDICT_GRADIENT 1
#define HUFFYUV_PREDICT_MEDIAN	 2
#define HUFFYUV_PREDICT_DECORR	 0x40



/***************************************************************************
    TYPE DEFINITIONS
***************************************************************************/

typedef struct _avi_chunk avi_chunk;
struct _avi_chunk
{
	UINT64				offset;					/* file offset of chunk header */
	UINT64				size;					/* size of this chunk */
	UINT32				type;					/* type of this chunk */
	UINT32				listtype;				/* type of this list (if we are a list) */
};


typedef struct _avi_chunk_list avi_chunk_list;
struct _avi_chunk_list
{
	UINT64				offset;					/* offset in the file of header */
	UINT32				length;					/* length of the chunk including header */
};


typedef struct _huffyuv_table huffyuv_table;
struct _huffyuv_table
{
	UINT8				shift[256];				/* bit shift amounts */
	UINT32				bits[256];				/* bit match values */
	UINT32				mask[256];				/* bit mask values */
	UINT16				baselookup[65536];		/* base lookup table */
	UINT16 *			extralookup;			/* extra lookup tables */
};


typedef struct _huffyuv_data huffyuv_data;
struct _huffyuv_data
{
	UINT8				predictor;				/* predictor */
	huffyuv_table		table[3];				/* array of tables */
};


typedef struct _avi_stream avi_stream;
struct _avi_stream
{
	UINT32				type;					/* subtype of stream */
	UINT32				format;					/* format of stream data */

	UINT32				rate;					/* timescale for stream */
	UINT32				scale;					/* duration of one sample in the stream */
	UINT32				samples;				/* number of samples */

	avi_chunk_list *	chunk;					/* list of chunks */
	UINT32				chunks;					/* chunks currently known */
	UINT32				chunksalloc;			/* number of chunks allocated */

	UINT32				width;					/* width of video */
	UINT32				height;					/* height of video */
	UINT32				depth;					/* depth of video */
	UINT8				interlace;				/* interlace parameters */
	huffyuv_data *		huffyuv;				/* huffyuv decompression data */

	UINT16				channels;				/* audio channels */
	UINT16				samplebits;				/* audio bits per sample */
	UINT32				samplerate;				/* audio sample rate */

	/* only used when creating */
	UINT64				saved_strh_offset;		/* writeoffset of strh chunk */
	UINT64				saved_indx_offset;		/* writeoffset of indx chunk */
};


struct _avi_file
{
	/* shared data */
	osd_file *		 	file;					/* pointer to open file */
	int					type;					/* type of access (read/create) */
	avi_movie_info		info;					/* movie info structure */
	UINT8 *				tempbuffer;				/* temporary buffer */
	UINT32				tempbuffersize;			/* size of the temporary buffer */

	/* only used when reading */
	int					streams;				/* number of streams */
	avi_stream *		stream;					/* allocated array of stream information */
	avi_chunk			rootchunk;				/* dummy root chunk that wraps the whole file */

	/* only used when creating */
	UINT64				writeoffs;				/* current file write offset */
	UINT64				riffbase;				/* base of the current RIFF */

	avi_chunk			chunkstack[8];			/* stack of chunks we are writing */
	int					chunksp;				/* stack pointer for the current chunk */

	UINT64				saved_movi_offset;		/* writeoffset of movi list */
	UINT64				saved_avih_offset;		/* writeoffset of avih chunk */

	INT16 *				soundbuf;				/* buffer for sound data */
	UINT32				soundbuf_samples;		/* length of sound buffer in samples */
	UINT32				soundbuf_chansamples[MAX_SOUND_CHANNELS]; /* samples in buffer for each channel */
	UINT32				soundbuf_chunks;		/* number of chunks completed so far */
	UINT32				soundbuf_frames;		/* number of frames ahead of the video */
};



/***************************************************************************
    PROTOTYPES
***************************************************************************/

/* stream helpers */

/* core chunk read routines */
static avi_error get_first_chunk(avi_file *file, const avi_chunk *parent, avi_chunk *newchunk);
static avi_error get_next_chunk(avi_file *file, const avi_chunk *parent, avi_chunk *newchunk);
static avi_error find_first_chunk(avi_file *file, UINT32 findme, const avi_chunk *container, avi_chunk *result);
static avi_error find_next_chunk(avi_file *file, UINT32 findme, const avi_chunk *container, avi_chunk *result);
static avi_error get_next_chunk_internal(avi_file *file, const avi_chunk *parent, avi_chunk *newchunk, UINT64 offset);
static avi_error read_chunk_data(avi_file *file, const avi_chunk *chunk, UINT8 **buffer);

/* chunk read helpers */
static avi_error read_movie_data(avi_file *file);
static avi_error extract_movie_info(avi_file *file);
static avi_error parse_avih_chunk(avi_file *file, avi_chunk *avih);
static avi_error parse_strh_chunk(avi_file *file, avi_stream *stream, avi_chunk *strh);
static avi_error parse_strf_chunk(avi_file *file, avi_stream *stream, avi_chunk *strf);
static avi_error parse_indx_chunk(avi_file *file, avi_stream *stream, avi_chunk *strf);
static avi_error parse_idx1_chunk(avi_file *file, UINT64 baseoffset, avi_chunk *idx1);

/* core chunk write routines */
static avi_error chunk_open(avi_file *file, UINT32 type, UINT32 listtype, UINT32 estlength);
static avi_error chunk_close(avi_file *file);
static avi_error chunk_write(avi_file *file, UINT32 type, const void *data, UINT32 length);
static avi_error chunk_overwrite(avi_file *file, UINT32 type, const void *data, UINT32 length, UINT64 *offset, int initial_write);

/* chunk write helpers */
static avi_error write_initial_headers(avi_file *file);
static avi_error write_avih_chunk(avi_file *file, int initial_write);
static avi_error write_strh_chunk(avi_file *file, avi_stream *stream, int initial_write);
static avi_error write_strf_chunk(avi_file *file, avi_stream *stream);
static avi_error write_indx_chunk(avi_file *file, avi_stream *stream, int initial_write);
static avi_error write_idx1_chunk(avi_file *file);

/* sound buffering helpers */
static avi_error soundbuf_initialize(avi_file *file);
static avi_error soundbuf_write_chunk(avi_file *file, UINT32 framenum);
static avi_error soundbuf_flush(avi_file *file, int only_flush_full);

/* RGB helpers */
static avi_error rgb32_compress_to_rgb(avi_stream *stream, const bitmap_t *bitmap, UINT8 *data, UINT32 numbytes);

/* YUY helpers */
static avi_error yuv_decompress_to_yuy16(avi_stream *stream, const UINT8 *data, UINT32 numbytes, bitmap_t *bitmap);
static avi_error yuy16_compress_to_yuy(avi_stream *stream, const bitmap_t *bitmap, UINT8 *data, UINT32 numbytes);

/* HuffYUV helpers */
static avi_error huffyuv_extract_tables(avi_stream *stream, const UINT8 *chunkdata, UINT32 size);
static avi_error huffyuv_decompress_to_yuy16(avi_stream *stream, const UINT8 *data, UINT32 numbytes, bitmap_t *bitmap);

/* debugging */
static void printf_chunk_recursive(avi_file *file, avi_chunk *chunk, int indent);



/***************************************************************************
    INLINE FUNCTIONS
***************************************************************************/

/*-------------------------------------------------
    fetch_16bits - read 16 bits in LSB order
    from the given buffer
-------------------------------------------------*/

INLINE UINT16 fetch_16bits(const UINT8 *data)
{
	return data[0] | (data[1] << 8);
}


/*-------------------------------------------------
    fetch_32bits - read 32 bits in LSB order
    from the given buffer
-------------------------------------------------*/

INLINE UINT32 fetch_32bits(const UINT8 *data)
{
	return data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24);
}


/*-------------------------------------------------
    fetch_64bits - read 64 bits in LSB order
    from the given buffer
-------------------------------------------------*/

INLINE UINT64 fetch_64bits(const UINT8 *data)
{
	return (UINT64)data[0] | ((UINT64)data[1] << 8) |
		   ((UINT64)data[2] << 16) | ((UINT64)data[3] << 24) |
		   ((UINT64)data[4] << 32) | ((UINT64)data[5] << 40) |
		   ((UINT64)data[6] << 48) | ((UINT64)data[7] << 56);
}


/*-------------------------------------------------
    put_16bits - write 16 bits in LSB order
    to the given buffer
-------------------------------------------------*/

INLINE void put_16bits(UINT8 *data, UINT16 value)
{
	data[0] = value >> 0;
	data[1] = value >> 8;
}


/*-------------------------------------------------
    put_32bits - write 32 bits in LSB order
    to the given buffer
-------------------------------------------------*/

INLINE void put_32bits(UINT8 *data, UINT32 value)
{
	data[0] = value >> 0;
	data[1] = value >> 8;
	data[2] = value >> 16;
	data[3] = value >> 24;
}


/*-------------------------------------------------
    put_64bits - write 64 bits in LSB order
    to the given buffer
-------------------------------------------------*/

INLINE void put_64bits(UINT8 *data, UINT64 value)
{
	data[0] = value >> 0;
	data[1] = value >> 8;
	data[2] = value >> 16;
	data[3] = value >> 24;
	data[4] = value >> 32;
	data[5] = value >> 40;
	data[6] = value >> 48;
	data[7] = value >> 56;
}


/*-------------------------------------------------
    get_video_stream - return a pointer to the
    video stream
-------------------------------------------------*/

INLINE avi_stream *get_video_stream(avi_file *file)
{
	int streamnum;

	/* find the video stream */
	for (streamnum = 0; streamnum < file->streams; streamnum++)
		if (file->stream[streamnum].type == STREAMTYPE_VIDS)
			return &file->stream[streamnum];

	return NULL;
}


/*-------------------------------------------------
    get_audio_stream - return a pointer to the
    audio stream for the 'n'th channel
-------------------------------------------------*/

INLINE avi_stream *get_audio_stream(avi_file *file, int channel, int *offset)
{
	int streamnum;

	/* find the audios stream */
	for (streamnum = 0; streamnum < file->streams; streamnum++)
		if (file->stream[streamnum].type == STREAMTYPE_AUDS)
		{
			if (channel < file->stream[streamnum].channels)
			{
				if (offset != NULL)
					*offset = channel;
				return &file->stream[streamnum];
			}
			channel -= file->stream[streamnum].channels;
		}

	return NULL;
}


/*-------------------------------------------------
    set_stream_chunk_info - set the chunk info
    for a given chunk within a stream
-------------------------------------------------*/

INLINE avi_error set_stream_chunk_info(avi_stream *stream, UINT32 index, UINT64 offset, UINT32 length)
{
	/* if we need to allocate more, allocate more */
	if (index >= stream->chunksalloc)
	{
		UINT32 newcount = MAX(index, stream->chunksalloc + 1000);
		stream->chunk = (avi_chunk_list *)realloc(stream->chunk, newcount * sizeof(stream->chunk[0]));
		if (stream->chunk == NULL)
			return AVIERR_NO_MEMORY;
		stream->chunksalloc = newcount;
	}

	/* set the data */
	stream->chunk[index].offset = offset;
	stream->chunk[index].length = length;

	/* update the number of chunks */
	stream->chunks = MAX(stream->chunks, index + 1);
	return AVIERR_NONE;
}


/*-------------------------------------------------
    compute_idx1_size - compute the size of the
    idx1 chunk
-------------------------------------------------*/

INLINE UINT32 compute_idx1_size(avi_file *file)
{
	int chunks = 0;
	int strnum;

	/* count chunks in streams */
	for (strnum = 0; strnum < file->streams; strnum++)
		chunks += file->stream[strnum].chunks;

	return chunks * 16 + 8;
}


/*-------------------------------------------------
    get_chunkid_for_stream - make a chunk id for
    a given stream
-------------------------------------------------*/

INLINE UINT32 get_chunkid_for_stream(avi_file *file, avi_stream *stream)
{
	UINT32 chunkid;

	chunkid = AVI_FOURCC('0' + (stream - file->stream) / 10, '0' +  (stream - file->stream) % 10, 0, 0);
	if (stream->type == STREAMTYPE_VIDS)
		chunkid |= (stream->format == 0) ? CHUNKTYPE_XXDB : CHUNKTYPE_XXDC;
	else if (stream->type == STREAMTYPE_AUDS)
		chunkid |= CHUNKTYPE_XXWB;

	return chunkid;
}


/*-------------------------------------------------
    framenum_to_samplenum - given a video frame
    number, get the first sample number
-------------------------------------------------*/

INLINE UINT32 framenum_to_samplenum(avi_file *file, UINT32 framenum)
{
	return ((UINT64)file->info.audio_samplerate * (UINT64)framenum * (UINT64)file->info.video_sampletime + file->info.video_timescale - 1) / (UINT64)file->info.video_timescale;
}


/*-------------------------------------------------
    expand_tempbuffer - expand the file's
    tempbuffer if necessary to contain the
    requested amount of data
-------------------------------------------------*/

INLINE avi_error expand_tempbuffer(avi_file *file, UINT32 length)
{
	/* expand the tempbuffer to hold the data if necessary */
	if (length > file->tempbuffersize)
	{
		file->tempbuffersize = 2 * length;
		file->tempbuffer = (UINT8 *)realloc(file->tempbuffer, file->tempbuffersize);
		if (file->tempbuffer == NULL)
			return AVIERR_NO_MEMORY;
	}
	return AVIERR_NONE;
}



/***************************************************************************
    IMPLEMENTATION
***************************************************************************/

/*-------------------------------------------------
    avi_open - open an AVI movie file for read
-------------------------------------------------*/

avi_error avi_open(const char *filename, avi_file **file)
{
	avi_file *newfile = NULL;
	file_error filerr;
	avi_error avierr;
	UINT64 length;

	/* allocate the file */
	newfile = (avi_file *)malloc(sizeof(*newfile));
	if (newfile == NULL)
		return AVIERR_NO_MEMORY;
	memset(newfile, 0, sizeof(*newfile));
	newfile->type = FILETYPE_READ;

	/* open the file */
	filerr = osd_open(filename, OPEN_FLAG_READ, &newfile->file, &length);
	if (filerr != FILERR_NONE)
	{
		avierr = AVIERR_CANT_OPEN_FILE;
		goto error;
	}

	/* make a root atom */
	newfile->rootchunk.offset = 0;
	newfile->rootchunk.size = length;
	newfile->rootchunk.type = 0;
	newfile->rootchunk.listtype = 0;

	/* parse the data */
	avierr = read_movie_data(newfile);
	if (avierr != AVIERR_NONE)
		goto error;

	*file = newfile;
	return AVIERR_NONE;

error:
	/* clean up after an error */
	if (newfile != NULL)
	{
		if (newfile->file != NULL)
			osd_close(newfile->file);
		free(newfile);
	}
	return avierr;
}


/*-------------------------------------------------
    avi_create - create a new QuickTime movie file
-------------------------------------------------*/

avi_error avi_create(const char *filename, const avi_movie_info *info, avi_file **file)
{
	avi_file *newfile = NULL;
	file_error filerr;
	avi_stream *stream;
	avi_error avierr;
	UINT64 length;

	/* validate video info */
	if ((info->video_format != 0 && info->video_format != FORMAT_UYVY && info->video_format != FORMAT_VYUY && info->video_format != FORMAT_YUY2)  ||
		info->video_width == 0 ||
		info->video_height == 0 ||
		info->video_depth == 0 || info->video_depth % 8 != 0)
		return AVIERR_UNSUPPORTED_VIDEO_FORMAT;

	/* validate audio info */
	if (info->audio_format != 0 ||
		info->audio_channels > MAX_SOUND_CHANNELS ||
		info->audio_samplebits != 16)
		return AVIERR_UNSUPPORTED_AUDIO_FORMAT;

	/* allocate the file */
	newfile = (avi_file *)malloc(sizeof(*newfile));
	if (newfile == NULL)
		return AVIERR_NO_MEMORY;
	memset(newfile, 0, sizeof(*newfile));
	newfile->type = FILETYPE_CREATE;

	/* open the file */
	filerr = osd_open(filename, OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS, &newfile->file, &length);
	if (filerr != FILERR_NONE)
	{
		avierr = AVIERR_CANT_OPEN_FILE;
		goto error;
	}

	/* copy the movie info */
	newfile->info = *info;
	newfile->info.video_numsamples = 0;
	newfile->info.audio_numsamples = 0;

	/* allocate two streams */
	newfile->stream = (avi_stream *)malloc(2 * sizeof(newfile->stream[0]));
	if (newfile->stream == NULL)
	{
		avierr = AVIERR_NO_MEMORY;
		goto error;
	}
	memset(newfile->stream, 0, 2 * sizeof(newfile->stream[0]));

	/* initialize the video track */
	stream = &newfile->stream[newfile->streams++];
	stream->type = STREAMTYPE_VIDS;
	stream->format = newfile->info.video_format;
	stream->rate = newfile->info.video_timescale;
	stream->scale = newfile->info.video_sampletime;
	stream->width = newfile->info.video_width;
	stream->height = newfile->info.video_height;
	stream->depth = newfile->info.video_depth;

	/* initialize the audio track */
	if (newfile->info.audio_channels > 0)
	{
		stream = &newfile->stream[newfile->streams++];
		stream->type = STREAMTYPE_AUDS;
		stream->format = newfile->info.audio_format;
		stream->rate = newfile->info.audio_timescale;
		stream->scale = newfile->info.audio_sampletime;
		stream->channels = newfile->info.audio_channels;
		stream->samplebits = newfile->info.audio_samplebits;
		stream->samplerate = newfile->info.audio_samplerate;

		/* initialize the sound buffering */
		avierr = soundbuf_initialize(newfile);
		if (avierr != AVIERR_NONE)
			goto error;
	}

	/* write the initial headers */
	avierr = write_initial_headers(newfile);

	*file = newfile;
	return AVIERR_NONE;

error:
	/* clean up after an error */
	if (newfile != NULL)
	{
		if (newfile->stream != NULL)
			free(newfile->stream);
		if (newfile->file != NULL)
		{
			osd_close(newfile->file);
			osd_rmfile(filename);
		}
		free(newfile);
	}
	return avierr;
}


/*-------------------------------------------------
    avi_close - close a QuickTime movie file
-------------------------------------------------*/

avi_error avi_close(avi_file *file)
{
	avi_error avierr = AVIERR_NONE;
	int strnum;

	/* if we're creating a new file, finalize it by writing out the non-media chunks */
	if (file->type == FILETYPE_CREATE)
	{
		/* flush any pending sound data */
		avierr = soundbuf_flush(file, FALSE);

		/* close the movi chunk */
		if (avierr == AVIERR_NONE)
			avierr = chunk_close(file);

		/* if this is the first RIFF chunk, write an idx1 */
		if (avierr == AVIERR_NONE && file->riffbase == 0)
			avierr = write_idx1_chunk(file);

		/* update the strh and indx chunks for each stream */
		for (strnum = 0; strnum < file->streams; strnum++)
		{
			if (avierr == AVIERR_NONE)
				avierr = write_strh_chunk(file, &file->stream[strnum], FALSE);
			if (avierr == AVIERR_NONE)
				avierr = write_indx_chunk(file, &file->stream[strnum], FALSE);
		}

		/* update the avih chunk */
		if (avierr == AVIERR_NONE)
			avierr = write_avih_chunk(file, FALSE);

		/* close the RIFF chunk */
		if (avierr == AVIERR_NONE)
			avierr = chunk_close(file);
	}

	/* close the file */
	osd_close(file->file);

	/* free the stream-specific data */
	for (strnum = 0; strnum < file->streams; strnum++)
	{
		avi_stream *stream = &file->stream[strnum];
		if (stream->huffyuv != NULL)
		{
			huffyuv_data *huffyuv = stream->huffyuv;
			int table;

			for (table = 0; table < ARRAY_LENGTH(huffyuv->table); table++)
				if (huffyuv->table[table].extralookup != NULL)
					free(huffyuv->table[table].extralookup);
			free(huffyuv);
		}
		if (stream->chunk != NULL)
			free(stream->chunk);
	}

	/* free the file itself */
	if (file->soundbuf != NULL)
		free(file->soundbuf);
	if (file->stream != NULL)
		free(file->stream);
	if (file->tempbuffer != NULL)
		free(file->tempbuffer);
	free(file);
	return avierr;
}


/*-------------------------------------------------
    avi_printf_chunks - print the chunks in a file
-------------------------------------------------*/

void avi_printf_chunks(avi_file *file)
{
	printf_chunk_recursive(file, &file->rootchunk, 0);
}


/*-------------------------------------------------
    avi_error_string - get the error string for
    an avi_error
-------------------------------------------------*/

const char *avi_error_string(avi_error err)
{
	switch (err)
	{
		case AVIERR_NONE:						return "success";
		case AVIERR_END:						return "hit end of file";
		case AVIERR_INVALID_DATA:				return "invalid data";
		case AVIERR_NO_MEMORY:					return "out of memory";
		case AVIERR_READ_ERROR:					return "read error";
		case AVIERR_WRITE_ERROR:				return "write error";
		case AVIERR_STACK_TOO_DEEP:				return "stack overflow";
		case AVIERR_UNSUPPORTED_FEATURE:		return "unsupported feature";
		case AVIERR_CANT_OPEN_FILE:				return "unable to open file";
		case AVIERR_INCOMPATIBLE_AUDIO_STREAMS:	return "found incompatible audio streams";
		case AVIERR_INVALID_SAMPLERATE:			return "found invalid sample rate";
		case AVIERR_INVALID_STREAM:				return "invalid stream";
		case AVIERR_INVALID_FRAME:				return "invalid frame index";
		case AVIERR_INVALID_BITMAP:				return "invalid bitmap";
		case AVIERR_UNSUPPORTED_VIDEO_FORMAT:	return "unsupported video format";
		case AVIERR_UNSUPPORTED_AUDIO_FORMAT:	return "unsupported audio format";
		case AVIERR_EXCEEDED_SOUND_BUFFER:		return "sound buffer overflow";
		default:								return "undocumented error";
	}
}


/*-------------------------------------------------
    avi_get_movie_info - return a pointer to the
    movie info
-------------------------------------------------*/

const avi_movie_info *avi_get_movie_info(avi_file *file)
{
	return &file->info;
}


/*-------------------------------------------------
    avi_frame_to_sample - convert a frame index
    to a sample index
-------------------------------------------------*/

UINT32 avi_first_sample_in_frame(avi_file *file, UINT32 framenum)
{
	return framenum_to_samplenum(file, framenum);
}


/*-------------------------------------------------
    avi_read_video_frame_yuy16 - read video data
    for a particular frame from the AVI file,
    converting to YUY16 format
-------------------------------------------------*/

avi_error avi_read_video_frame_yuy16(avi_file *file, UINT32 framenum, bitmap_t *bitmap)
{
	avi_error avierr = AVIERR_NONE;
	UINT32 bytes_read, chunkid;
	file_error filerr;
	avi_stream *stream;

	/* get the video stream */
	stream = get_video_stream(file);
	if (stream == NULL)
		return AVIERR_INVALID_STREAM;

	/* validate our ability to handle the data */
	if (stream->format != FORMAT_UYVY && stream->format != FORMAT_VYUY && stream->format != FORMAT_YUY2 && stream->format != FORMAT_HFYU)
		return AVIERR_UNSUPPORTED_VIDEO_FORMAT;

	/* assume one chunk == one frame */
	if (framenum >= stream->chunks)
		return AVIERR_INVALID_FRAME;

	/* we only support YUY-style bitmaps (16bpp) */
	if (bitmap->format != BITMAP_FORMAT_YUY16 || bitmap->width < stream->width || bitmap->height < stream->height)
		return AVIERR_INVALID_BITMAP;

	/* expand the tempbuffer to hold the data if necessary */
	avierr = expand_tempbuffer(file, stream->chunk[framenum].length);
	if (avierr != AVIERR_NONE)
		return avierr;

	/* read in the data */
	filerr = osd_read(file->file, file->tempbuffer, stream->chunk[framenum].offset, stream->chunk[framenum].length, &bytes_read);
	if (filerr != FILERR_NONE || bytes_read != stream->chunk[framenum].length)
		return AVIERR_READ_ERROR;

	/* validate this is good data */
	chunkid = fetch_32bits(&file->tempbuffer[0]);
	if (chunkid == get_chunkid_for_stream(file, stream))
	{
		/* HuffYUV-compressed */
		if (stream->format == FORMAT_HFYU)
			avierr = huffyuv_decompress_to_yuy16(stream, file->tempbuffer + 8, stream->chunk[framenum].length - 8, bitmap);

		/* other YUV-compressed */
		else
			avierr = yuv_decompress_to_yuy16(stream, file->tempbuffer + 8, stream->chunk[framenum].length - 8, bitmap);
	}
	else
		avierr = AVIERR_INVALID_DATA;

	return avierr;
}


/*-------------------------------------------------
    avi_read_sound_samples - read sound sample
    data from an AVI file
-------------------------------------------------*/

avi_error avi_read_sound_samples(avi_file *file, int channel, UINT32 firstsample, UINT32 numsamples, INT16 *output)
{
	avi_error avierr = AVIERR_NONE;
	UINT32 bytes_per_sample;
	file_error filerr;
	avi_stream *stream;
	int offset;

	/* get the audio stream */
	stream = get_audio_stream(file, channel, &offset);
	if (stream == NULL)
		return AVIERR_INVALID_STREAM;

	/* validate our ability to handle the data */
	if (stream->format != 0 || (stream->samplebits != 8 && stream->samplebits != 16))
		return AVIERR_UNSUPPORTED_AUDIO_FORMAT;

	/* verify we are in range */
	if (firstsample >= stream->samples)
		return AVIERR_INVALID_FRAME;
	if (firstsample + numsamples > stream->samples)
		numsamples = stream->samples - firstsample;

	/* determine bytes per sample */
	bytes_per_sample = (stream->samplebits / 8) * stream->channels;

	/* loop until all samples have been extracted */
	while (numsamples > 0)
	{
		UINT32 chunkbase = 0, chunkend = 0, chunkid;
		UINT32 bytes_read, samples_this_chunk;
		int chunknum, sampnum;

		/* locate the chunk with the first sample */
		for (chunknum = 0; chunknum < stream->chunks; chunknum++)
		{
			chunkend = chunkbase + (stream->chunk[chunknum].length - 8) / bytes_per_sample;
			if (firstsample < chunkend)
				break;
			chunkbase = chunkend;
		}

		/* if we hit the end, fill the rest with silence */
		if (chunknum == stream->chunks)
		{
			memset(output, 0, numsamples * 2);
			break;
		}

		/* expand the tempbuffer to hold the data if necessary */
		avierr = expand_tempbuffer(file, stream->chunk[chunknum].length);
		if (avierr != AVIERR_NONE)
			return avierr;

		/* read in the data */
		filerr = osd_read(file->file, file->tempbuffer, stream->chunk[chunknum].offset, stream->chunk[chunknum].length, &bytes_read);
		if (filerr != FILERR_NONE || bytes_read != stream->chunk[chunknum].length)
			return AVIERR_READ_ERROR;

		/* validate this is good data */
		chunkid = fetch_32bits(&file->tempbuffer[0]);
		if (chunkid != get_chunkid_for_stream(file, stream))
			return AVIERR_INVALID_DATA;

		/* determine how many samples to copy */
		samples_this_chunk = chunkend - firstsample;
		samples_this_chunk = MIN(samples_this_chunk, numsamples);

		/* extract 16-bit samples from the chunk */
		if (stream->samplebits == 16)
		{
			const INT16 *base = (const INT16 *)(file->tempbuffer + 8);
			base += stream->channels * (firstsample - chunkbase) + offset;
			for (sampnum = 0; sampnum < samples_this_chunk; sampnum++)
			{
				*output++ = LITTLE_ENDIANIZE_INT16(*base);
				base += stream->channels;
			}
		}

		/* extract 8-bit samples from the chunk */
		else if (stream->samplebits == 8)
		{
			const UINT8 *base = (const UINT8 *)(file->tempbuffer + 8);
			base += stream->channels * (firstsample - chunkbase) + offset;
			for (sampnum = 0; sampnum < samples_this_chunk; sampnum++)
			{
				*output++ = (*base << 8) - 0x8000;
				base += stream->channels;
			}
		}

		/* update our counters */
		firstsample += samples_this_chunk;
		numsamples -= samples_this_chunk;
	}

	return avierr;
}


/*-------------------------------------------------
    avi_append_video_frame_yuy16 - append a frame
    of video in YUY16 format
-------------------------------------------------*/

avi_error avi_append_video_frame_yuy16(avi_file *file, const bitmap_t *bitmap)
{
	avi_stream *stream = get_video_stream(file);
	avi_error avierr;
	UINT32 maxlength;

	/* validate our ability to handle the data */
	if (stream->format != FORMAT_UYVY && stream->format != FORMAT_VYUY && stream->format != FORMAT_YUY2 && stream->format != FORMAT_HFYU)
		return AVIERR_UNSUPPORTED_VIDEO_FORMAT;

	/* double check bitmap format */
	if (bitmap->format != BITMAP_FORMAT_YUY16)
		return AVIERR_INVALID_BITMAP;

	/* write out any sound data first */
	avierr = soundbuf_write_chunk(file, stream->chunks);
	if (avierr != AVIERR_NONE)
		return avierr;

	/* make sure we have enough room */
	maxlength = 2 * stream->width * stream->height;
	avierr = expand_tempbuffer(file, maxlength);
	if (avierr != AVIERR_NONE)
		return avierr;

	/* now compress the data */
	avierr = yuy16_compress_to_yuy(stream, bitmap, file->tempbuffer, maxlength);
	if (avierr != AVIERR_NONE)
		return avierr;

	/* set the info for this new chunk */
	avierr = set_stream_chunk_info(stream, stream->chunks, file->writeoffs, maxlength + 8);
	if (avierr != AVIERR_NONE)
		return avierr;
	stream->samples = file->info.video_numsamples = stream->chunks;

	/* write the data */
	return chunk_write(file, get_chunkid_for_stream(file, stream), file->tempbuffer, maxlength);
}


/*-------------------------------------------------
    avi_append_video_frame_rgb32 - append a frame
    of video in RGB32 format
-------------------------------------------------*/

avi_error avi_append_video_frame_rgb32(avi_file *file, const bitmap_t *bitmap)
{
	avi_stream *stream = get_video_stream(file);
	avi_error avierr;
	UINT32 maxlength;

	/* validate our ability to handle the data */
	if (stream->format != 0)
		return AVIERR_UNSUPPORTED_VIDEO_FORMAT;

	/* depth must be 24 */
	if (stream->depth != 24)
		return AVIERR_UNSUPPORTED_VIDEO_FORMAT;

	/* double check bitmap format */
	if (bitmap->format != BITMAP_FORMAT_RGB32)
		return AVIERR_INVALID_BITMAP;

	/* write out any sound data first */
	avierr = soundbuf_write_chunk(file, stream->chunks);
	if (avierr != AVIERR_NONE)
		return avierr;

	/* make sure we have enough room */
	maxlength = 3 * stream->width * stream->height;
	avierr = expand_tempbuffer(file, maxlength);
	if (avierr != AVIERR_NONE)
		return avierr;

	/* copy the RGB data to the destination */
	avierr = rgb32_compress_to_rgb(stream, bitmap, file->tempbuffer, maxlength);
	if (avierr != AVIERR_NONE)
		return avierr;

	/* set the info for this new chunk */
	avierr = set_stream_chunk_info(stream, stream->chunks, file->writeoffs, maxlength + 8);
	if (avierr != AVIERR_NONE)
		return avierr;
	stream->samples = file->info.video_numsamples = stream->chunks;

	/* write the data */
	return chunk_write(file, get_chunkid_for_stream(file, stream), file->tempbuffer, maxlength);
}


/*-------------------------------------------------
    avi_append_sound_samples - append sound
    samples
-------------------------------------------------*/

avi_error avi_append_sound_samples(avi_file *file, int channel, const INT16 *samples, UINT32 numsamples, UINT32 sampleskip)
{
	UINT32 sampoffset = file->soundbuf_chansamples[channel];
	UINT32 sampnum;

	/* see if we have enough room in the buffer */
	if (sampoffset + numsamples > file->soundbuf_samples)
		return AVIERR_EXCEEDED_SOUND_BUFFER;

	/* append samples to the buffer in little-endian format */
	for (sampnum = 0; sampnum < numsamples; sampnum++)
	{
		INT16 data = *samples++;
		samples += sampleskip;
		data = LITTLE_ENDIANIZE_INT16(data);
		file->soundbuf[sampoffset++ * file->info.audio_channels + channel] = data;
	}
	file->soundbuf_chansamples[channel] = sampoffset;

	/* flush any full sound chunks to disk */
	return soundbuf_flush(file, TRUE);
}


/*-------------------------------------------------
    read_chunk_data - read a chunk's data into
    memory
-------------------------------------------------*/

static avi_error read_chunk_data(avi_file *file, const avi_chunk *chunk, UINT8 **buffer)
{
	file_error filerr;
	UINT32 bytes_read;

	/* allocate memory for the data */
	*buffer = (UINT8 *)malloc(chunk->size);
	if (*buffer == NULL)
		return AVIERR_NO_MEMORY;

	/* read from the file */
	filerr = osd_read(file->file, *buffer, chunk->offset + 8, chunk->size, &bytes_read);
	if (filerr != FILERR_NONE || bytes_read != chunk->size)
	{
		free(*buffer);
		*buffer = NULL;
		return AVIERR_READ_ERROR;
	}

	return AVIERR_NONE;
}


/*-------------------------------------------------
    get_first_chunk - get information about the
    first chunk in a container
-------------------------------------------------*/

static avi_error get_first_chunk(avi_file *file, const avi_chunk *parent, avi_chunk *newchunk)
{
	UINT64 startoffset = (parent != NULL && parent->type != 0) ? parent->offset + 12 : 0;
	if (parent != NULL && parent->type != CHUNKTYPE_LIST && parent->type != CHUNKTYPE_RIFF && parent->type != 0)
		return AVIERR_INVALID_DATA;
	return get_next_chunk_internal(file, parent, newchunk, startoffset);
}


/*-------------------------------------------------
    get_next_chunk - get information about the
    next chunk in a container
-------------------------------------------------*/

static avi_error get_next_chunk(avi_file *file, const avi_chunk *parent, avi_chunk *newchunk)
{
	UINT64 nextoffset = newchunk->offset + 8 + newchunk->size + (newchunk->size & 1);
	return get_next_chunk_internal(file, parent, newchunk, nextoffset);
}


/*-------------------------------------------------
    find_first_chunk - get information about the
    first chunk of a particular type in a container
-------------------------------------------------*/

static avi_error find_first_chunk(avi_file *file, UINT32 findme, const avi_chunk *container, avi_chunk *result)
{
	avi_error avierr;

	for (avierr = get_first_chunk(file, container, result); avierr == AVIERR_NONE; avierr = get_next_chunk(file, container, result))
		if (result->type == findme)
			return AVIERR_NONE;

	return avierr;
}


/*-------------------------------------------------
    find_next_chunk - get information about the
    next chunk of a particular type in a container
-------------------------------------------------*/

static avi_error find_next_chunk(avi_file *file, UINT32 findme, const avi_chunk *container, avi_chunk *result)
{
	avi_error avierr;

	for (avierr = get_next_chunk(file, container, result); avierr == AVIERR_NONE; avierr = get_next_chunk(file, container, result))
		if (result->type == findme)
			return AVIERR_NONE;

	return avierr;
}


/*-------------------------------------------------
    find_first_list - get information about the
    first list of a particular type in a container
-------------------------------------------------*/

static avi_error find_first_list(avi_file *file, UINT32 findme, const avi_chunk *container, avi_chunk *result)
{
	avi_error avierr;

	for (avierr = find_first_chunk(file, CHUNKTYPE_LIST, container, result); avierr == AVIERR_NONE; avierr = find_next_chunk(file, CHUNKTYPE_LIST, container, result))
		if (result->listtype == findme)
			return AVIERR_NONE;

	return avierr;
}


/*-------------------------------------------------
    find_next_list - get information about the
    next list of a particular type in a container
-------------------------------------------------*/

static avi_error find_next_list(avi_file *file, UINT32 findme, const avi_chunk *container, avi_chunk *result)
{
	avi_error avierr;

	for (avierr = find_next_chunk(file, CHUNKTYPE_LIST, container, result); avierr == AVIERR_NONE; avierr = find_next_chunk(file, CHUNKTYPE_LIST, container, result))
		if (result->listtype == findme)
			return AVIERR_NONE;

	return avierr;
}


/*-------------------------------------------------
    get_next_chunk_internal - fetch the next
    chunk relative to the current one
-------------------------------------------------*/

static avi_error get_next_chunk_internal(avi_file *file, const avi_chunk *parent, avi_chunk *newchunk, UINT64 offset)
{
	file_error filerr;
	UINT8 buffer[12];
	UINT32 bytesread;

	/* NULL parent implies the root */
	if (parent == NULL)
		parent = &file->rootchunk;

	/* start at the current offset */
	newchunk->offset = offset;

	/* if we're past the bounds of the parent, bail */
	if (newchunk->offset + 8 >= parent->offset + 8 + parent->size)
		return AVIERR_END;

	/* read the header */
	filerr = osd_read(file->file, buffer, newchunk->offset, 8, &bytesread);
	if (filerr != FILERR_NONE || bytesread != 8)
		return AVIERR_INVALID_DATA;

	/* fill in the new chunk */
	newchunk->type = fetch_32bits(&buffer[0]);
	newchunk->size = fetch_32bits(&buffer[4]);

	/* if we are a list, fetch the list type */
	if (newchunk->type == CHUNKTYPE_LIST || newchunk->type == CHUNKTYPE_RIFF)
	{
		filerr = osd_read(file->file, &buffer[8], newchunk->offset + 8, 4, &bytesread);
		if (filerr != FILERR_NONE || bytesread != 4)
			return AVIERR_INVALID_DATA;
		newchunk->listtype = fetch_32bits(&buffer[8]);
	}

	return AVIERR_NONE;
}


/*-------------------------------------------------
    read_movie_data - get data about a movie
-------------------------------------------------*/

static avi_error read_movie_data(avi_file *file)
{
	avi_chunk riff, hdrl, avih, strl, strh, strf, indx, movi, idx1;
	avi_error avierr;
	int strindex;

	/* find the RIFF chunk */
	avierr = find_first_chunk(file, CHUNKTYPE_RIFF, NULL, &riff);
	if (avierr != AVIERR_NONE)
		goto error;

	/* verify that the RIFF type is AVI */
	if (riff.listtype != LISTTYPE_AVI)
	{
		avierr = AVIERR_INVALID_DATA;
		goto error;
	}

	/* find the hdrl LIST chunk within the RIFF */
	avierr = find_first_list(file, LISTTYPE_HDRL, &riff, &hdrl);
	if (avierr != AVIERR_NONE)
		goto error;

		/* find the avih chunk */
		avierr = find_first_chunk(file, CHUNKTYPE_AVIH, &hdrl, &avih);
		if (avierr != AVIERR_NONE)
			goto error;

		/* parse the avih chunk */
		avierr = parse_avih_chunk(file, &avih);
		if (avierr != AVIERR_NONE)
			goto error;

		/* loop over strl LIST chunks */
		strindex = 0;
		for (avierr = find_first_list(file, LISTTYPE_STRL, &hdrl, &strl); avierr == AVIERR_NONE; avierr = find_next_list(file, LISTTYPE_STRL, &hdrl, &strl))
		{
			/* if we have too many, it's a bad file */
			if (strindex >= file->streams)
				goto error;

			/* find the strh chunk */
			avierr = find_first_chunk(file, CHUNKTYPE_STRH, &strl, &strh);
			if (avierr != AVIERR_NONE)
				goto error;

			/* parse the data */
			avierr = parse_strh_chunk(file, &file->stream[strindex], &strh);
			if (avierr != AVIERR_NONE)
				goto error;

			/* find the strf chunk */
			avierr = find_first_chunk(file, CHUNKTYPE_STRF, &strl, &strf);
			if (avierr != AVIERR_NONE)
				goto error;

			/* parse the data */
			avierr = parse_strf_chunk(file, &file->stream[strindex], &strf);
			if (avierr != AVIERR_NONE)
				goto error;

			/* find the indx chunk, if present */
			avierr = find_first_chunk(file, CHUNKTYPE_INDX, &strl, &indx);
			if (avierr == AVIERR_NONE)
				avierr = parse_indx_chunk(file, &file->stream[strindex], &indx);

			/* next stream */
			strindex++;
		}

		/* normalize the error after parsing the stream headers */
		if (avierr == AVIERR_END)
			avierr = AVIERR_NONE;
		if (avierr != AVIERR_NONE)
			goto error;

	/* find the base of the movi data */
	avierr = find_first_list(file, LISTTYPE_MOVI, &riff, &movi);
	if (avierr != AVIERR_NONE)
		goto error;

	/* find and parse the idx1 chunk within the RIFF (if present) */
	avierr = find_first_chunk(file, CHUNKTYPE_IDX1, &riff, &idx1);
	if (avierr == AVIERR_NONE)
		avierr = parse_idx1_chunk(file, movi.offset + 8, &idx1);
	avierr = AVIERR_NONE;

	/* now extract the movie info */
	avierr = extract_movie_info(file);

error:
	return avierr;
}


/*-------------------------------------------------
    extract_movie_info - extract the movie info
    from the streams we've read
-------------------------------------------------*/

static avi_error extract_movie_info(avi_file *file)
{
	avi_stream *audiostream;
	avi_stream *stream;

	/* get the video stream */
	stream = get_video_stream(file);
	if (stream != NULL)
	{
		/* fill in the info */
		file->info.video_format = stream->format;
		file->info.video_timescale = stream->rate;
		file->info.video_sampletime = stream->scale;
		file->info.video_numsamples = stream->samples;
		file->info.video_width = stream->width;
		file->info.video_height = stream->height;
	}

	/* get the first audio stream */
	stream = get_audio_stream(file, 0, NULL);
	if (stream != NULL)
	{
		/* fill in the info */
		file->info.audio_format = stream->format;
		file->info.audio_timescale = stream->rate;
		file->info.audio_sampletime = stream->scale;
		file->info.audio_numsamples = stream->samples;
		file->info.audio_channels = 1;
		file->info.audio_samplebits = stream->samplebits;
		file->info.audio_samplerate = stream->samplerate;
	}

	/* now make sure all other audio streams are valid */
	audiostream = stream;
	while (1)
	{
		/* get the stream info */
		stream = get_audio_stream(file, file->info.audio_channels, NULL);
		if (stream == NULL)
			break;
		file->info.audio_channels++;

		/* verify compatibility */
		if (file->info.audio_format != stream->format ||
			file->info.audio_timescale != stream->rate ||
			file->info.audio_sampletime != stream->scale ||
			file->info.audio_numsamples != stream->samples ||
			file->info.audio_samplebits != stream->samplebits ||
			file->info.audio_samplerate != stream->samplerate)
			return AVIERR_INCOMPATIBLE_AUDIO_STREAMS;
	}

	return AVIERR_NONE;
}


/*-------------------------------------------------
    parse_avih_chunk - parse an avih header
    chunk
-------------------------------------------------*/

static avi_error parse_avih_chunk(avi_file *file, avi_chunk *avih)
{
	UINT8 *chunkdata = NULL;
	avi_error avierr;

	/* read the data */
	avierr = read_chunk_data(file, avih, &chunkdata);
	if (avierr != AVIERR_NONE)
		goto error;

	/* extract the data */
	file->streams = fetch_32bits(&chunkdata[24]);

	/* allocate memory for the streams */
	file->stream = (avi_stream *)malloc(sizeof(*file->stream) * file->streams);
	if (file->stream == NULL)
		goto error;
	memset(file->stream, 0, sizeof(*file->stream) * file->streams);

error:
	if (chunkdata != NULL)
		free(chunkdata);
	return avierr;
}


/*-------------------------------------------------
    parse_strh_chunk - parse a strh header
    chunk
-------------------------------------------------*/

static avi_error parse_strh_chunk(avi_file *file, avi_stream *stream, avi_chunk *strh)
{
	UINT8 *chunkdata = NULL;
	avi_error avierr;

	/* read the data */
	avierr = read_chunk_data(file, strh, &chunkdata);
	if (avierr != AVIERR_NONE)
		goto error;

	/* extract the data */
	stream->type = fetch_32bits(&chunkdata[0]);
	stream->scale = fetch_32bits(&chunkdata[20]);
	stream->rate = fetch_32bits(&chunkdata[24]);
	stream->samples = fetch_32bits(&chunkdata[32]);

error:
	if (chunkdata != NULL)
		free(chunkdata);
	return avierr;
}


/*-------------------------------------------------
    parse_strf_chunk - parse a strf header
    chunk
-------------------------------------------------*/

static avi_error parse_strf_chunk(avi_file *file, avi_stream *stream, avi_chunk *strf)
{
	UINT8 *chunkdata = NULL;
	avi_error avierr;

	/* read the data */
	avierr = read_chunk_data(file, strf, &chunkdata);
	if (avierr != AVIERR_NONE)
		goto error;

	/* audio and video streams have differing headers */
	if (stream->type == STREAMTYPE_VIDS)
	{
		stream->width = fetch_32bits(&chunkdata[4]);
		stream->height = fetch_32bits(&chunkdata[8]);
		stream->depth = fetch_16bits(&chunkdata[14]);
		stream->format = fetch_32bits(&chunkdata[16]);

		/* extra extraction for HuffYUV data */
		if (stream->format == FORMAT_HFYU && strf->size >= 56)
		{
			avierr = huffyuv_extract_tables(stream, chunkdata, strf->size);
			if (avierr != AVIERR_NONE)
				goto error;
		}
	}
	else if (stream->type == STREAMTYPE_AUDS)
	{
		stream->channels = fetch_16bits(&chunkdata[2]);
		stream->samplebits = fetch_16bits(&chunkdata[14]);
		stream->samplerate = fetch_32bits(&chunkdata[4]);
	}

error:
	if (chunkdata != NULL)
		free(chunkdata);
	return avierr;
}


/*-------------------------------------------------
    parse_indx_chunk - parse an indx chunk
-------------------------------------------------*/

static avi_error parse_indx_chunk(avi_file *file, avi_stream *stream, avi_chunk *strf)
{
	UINT32 entries, entry, id;
	UINT8 *chunkdata = NULL;
	UINT16 longs_per_entry;
	UINT8 subtype, type;
	UINT64 baseoffset;
	avi_error avierr;

	/* read the data */
	avierr = read_chunk_data(file, strf, &chunkdata);
	if (avierr != AVIERR_NONE)
		goto error;

	/* extract the data */
	longs_per_entry = fetch_16bits(&chunkdata[0]);
	subtype = chunkdata[2];
	type = chunkdata[3];
	entries = fetch_32bits(&chunkdata[4]);
	id = fetch_32bits(&chunkdata[8]);
	baseoffset = fetch_64bits(&chunkdata[12]);

	/* if this is a superindex, loop over entries and call ourselves recursively */
	if (type == AVI_INDEX_OF_INDEXES)
	{
		/* validate the size of each entry */
		if (longs_per_entry != 4)
			return AVIERR_INVALID_DATA;

		/* loop over entries and create subchunks for each */
		for (entry = 0; entry < entries; entry++)
		{
			const UINT8 *base = &chunkdata[24 + entry * 16];
			file_error filerr;
			avi_chunk subchunk;
			UINT32 bytes_read;
			UINT8 buffer[8];

			/* go read the subchunk */
			subchunk.offset = fetch_64bits(&base[0]);
			filerr = osd_read(file->file, buffer, subchunk.offset, sizeof(buffer), &bytes_read);
			if (filerr != FILERR_NONE || bytes_read != sizeof(buffer))
			{
				avierr = AVIERR_READ_ERROR;
				break;
			}

			/* parse the data */
			subchunk.type = fetch_32bits(&buffer[0]);
			subchunk.size = fetch_32bits(&buffer[4]);

			/* recursively parse each referenced chunk; stop if we hit an error */
			avierr = parse_indx_chunk(file, stream, &subchunk);
			if (avierr != AVIERR_NONE)
				break;
		}
	}

	/* otherwise, this is a standard index */
	else if (type == AVI_INDEX_OF_CHUNKS)
	{
		/* validate the size of each entry */
		if (longs_per_entry != 2 && longs_per_entry != 3)
			return AVIERR_INVALID_DATA;

		/* loop over entries and parse out the data */
		for (entry = 0; entry < entries; entry++)
		{
			const UINT8 *base = &chunkdata[24 + entry * 4 * longs_per_entry];
			UINT32 offset = fetch_32bits(&base[0]);
			UINT32 size = fetch_32bits(&base[4]);

			/* set the info for this chunk and advance */
			avierr = set_stream_chunk_info(stream, stream->chunks++, baseoffset + offset - 8, size + 8);
			if (avierr != AVIERR_NONE)
				break;
		}
	}

error:
	if (chunkdata != NULL)
		free(chunkdata);
	return avierr;
}


/*-------------------------------------------------
    parse_idx1_chunk - parse an idx1 chunk
-------------------------------------------------*/

static avi_error parse_idx1_chunk(avi_file *file, UINT64 baseoffset, avi_chunk *idx1)
{
	UINT8 *chunkdata = NULL;
	avi_error avierr;
	UINT32 entries;
	UINT32 entry;

	/* read the data */
	avierr = read_chunk_data(file, idx1, &chunkdata);
	if (avierr != AVIERR_NONE)
		goto error;

	/* loop over entries */
	entries = idx1->size / 16;
	for (entry = 0; entry < entries; entry++)
	{
		const UINT8 *base = &chunkdata[entry * 16];
		UINT32 chunkid = fetch_32bits(&base[0]);
		UINT32 offset = fetch_32bits(&base[8]);
		UINT32 size = fetch_32bits(&base[12]);
		avi_stream *stream;
		int streamnum;

		/* determine the stream index */
		streamnum = ((chunkid >> 8) & 0xff) - '0';
		streamnum += 10 * ((chunkid & 0xff) - '0');
		if (streamnum >= file->streams)
		{
			avierr = AVIERR_INVALID_DATA;
			goto error;
		}
		stream = &file->stream[streamnum];

		/* set the appropriate entry */
		avierr = set_stream_chunk_info(stream, stream->chunks++, baseoffset + offset, size + 8);
		if (avierr != AVIERR_NONE)
			goto error;
	}

error:
	if (chunkdata != NULL)
		free(chunkdata);
	return avierr;
}


/*-------------------------------------------------
    chunk_open - open a new chunk for writing
-------------------------------------------------*/

static avi_error chunk_open(avi_file *file, UINT32 type, UINT32 listtype, UINT32 estlength)
{
	file_error filerr;
	avi_chunk *chunk;
	UINT32 written;

	/* if we're out of stack entries, bail */
	if (file->chunksp >= ARRAY_LENGTH(file->chunkstack))
		return AVIERR_STACK_TOO_DEEP;
	chunk = &file->chunkstack[file->chunksp++];

	/* set up the chunk information */
	chunk->offset = file->writeoffs;
	chunk->size = estlength;
	chunk->type = type;
	chunk->listtype = listtype;

	/* non-list types */
	if (type != CHUNKTYPE_RIFF && type != CHUNKTYPE_LIST)
	{
		UINT8 buffer[8];

		/* populate the header */
		put_32bits(&buffer[0], chunk->type);
		put_32bits(&buffer[4], chunk->size);

		/* write the header */
		filerr = osd_write(file->file, buffer, file->writeoffs, sizeof(buffer), &written);
		if (filerr != FILERR_NONE || written != sizeof(buffer))
			return AVIERR_WRITE_ERROR;
		file->writeoffs += written;
	}

	/* list types */
	else
	{
		UINT8 buffer[12];

		/* populate the header */
		put_32bits(&buffer[0], chunk->type);
		put_32bits(&buffer[4], chunk->size);
		put_32bits(&buffer[8], chunk->listtype);

		/* write the header */
		filerr = osd_write(file->file, buffer, file->writeoffs, sizeof(buffer), &written);
		if (filerr != FILERR_NONE || written != sizeof(buffer))
			return AVIERR_WRITE_ERROR;
		file->writeoffs += written;
	}

	return AVIERR_NONE;
}


/*-------------------------------------------------
    chunk_close - finish writing an chunk
-------------------------------------------------*/

static avi_error chunk_close(avi_file *file)
{
	avi_chunk *chunk = &file->chunkstack[--file->chunksp];
	UINT64 chunksize = file->writeoffs - (chunk->offset + 8);
	UINT32 written;

	/* error if we don't fit into 32 bits */
	if (chunksize != (UINT32)chunksize)
		return AVIERR_INVALID_DATA;

	/* write the final size if it is different from the guess */
	if (chunk->size != chunksize)
	{
		file_error filerr;
		UINT8 buffer[4];

		put_32bits(&buffer[0], (UINT32)chunksize);
		filerr = osd_write(file->file, buffer, chunk->offset + 4, sizeof(buffer), &written);
		if (filerr != FILERR_NONE || written != sizeof(buffer))
			return AVIERR_WRITE_ERROR;
	}

	/* round up to the next word */
	file->writeoffs += chunksize & 1;

	return AVIERR_NONE;
}


/*-------------------------------------------------
    chunk_write - write an chunk and its data
-------------------------------------------------*/

static avi_error chunk_write(avi_file *file, UINT32 type, const void *data, UINT32 length)
{
	file_error filerr;
	avi_error avierr;
	UINT32 idxreserve;
	UINT32 written;

	/* if we are the first RIFF, we must reserve enough space for the IDX chunk */
	idxreserve = 0;
	if (file->riffbase == 0 && type != CHUNKTYPE_IDX1)
		idxreserve = compute_idx1_size(file);

	/* if we are getting too big, split the RIFF */
	/* note that we ignore writes before the current RIFF base, as those are assumed to be
       overwrites of a chunk from the previous RIFF */
	if (file->writeoffs >= file->riffbase && file->writeoffs + length + idxreserve - file->riffbase >= MAX_RIFF_SIZE)
	{
		/* close the movi list */
		avierr = chunk_close(file);
		if (avierr != AVIERR_NONE)
			return avierr;

		/* write the idx1 chunk if this is the first */
		if (file->riffbase == 0)
		{
			avierr = write_idx1_chunk(file);
			if (avierr != AVIERR_NONE)
				return avierr;
		}

		/* close the RIFF */
		avierr = chunk_close(file);
		if (avierr != AVIERR_NONE)
			return avierr;

		/* open a new RIFF */
		file->riffbase = file->writeoffs;
		avierr = chunk_open(file, CHUNKTYPE_RIFF, LISTTYPE_AVIX, 0);
		if (avierr != AVIERR_NONE)
			return avierr;

		/* open a nested movi list */
		file->saved_movi_offset = file->writeoffs;
		avierr = chunk_open(file, CHUNKTYPE_LIST, LISTTYPE_MOVI, 0);
		if (avierr != AVIERR_NONE)
			return avierr;
	}

	/* open the chunk */
	avierr = chunk_open(file, type, 0, length);
	if (avierr != AVIERR_NONE)
		return avierr;

	/* write the data */
	filerr = osd_write(file->file, data, file->writeoffs, length, &written);
	if (filerr != FILERR_NONE || written != length)
		return AVIERR_WRITE_ERROR;
	file->writeoffs += written;

	/* close the chunk */
	return chunk_close(file);
}


/*-------------------------------------------------
    chunk_overwrite - write a chunk in two passes;
    first pass writes to the end of file and
    saves the offset; second pass overwrites the
    original
-------------------------------------------------*/

static avi_error chunk_overwrite(avi_file *file, UINT32 type, const void *data, UINT32 length, UINT64 *offset, int initial_write)
{
	UINT64 savedoffset = 0;
	avi_error avierr;

	/* if this is our initial write, save the offset */
	if (initial_write)
		*offset = file->writeoffs;

	/* otherwise, remember the current write offset and replace it with the original */
	else
	{
		savedoffset = file->writeoffs;
		file->writeoffs = *offset;
	}

	/* write the chunk */
	avierr = chunk_write(file, type, data, length);

	/* if this isn't the initial write, restore the previous offset */
	if (!initial_write)
		file->writeoffs = savedoffset;

	return avierr;
}


/*-------------------------------------------------
    write_initial_headers - write out the inital
    set of AVI and stream headers
-------------------------------------------------*/

static avi_error write_initial_headers(avi_file *file)
{
	avi_error avierr;
	int strnum;

	/* reset the write pointer */
	file->writeoffs = 0;

	/* open a RIFF chunk */
	avierr = chunk_open(file, CHUNKTYPE_RIFF, LISTTYPE_AVI, 0);
	if (avierr != AVIERR_NONE)
		return avierr;

	/* open a hdlr LIST */
	avierr = chunk_open(file, CHUNKTYPE_LIST, LISTTYPE_HDRL, 0);
	if (avierr != AVIERR_NONE)
		return avierr;

	/* write an avih chunk */
	avierr = write_avih_chunk(file, TRUE);
	if (avierr != AVIERR_NONE)
		return avierr;

	/* for each stream, write a strl LIST */
	for (strnum = 0; strnum < file->streams; strnum++)
	{
		/* open a strl LIST */
		avierr = chunk_open(file, CHUNKTYPE_LIST, LISTTYPE_STRL, 0);
		if (avierr != AVIERR_NONE)
			return avierr;

		/* write the strh chunk */
		avierr = write_strh_chunk(file, &file->stream[strnum], TRUE);
		if (avierr != AVIERR_NONE)
			return avierr;

		/* write the strf chunk */
		avierr = write_strf_chunk(file, &file->stream[strnum]);
		if (avierr != AVIERR_NONE)
			return avierr;

		/* write the indx chunk */
		avierr = write_indx_chunk(file, &file->stream[strnum], TRUE);
		if (avierr != AVIERR_NONE)
			return avierr;

		/* close the strl LIST */
		avierr = chunk_close(file);
		if (avierr != AVIERR_NONE)
			return avierr;
	}

	/* close the hdlr LIST */
	avierr = chunk_close(file);
	if (avierr != AVIERR_NONE)
		return avierr;

	/* open a movi LIST */
	file->saved_movi_offset = file->writeoffs;
	avierr = chunk_open(file, CHUNKTYPE_LIST, LISTTYPE_MOVI, 0);
	if (avierr != AVIERR_NONE)
		return avierr;

	return avierr;
}


/*-------------------------------------------------
    write_avih_chunk - write the avih header
    chunk
-------------------------------------------------*/

static avi_error write_avih_chunk(avi_file *file, int initial_write)
{
	avi_stream *video = get_video_stream(file);
	UINT8 buffer[56];

	/* reset the buffer */
	memset(buffer, 0, sizeof(buffer));

	put_32bits(&buffer[0], 1000000 * (INT64)video->scale / video->rate); /* dwMicroSecPerFrame */
	put_32bits(&buffer[12], AVIF_HASINDEX | AVIF_ISINTERLEAVED); /* dwFlags */
	put_32bits(&buffer[16], video->samples);			/* dwTotalFrames */
	put_32bits(&buffer[24], file->streams);				/* dwStreams */
	put_32bits(&buffer[32], video->width);				/* dwWidth */
	put_32bits(&buffer[36], video->height);				/* dwHeight */

	/* (over)write the chunk */
	return chunk_overwrite(file, CHUNKTYPE_AVIH, buffer, sizeof(buffer), &file->saved_avih_offset, initial_write);
}


/*-------------------------------------------------
    write_strh_chunk - write the strh header
    chunk
-------------------------------------------------*/

static avi_error write_strh_chunk(avi_file *file, avi_stream *stream, int initial_write)
{
	UINT8 buffer[56];

	/* reset the buffer */
	memset(buffer, 0, sizeof(buffer));

	put_32bits(&buffer[0], stream->type);				/* fccType */
	put_32bits(&buffer[20], stream->scale);				/* dwScale */
	put_32bits(&buffer[24], stream->rate);				/* dwRate */
	put_32bits(&buffer[32], stream->samples);			/* dwLength */
	put_32bits(&buffer[40], 10000);						/* dwQuality */

	/* video-stream specific data */
	if (stream->type == STREAMTYPE_VIDS)
	{
		put_32bits(&buffer[4], 							/* fccHandler */
					(stream->format == FORMAT_HFYU) ? HANDLER_HFYU : HANDLER_DIB);
		put_32bits(&buffer[36],							/* dwSuggestedBufferSize */
					stream->width * stream->height * 4);
		put_16bits(&buffer[52], stream->width);			/* rcFrame.right */
		put_16bits(&buffer[54], stream->height);		/* rcFrame.bottom */
	}

	/* audio-stream specific data */
	if (stream->type == STREAMTYPE_AUDS)
	{
		put_32bits(&buffer[36],							/* dwSuggestedBufferSize */
					stream->samplerate * stream->channels * (stream->samplebits / 8));
		put_32bits(&buffer[44], 						/* dwSampleSize */
					stream->channels * (stream->samplebits / 8));
	}

	/* write the chunk */
	return chunk_overwrite(file, CHUNKTYPE_STRH, buffer, sizeof(buffer), &stream->saved_strh_offset, initial_write);
}


/*-------------------------------------------------
    write_strf_chunk - write the strf header
    chunk
-------------------------------------------------*/

static avi_error write_strf_chunk(avi_file *file, avi_stream *stream)
{
	/* video stream */
	if (stream->type == STREAMTYPE_VIDS)
	{
		UINT8 buffer[40];

		/* reset the buffer */
		memset(buffer, 0, sizeof(buffer));

		put_32bits(&buffer[0], sizeof(buffer));			/* biSize */
		put_32bits(&buffer[4], stream->width);			/* biWidth */
		put_32bits(&buffer[8], stream->height);			/* biHeight */
		put_16bits(&buffer[12], 1);						/* biPlanes */
		put_16bits(&buffer[14], stream->depth);			/* biBitCount */
		put_32bits(&buffer[16], stream->format);		/* biCompression */
		put_32bits(&buffer[20], 						/* biSizeImage */
					stream->width * stream->height * (stream->depth + 7) / 8);

		/* write the chunk */
		return chunk_write(file, CHUNKTYPE_STRF, buffer, sizeof(buffer));
	}

	/* audio stream */
	if (stream->type == STREAMTYPE_AUDS)
	{
		UINT8 buffer[16];

		/* reset the buffer */
		memset(buffer, 0, sizeof(buffer));

		put_16bits(&buffer[0], 1);						/* wFormatTag */
		put_16bits(&buffer[2], stream->channels);		/* nChannels */
		put_32bits(&buffer[4], stream->samplerate);		/* nSamplesPerSec */
		put_32bits(&buffer[8], 							/* nAvgBytesPerSec */
					stream->samplerate * stream->channels * (stream->samplebits / 8));
		put_16bits(&buffer[12],							/* nBlockAlign */
					stream->channels * (stream->samplebits / 8));
		put_16bits(&buffer[14], stream->samplebits);	/* wBitsPerSample */

		/* write the chunk */
		return chunk_write(file, CHUNKTYPE_STRF, buffer, sizeof(buffer));
	}

	return AVIERR_INVALID_DATA;
}


/*-------------------------------------------------
    write_indx_chunk - write the indx header
    chunk
-------------------------------------------------*/

static avi_error write_indx_chunk(avi_file *file, avi_stream *stream, int initial_write)
{
	UINT8 buffer[24 + 16 * MAX_AVI_SIZE_IN_GB / 4];
	UINT32 chunkid, indexchunkid;
	UINT32 master_entries = 0;

	/* reset the buffer */
	memset(buffer, 0, sizeof(buffer));

	/* construct the chunk ID and index chunk ID */
	chunkid = get_chunkid_for_stream(file, stream);
	indexchunkid = AVI_FOURCC('i', 'x', '0' + (stream - file->stream) / 10, '0' + (stream - file->stream) % 10);

	/* loop over chunks of 4GB and write out indexes for them first */
	if (!initial_write && file->riffbase != 0)
	{
		UINT64 currentbase;
		for (currentbase = 0; currentbase < file->writeoffs; currentbase += FOUR_GB)
		{
			UINT64 currentend = currentbase + FOUR_GB;
			UINT32 chunks_this_index = 0;
			UINT32 bytes_this_index = 0;
			avi_error avierr;
			UINT32 chunknum;
			UINT8 *tempbuf;

			/* count chunks that are in this region */
			for (chunknum = 0; chunknum < stream->chunks; chunknum++)
				if (stream->chunk[chunknum].offset >= currentbase && stream->chunk[chunknum].offset < currentend)
					chunks_this_index++;

			/* if no chunks, skip */
			if (chunks_this_index == 0)
				continue;

			/* allocate memory */
			tempbuf = (UINT8 *)malloc(24 + 8 * chunks_this_index);
			if (tempbuf == NULL)
				return AVIERR_NO_MEMORY;
			memset(tempbuf, 0, 24 + 8 * chunks_this_index);

			/* make a regular index */
			put_16bits(&tempbuf[0], 2);						/* wLongsPerEntry */
			tempbuf[2] = 0;									/* bIndexSubType */
			tempbuf[3] = AVI_INDEX_OF_CHUNKS;				/* bIndexType */
			put_32bits(&tempbuf[4], chunks_this_index);		/* nEntriesInUse */
			put_32bits(&tempbuf[8], chunkid);				/* dwChunkId */
			put_64bits(&tempbuf[12], currentbase);			/* qwBaseOffset */

			/* now fill in the indexes */
			chunks_this_index = 0;
			for (chunknum = 0; chunknum < stream->chunks; chunknum++)
				if (stream->chunk[chunknum].offset >= currentbase && stream->chunk[chunknum].offset < currentend)
				{
					put_32bits(&tempbuf[24 + 8 * chunks_this_index + 0], stream->chunk[chunknum].offset + 8 - currentbase);
					put_32bits(&tempbuf[24 + 8 * chunks_this_index + 4], stream->chunk[chunknum].length - 8);
					bytes_this_index += stream->chunk[chunknum].length;
					chunks_this_index++;
				}

			/* write the offset of this index to the master table */
			put_64bits(&buffer[24 + 16 * master_entries + 0], file->writeoffs);
			put_32bits(&buffer[24 + 16 * master_entries + 8], 24 + 8 * chunks_this_index + 8);
			if (stream->type == STREAMTYPE_VIDS)
				put_32bits(&buffer[24 + 16 * master_entries + 12], chunks_this_index);
			else if (stream->type == STREAMTYPE_AUDS)
				put_32bits(&buffer[24 + 16 * master_entries + 12], bytes_this_index / ((stream->samplebits / 8) * stream->channels));
			master_entries++;

			/* write the index */
			avierr = chunk_write(file, indexchunkid, tempbuf, 24 + 8 * chunks_this_index);
			free(tempbuf);
			if (avierr != AVIERR_NONE)
				return avierr;
		}
	}

	/* build up the master index */
	if (master_entries != 0)
	{
		put_16bits(&buffer[0], 4);						/* wLongsPerEntry */
		buffer[2] = 0;									/* bIndexSubType */
		buffer[3] = AVI_INDEX_OF_INDEXES;				/* bIndexType */
		put_32bits(&buffer[4], master_entries);			/* nEntriesInUse */
		put_32bits(&buffer[8], chunkid);				/* dwChunkId */
	}

	/* (over)write the chunk */
	return chunk_overwrite(file, (master_entries == 0) ? CHUNKTYPE_JUNK : CHUNKTYPE_INDX, buffer, sizeof(buffer), &stream->saved_indx_offset, initial_write);
}


/*-------------------------------------------------
    write_idx1_chunk - write the idx1 chunk
-------------------------------------------------*/

static avi_error write_idx1_chunk(avi_file *file)
{
	UINT32 tempbuflength = compute_idx1_size(file) - 8;
	UINT32 curchunk[2] = { 0 };
	UINT32 curoffset;
	avi_error avierr;
	UINT8 *tempbuf;

	/* allocate a temporary buffer */
	tempbuf = (UINT8 *)malloc(tempbuflength);
	if (tempbuf == NULL)
		return AVIERR_NO_MEMORY;

	/* fill it in */
	for (curoffset = 0; curoffset < tempbuflength; curoffset += 16)
	{
		UINT64 minoffset = ~(UINT64)0;
		int strnum, minstr = 0;

		/* determine which stream has the next chunk */
		for (strnum = 0; strnum < file->streams; strnum++)
			if (curchunk[strnum] < file->stream[strnum].chunks && file->stream[strnum].chunk[curchunk[strnum]].offset < minoffset)
			{
				minoffset = file->stream[strnum].chunk[curchunk[strnum]].offset;
				minstr = strnum;
			}

		/* make an entry for this index */
		put_32bits(&tempbuf[curoffset + 0], get_chunkid_for_stream(file, &file->stream[minstr]));
		put_32bits(&tempbuf[curoffset + 4], 0x0010 /* AVIIF_KEYFRAME */);
		put_32bits(&tempbuf[curoffset + 8], minoffset - (file->saved_movi_offset + 8));
		put_32bits(&tempbuf[curoffset + 12], file->stream[minstr].chunk[curchunk[minstr]].length - 8);

		/* advance the chunk counter for this stream */
		curchunk[minstr]++;
	}

	/* write the chunk */
	avierr = chunk_write(file, CHUNKTYPE_IDX1, tempbuf, tempbuflength);
	free(tempbuf);
	return avierr;
}


/*-------------------------------------------------
    soundbuf_initialize - initialize the sound
    buffering system
-------------------------------------------------*/

static avi_error soundbuf_initialize(avi_file *file)
{
	avi_stream *audio = get_audio_stream(file, 0, NULL);
	avi_stream *video = get_video_stream(file);

	/* we require a video stream */
	if (video == NULL)
		return AVIERR_UNSUPPORTED_VIDEO_FORMAT;

	/* skip if no audio stream */
	if (audio == NULL)
		return AVIERR_NONE;

	/* determine the number of samples we want in our buffer; 2 seconds should be enough */
	file->soundbuf_samples = file->info.audio_samplerate * SOUND_BUFFER_MSEC / 1000;

	/* allocate a buffer */
	file->soundbuf = (INT16 *)malloc(file->soundbuf_samples * file->info.audio_channels * sizeof(file->soundbuf[0]));
	if (file->soundbuf == NULL)
		return AVIERR_NO_MEMORY;
	memset(file->soundbuf, 0, file->soundbuf_samples * file->info.audio_channels * sizeof(file->soundbuf[0]));

	/* determine the number of frames to be ahead (0.75secs) */
	file->soundbuf_frames = ((UINT64)video->rate * 75) / ((UINT64)video->scale * 100) + 1;
	return AVIERR_NONE;
}


/*-------------------------------------------------
    soundbuf_write_chunk - write the initial
    chunk data
-------------------------------------------------*/

static avi_error soundbuf_write_chunk(avi_file *file, UINT32 framenum)
{
	avi_stream *stream = get_audio_stream(file, 0, NULL);
	avi_error avierr;
	UINT32 chunknum;
	UINT32 length;

	/* skip if no audio stream */
	if (stream == NULL)
		return AVIERR_NONE;

	/* determine the length of this chunk */
	if (framenum == 0)
		length = framenum_to_samplenum(file, file->soundbuf_frames);
	else
		length = framenum_to_samplenum(file, framenum + 1 + file->soundbuf_frames) - framenum_to_samplenum(file, framenum + file->soundbuf_frames);
	length *= stream->channels * sizeof(INT16);

	/* set the info for this new chunk */
	chunknum = stream->chunks;
	avierr = set_stream_chunk_info(stream, chunknum, file->writeoffs, length + 8);
	if (avierr != AVIERR_NONE)
		return avierr;

	/* then do the initial write */
	return chunk_overwrite(file, get_chunkid_for_stream(file, stream), file->soundbuf, length, &stream->chunk[chunknum].offset, TRUE);
}


/*-------------------------------------------------
    soundbuf_flush - flush data from the sound
    buffers
-------------------------------------------------*/

static avi_error soundbuf_flush(avi_file *file, int only_flush_full)
{
	avi_stream *stream = get_audio_stream(file, 0, NULL);
	INT32 channelsamples = file->soundbuf_samples;
	INT32 processedsamples = 0;
	UINT32 bytes_per_sample;
	UINT32 finalchunks;
	avi_error avierr;
	UINT32 chunknum;
	UINT32 chunkid;
	int channel;

	/* skip if no stream */
	if (stream == NULL)
		return AVIERR_NONE;

	/* get the chunk ID for this stream */
	chunkid = get_chunkid_for_stream(file, stream);
	bytes_per_sample = stream->channels * sizeof(INT16);
	finalchunks = stream->chunks;

	/* find out how many samples we've accumulated */
	for (channel = 0; channel < stream->channels; channel++)
		channelsamples = MIN(channelsamples, file->soundbuf_chansamples[channel]);

	/* loop over pending sound chunks */
	for (chunknum = file->soundbuf_chunks; chunknum < stream->chunks; chunknum++)
	{
		avi_chunk_list *chunk = &stream->chunk[chunknum];
		UINT32 chunksamples = (chunk->length - 8) / bytes_per_sample;

		/* stop if we don't have enough to satisfy this chunk */
		if (only_flush_full && channelsamples < chunksamples)
			break;

		/* if we don't have all the samples we need, pad with 0's */
		if (channelsamples > 0 && channelsamples < chunksamples)
		{
			if (processedsamples + chunksamples > file->soundbuf_samples)
				return AVIERR_EXCEEDED_SOUND_BUFFER;
			memset(&file->soundbuf[(processedsamples + channelsamples) * stream->channels], 0, (chunksamples - channelsamples) * bytes_per_sample);
		}

		/* if we're completely out of samples, clear the buffer entirely and use the end */
		else if (channelsamples <= 0)
		{
			processedsamples = file->soundbuf_samples - chunksamples;
			memset(&file->soundbuf[processedsamples * stream->channels], 0, chunksamples * bytes_per_sample);
			chunkid = CHUNKTYPE_JUNK;
			finalchunks--;
		}

		/* copy the sample data in */
		avierr = chunk_overwrite(file, chunkid, &file->soundbuf[processedsamples * stream->channels], chunk->length - 8, &chunk->offset, FALSE);
		if (avierr != AVIERR_NONE)
			return avierr;

		/* add up the samples */
		if (channelsamples > 0)
			file->info.audio_numsamples = stream->samples += channelsamples;

		/* advance past those */
		processedsamples += chunksamples;
		channelsamples -= chunksamples;
		channelsamples = MAX(0, channelsamples);
	}

	/* if we have a non-zero offset, shift everything down */
	if (processedsamples > 0)
	{
		/* first account for the samples we processed */
		memmove(&file->soundbuf[0], &file->soundbuf[processedsamples * stream->channels], (file->soundbuf_samples - processedsamples) * bytes_per_sample);
		for (channel = 0; channel < stream->channels; channel++)
			file->soundbuf_chansamples[channel] -= processedsamples;
	}

	/* update the final chunk count */
	if (!only_flush_full)
		stream->chunks = finalchunks;

	/* account for flushed chunks */
	file->soundbuf_chunks = chunknum;
	return AVIERR_NONE;
}


/*-------------------------------------------------
    rgb32_compress_to_rgb - "compress" an RGB32
    bitmap to an RGB encoded frame
-------------------------------------------------*/

static avi_error rgb32_compress_to_rgb(avi_stream *stream, const bitmap_t *bitmap, UINT8 *data, UINT32 numbytes)
{
	int height = MIN(stream->height, bitmap->height);
	int width = MIN(stream->width, bitmap->width);
	UINT8 *dataend = data + numbytes;
	int x, y;

	/* compressed video */
	for (y = 0; y < height; y++)
	{
		const UINT32 *source = (UINT32 *)bitmap->base + y * bitmap->rowpixels;
		UINT8 *dest = data + (stream->height - 1 - y) * stream->width * 3;

		for (x = 0; x < width && dest < dataend; x++)
		{
			UINT32 pix = *source++;
			*dest++ = RGB_BLUE(pix);
			*dest++ = RGB_GREEN(pix);
			*dest++ = RGB_RED(pix);
		}

		/* fill in any blank space on the right */
		for ( ; x < stream->width && dest < dataend; x++)
		{
			*dest++ = 0;
			*dest++ = 0;
			*dest++ = 0;
		}
	}

	/* fill in any blank space on the bottom */
	for ( ; y < stream->height; y++)
	{
		UINT8 *dest = data + (stream->height - 1 - y) * stream->width * 3;
		for (x = 0; x < stream->width && dest < dataend; x++)
		{
			*dest++ = 0;
			*dest++ = 0;
			*dest++ = 0;
		}
	}

	return AVIERR_NONE;
}


/*-------------------------------------------------
    yuv_decompress_to_yuy16 - decompress a YUV
    encoded frame to a YUY16 bitmap
-------------------------------------------------*/

static avi_error yuv_decompress_to_yuy16(avi_stream *stream, const UINT8 *data, UINT32 numbytes, bitmap_t *bitmap)
{
	const UINT16 *dataend = (const UINT16 *)(data + numbytes);
	int x, y;

	/* compressed video */
	for (y = 0; y < stream->height; y++)
	{
		const UINT16 *source = (const UINT16 *)data + y * stream->width;
		UINT16 *dest = (UINT16 *)bitmap->base + y * bitmap->rowpixels;

		/* switch off the compression */
		switch (stream->format)
		{
			case FORMAT_UYVY:
				for (x = 0; x < stream->width && source < dataend; x++)
					*dest++ = *source++;
				break;

			case FORMAT_VYUY:
			case FORMAT_YUY2:
				for (x = 0; x < stream->width && source < dataend; x++)
				{
					UINT16 pix = *source++;
					*dest++ = (pix >> 8) | (pix << 8);
				}
				break;
		}
	}

	return AVIERR_NONE;
}


/*-------------------------------------------------
    yuy16_compress_to_yuy - "compress" a YUY16
    bitmap to a YUV encoded frame
-------------------------------------------------*/

static avi_error yuy16_compress_to_yuy(avi_stream *stream, const bitmap_t *bitmap, UINT8 *data, UINT32 numbytes)
{
	const UINT16 *dataend = (const UINT16 *)(data + numbytes);
	int x, y;

	/* compressed video */
	for (y = 0; y < stream->height; y++)
	{
		const UINT16 *source = (UINT16 *)bitmap->base + y * bitmap->rowpixels;
		UINT16 *dest = (UINT16 *)data + y * stream->width;

		/* switch off the compression */
		switch (stream->format)
		{
			case FORMAT_UYVY:
				for (x = 0; x < stream->width && dest < dataend; x++)
					*dest++ = *source++;
				break;

			case FORMAT_VYUY:
			case FORMAT_YUY2:
				for (x = 0; x < stream->width && source < dataend; x++)
				{
					UINT16 pix = *source++;
					*dest++ = (pix >> 8) | (pix << 8);
				}
				break;
		}
	}

	return AVIERR_NONE;
}


/*-------------------------------------------------
    huffyuv_extract_tables - extract HuffYUV
    tables
-------------------------------------------------*/

static avi_error huffyuv_extract_tables(avi_stream *stream, const UINT8 *chunkdata, UINT32 size)
{
	const UINT8 *chunkend = chunkdata + size;
	avi_error avierr = AVIERR_NONE;
	int tabnum;

	/* allocate memory for the data */
	stream->huffyuv = (huffyuv_data *)malloc(sizeof(*stream->huffyuv));
	if (stream->huffyuv == NULL)
	{
		avierr = AVIERR_NO_MEMORY;
		goto error;
	}

	/* extract predictor information */
	if (&chunkdata[40] >= chunkend)
		return AVIERR_INVALID_DATA;
	stream->huffyuv->predictor = chunkdata[40];

	/* make sure it's the left predictor */
	if ((stream->huffyuv->predictor & ~HUFFYUV_PREDICT_DECORR) != HUFFYUV_PREDICT_LEFT)
		return AVIERR_UNSUPPORTED_VIDEO_FORMAT;

	/* make sure it's 16bpp YUV data */
	if (chunkdata[41] != 16)
		return AVIERR_UNSUPPORTED_VIDEO_FORMAT;
	chunkdata += 44;

	/* loop over tables */
	for (tabnum = 0; tabnum < 3; tabnum++)
	{
		huffyuv_table *table = &stream->huffyuv->table[tabnum];
		UINT32 curbits, bitadd;
		UINT16 bitsat16 = 0;
		int offset = 0, bits;

		/* loop until we populate the whole table */
		while (offset < 256)
		{
			int data, shift, count, i;

			/* extract the next run */
			if (chunkdata >= chunkend)
			{
				avierr = AVIERR_INVALID_DATA;
				goto error;
			}
			data = *chunkdata++;
			shift = data & 0x1f;
			count = data >> 5;

			/* zero count means next whole byte is a count */
			if (count == 0)
			{
				if (chunkdata >= chunkend)
				{
					avierr = AVIERR_INVALID_DATA;
					goto error;
				}
				count = *chunkdata++;
			}
			for (i = 0; i < count; i++)
				table->shift[offset++] = shift;
		}

		/* now determine bit patterns and masks */
		curbits = 0;
		for (bits = 31; bits >= 0; bits--)
		{
			bitadd = 1 << (32 - bits);

			/* make sure we've cleared out all the bits below */
			if ((curbits & (bitadd - 1)) != 0)
			{
				avierr = AVIERR_INVALID_DATA;
				goto error;
			}

			/* find all entries with this shift count and assign them */
			for (offset = 0; offset < 256; offset++)
				if (table->shift[offset] == bits)
				{
					table->bits[offset] = curbits;
					table->mask[offset] = ~(bitadd - 1);
					curbits += bitadd;
				}

			/* remember the bit pattern when we complete all the 17-bit codes */
			if (bits == 17)
				bitsat16 = curbits >> 16;
		}

		/* allocate the number of extra lookup tables we need */
		if (bitsat16 > 0)
		{
			table->extralookup = (UINT16 *)malloc(bitsat16 * 65536 * sizeof(table->extralookup[0]));
			if (table->extralookup == NULL)
			{
				avierr = AVIERR_NO_MEMORY;
				goto error;
			}
			for (offset = 0; offset < bitsat16; offset++)
				table->baselookup[offset] = (offset << 8) | 0;
		}

		/* then create lookup tables */
		for (offset = 0; offset < 256; offset++)
			if (table->shift[offset] > 16)
			{
				UINT16 *tablebase = table->extralookup + (table->bits[offset] >> 16) * 65536;
				UINT32 start = table->bits[offset] & 0xffff;
				UINT32 end = start + ((1 << (32 - table->shift[offset])) - 1);
				while (start <= end)
					tablebase[start++] = (offset << 8) | (table->shift[offset] - 16);
			}
			else if (table->shift[offset] > 0)
			{
				UINT32 start = table->bits[offset] >> 16;
				UINT32 end = start + ((1 << (16 - table->shift[offset])) - 1);
				while (start <= end)
					table->baselookup[start++] = (offset << 8) | table->shift[offset];
			}
	}

error:
	if (avierr != AVIERR_NONE && stream->huffyuv != NULL)
	{
		free(stream->huffyuv);
		stream->huffyuv = NULL;
	}
	return avierr;
}


/*-------------------------------------------------
    huffyuv_decompress_to_yuy16 - decompress a
    HuffYUV-encoded frame to a YUY16 bitmap
-------------------------------------------------*/

static avi_error huffyuv_decompress_to_yuy16(avi_stream *stream, const UINT8 *data, UINT32 numbytes, bitmap_t *bitmap)
{
	huffyuv_data *huffyuv = stream->huffyuv;
	int prevlines = (stream->height > 288) ? 2 : 1;
	UINT8 lastprevy = 0, lastprevcb = 0, lastprevcr = 0;
	UINT8 lasty = 0, lastcb = 0, lastcr = 0;
	UINT8 bitsinbuffer = 0;
	UINT32 bitbuffer = 0;
	UINT32 dataoffs = 0;
	int x, y;

	/* compressed video */
	for (y = 0; y < stream->height; y++)
	{
		UINT16 *dest = BITMAP_ADDR16(bitmap, y, 0);

		/* handle the first four bytes independently */
		x = 0;
		if (y == 0)
		{
			/* first DWORD is stored as YUY2 */
			lasty = data[dataoffs++];
			lastcb = data[dataoffs++];
			*dest++ = (lasty << 8) | lastcb;
			lasty = data[dataoffs++];
			lastcr = data[dataoffs++];
			*dest++ = (lasty << 8) | lastcr;
			x = 2;
		}

		/* loop over pixels */
		for ( ; x < stream->width; x++)
		{
			huffyuv_table *ytable = &huffyuv->table[0];
			huffyuv_table *ctable = &huffyuv->table[1 + (x & 1)];
			UINT16 pixel, huffdata;
			int shift;

			/* fill up the buffer; they store little-endian DWORDs, so we XOR with 3 */
			while (bitsinbuffer <= 24 && dataoffs < numbytes)
			{
				bitbuffer |= data[dataoffs++ ^ 3] << (24 - bitsinbuffer);
				bitsinbuffer += 8;
			}

			/* look up the Y component */
			huffdata = ytable->baselookup[bitbuffer >> 16];
			shift = huffdata & 0xff;
			if (shift == 0)
			{
				bitsinbuffer -= 16;
				bitbuffer <<= 16;

				/* fill up the buffer; they store little-endian DWORDs, so we XOR with 3 */
				while (bitsinbuffer <= 24 && dataoffs < numbytes)
				{
					bitbuffer |= data[dataoffs++ ^ 3] << (24 - bitsinbuffer);
					bitsinbuffer += 8;
				}

				huffdata = ytable->extralookup[(huffdata >> 8) * 65536 + (bitbuffer >> 16)];
				shift = huffdata & 0xff;
			}
			bitsinbuffer -= shift;
			bitbuffer <<= shift;
			pixel = huffdata & 0xff00;

			/* fill up the buffer; they store little-endian DWORDs, so we XOR with 3 */
			while (bitsinbuffer <= 24 && dataoffs < numbytes)
			{
				bitbuffer |= data[dataoffs++ ^ 3] << (24 - bitsinbuffer);
				bitsinbuffer += 8;
			}

			/* look up the Cb/Cr component */
			huffdata = ctable->baselookup[bitbuffer >> 16];
			shift = huffdata & 0xff;
			if (shift == 0)
			{
				bitsinbuffer -= 16;
				bitbuffer <<= 16;

				/* fill up the buffer; they store little-endian DWORDs, so we XOR with 3 */
				while (bitsinbuffer <= 24 && dataoffs < numbytes)
				{
					bitbuffer |= data[dataoffs++ ^ 3] << (24 - bitsinbuffer);
					bitsinbuffer += 8;
				}

				huffdata = ctable->extralookup[(huffdata >> 8) * 65536 + (bitbuffer >> 16)];
				shift = huffdata & 0xff;
			}
			bitsinbuffer -= shift;
			bitbuffer <<= shift;
			*dest++ = pixel | (huffdata >> 8);
		}
	}

	/* apply deltas */
	lastprevy = lastprevcb = lastprevcr = 0;
	for (y = 0; y < stream->height; y++)
	{
		UINT16 *prevrow = BITMAP_ADDR16(bitmap, y - prevlines, 0);
		UINT16 *dest = BITMAP_ADDR16(bitmap, y, 0);

		/* handle the first four bytes independently */
		x = 0;
		if (y == 0)
		{
			/* lasty, lastcr, lastcb are set up previously */
			x = 2;
		}

		/* left predict or gradient predict */
		if ((huffyuv->predictor & ~HUFFYUV_PREDICT_DECORR) == HUFFYUV_PREDICT_LEFT ||
			((huffyuv->predictor & ~HUFFYUV_PREDICT_DECORR) == HUFFYUV_PREDICT_GRADIENT))
		{
			/* first do left deltas */
			for ( ; x < stream->width; x += 2)
			{
				UINT16 pixel0 = dest[x + 0];
				UINT16 pixel1 = dest[x + 1];

				lasty += pixel0 >> 8;
				lastcb += pixel0;
				dest[x + 0] = (lasty << 8) | lastcb;

				lasty += pixel1 >> 8;
				lastcr += pixel1;
				dest[x + 1] = (lasty << 8) | lastcr;
			}

			/* for gradient, we then add in the previous row */
			if ((huffyuv->predictor & ~HUFFYUV_PREDICT_DECORR) == HUFFYUV_PREDICT_GRADIENT && y >= prevlines)
				for (x = 0; x < stream->width; x++)
				{
					UINT16 curpix = dest[x];
					UINT16 prevpix = prevrow[x];
					UINT8 ysum = (curpix >> 8) + (prevpix >> 8);
					UINT8 csum = curpix + prevpix;
					dest[x] = (ysum << 8) | csum;
				}
		}

		/* median predict on rows > 0 */
		else if ((huffyuv->predictor & ~HUFFYUV_PREDICT_DECORR) == HUFFYUV_PREDICT_MEDIAN && y >= prevlines)
		{
			for ( ; x < stream->width; x += 2)
			{
				UINT16 prevpixel0 = prevrow[x + 0];
				UINT16 prevpixel1 = prevrow[x + 1];
				UINT16 pixel0 = dest[x + 0];
				UINT16 pixel1 = dest[x + 1];
				UINT8 a, b, c;

				/* compute previous, above, and (prev + above - above-left) */
				a = lasty;
				b = prevpixel0 >> 8;
				c = lastprevy;
				lastprevy = b;
				if (a > b) { UINT8 tmp = a; a = b; b = tmp; }
				if (a > c) { UINT8 tmp = a; a = c; c = tmp; }
				if (b > c) { UINT8 tmp = b; b = c; c = tmp; }
				lasty = (pixel0 >> 8) + b;

				/* compute previous, above, and (prev + above - above-left) */
				a = lastcb;
				b = prevpixel0 & 0xff;
				c = lastprevcb;
				lastprevcb = b;
				if (a > b) { UINT8 tmp = a; a = b; b = tmp; }
				if (a > c) { UINT8 tmp = a; a = c; c = tmp; }
				if (b > c) { UINT8 tmp = b; b = c; c = tmp; }
				lastcb = (pixel0 & 0xff) + b;
				dest[x + 0] = (lasty << 8) | lastcb;

				/* compute previous, above, and (prev + above - above-left) */
				a = lasty;
				b = prevpixel1 >> 8;
				c = lastprevy;
				lastprevy = b;
				if (a > b) { UINT8 tmp = a; a = b; b = tmp; }
				if (a > c) { UINT8 tmp = a; a = c; c = tmp; }
				if (b > c) { UINT8 tmp = b; b = c; c = tmp; }
				lasty = (pixel1 >> 8) + b;

				/* compute previous, above, and (prev + above - above-left) */
				a = lastcr;
				b = prevpixel1 & 0xff;
				c = lastprevcr;
				lastprevcr = b;
				if (a > b) { UINT8 tmp = a; a = b; b = tmp; }
				if (a > c) { UINT8 tmp = a; a = c; c = tmp; }
				if (b > c) { UINT8 tmp = b; b = c; c = tmp; }
				lastcr = (pixel1 & 0xff) + b;
				dest[x + 1] = (lasty << 8) | lastcr;
			}
		}
	}

	return AVIERR_NONE;
}


static void u64toa(UINT64 val, char *output)
{
	UINT32 lo = (UINT32)(val & 0xffffffff);
	UINT32 hi = (UINT32)(val >> 32);
	if (hi != 0)
		sprintf(output, "%X%08X", hi, lo);
	else
		sprintf(output, "%X", lo);
}


/*-------------------------------------------------
    printf_chunk_recursive - print information
    about a chunk recursively
-------------------------------------------------*/

static void printf_chunk_recursive(avi_file *file, avi_chunk *container, int indent)
{
	char size[20], offset[20];
	avi_chunk curchunk;
	int avierr;

	/* iterate over chunks in this container */
	for (avierr = get_first_chunk(file, container, &curchunk); avierr == AVIERR_NONE; avierr = get_next_chunk(file, container, &curchunk))
	{
		UINT32 chunksize = curchunk.size;
		int recurse = FALSE;

		u64toa(curchunk.size, size);
		u64toa(curchunk.offset, offset);
		printf("%*schunk = %c%c%c%c, size=%s (%s)\n", indent, "",
				(UINT8)(curchunk.type >> 0),
				(UINT8)(curchunk.type >> 8),
				(UINT8)(curchunk.type >> 16),
				(UINT8)(curchunk.type >> 24),
				size, offset);

		/* certain chunks are just containers; recurse into them */
		switch (curchunk.type)
		{
			/* basic containers */
			case CHUNKTYPE_RIFF:
			case CHUNKTYPE_LIST:
				printf("%*stype = %c%c%c%c\n", indent, "",
						(UINT8)(curchunk.listtype >> 0),
						(UINT8)(curchunk.listtype >> 8),
						(UINT8)(curchunk.listtype >> 16),
						(UINT8)(curchunk.listtype >> 24));
				recurse = TRUE;
				chunksize = 0;
				break;
		}

		/* print data within the chunk */
		if (chunksize > 0 && curchunk.size < 1024 * 1024)
		{
			UINT8 *data = NULL;
			int i;

			/* read the data for a chunk */
			avierr = read_chunk_data(file, &curchunk, &data);
			if (avierr == AVIERR_NONE)
			{
				int bytes = MIN(512, chunksize);
				for (i = 0; i < bytes; i++)
				{
					if (i % 16 == 0) printf("%*s   ", indent, "");
					printf("%02X ", data[i]);
					if (i % 16 == 15) printf("\n");
				}
				if (chunksize % 16 != 0) printf("\n");
				free(data);
			}
		}

		/* if we're recursing, dive down */
		if (recurse)
			printf_chunk_recursive(file, &curchunk, indent + 4);
	}

	/* if we didn't get a legitimate error, indicate that */
	if (avierr != AVIERR_END)
		printf("[chunk error %d]\n", avierr);
}
