// license:BSD-3-Clause
// copyright-holders:Aaron Giles, Vas Crabb
/***************************************************************************

    aviio.c

    AVI movie format parsing helpers.

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

#include "aviio.h"
#include "osdcomm.h"
#include "osdfile.h"

#include <array>
#include <cassert>
#include <cstdlib>
#include <cstring>


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

/**
 * @def FILETYPE_READ
 *
 * @brief   A macro that defines filetype read.
 */

#define FILETYPE_READ           1

/**
 * @def FILETYPE_CREATE
 *
 * @brief   A macro that defines filetype create.
 */

#define FILETYPE_CREATE         2

/** @brief  Size of the maximum riff. */
#define MAX_RIFF_SIZE           (2UL * 1024 * 1024 * 1024 - 1024)   /* just under 2GB */
/** @brief  The maximum avi size in gigabytes. */
#define MAX_AVI_SIZE_IN_GB      (1024)

/**
 * @def FOUR_GB
 *
 * @brief   A macro that defines four gigabytes.
 */

#define FOUR_GB                 (std::uint64_t(1) << 32)

/**
 * @def MAX_SOUND_CHANNELS
 *
 * @brief   A macro that defines maximum sound channels.
 */

#define MAX_SOUND_CHANNELS      2

/**
 * @def SOUND_BUFFER_MSEC
 *
 * @brief   A macro that defines sound buffer msec.
 */

#define SOUND_BUFFER_MSEC       2000        /* milliseconds of sound buffering */

/** @brief  The chunktype riff. */
#define CHUNKTYPE_RIFF          AVI_FOURCC('R','I','F','F')
/** @brief  List of chunktypes. */
#define CHUNKTYPE_LIST          AVI_FOURCC('L','I','S','T')
/** @brief  The chunktype junk. */
#define CHUNKTYPE_JUNK          AVI_FOURCC('J','U','N','K')
/** @brief  The chunktype avih. */
#define CHUNKTYPE_AVIH          AVI_FOURCC('a','v','i','h')
/** @brief  The chunktype strh. */
#define CHUNKTYPE_STRH          AVI_FOURCC('s','t','r','h')
/** @brief  The chunktype strf. */
#define CHUNKTYPE_STRF          AVI_FOURCC('s','t','r','f')
/** @brief  The first chunktype index. */
#define CHUNKTYPE_IDX1          AVI_FOURCC('i','d','x','1')
/** @brief  The chunktype indx. */
#define CHUNKTYPE_INDX          AVI_FOURCC('i','n','d','x')
/** @brief  The chunktype xxdb. */
#define CHUNKTYPE_XXDB          AVI_FOURCC(0x00,0x00,'d','b')
/** @brief  The chunktype xxdc. */
#define CHUNKTYPE_XXDC          AVI_FOURCC(0x00,0x00,'d','c')
/** @brief  The chunktype xxwb. */
#define CHUNKTYPE_XXWB          AVI_FOURCC(0x00,0x00,'w','b')
/** @brief  The chunktype ixxx. */
#define CHUNKTYPE_IXXX          AVI_FOURCC('i','x',0x00,0x00)
/** @brief  The chunktype xx mask. */
#define CHUNKTYPE_XX_MASK       AVI_FOURCC(0x00,0x00,0xff,0xff)

/** @brief  The listtype avi. */
#define LISTTYPE_AVI            AVI_FOURCC('A','V','I',' ')
/** @brief  The listtype avix. */
#define LISTTYPE_AVIX           AVI_FOURCC('A','V','I','X')
/** @brief  The listtype hdrl. */
#define LISTTYPE_HDRL           AVI_FOURCC('h','d','r','l')
/** @brief  The listtype strl. */
#define LISTTYPE_STRL           AVI_FOURCC('s','t','r','l')
/** @brief  The listtype movi. */
#define LISTTYPE_MOVI           AVI_FOURCC('m','o','v','i')

/** @brief  The streamtype vids. */
#define STREAMTYPE_VIDS         AVI_FOURCC('v','i','d','s')
/** @brief  The streamtype auds. */
#define STREAMTYPE_AUDS         AVI_FOURCC('a','u','d','s')

/** @brief  The handler bitmap. */
#define HANDLER_DIB             AVI_FOURCC('D','I','B',' ')
/** @brief  The handler hfyu. */
#define HANDLER_HFYU            AVI_FOURCC('h','f','y','u')

/* main AVI header files */

/**
 * @def AVIF_HASINDEX
 *
 * @brief   A macro that defines avif hasindex.
 */

#define AVIF_HASINDEX           0x00000010

/**
 * @def AVIF_MUSTUSEINDEX
 *
 * @brief   A macro that defines avif mustuseindex.
 */

#define AVIF_MUSTUSEINDEX       0x00000020

/**
 * @def AVIF_ISINTERLEAVED
 *
 * @brief   A macro that defines avif isinterleaved.
 */

#define AVIF_ISINTERLEAVED      0x00000100

/**
 * @def AVIF_COPYRIGHTED
 *
 * @brief   A macro that defines avif copyrighted.
 */

#define AVIF_COPYRIGHTED        0x00010000

/**
 * @def AVIF_WASCAPTUREFILE
 *
 * @brief   A macro that defines avif wascapturefile.
 */

#define AVIF_WASCAPTUREFILE     0x00020000

/* index definitions */

/**
 * @def AVI_INDEX_OF_INDEXES
 *
 * @brief   A macro that defines avi index of indexes.
 */

#define AVI_INDEX_OF_INDEXES    0x00

/**
 * @def AVI_INDEX_OF_CHUNKS
 *
 * @brief   A macro that defines avi index of chunks.
 */

#define AVI_INDEX_OF_CHUNKS     0x01

/**
 * @def AVI_INDEX_IS_DATA
 *
 * @brief   A macro that defines avi index is data.
 */

#define AVI_INDEX_IS_DATA       0x80

/**
 * @def AVI_INDEX_2FIELD
 *
 * @brief   A macro that defines avi index 2 field.
 */

#define AVI_INDEX_2FIELD        0x01

	/**
	* @def AVI_INTEGRAL_MULTIPLE
	*
	* @brief   Ensures the integral multiple of the video dimension, because most video players are not capable to playback a video stream with a lower multiple.
	*/

#define AVI_INTEGRAL_MULTIPLE   4

/* HuffYUV definitions */

/**
 * @def HUFFYUV_PREDICT_LEFT
 *
 * @brief   A macro that defines huffyuv predict left.
 */

#define HUFFYUV_PREDICT_LEFT     0

/**
 * @def HUFFYUV_PREDICT_GRADIENT
 *
 * @brief   A macro that defines huffyuv predict gradient.
 */

#define HUFFYUV_PREDICT_GRADIENT 1

/**
 * @def HUFFYUV_PREDICT_MEDIAN
 *
 * @brief   A macro that defines huffyuv predict median.
 */

#define HUFFYUV_PREDICT_MEDIAN   2

/**
 * @def HUFFYUV_PREDICT_DECORR
 *
 * @brief   A macro that defines huffyuv predict decorr.
 */

#define HUFFYUV_PREDICT_DECORR   0x40


namespace {
/***************************************************************************
    TYPE DEFINITIONS
***************************************************************************/

/**
 * @struct  avi_chunk
 *
 * @brief   An avi chunk.
 */

struct avi_chunk
{
	avi_chunk() : offset(0), size(0), type(0), listtype(0) { }

	std::uint64_t       offset;                 /* file offset of chunk header */
	std::uint64_t       size;                   /* size of this chunk */
	std::uint32_t       type;                   /* type of this chunk */
	std::uint32_t       listtype;               /* type of this list (if we are a list) */
};

/**
 * @struct  avi_chunk_list
 *
 * @brief   List of avi chunks.
 */

struct avi_chunk_list
{
	std::uint64_t       offset;                 /* offset in the file of header */
	std::uint32_t       length;                 /* length of the chunk including header */
};

/**
 * @struct  avi_stream
 *
 * @brief   An avi stream.
 */

class avi_stream
{
public:
	avi_stream()
		: m_type(0)
		, m_format(0)
		, m_rate(0)
		, m_scale(0)
		, m_samples(0)
		, m_chunks()
		, m_width(0)
		, m_height(0)
		, m_depth(0)
		, m_huffyuv()
		, m_channels(0)
		, m_samplebits(0)
		, m_samplerate(0)
		, m_saved_strh_offset(0)
		, m_saved_indx_offset(0)
	{
	}

	void initialize_video(avi_file::movie_info const &info)
	{
		std::uint32_t width = info.video_width;
		std::uint32_t height = info.video_height;

		auto integal_multiple = std::uint32_t(AVI_INTEGRAL_MULTIPLE);
		if (integal_multiple > 1)
		{
			width = width - (width % integal_multiple);
			height = height - (height % integal_multiple);
		}

		m_type = STREAMTYPE_VIDS;
		m_format = info.video_format;
		m_rate = info.video_timescale;
		m_scale = info.video_sampletime;
		m_width = width;
		m_height = height;
		m_depth = info.video_depth;
	}

	void initialize_audio(avi_file::movie_info const &info)
	{
		m_type = STREAMTYPE_AUDS;
		m_format = info.audio_format;
		m_rate = info.audio_timescale;
		m_scale = info.audio_sampletime;
		m_channels = info.audio_channels;
		m_samplebits = info.audio_samplebits;
		m_samplerate = info.audio_samplerate;
	}

	std::uint32_t type() const { return m_type; }
	std::uint32_t format() const { return m_format; }

	std::uint32_t rate() const { return m_rate; }
	std::uint32_t scale() const { return m_scale; }
	std::uint32_t samples() const { return m_samples; }
	void set_samples(std::uint32_t value) { m_samples = value; }
	std::uint32_t add_samples(std::uint32_t value) { return m_samples += value; }

	std::uint32_t chunks() const { return m_chunks.size(); }
	avi_chunk_list const &chunk(std::uint32_t index) const { assert(index < m_chunks.size()); return m_chunks[index]; }
	avi_chunk_list &chunk(std::uint32_t index) { assert(index < m_chunks.size()); return m_chunks[index]; }
	avi_file::error set_chunk_info(std::uint32_t index, std::uint64_t offset, std::uint32_t length);
	void set_chunks(std::uint32_t count) { assert(count <= m_chunks.size()); m_chunks.resize(count); }

	std::uint32_t width() const { return m_width; }
	std::uint32_t height() const { return m_height; }
	std::uint32_t depth() const { return m_depth; }

	std::uint16_t channels() const { return m_channels; }
	std::uint16_t samplebits() const { return m_samplebits; }
	std::uint32_t samplerate() const { return m_samplerate; }
	std::uint32_t bytes_per_sample() const { return (m_samplebits / 8) * m_channels; }

	avi_file::error set_strh_data(std::uint8_t const *data, std::uint32_t size);
	avi_file::error set_strf_data(std::uint8_t const *data, std::uint32_t size);

	std::uint64_t &saved_strh_offset() { return m_saved_strh_offset; }
	std::uint64_t &saved_indx_offset() { return m_saved_indx_offset; }

	// RGB helpers
	avi_file::error rgb32_compress_to_rgb(const bitmap_rgb32 &bitmap, std::uint8_t *data, std::uint32_t numbytes) const;

	// YUY helpers
	avi_file::error yuv_decompress_to_yuy16(const std::uint8_t *data, std::uint32_t numbytes, bitmap_yuy16 &bitmap) const;
	avi_file::error yuy16_compress_to_yuy(const bitmap_yuy16 &bitmap, std::uint8_t *data, std::uint32_t numbytes) const;

	// HuffYUV helpers
	avi_file::error huffyuv_decompress_to_yuy16(const std::uint8_t *data, std::uint32_t numbytes, bitmap_yuy16 &bitmap) const;

	// Uncompressed helpers
	avi_file::error uncompressed_rgb24_to_argb32(const std::uint8_t *data, std::uint32_t numbytes, bitmap_argb32 &bitmap) const;
	avi_file::error uncompressed_yuv420p_to_argb32(const std::uint8_t *data, std::uint32_t numbytes, bitmap_argb32 &bitmap) const;

private:
	struct huffyuv_table
	{
		std::uint8_t        shift[256];             /* bit shift amounts */
		std::uint32_t       bits[256];              /* bit match values */
		std::uint32_t       mask[256];              /* bit mask values */
		std::uint16_t       baselookup[65536];      /* base lookup table */
		std::vector<std::uint16_t> extralookup;     /* extra lookup tables */
	};

	struct huffyuv_data
	{
		std::uint8_t        predictor;              /* predictor */
		huffyuv_table       table[3];               /* array of tables */
	};

	avi_file::error huffyuv_extract_tables(const std::uint8_t *chunkdata, std::uint32_t size);

	std::uint32_t       m_type;                 /* subtype of stream */
	std::uint32_t       m_format;               /* format of stream data */

	std::uint32_t       m_rate;                 /* timescale for stream */
	std::uint32_t       m_scale;                /* duration of one sample in the stream */
	std::uint32_t       m_samples;              /* number of samples */

	std::vector<avi_chunk_list> m_chunks;       /* list of chunks */

	std::uint32_t       m_width;                /* width of video */
	std::uint32_t       m_height;               /* height of video */
	std::uint32_t       m_depth;                /* depth of video */
	std::unique_ptr<huffyuv_data const> m_huffyuv; /* huffyuv decompression data */

	std::uint16_t       m_channels;             /* audio channels */
	std::uint16_t       m_samplebits;           /* audio bits per sample */
	std::uint32_t       m_samplerate;           /* audio sample rate */

	/* only used when creating */
	std::uint64_t       m_saved_strh_offset;    /* writeoffset of strh chunk */
	std::uint64_t       m_saved_indx_offset;    /* writeoffset of indx chunk */
};

/**
 * @class   avi_file_impl
 *
 * @brief   An avi file.
 */

class avi_file_impl : public avi_file
{
public:
	avi_file_impl(osd_file::ptr &&file, std::uint64_t length) : avi_file_impl()
	{
		m_file = std::move(file);
		m_type = FILETYPE_READ;

		// make a root atom
		m_rootchunk.offset = 0;
		m_rootchunk.size = length;
		m_rootchunk.type = 0;
		m_rootchunk.listtype = 0;
	}

	avi_file_impl(osd_file::ptr &&file, movie_info const &info) : avi_file_impl()
	{
		m_file = std::move(file);
		m_type = FILETYPE_CREATE;

		// copy the movie info
		m_info = info;
		m_info.video_numsamples = 0;
		m_info.audio_numsamples = 0;

		// initialize the video track
		m_streams.emplace_back();
		m_streams.back().initialize_video(m_info);

		// initialize the audio track
		if (m_info.audio_channels > 0)
		{
			m_streams.emplace_back();
			m_streams.back().initialize_audio(m_info);
		}
	}

	virtual ~avi_file_impl() override;

	virtual void printf_chunks() override;

	virtual movie_info const &get_movie_info() const override;
	virtual std::uint32_t first_sample_in_frame(std::uint32_t framenum) const override;

	virtual error read_uncompressed_video_frame(std::uint32_t framenum, bitmap_argb32 &bitmap) override;
	virtual error read_video_frame(std::uint32_t framenum, bitmap_yuy16 &bitmap) override;
	virtual error read_sound_samples(int channel, std::uint32_t firstsample, std::uint32_t numsamples, std::int16_t *output) override;

	virtual error append_video_frame(bitmap_yuy16 &bitmap) override;
	virtual error append_video_frame(bitmap_rgb32 &bitmap) override;
	virtual error append_sound_samples(int channel, std::int16_t const *samples, std::uint32_t numsamples, std::uint32_t sampleskip) override;

	error read_movie_data();
	error write_initial_headers();
	error soundbuf_initialize();

private:
	avi_file_impl()
		: m_file()
		, m_type(0)
		, m_info({ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 })
		, m_tempbuffer()
		, m_streams()
		, m_rootchunk()
		, m_writeoffs(0)
		, m_riffbase(0)
		, m_chunkstack()
		, m_chunksp(0)
		, m_saved_movi_offset(0)
		, m_saved_avih_offset(0)
		, m_soundbuf()
		, m_soundbuf_samples(0)
		, m_soundbuf_chunks(0)
		, m_soundbuf_frames(0)
	{
		std::fill(std::begin(m_soundbuf_chansamples), std::end(m_soundbuf_chansamples), 0);
	}

	avi_stream *get_video_stream();
	avi_stream *get_audio_stream(int channel, int &offset);
	std::uint32_t compute_idx1_size() const;
	std::uint32_t get_chunkid_for_stream(const avi_stream *stream) const;
	std::uint32_t framenum_to_samplenum(std::uint32_t framenum) const;
	error expand_tempbuffer(std::uint32_t length);

	// core chunk read routines
	error get_first_chunk(avi_chunk const *parent, avi_chunk &newchunk);
	error get_next_chunk(avi_chunk const *parent, avi_chunk &newchunk);
	error find_first_chunk(std::uint32_t findme, avi_chunk const *container, avi_chunk &result);
	error find_next_chunk(std::uint32_t findme, avi_chunk const *container, avi_chunk &result);
	error find_first_list(std::uint32_t findme, avi_chunk const *container, avi_chunk &result);
	error find_next_list(std::uint32_t findme, avi_chunk const *container, avi_chunk &result);
	error get_next_chunk_internal(avi_chunk const *parent, avi_chunk &newchunk, std::uint64_t offset);
	error read_chunk_data(avi_chunk const &chunk, std::unique_ptr<std::uint8_t []> &buffer);

	// chunk read helpers
	error extract_movie_info();
	error parse_avih_chunk(avi_chunk const &avih);
	error parse_strh_chunk(avi_stream &stream, avi_chunk const &strh);
	error parse_strf_chunk(avi_stream &stream, avi_chunk const &strf);
	error parse_indx_chunk(avi_stream &stream, avi_chunk const &strf);
	error parse_idx1_chunk(std::uint64_t baseoffset, avi_chunk const &idx1);

	// core chunk write routines
	error chunk_open(std::uint32_t type, std::uint32_t listtype, std::uint32_t estlength);
	error chunk_close();
	error chunk_write(std::uint32_t type, const void *data, std::uint32_t length);
	error chunk_overwrite(std::uint32_t type, const void *data, std::uint32_t length, std::uint64_t &offset, bool initial_write);

	// chunk write helpers
	error write_avih_chunk(bool initial_write);
	error write_strh_chunk(avi_stream &stream, bool initial_write);
	error write_strf_chunk(avi_stream const &stream);
	error write_indx_chunk(avi_stream &stream, bool initial_write);
	error write_idx1_chunk();

	// sound buffering helpers
	error soundbuf_write_chunk(std::uint32_t framenum);
	error soundbuf_flush(bool only_flush_full);

	// debugging
	void printf_chunk_recursive(avi_chunk const *chunk, int indent);

	/* shared data */
	osd_file::ptr       m_file;                 /* pointer to open file */
	int                 m_type;                 /* type of access (read/create) */
	movie_info          m_info;                 /* movie info structure */
	std::vector<std::uint8_t> m_tempbuffer;     /* temporary buffer */

	/* only used when reading */
	std::vector<avi_stream> m_streams;          /* allocated array of stream information */
	avi_chunk           m_rootchunk;            /* dummy root chunk that wraps the whole file */

	/* only used when creating */
	std::uint64_t       m_writeoffs;            /* current file write offset */
	std::uint64_t       m_riffbase;             /* base of the current RIFF */

	std::array<avi_chunk, 8> m_chunkstack;      /* stack of chunks we are writing */
	int                 m_chunksp;              /* stack pointer for the current chunk */

	std::uint64_t       m_saved_movi_offset;    /* writeoffset of movi list */
	std::uint64_t       m_saved_avih_offset;    /* writeoffset of avih chunk */

	std::vector<std::int16_t> m_soundbuf;       /* buffer for sound data */
	std::uint32_t       m_soundbuf_samples;     /* length of sound buffer in samples */
	std::uint32_t       m_soundbuf_chansamples[MAX_SOUND_CHANNELS]; /* samples in buffer for each channel */
	std::uint32_t       m_soundbuf_chunks;      /* number of chunks completed so far */
	std::uint32_t       m_soundbuf_frames;      /* number of frames ahead of the video */
};



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

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

/**
 * @fn  static inline std::uint16_t fetch_16bits(const std::uint8_t *data)
 *
 * @brief   Fetches the 16bits.
 *
 * @param   data    The data.
 *
 * @return  The 16bits.
 */

inline std::uint16_t fetch_16bits(const std::uint8_t *data)
{
	return data[0] | (data[1] << 8);
}


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

/**
 * @fn  static inline std::uint32_t fetch_32bits(const std::uint8_t *data)
 *
 * @brief   Fetches the 32bits.
 *
 * @param   data    The data.
 *
 * @return  The 32bits.
 */

inline std::uint32_t fetch_32bits(const std::uint8_t *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
-------------------------------------------------*/

/**
 * @fn  static inline std::uint64_t fetch_64bits(const std::uint8_t *data)
 *
 * @brief   Fetches the 64bits.
 *
 * @param   data    The data.
 *
 * @return  The 64bits.
 */

inline std::uint64_t fetch_64bits(const std::uint8_t *data)
{
	return std::uint64_t(data[0]) | (std::uint64_t(data[1]) << 8) |
			(std::uint64_t(data[2]) << 16) | (std::uint64_t(data[3]) << 24) |
			(std::uint64_t(data[4]) << 32) | (std::uint64_t(data[5]) << 40) |
			(std::uint64_t(data[6]) << 48) | (std::uint64_t(data[7]) << 56);
}


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

/**
 * @fn  static inline void put_16bits(std::uint8_t *data, std::uint16_t value)
 *
 * @brief   Puts the 16bits.
 *
 * @param [in,out]  data    If non-null, the data.
 * @param   value           The value.
 */

inline void put_16bits(std::uint8_t *data, std::uint16_t value)
{
	data[0] = value >> 0;
	data[1] = value >> 8;
}


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

/**
 * @fn  static inline void put_32bits(std::uint8_t *data, std::uint32_t value)
 *
 * @brief   Puts the 32bits.
 *
 * @param [in,out]  data    If non-null, the data.
 * @param   value           The value.
 */

inline void put_32bits(std::uint8_t *data, std::uint32_t 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
-------------------------------------------------*/

/**
 * @fn  static inline void put_64bits(std::uint8_t *data, std::uint64_t value)
 *
 * @brief   Puts the 64bits.
 *
 * @param [in,out]  data    If non-null, the data.
 * @param   value           The value.
 */

inline void put_64bits(std::uint8_t *data, std::uint64_t 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;
}


/**
 * @fn  static void u64toa(std::uint64_t val, char *output)
 *
 * @brief   64toas.
 *
 * @param   val             The value.
 * @param [in,out]  output  If non-null, the output.
 */

inline void u64toa(std::uint64_t val, char *output)
{
	auto lo = std::uint32_t(val & 0xffffffff);
	auto hi = std::uint32_t(val >> 32);
	if (hi != 0)
		sprintf(output, "%X%08X", hi, lo);
	else
		sprintf(output, "%X", lo);
}


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

/**
 * @fn  static inline avi_error set_stream_chunk_info(avi_stream *stream, std::uint32_t index, std::uint64_t offset, std::uint32_t length)
 *
 * @brief   Sets stream chunk information.
 *
 * @param [in,out]  stream  If non-null, the stream.
 * @param   index           Zero-based index of the.
 * @param   offset          The offset.
 * @param   length          The length.
 *
 * @return  An avi_error.
 */

inline avi_file::error avi_stream::set_chunk_info(std::uint32_t index, std::uint64_t offset, std::uint32_t length)
{
	/* if we need to allocate more, allocate more */
	if (index >= m_chunks.capacity())
	{
		try { m_chunks.reserve((std::max<std::size_t>)(index, m_chunks.capacity() + 1000)); }
		catch (...) { return avi_file::error::NO_MEMORY; }
	}

	/* update the number of chunks */
	m_chunks.resize((std::max<std::size_t>)(m_chunks.size(), index + 1));

	/* set the data */
	m_chunks[index].offset = offset;
	m_chunks[index].length = length;

	return avi_file::error::NONE;
}


inline avi_file::error avi_stream::set_strh_data(std::uint8_t const *data, std::uint32_t size)
{
	m_type = fetch_32bits(&data[0]);
	m_scale = fetch_32bits(&data[20]);
	m_rate = fetch_32bits(&data[24]);
	m_samples = fetch_32bits(&data[32]);
	return avi_file::error::NONE;
}


inline avi_file::error avi_stream::set_strf_data(std::uint8_t const *data, std::uint32_t size)
{
	/* audio and video streams have differing headers */
	if (m_type == STREAMTYPE_VIDS)
	{
		m_width = fetch_32bits(&data[4]);
		m_height = fetch_32bits(&data[8]);
		m_depth = fetch_16bits(&data[14]);
		m_format = fetch_32bits(&data[16]);

		/* extra extraction for HuffYUV data */
		if ((m_format == FORMAT_HFYU) && (size >= 56))
		{
			avi_file::error const avierr = huffyuv_extract_tables(data, size);
			if (avierr != avi_file::error::NONE)
				return avierr;
		}
	}
	else if (m_type == STREAMTYPE_AUDS)
	{
		m_channels = fetch_16bits(&data[2]);
		m_samplebits = fetch_16bits(&data[14]);
		m_samplerate = fetch_32bits(&data[4]);
	}
	return avi_file::error::NONE;
}


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

/**
 * @fn  static inline avi_stream *get_video_stream()
 *
 * @brief   Gets video stream.
 *
 * @return  null if it fails, else the video stream.
 */

inline avi_stream *avi_file_impl::get_video_stream()
{
	for (int streamnum = 0; streamnum < m_streams.size(); streamnum++)
		if (m_streams[streamnum].type() == STREAMTYPE_VIDS)
			return &m_streams[streamnum];

	return nullptr;
}


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

/**
 * @fn  static inline avi_stream *get_audio_stream(avi_file *file, int channel, int *offset)
 *
 * @brief   Gets audio stream.
 *
 * @param [in,out]  file    If non-null, the file.
 * @param   channel         The channel.
 * @param [in,out]  offset  If non-null, the offset.
 *
 * @return  null if it fails, else the audio stream.
 */

inline avi_stream *avi_file_impl::get_audio_stream(int channel, int &offset)
{
	/* find the audios stream */
	for (int streamnum = 0; streamnum < m_streams.size(); streamnum++)
		if (m_streams[streamnum].type() == STREAMTYPE_AUDS)
		{
			if (channel < m_streams[streamnum].channels())
			{
				offset = channel;
				return &m_streams[streamnum];
			}
			channel -= m_streams[streamnum].channels();
		}

	return nullptr;
}


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

/**
 * @fn  static inline std::uint32_t compute_idx1_size(avi_file *file)
 *
 * @brief   Calculates the index 1 size.
 *
 * @param [in,out]  file    If non-null, the file.
 *
 * @return  The calculated index 1 size.
 */

inline std::uint32_t avi_file_impl::compute_idx1_size() const
{
	int chunks = 0;

	/* count chunks in streams */
	for (int strnum = 0; strnum < m_streams.size(); strnum++)
		chunks += m_streams[strnum].chunks();

	return chunks * 16 + 8;
}


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

/**
 * @fn  static inline std::uint32_t get_chunkid_for_stream(avi_file *file, avi_stream *stream)
 *
 * @brief   Gets chunkid for stream.
 *
 * @param [in,out]  file    If non-null, the file.
 * @param [in,out]  stream  If non-null, the stream.
 *
 * @return  The chunkid for stream.
 */

inline std::uint32_t avi_file_impl::get_chunkid_for_stream(const avi_stream *stream) const
{
	std::uint32_t chunkid;

	chunkid = AVI_FOURCC('0' + (stream - &m_streams[0]) / 10, '0' +  (stream - &m_streams[0]) % 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
-------------------------------------------------*/

/**
 * @fn  static inline std::uint32_t framenum_to_samplenum(avi_file *file, std::uint32_t framenum)
 *
 * @brief   Framenum to samplenum.
 *
 * @param [in,out]  file    If non-null, the file.
 * @param   framenum        The framenum.
 *
 * @return  An std::uint32_t.
 */

inline std::uint32_t avi_file_impl::framenum_to_samplenum(std::uint32_t framenum) const
{
	return (std::uint64_t(m_info.audio_samplerate) * std::uint64_t(framenum) * std::uint64_t(m_info.video_sampletime) + m_info.video_timescale - 1) / std::uint64_t(m_info.video_timescale);
}


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

/**
 * @fn  static inline avi_error expand_tempbuffer(avi_file *file, std::uint32_t length)
 *
 * @brief   Expand tempbuffer.
 *
 * @param [in,out]  file    If non-null, the file.
 * @param   length          The length.
 *
 * @return  An avi_error.
 */

inline avi_file::error avi_file_impl::expand_tempbuffer(std::uint32_t length)
{
	/* expand the tempbuffer to hold the data if necessary */
	if (length > m_tempbuffer.size())
	{
		try { m_tempbuffer.resize(2 * length); }
		catch (...) { return error::NO_MEMORY; }
	}
	return error::NONE;
}


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

/**
 * @fn  static avi_error rgb32_compress_to_rgb(avi_stream *stream, const bitmap_rgb32 &bitmap, std::uint8_t *data, std::uint32_t numbytes)
 *
 * @brief   RGB 32 compress to RGB.
 *
 * @param [in,out]  stream  If non-null, the stream.
 * @param   bitmap          The bitmap.
 * @param [in,out]  data    If non-null, the data.
 * @param   numbytes        The numbytes.
 *
 * @return  An avi_error.
 */

avi_file::error avi_stream::rgb32_compress_to_rgb(const bitmap_rgb32 &bitmap, std::uint8_t *data, std::uint32_t numbytes) const
{
	int const height = (std::min<int>)(m_height, bitmap.height());
	int const width = (std::min<int>)(m_width, bitmap.width());
	std::uint8_t *const dataend = data + numbytes;
	int x, y;

	/* compressed video */
	for (y = 0; y < height; y++)
	{
		const std::uint32_t *source = &bitmap.pix(y);
		std::uint8_t *dest = data + (m_height - 1 - y) * m_width * 3;

		for (x = 0; x < width && dest < dataend; x++)
		{
			rgb_t pix = *source++;
			*dest++ = pix.b();
			*dest++ = pix.g();
			*dest++ = pix.r();
		}

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

	/* fill in any blank space on the bottom */
	for ( ; y < m_height; y++)
	{
		std::uint8_t *dest = data + (m_height - 1 - y) * m_width * 3;
		for (x = 0; x < m_width && dest < dataend; x++)
		{
			*dest++ = 0;
			*dest++ = 0;
			*dest++ = 0;
		}
	}

	return avi_file::error::NONE;
}


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

/**
 * @fn  static avi_error yuv_decompress_to_yuy16(avi_stream *stream, const std::uint8_t *data, std::uint32_t numbytes, bitmap_yuy16 &bitmap)
 *
 * @brief   Yuv decompress to yuy 16.
 *
 * @param [in,out]  stream  If non-null, the stream.
 * @param   data            The data.
 * @param   numbytes        The numbytes.
 * @param [in,out]  bitmap  The bitmap.
 *
 * @return  An avi_error.
 */

avi_file::error avi_stream::yuv_decompress_to_yuy16(const std::uint8_t *data, std::uint32_t numbytes, bitmap_yuy16 &bitmap) const
{
	auto const *const dataend = reinterpret_cast<const std::uint16_t *>(data + numbytes);
	int x, y;

	/* compressed video */
	for (y = 0; y < m_height; y++)
	{
		const std::uint16_t *source = reinterpret_cast<const std::uint16_t *>(data) + y * m_width;
		std::uint16_t *dest = &bitmap.pix(y);

		/* switch off the compression */
		switch (m_format)
		{
		case FORMAT_UYVY:
			for (x = 0; x < m_width && source < dataend; x++)
				*dest++ = *source++;
			break;

		case FORMAT_VYUY:
		case FORMAT_YUY2:
			for (x = 0; x < m_width && source < dataend; x++)
			{
				std::uint16_t pix = *source++;
				*dest++ = (pix >> 8) | (pix << 8);
			}
			break;
		}
	}

	return avi_file::error::NONE;
}


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

/**
 * @fn  static avi_error yuy16_compress_to_yuy(avi_stream *stream, const bitmap_yuy16 &bitmap, std::uint8_t *data, std::uint32_t numbytes)
 *
 * @brief   Yuy 16 compress to yuy.
 *
 * @param [in,out]  stream  If non-null, the stream.
 * @param   bitmap          The bitmap.
 * @param [in,out]  data    If non-null, the data.
 * @param   numbytes        The numbytes.
 *
 * @return  An avi_error.
 */

avi_file::error avi_stream::yuy16_compress_to_yuy(const bitmap_yuy16 &bitmap, std::uint8_t *data, std::uint32_t numbytes) const
{
	auto *const dataend = reinterpret_cast<std::uint16_t *>(data + numbytes);
	int x, y;

	/* compressed video */
	for (y = 0; y < m_height; y++)
	{
		const std::uint16_t *source = &bitmap.pix(y);
		std::uint16_t *dest = reinterpret_cast<std::uint16_t *>(data) + y * m_width;

		/* switch off the compression */
		switch (m_format)
		{
		case FORMAT_UYVY:
			for (x = 0; x < m_width && dest < dataend; x++)
				*dest++ = *source++;
			break;

		case FORMAT_VYUY:
		case FORMAT_YUY2:
			for (x = 0; x < m_width && dest < dataend; x++)
			{
				std::uint16_t pix = *source++;
				*dest++ = (pix >> 8) | (pix << 8);
			}
			break;
		}
	}

	return avi_file::error::NONE;
}


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

/**
 * @fn  static avi_error huffyuv_extract_tables(avi_stream *stream, const std::uint8_t *chunkdata, std::uint32_t size)
 *
 * @brief   Huffyuv extract tables.
 *
 * @param [in,out]  stream  If non-null, the stream.
 * @param   chunkdata       The chunkdata.
 * @param   size            The size.
 *
 * @return  An avi_error.
 */

avi_file::error avi_stream::huffyuv_extract_tables(const std::uint8_t *chunkdata, std::uint32_t size)
{
	const std::uint8_t *const chunkend = chunkdata + size;

	/* allocate memory for the data */
	std::unique_ptr<huffyuv_data> huffyuv;
	try { huffyuv = std::make_unique<huffyuv_data>(); }
	catch (...) { return avi_file::error::NO_MEMORY; }

	/* extract predictor information */
	if (&chunkdata[40] >= chunkend)
		return avi_file::error::INVALID_DATA;
	huffyuv->predictor = chunkdata[40];

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

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

	/* loop over tables */
	for (int tabnum = 0; tabnum < 3; tabnum++)
	{
		huffyuv_table &table = huffyuv->table[tabnum];
		std::uint32_t curbits, bitadd;
		std::uint16_t 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)
				return avi_file::error::INVALID_DATA;
			data = *chunkdata++;
			shift = data & 0x1f;
			count = data >> 5;

			/* zero count means next whole byte is a count */
			if (count == 0)
			{
				if (chunkdata >= chunkend)
					return avi_file::error::INVALID_DATA;
				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)
				return avi_file::error::INVALID_DATA;

			/* 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)
		{
			try { table.extralookup.resize(bitsat16 * 65536); }
			catch (...) { return avi_file::error::NO_MEMORY; }
			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)
			{
				std::uint16_t *tablebase = &table.extralookup[(table.bits[offset] >> 16) * 65536];
				std::uint32_t start = table.bits[offset] & 0xffff;
				std::uint32_t 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)
			{
				std::uint32_t start = table.bits[offset] >> 16;
				std::uint32_t end = start + ((1 << (16 - table.shift[offset])) - 1);
				while (start <= end)
					table.baselookup[start++] = (offset << 8) | table.shift[offset];
			}
	}

	m_huffyuv = std::move(huffyuv);
	return avi_file::error::NONE;
}


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

/**
 * @fn  static avi_error huffyuv_decompress_to_yuy16(avi_stream *stream, const std::uint8_t *data, std::uint32_t numbytes, bitmap_yuy16 &bitmap)
 *
 * @brief   Huffyuv decompress to yuy 16.
 *
 * @param [in,out]  stream  If non-null, the stream.
 * @param   data            The data.
 * @param   numbytes        The numbytes.
 * @param [in,out]  bitmap  The bitmap.
 *
 * @return  An avi_error.
 */

avi_file::error avi_stream::huffyuv_decompress_to_yuy16(const std::uint8_t *data, std::uint32_t numbytes, bitmap_yuy16 &bitmap) const
{
	int prevlines = (m_height > 288) ? 2 : 1;
	std::uint8_t lastprevy = 0, lastprevcb = 0, lastprevcr = 0;
	std::uint8_t lasty = 0, lastcb = 0, lastcr = 0;
	std::uint8_t bitsinbuffer = 0;
	std::uint32_t bitbuffer = 0;
	std::uint32_t dataoffs = 0;
	int x, y;

	/* compressed video */
	for (y = 0; y < m_height; y++)
	{
		std::uint16_t *dest = &bitmap.pix(y);

		/* 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 < m_width; x++)
		{
			huffyuv_table const &ytable = m_huffyuv->table[0];
			huffyuv_table const &ctable = m_huffyuv->table[1 + (x & 1)];
			std::uint16_t 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;
			std::uint16_t const 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 < m_height; y++)
	{
		std::uint16_t *prevrow = &bitmap.pix(y - prevlines);
		std::uint16_t *dest = &bitmap.pix(y);

		/* 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 ((m_huffyuv->predictor & ~HUFFYUV_PREDICT_DECORR) == HUFFYUV_PREDICT_LEFT ||
			((m_huffyuv->predictor & ~HUFFYUV_PREDICT_DECORR) == HUFFYUV_PREDICT_GRADIENT))
		{
			/* first do left deltas */
			for ( ; x < m_width; x += 2)
			{
				std::uint16_t pixel0 = dest[x + 0];
				std::uint16_t 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 ((m_huffyuv->predictor & ~HUFFYUV_PREDICT_DECORR) == HUFFYUV_PREDICT_GRADIENT && y >= prevlines)
				for (x = 0; x < m_width; x++)
				{
					std::uint16_t curpix = dest[x];
					std::uint16_t prevpix = prevrow[x];
					std::uint8_t ysum = (curpix >> 8) + (prevpix >> 8);
					std::uint8_t csum = curpix + prevpix;
					dest[x] = (ysum << 8) | csum;
				}
		}

		/* median predict on rows > 0 */
		else if ((m_huffyuv->predictor & ~HUFFYUV_PREDICT_DECORR) == HUFFYUV_PREDICT_MEDIAN && y >= prevlines)
		{
			for ( ; x < m_width; x += 2)
			{
				std::uint16_t prevpixel0 = prevrow[x + 0];
				std::uint16_t prevpixel1 = prevrow[x + 1];
				std::uint16_t pixel0 = dest[x + 0];
				std::uint16_t pixel1 = dest[x + 1];
				std::uint8_t a, b, c;

				/* compute previous, above, and (prev + above - above-left) */
				a = lasty;
				b = prevpixel0 >> 8;
				c = lastprevy;
				lastprevy = b;
				if (a > b) { std::uint8_t tmp = a; a = b; b = tmp; }
				if (a > c) { std::uint8_t tmp = a; a = c; c = tmp; }
				if (b > c) { std::uint8_t 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) { std::uint8_t tmp = a; a = b; b = tmp; }
				if (a > c) { std::uint8_t tmp = a; a = c; c = tmp; }
				if (b > c) { std::uint8_t 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) { std::uint8_t tmp = a; a = b; b = tmp; }
				if (a > c) { std::uint8_t tmp = a; a = c; c = tmp; }
				if (b > c) { std::uint8_t 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) { std::uint8_t tmp = a; a = b; b = tmp; }
				if (a > c) { std::uint8_t tmp = a; a = c; c = tmp; }
				if (b > c) { std::uint8_t tmp = b; b = c; c = tmp; }
				lastcr = (pixel1 & 0xff) + b;
				dest[x + 1] = (lasty << 8) | lastcr;
			}
		}
	}

	return avi_file::error::NONE;
}


/*-------------------------------------------------
    uncompressed_rgb24_to_argb32 - convert a raw
    RGB24-encoded frame to an ARGB32 bitmap
-------------------------------------------------*/

avi_file::error avi_stream::uncompressed_rgb24_to_argb32(const std::uint8_t *data, std::uint32_t numbytes, bitmap_argb32 &bitmap) const
{
	std::uint32_t dataoffs = 0;

	/* uncompressed video */
	for (int y = 0; y < m_height; y++)
	{
		std::uint32_t *dest = &bitmap.pix(y);

		/* loop over pixels */
		for (int x = 0; x < m_width; x++)
		{
			const uint8_t b = data[dataoffs++];
			const uint8_t g = data[dataoffs++];
			const uint8_t r = data[dataoffs++];
			*dest++ = 0xff << 24 | r << 16 | g << 8 | b;
		}
	}

	return avi_file::error::NONE;
}


/*-------------------------------------------------
    uncompressed_yuv420p_to_argb32 - convert a
    YUV420p-encoded frame to an ARGB32 bitmap
-------------------------------------------------*/

avi_file::error avi_stream::uncompressed_yuv420p_to_argb32(const std::uint8_t *data, std::uint32_t numbytes, bitmap_argb32 &bitmap) const
{
	const int width = bitmap.width();
	const int height = bitmap.height();
	const int size_total = width * height;

	/* uncompressed video */
	for (int y = 0; y < m_height; y++)
	{
		std::uint32_t *dest = &bitmap.pix(y);

		/* loop over pixels */
		for (int x = 0; x < m_width; x++)
		{
			const uint8_t luma = data[y * width + x];
			const uint8_t u = data[(y / 2) * (width / 2) + x / 2 + size_total];
			const uint8_t v = data[(y / 2) * (width / 2) + x / 2 + size_total + size_total / 4];

			int r = luma + (1.370705f * (v - 0x80));
			int g = luma - (0.698001f * (v - 0x80)) - (0.337633f * (u - 0x80));
			int b = luma + (1.732446f * (u - 0x80));

			r = (r < 0) ? 0 : ((r > 255) ? 255 : r);
			g = (g < 0) ? 0 : ((g > 255) ? 255 : g);
			b = (b < 0) ? 0 : ((b > 255) ? 255 : b);

			*dest++ = 0xff << 24 | r << 16 | g << 8 | b;
		}
	}

	return avi_file::error::NONE;
}


/*-------------------------------------------------
    avi_close - close an AVI movie file
-------------------------------------------------*/

/**
 * @fn  avi_error avi_close(avi_file *file)
 *
 * @brief   Avi close.
 *
 * @param [in,out]  file    If non-null, the file.
 *
 * @return  An avi_error.
 */

avi_file_impl::~avi_file_impl()
{
	error avierr = error::NONE;

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

		/* close the movi chunk */
		if (avierr == error::NONE)
			avierr = chunk_close();

		/* if this is the first RIFF chunk, write an idx1 */
		if (avierr == error::NONE && m_riffbase == 0)
			avierr = write_idx1_chunk();

		/* update the strh and indx chunks for each stream */
		for (int strnum = 0; strnum < m_streams.size(); strnum++)
		{
			if (avierr == error::NONE)
				avierr = write_strh_chunk(m_streams[strnum], false);
			if (avierr == error::NONE)
				avierr = write_indx_chunk(m_streams[strnum], false);
		}

		/* update the avih chunk */
		if (avierr == error::NONE)
			avierr = write_avih_chunk(false);

		/* close the RIFF chunk */
		if (avierr == error::NONE)
			avierr = chunk_close();
	}

	/* close the file */
	m_file.reset();

	//return avierr;
}


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

/**
 * @fn  void avi_printf_chunks(avi_file *file)
 *
 * @brief   Avi printf chunks.
 *
 * @param [in,out]  file    If non-null, the file.
 */

void avi_file_impl::printf_chunks()
{
	printf_chunk_recursive(&m_rootchunk, 0);
}


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

/**
 * @fn  const avi_movie_info *avi_get_movie_info(avi_file *file)
 *
 * @brief   Avi get movie information.
 *
 * @param [in,out]  file    If non-null, the file.
 *
 * @return  null if it fails, else an avi_movie_info*.
 */

avi_file::movie_info const &avi_file_impl::get_movie_info() const
{
	return m_info;
}


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

/**
 * @fn  std::uint32_t avi_first_sample_in_frame(avi_file *file, std::uint32_t framenum)
 *
 * @brief   Avi first sample in frame.
 *
 * @param [in,out]  file    If non-null, the file.
 * @param   framenum        The framenum.
 *
 * @return  An std::uint32_t.
 */

std::uint32_t avi_file_impl::first_sample_in_frame(std::uint32_t framenum) const
{
	return framenum_to_samplenum(framenum);
}


/*-------------------------------------------------
    read_uncompressed_video_frame - read raw video
    data for a particular frame from the AVI file,
    converting to ARGB32 format
-------------------------------------------------*/

avi_file::error avi_file_impl::read_uncompressed_video_frame(std::uint32_t framenum, bitmap_argb32 &bitmap)
{
	/* get the video stream */
	avi_stream *const stream = get_video_stream();
	if (!stream)
		return error::INVALID_STREAM;

	if (stream->format() != FORMAT_UNCOMPRESSED && stream->format() != FORMAT_DIB && stream->format() != FORMAT_RGB && stream->format() != FORMAT_RAW && stream->format() != FORMAT_I420)
		return error::UNSUPPORTED_VIDEO_FORMAT;

	if (bitmap.width() < stream->width() || bitmap.height() < stream->height())
		bitmap.resize(stream->width(), stream->height());

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

	/* we only support YUY-style bitmaps (16bpp) */
	if (bitmap.width() < stream->width() || bitmap.height() < stream->height())
		return error::INVALID_BITMAP;

	/* expand the tempbuffer to hold the data if necessary */
	error avierr = error::NONE;
	avierr = expand_tempbuffer(stream->chunk(framenum).length);
	if (avierr != error::NONE)
		return avierr;

	/* read in the data */
	std::uint32_t bytes_read;
	std::error_condition const filerr = m_file->read(&m_tempbuffer[0], stream->chunk(framenum).offset, stream->chunk(framenum).length, bytes_read);
	if (filerr || bytes_read != stream->chunk(framenum).length)
		return error::READ_ERROR;

	/* validate this is good data */
	std::uint32_t const chunkid = fetch_32bits(&m_tempbuffer[0]);
	if (chunkid == get_chunkid_for_stream(stream))
	{
		/* uncompressed YUV420p */
		if (stream->format() == FORMAT_I420)
			avierr = stream->uncompressed_yuv420p_to_argb32(&m_tempbuffer[8], stream->chunk(framenum).length - 8, bitmap);
		else
			avierr = stream->uncompressed_rgb24_to_argb32(&m_tempbuffer[8], stream->chunk(framenum).length - 8, bitmap);
	}
	else
	{
		avierr = error::INVALID_DATA;
	}

	return avierr;
}

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

avi_file::error avi_file_impl::read_video_frame(std::uint32_t framenum, bitmap_yuy16 &bitmap)
{
	/* get the video stream */
	avi_stream *const stream = get_video_stream();
	if (!stream)
		return error::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 error::UNSUPPORTED_VIDEO_FORMAT;

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

	/* we only support YUY-style bitmaps (16bpp) */
	if (bitmap.width() < stream->width() || bitmap.height() < stream->height())
		return error::INVALID_BITMAP;

	/* expand the tempbuffer to hold the data if necessary */
	error avierr = error::NONE;
	avierr = expand_tempbuffer(stream->chunk(framenum).length);
	if (avierr != error::NONE)
		return avierr;

	/* read in the data */
	std::uint32_t bytes_read;
	std::error_condition const filerr = m_file->read(&m_tempbuffer[0], stream->chunk(framenum).offset, stream->chunk(framenum).length, bytes_read);
	if (filerr || bytes_read != stream->chunk(framenum).length)
		return error::READ_ERROR;

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

		/* other YUV-compressed */
		else
			avierr = stream->yuv_decompress_to_yuy16(&m_tempbuffer[8], stream->chunk(framenum).length - 8, bitmap);
	}
	else
		avierr = error::INVALID_DATA;

	return avierr;
}


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

/**
 * @fn  avi_error avi_read_sound_samples(avi_file *file, int channel, std::uint32_t firstsample, std::uint32_t numsamples, std::int16_t *output)
 *
 * @brief   Avi read sound samples.
 *
 * @param [in,out]  file    If non-null, the file.
 * @param   channel         The channel.
 * @param   firstsample     The firstsample.
 * @param   numsamples      The numsamples.
 * @param [in,out]  output  If non-null, the output.
 *
 * @return  An avi_error.
 */

avi_file::error avi_file_impl::read_sound_samples(int channel, std::uint32_t firstsample, std::uint32_t numsamples, std::int16_t *output)
{
	/* get the audio stream */
	int offset = 0;
	avi_stream *const stream = get_audio_stream(channel, offset);
	if (!stream)
		return error::INVALID_STREAM;

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

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

	/* determine bytes per sample */
	std::uint32_t const bytes_per_sample = stream->bytes_per_sample();

	/* loop until all samples have been extracted */
	while (numsamples > 0)
	{
		std::uint32_t chunkbase = 0, chunkend = 0, chunkid;
		std::uint32_t 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())
		{
			std::memset(output, 0, numsamples * 2);
			break;
		}

		/* expand the tempbuffer to hold the data if necessary */
		error avierr = expand_tempbuffer(stream->chunk(chunknum).length);
		if (avierr != error::NONE)
			return avierr;

		/* read in the data */
		std::error_condition const filerr = m_file->read(&m_tempbuffer[0], stream->chunk(chunknum).offset, stream->chunk(chunknum).length, bytes_read);
		if (filerr || bytes_read != stream->chunk(chunknum).length)
			return error::READ_ERROR;

		/* validate this is good data */
		chunkid = fetch_32bits(&m_tempbuffer[0]);
		if (chunkid != get_chunkid_for_stream(stream))
			return error::INVALID_DATA;

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

		/* extract 16-bit samples from the chunk */
		if (stream->samplebits() == 16)
		{
			const auto *base = reinterpret_cast<const std::int16_t *>(&m_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 std::uint8_t *base = &m_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 error::NONE;
}


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

/**
 * @fn  avi_error avi_append_video_frame(avi_file *file, bitmap_yuy16 &bitmap)
 *
 * @brief   Avi append video frame.
 *
 * @param [in,out]  file    If non-null, the file.
 * @param [in,out]  bitmap  The bitmap.
 *
 * @return  An avi_error.
 */

avi_file::error avi_file_impl::append_video_frame(bitmap_yuy16 &bitmap)
{
	avi_stream *const stream = get_video_stream();
	error avierr;
	std::uint32_t 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 error::UNSUPPORTED_VIDEO_FORMAT;

	/* write out any sound data first */
	avierr = soundbuf_write_chunk(stream->chunks());
	if (avierr != error::NONE)
		return avierr;

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

	/* now compress the data */
	avierr = stream->yuy16_compress_to_yuy(bitmap, &m_tempbuffer[0], maxlength);
	if (avierr != error::NONE)
		return avierr;

	/* write the data */
	avierr = chunk_write(get_chunkid_for_stream(stream), &m_tempbuffer[0], maxlength);
	if (avierr != error::NONE)
		return avierr;

	/* set the info for this new chunk */
	avierr = stream->set_chunk_info(stream->chunks(), m_writeoffs - maxlength - 8, maxlength + 8);
	if (avierr != error::NONE)
		return avierr;

	stream->set_samples(m_info.video_numsamples = stream->chunks());

	return error::NONE;
}


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

/**
 * @fn  avi_error avi_append_video_frame(avi_file *file, bitmap_rgb32 &bitmap)
 *
 * @brief   Avi append video frame.
 *
 * @param [in,out]  file    If non-null, the file.
 * @param [in,out]  bitmap  The bitmap.
 *
 * @return  An avi_error.
 */

avi_file::error avi_file_impl::append_video_frame(bitmap_rgb32 &bitmap)
{
	avi_stream *const stream = get_video_stream();
	error avierr;
	std::uint32_t maxlength;

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

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

	/* write out any sound data first */
	avierr = soundbuf_write_chunk(stream->chunks());
	if (avierr != error::NONE)
		return avierr;

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

	/* copy the RGB data to the destination */
	avierr = stream->rgb32_compress_to_rgb(bitmap, &m_tempbuffer[0], maxlength);
	if (avierr != error::NONE)
		return avierr;

	/* write the data */
	avierr = chunk_write(get_chunkid_for_stream(stream), &m_tempbuffer[0], maxlength);
	if (avierr != error::NONE)
		return avierr;

	/* set the info for this new chunk */
	avierr = stream->set_chunk_info(stream->chunks(), m_writeoffs - maxlength - 8, maxlength + 8);
	if (avierr != error::NONE)
		return avierr;

	stream->set_samples(m_info.video_numsamples = stream->chunks());

	return error::NONE;
}


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

/**
 * @fn  avi_error avi_append_sound_samples(avi_file *file, int channel, const std::int16_t *samples, std::uint32_t numsamples, std::uint32_t sampleskip)
 *
 * @brief   Avi append sound samples.
 *
 * @param [in,out]  file    If non-null, the file.
 * @param   channel         The channel.
 * @param   samples         The samples.
 * @param   numsamples      The numsamples.
 * @param   sampleskip      The sampleskip.
 *
 * @return  An avi_error.
 */

avi_file::error avi_file_impl::append_sound_samples(int channel, const std::int16_t *samples, std::uint32_t numsamples, std::uint32_t sampleskip)
{
	std::uint32_t sampoffset = m_soundbuf_chansamples[channel];
	std::uint32_t sampnum;

	/* see if we have enough room in the buffer */
	if (sampoffset + numsamples > m_soundbuf_samples)
		return error::EXCEEDED_SOUND_BUFFER;

	/* append samples to the buffer in little-endian format */
	for (sampnum = 0; sampnum < numsamples; sampnum++)
	{
		std::int16_t data = *samples++;
		samples += sampleskip;
		data = little_endianize_int16(data);
		m_soundbuf[sampoffset++ * m_info.audio_channels + channel] = data;
	}
	m_soundbuf_chansamples[channel] = sampoffset;

	/* flush any full sound chunks to disk */
	return soundbuf_flush(true);
}


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

/**
 * @fn  static avi_error read_chunk_data(avi_file *file, const avi_chunk *chunk, std::uint8_t **buffer)
 *
 * @brief   Reads chunk data.
 *
 * @param [in,out]  file    If non-null, the file.
 * @param   chunk           The chunk.
 * @param [in,out]  buffer  If non-null, the buffer.
 *
 * @return  The chunk data.
 */

avi_file::error avi_file_impl::read_chunk_data(avi_chunk const &chunk, std::unique_ptr<std::uint8_t []> &buffer)
{
	// allocate memory for the data
	buffer.reset(new (std::nothrow) std::uint8_t[chunk.size]);
	if (!buffer)
		return error::NO_MEMORY;

	// read from the file
	std::uint32_t bytes_read;
	std::error_condition const filerr = m_file->read(&buffer[0], chunk.offset + 8, chunk.size, bytes_read);
	if (filerr || bytes_read != chunk.size)
	{
		buffer.reset();
		return error::READ_ERROR;
	}

	return error::NONE;
}


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

/**
 * @fn  static avi_error get_first_chunk(avi_file *file, const avi_chunk *parent, avi_chunk *newchunk)
 *
 * @brief   Gets the first chunk.
 *
 * @param [in,out]  file        If non-null, the file.
 * @param   parent              The parent.
 * @param [in,out]  newchunk    If non-null, the newchunk.
 *
 * @return  The first chunk.
 */

avi_file::error avi_file_impl::get_first_chunk(avi_chunk const *parent, avi_chunk &newchunk)
{
	std::uint64_t startoffset = (parent != nullptr && parent->type != 0) ? parent->offset + 12 : 0;
	if (parent != nullptr && parent->type != CHUNKTYPE_LIST && parent->type != CHUNKTYPE_RIFF && parent->type != 0)
		return error::INVALID_DATA;
	return get_next_chunk_internal(parent, newchunk, startoffset);
}


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

/**
 * @fn  static avi_error get_next_chunk(avi_file *file, const avi_chunk *parent, avi_chunk *newchunk)
 *
 * @brief   Gets the next chunk.
 *
 * @param [in,out]  file        If non-null, the file.
 * @param   parent              The parent.
 * @param [in,out]  newchunk    If non-null, the newchunk.
 *
 * @return  The next chunk.
 */

avi_file::error avi_file_impl::get_next_chunk(avi_chunk const *parent, avi_chunk &newchunk)
{
	std::uint64_t nextoffset = newchunk.offset + 8 + newchunk.size + (newchunk.size & 1);
	return get_next_chunk_internal(parent, newchunk, nextoffset);
}


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

/**
 * @fn  static avi_error find_first_chunk(avi_file *file, std::uint32_t findme, const avi_chunk *container, avi_chunk *result)
 *
 * @brief   Searches for the first chunk.
 *
 * @param [in,out]  file    If non-null, the file.
 * @param   findme          The findme.
 * @param   container       The container.
 * @param [out] result      If non-null, the result.
 *
 * @return  The found chunk.
 */

avi_file::error avi_file_impl::find_first_chunk(std::uint32_t findme, const avi_chunk *container, avi_chunk &result)
{
	error avierr;

	for (avierr = get_first_chunk(container, result); avierr == error::NONE; avierr = get_next_chunk(container, result))
		if (result.type == findme)
			return error::NONE;

	return avierr;
}


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

/**
 * @fn  static avi_error find_next_chunk(avi_file *file, std::uint32_t findme, const avi_chunk *container, avi_chunk *result)
 *
 * @brief   Searches for the next chunk.
 *
 * @param [in,out]  file    If non-null, the file.
 * @param   findme          The findme.
 * @param   container       The container.
 * @param [out] result      If non-null, the result.
 *
 * @return  The found chunk.
 */

avi_file::error avi_file_impl::find_next_chunk(std::uint32_t findme, const avi_chunk *container, avi_chunk &result)
{
	error avierr;

	for (avierr = get_next_chunk(container, result); avierr == error::NONE; avierr = get_next_chunk(container, result))
		if (result.type == findme)
			return error::NONE;

	return avierr;
}


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

/**
 * @fn  static avi_error find_first_list(avi_file *file, std::uint32_t findme, const avi_chunk *container, avi_chunk *result)
 *
 * @brief   Searches for the first list.
 *
 * @param [in,out]  file    If non-null, the file.
 * @param   findme          The findme.
 * @param   container       The container.
 * @param [out] result      If non-null, the result.
 *
 * @return  The found list.
 */

avi_file::error avi_file_impl::find_first_list(std::uint32_t findme, const avi_chunk *container, avi_chunk &result)
{
	error avierr;

	for (avierr = find_first_chunk(CHUNKTYPE_LIST, container, result); avierr == error::NONE; avierr = find_next_chunk(CHUNKTYPE_LIST, container, result))
		if (result.listtype == findme)
			return error::NONE;

	return avierr;
}


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

/**
 * @fn  static avi_error find_next_list(avi_file *file, std::uint32_t findme, const avi_chunk *container, avi_chunk *result)
 *
 * @brief   Searches for the next list.
 *
 * @param [in,out]  file    If non-null, the file.
 * @param   findme          The findme.
 * @param   container       The container.
 * @param [out] result      If non-null, the result.
 *
 * @return  The found list.
 */

avi_file::error avi_file_impl::find_next_list(std::uint32_t findme, const avi_chunk *container, avi_chunk &result)
{
	error avierr;

	for (avierr = find_next_chunk(CHUNKTYPE_LIST, container, result); avierr == error::NONE; avierr = find_next_chunk(CHUNKTYPE_LIST, container, result))
		if (result.listtype == findme)
			return error::NONE;

	return avierr;
}


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

/**
 * @fn  static avi_error get_next_chunk_internal(avi_file *file, const avi_chunk *parent, avi_chunk *newchunk, std::uint64_t offset)
 *
 * @brief   Gets the next chunk internal.
 *
 * @param [in,out]  file        If non-null, the file.
 * @param   parent              The parent.
 * @param [in,out]  newchunk    If non-null, the newchunk.
 * @param   offset              The offset.
 *
 * @return  The next chunk internal.
 */

avi_file::error avi_file_impl::get_next_chunk_internal(const avi_chunk *parent, avi_chunk &newchunk, std::uint64_t offset)
{
	// nullptr parent implies the root
	if (!parent)
		parent = &m_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 error::END;

	// read the header
	std::uint8_t buffer[12];
	std::uint32_t bytesread;
	std::error_condition filerr = m_file->read(buffer, newchunk.offset, 8, bytesread);
	if (filerr || bytesread != 8)
		return error::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 = m_file->read(&buffer[8], newchunk.offset + 8, 4, bytesread);
		if (filerr || bytesread != 4)
			return error::INVALID_DATA;
		newchunk.listtype = fetch_32bits(&buffer[8]);
	}

	return error::NONE;
}


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

/**
 * @fn  static avi_error read_movie_data(avi_file *file)
 *
 * @brief   Reads movie data.
 *
 * @param [in,out]  file    If non-null, the file.
 *
 * @return  The movie data.
 */

avi_file::error avi_file_impl::read_movie_data()
{
	avi_chunk riff, hdrl, avih, strl, strh, strf, indx, movi, idx1;
	error avierr;

	/* find the RIFF chunk */
	avierr = find_first_chunk(CHUNKTYPE_RIFF, nullptr, riff);
	if (avierr != error::NONE)
		return avierr;

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

	/* find the hdrl LIST chunk within the RIFF */
	avierr = find_first_list(LISTTYPE_HDRL, &riff, hdrl);
	if (avierr != error::NONE)
		return avierr;

	/* find the avih chunk */
	avierr = find_first_chunk(CHUNKTYPE_AVIH, &hdrl, avih);
	if (avierr != error::NONE)
		return avierr;

	/* parse the avih chunk */
	avierr = parse_avih_chunk(avih);
	if (avierr != error::NONE)
		return avierr;

	/* loop over strl LIST chunks */
	int strindex = 0;
	for (avierr = find_first_list(LISTTYPE_STRL, &hdrl, strl); avierr == error::NONE; avierr = find_next_list(LISTTYPE_STRL, &hdrl, strl))
	{
		/* if we have too many, it's a bad file */
		if (strindex >= m_streams.size())
			return avierr;

		/* find the strh chunk */
		avierr = find_first_chunk(CHUNKTYPE_STRH, &strl, strh);
		if (avierr != error::NONE)
			return avierr;

		/* parse the data */
		avierr = parse_strh_chunk(m_streams[strindex], strh);
		if (avierr != error::NONE)
			return avierr;

		/* find the strf chunk */
		avierr = find_first_chunk(CHUNKTYPE_STRF, &strl, strf);
		if (avierr != error::NONE)
			return avierr;

		/* parse the data */
		avierr = parse_strf_chunk(m_streams[strindex], strf);
		if (avierr != error::NONE)
			return avierr;

		/* find the indx chunk, if present */
		avierr = find_first_chunk(CHUNKTYPE_INDX, &strl, indx);
		if (avierr == error::NONE)
			avierr = parse_indx_chunk(m_streams[strindex], indx);

		/* next stream */
		strindex++;
	}

	/* normalize the error after parsing the stream headers */
	if (avierr == error::END)
		avierr = error::NONE;
	if (avierr != error::NONE)
		return avierr;

	/* find the base of the movi data */
	avierr = find_first_list(LISTTYPE_MOVI, &riff, movi);
	if (avierr != error::NONE)
		return avierr;

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

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


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

/**
 * @fn  static avi_error extract_movie_info(avi_file *file)
 *
 * @brief   Extracts the movie information described by file.
 *
 * @param [in,out]  file    If non-null, the file.
 *
 * @return  The extracted movie information.
 */

avi_file::error avi_file_impl::extract_movie_info()
{
	avi_stream *stream;
	int offset;

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

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

	/* now make sure all other audio streams are valid */
	while (nullptr != (stream = get_audio_stream(m_info.audio_channels, offset)))
	{
		/* get the stream info */
		m_info.audio_channels++;

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

	return error::NONE;
}


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

/**
 * @fn  static avi_error parse_avih_chunk(avi_file *file, avi_chunk *avih)
 *
 * @brief   Parse avih chunk.
 *
 * @param [in,out]  file    If non-null, the file.
 * @param [in,out]  avih    If non-null, the avih.
 *
 * @return  An avi_error.
 */

avi_file::error avi_file_impl::parse_avih_chunk(avi_chunk const &avih)
{
	/* read the data */
	std::unique_ptr<std::uint8_t []> chunkdata;
	error avierr = read_chunk_data(avih, chunkdata);
	if (avierr != error::NONE)
		return avierr;

	/* extract the data */
	std::uint32_t const streams = fetch_32bits(&chunkdata[24]);

	/* allocate memory for the streams */
	m_streams.clear();
	m_streams.resize(streams);

	return avierr;
}


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

/**
 * @fn  static avi_error parse_strh_chunk(avi_file *file, avi_stream *stream, avi_chunk *strh)
 *
 * @brief   Parse strh chunk.
 *
 * @param [in,out]  file    If non-null, the file.
 * @param [in,out]  stream  If non-null, the stream.
 * @param [in,out]  strh    If non-null, the strh.
 *
 * @return  An avi_error.
 */

avi_file::error avi_file_impl::parse_strh_chunk(avi_stream &stream, avi_chunk const &strh)
{
	/* read the data */
	std::unique_ptr<std::uint8_t []> chunkdata;
	error const avierr = read_chunk_data(strh, chunkdata);
	if (avierr != error::NONE)
		return avierr;

	/* extract the data */
	return stream.set_strh_data(&chunkdata[0], strh.size);
}


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

/**
 * @fn  static avi_error parse_strf_chunk(avi_file *file, avi_stream *stream, avi_chunk *strf)
 *
 * @brief   Parse strf chunk.
 *
 * @param [in,out]  file    If non-null, the file.
 * @param [in,out]  stream  If non-null, the stream.
 * @param [in,out]  strf    If non-null, the strf.
 *
 * @return  An avi_error.
 */

avi_file::error avi_file_impl::parse_strf_chunk(avi_stream &stream, avi_chunk const &strf)
{
	error avierr;

	/* read the data */
	std::unique_ptr<std::uint8_t []> chunkdata;
	avierr = read_chunk_data(strf, chunkdata);
	if (avierr != error::NONE)
		return avierr;

	return stream.set_strf_data(&chunkdata[0], strf.size);
}


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

/**
 * @fn  static avi_error parse_indx_chunk(avi_file *file, avi_stream *stream, avi_chunk *strf)
 *
 * @brief   Parse indx chunk.
 *
 * @param [in,out]  file    If non-null, the file.
 * @param [in,out]  stream  If non-null, the stream.
 * @param [in,out]  strf    If non-null, the strf.
 *
 * @return  An avi_error.
 */

avi_file::error avi_file_impl::parse_indx_chunk(avi_stream &stream, avi_chunk const &strf)
{
	error avierr;

	/* read the data */
	std::unique_ptr<std::uint8_t []> chunkdata;
	avierr = read_chunk_data(strf, chunkdata);
	if (avierr != error::NONE)
		return avierr;

	/* extract the data */
	std::uint16_t const longs_per_entry = fetch_16bits(&chunkdata[0]);
	//subtype = chunkdata[2];
	std::uint8_t const type = chunkdata[3];
	std::uint32_t const entries = fetch_32bits(&chunkdata[4]);
	//id = fetch_32bits(&chunkdata[8]);
	std::uint64_t const 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 error::INVALID_DATA;

		/* loop over entries and create subchunks for each */
		for (std::uint32_t entry = 0; entry < entries; entry++)
		{
			std::uint8_t const *const base = &chunkdata[24 + entry * 16];
			avi_chunk subchunk;

			/* go read the subchunk */
			subchunk.offset = fetch_64bits(&base[0]);
			std::uint32_t bytes_read;
			std::uint8_t buffer[8];
			std::error_condition const filerr = m_file->read(buffer, subchunk.offset, sizeof(buffer), bytes_read);
			if (filerr || bytes_read != sizeof(buffer))
			{
				avierr = error::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(stream, subchunk);
			if (avierr != error::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 error::INVALID_DATA;

		/* loop over entries and parse out the data */
		for (std::uint32_t entry = 0; entry < entries; entry++)
		{
			const std::uint8_t *base = &chunkdata[24 + entry * 4 * longs_per_entry];
			std::uint32_t offset = fetch_32bits(&base[0]);
			std::uint32_t size = fetch_32bits(&base[4]) & 0x7fffffff;  // bit 31 == NOT a keyframe

			/* set the info for this chunk and advance */
			avierr = stream.set_chunk_info(stream.chunks(), baseoffset + offset - 8, size + 8);
			if (avierr != error::NONE)
				break;
		}
	}

	return avierr;
}


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

/**
 * @fn  static avi_error parse_idx1_chunk(avi_file *file, std::uint64_t baseoffset, avi_chunk *idx1)
 *
 * @brief   Parse index 1 chunk.
 *
 * @param [in,out]  file    If non-null, the file.
 * @param   baseoffset      The baseoffset.
 * @param [in,out]  idx1    If non-null, the first index.
 *
 * @return  An avi_error.
 */

avi_file::error avi_file_impl::parse_idx1_chunk(std::uint64_t baseoffset, avi_chunk const &idx1)
{
	error avierr;

	/* read the data */
	std::unique_ptr<std::uint8_t []> chunkdata;
	avierr = read_chunk_data(idx1, chunkdata);
	if (avierr != error::NONE)
		return avierr;

	/* loop over entries */
	std::uint32_t const entries = idx1.size / 16;
	for (std::uint32_t entry = 0; entry < entries; entry++)
	{
		std::uint8_t const *const base = &chunkdata[entry * 16];
		std::uint32_t const chunkid = fetch_32bits(&base[0]);
		std::uint32_t const offset = fetch_32bits(&base[8]);
		std::uint32_t const size = fetch_32bits(&base[12]);
		int streamnum;

		/* determine the stream index */
		streamnum = ((chunkid >> 8) & 0xff) - '0';
		streamnum += 10 * ((chunkid & 0xff) - '0');
		if (streamnum >= m_streams.size())
			return error::INVALID_DATA;
		avi_stream &stream = m_streams[streamnum];

		/* set the appropriate entry */
		avierr = stream.set_chunk_info(stream.chunks(), baseoffset + offset, size + 8);
		if (avierr != error::NONE)
			return avierr;
	}

	return avierr;
}


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

/**
 * @fn  static avi_error chunk_open(avi_file *file, std::uint32_t type, std::uint32_t listtype, std::uint32_t estlength)
 *
 * @brief   Queries if a given chunk open.
 *
 * @param [in,out]  file    If non-null, the file.
 * @param   type            The type.
 * @param   listtype        The listtype.
 * @param   estlength       The estlength.
 *
 * @return  An avi_error.
 */

avi_file::error avi_file_impl::chunk_open(std::uint32_t type, std::uint32_t listtype, std::uint32_t estlength)
{
	/* if we're out of stack entries, bail */
	if (m_chunksp >= m_chunkstack.size())
		return error::STACK_TOO_DEEP;
	avi_chunk &chunk = m_chunkstack[m_chunksp++];

	/* set up the chunk information */
	chunk.offset = m_writeoffs;
	chunk.size = estlength;
	chunk.type = type;
	chunk.listtype = listtype;

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

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

		/* write the header */
		std::uint32_t written;
		std::error_condition const filerr = m_file->write(buffer, m_writeoffs, sizeof(buffer), written);
		if (filerr || written != sizeof(buffer))
			return error::WRITE_ERROR;
		m_writeoffs += written;
	}

	/* list types */
	else
	{
		std::uint8_t 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 */
		std::uint32_t written;
		std::error_condition const filerr = m_file->write(buffer, m_writeoffs, sizeof(buffer), written);
		if (filerr || written != sizeof(buffer))
			return error::WRITE_ERROR;
		m_writeoffs += written;
	}

	return error::NONE;
}


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

/**
 * @fn  static avi_error chunk_close(avi_file *file)
 *
 * @brief   Chunk close.
 *
 * @param [in,out]  file    If non-null, the file.
 *
 * @return  An avi_error.
 */

avi_file::error avi_file_impl::chunk_close()
{
	avi_chunk const &chunk = m_chunkstack[--m_chunksp];
	std::uint64_t const chunksize = m_writeoffs - (chunk.offset + 8);

	/* error if we don't fit into 32 bits */
	if (chunksize != std::uint32_t(chunksize))
		return error::INVALID_DATA;

	/* write the final size if it is different from the guess */
	if (chunk.size != chunksize)
	{
		std::uint8_t buffer[4];

		put_32bits(&buffer[0], std::uint32_t(chunksize));
		std::uint32_t written;
		std::error_condition const filerr = m_file->write(buffer, chunk.offset + 4, sizeof(buffer), written);
		if (filerr || written != sizeof(buffer))
			return error::WRITE_ERROR;
	}

	/* round up to the next word */
	m_writeoffs += chunksize & 1;

	return error::NONE;
}


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

/**
 * @fn  static avi_error chunk_write(avi_file *file, std::uint32_t type, const void *data, std::uint32_t length)
 *
 * @brief   Chunk write.
 *
 * @param [in,out]  file    If non-null, the file.
 * @param   type            The type.
 * @param   data            The data.
 * @param   length          The length.
 *
 * @return  An avi_error.
 */

avi_file::error avi_file_impl::chunk_write(std::uint32_t type, const void *data, std::uint32_t length)
{
	error avierr;

	/* if we are the first RIFF, we must reserve enough space for the IDX chunk */
	std::uint32_t const idxreserve = (m_riffbase == 0 && type != CHUNKTYPE_IDX1) ? compute_idx1_size() : 0;

	/* 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 ((m_writeoffs >= m_riffbase) && ((m_writeoffs + length + idxreserve - m_riffbase) >= MAX_RIFF_SIZE))
	{
		/* close the movi list */
		avierr = chunk_close();
		if (avierr != error::NONE)
			return avierr;

		/* write the idx1 chunk if this is the first */
		if (m_riffbase == 0)
		{
			avierr = write_idx1_chunk();
			if (avierr != error::NONE)
				return avierr;
		}

		/* close the RIFF */
		avierr = chunk_close();
		if (avierr != error::NONE)
			return avierr;

		/* open a new RIFF */
		m_riffbase = m_writeoffs;
		avierr = chunk_open(CHUNKTYPE_RIFF, LISTTYPE_AVIX, 0);
		if (avierr != error::NONE)
			return avierr;

		/* open a nested movi list */
		m_saved_movi_offset = m_writeoffs;
		avierr = chunk_open(CHUNKTYPE_LIST, LISTTYPE_MOVI, 0);
		if (avierr != error::NONE)
			return avierr;
	}

	/* open the chunk */
	avierr = chunk_open(type, 0, length);
	if (avierr != error::NONE)
		return avierr;

	/* write the data */
	std::uint32_t written;
	std::error_condition const filerr = m_file->write(data, m_writeoffs, length, written);
	if (filerr || written != length)
		return error::WRITE_ERROR;
	m_writeoffs += written;

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


/*-------------------------------------------------
    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
-------------------------------------------------*/

/**
 * @fn  static avi_error chunk_overwrite(avi_file *file, std::uint32_t type, const void *data, std::uint32_t length, std::uint64_t *offset, int initial_write)
 *
 * @brief   Chunk overwrite.
 *
 * @param [in,out]  file    If non-null, the file.
 * @param   type            The type.
 * @param   data            The data.
 * @param   length          The length.
 * @param [in,out]  offset  If non-null, the offset.
 * @param   initial_write   The initial write.
 *
 * @return  An avi_error.
 */

avi_file::error avi_file_impl::chunk_overwrite(std::uint32_t type, const void *data, std::uint32_t length, std::uint64_t &offset, bool initial_write)
{
	std::uint64_t savedoffset = 0;

	/* if this is our initial write, save the offset */
	if (initial_write)
		offset = m_writeoffs;

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

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

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

	return avierr;
}


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

/**
 * @fn  static avi_error write_initial_headers(avi_file *file)
 *
 * @brief   Writes an initial headers.
 *
 * @param [in,out]  file    If non-null, the file.
 *
 * @return  An avi_error.
 */

avi_file::error avi_file_impl::write_initial_headers()
{
	error avierr;

	/* reset the write pointer */
	m_writeoffs = 0;

	/* open a RIFF chunk */
	avierr = chunk_open(CHUNKTYPE_RIFF, LISTTYPE_AVI, 0);
	if (avierr != error::NONE)
		return avierr;

	/* open a hdlr LIST */
	avierr = chunk_open(CHUNKTYPE_LIST, LISTTYPE_HDRL, 0);
	if (avierr != error::NONE)
		return avierr;

	/* write an avih chunk */
	avierr = write_avih_chunk(true);
	if (avierr != error::NONE)
		return avierr;

	/* for each stream, write a strl LIST */
	for (int strnum = 0; strnum < m_streams.size(); strnum++)
	{
		/* open a strl LIST */
		avierr = chunk_open(CHUNKTYPE_LIST, LISTTYPE_STRL, 0);
		if (avierr != error::NONE)
			return avierr;

		/* write the strh chunk */
		avierr = write_strh_chunk(m_streams[strnum], true);
		if (avierr != error::NONE)
			return avierr;

		/* write the strf chunk */
		avierr = write_strf_chunk(m_streams[strnum]);
		if (avierr != error::NONE)
			return avierr;

		/* write the indx chunk */
		avierr = write_indx_chunk(m_streams[strnum], true);
		if (avierr != error::NONE)
			return avierr;

		/* close the strl LIST */
		avierr = chunk_close();
		if (avierr != error::NONE)
			return avierr;
	}

	/* close the hdlr LIST */
	avierr = chunk_close();
	if (avierr != error::NONE)
		return avierr;

	/* open a movi LIST */
	m_saved_movi_offset = m_writeoffs;
	avierr = chunk_open(CHUNKTYPE_LIST, LISTTYPE_MOVI, 0);
	if (avierr != error::NONE)
		return avierr;

	return avierr;
}


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

/**
 * @fn  static avi_error write_avih_chunk(avi_file *file, int initial_write)
 *
 * @brief   Writes an avih chunk.
 *
 * @param [in,out]  file    If non-null, the file.
 * @param   initial_write   The initial write.
 *
 * @return  An avi_error.
 */

avi_file::error avi_file_impl::write_avih_chunk(bool initial_write)
{
	avi_stream *video = get_video_stream();
	std::uint8_t buffer[56];

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

	put_32bits(&buffer[0], 1000000 * std::int64_t(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], m_streams.size());          /* dwStreams */
	put_32bits(&buffer[32], video->width());            /* dwWidth */
	put_32bits(&buffer[36], video->height());           /* dwHeight */

	/* (over)write the chunk */
	return chunk_overwrite(CHUNKTYPE_AVIH, buffer, sizeof(buffer), m_saved_avih_offset, initial_write);
}


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

/**
 * @fn  static avi_error write_strh_chunk(avi_file *file, avi_stream *stream, int initial_write)
 *
 * @brief   Writes a strh chunk.
 *
 * @param [in,out]  file    If non-null, the file.
 * @param [in,out]  stream  If non-null, the stream.
 * @param   initial_write   The initial write.
 *
 * @return  An avi_error.
 */

avi_file::error avi_file_impl::write_strh_chunk(avi_stream &stream, bool initial_write)
{
	std::uint8_t buffer[56];

	/* reset the buffer */
	std::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.bytes_per_sample());
		put_32bits(&buffer[44],                         /* dwSampleSize */
					stream.bytes_per_sample());
	}

	/* write the chunk */
	return chunk_overwrite(CHUNKTYPE_STRH, buffer, sizeof(buffer), stream.saved_strh_offset(), initial_write);
}


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

/**
 * @fn  static avi_error write_strf_chunk(avi_file *file, avi_stream *stream)
 *
 * @brief   Writes a strf chunk.
 *
 * @param [in,out]  file    If non-null, the file.
 * @param [in,out]  stream  If non-null, the stream.
 *
 * @return  An avi_error.
 */

avi_file::error avi_file_impl::write_strf_chunk(avi_stream const &stream)
{
	/* video stream */
	if (stream.type() == STREAMTYPE_VIDS)
	{
		std::uint8_t buffer[40];

		/* reset the buffer */
		std::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(CHUNKTYPE_STRF, buffer, sizeof(buffer));
	}

	/* audio stream */
	if (stream.type() == STREAMTYPE_AUDS)
	{
		std::uint8_t buffer[16];

		/* reset the buffer */
		std::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.bytes_per_sample());
		put_16bits(&buffer[12],                         /* nBlockAlign */
					stream.bytes_per_sample());
		put_16bits(&buffer[14], stream.samplebits());   /* wBitsPerSample */

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

	return error::INVALID_DATA;
}


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

/**
 * @fn  static avi_error write_indx_chunk(avi_file *file, avi_stream *stream, int initial_write)
 *
 * @brief   Writes an indx chunk.
 *
 * @param [in,out]  file    If non-null, the file.
 * @param [in,out]  stream  If non-null, the stream.
 * @param   initial_write   The initial write.
 *
 * @return  An avi_error.
 */

avi_file::error avi_file_impl::write_indx_chunk(avi_stream &stream, bool initial_write)
{
	std::uint8_t buffer[24 + 16 * MAX_AVI_SIZE_IN_GB / 4];
	std::uint32_t master_entries = 0;

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

	/* construct the chunk ID and index chunk ID */
	std::uint32_t const chunkid = get_chunkid_for_stream(&stream);
	std::uint32_t const indexchunkid = AVI_FOURCC('i', 'x', '0' + (&stream - &m_streams[0]) / 10, '0' + (&stream - &m_streams[0]) % 10);

	/* loop over chunks of 4GB and write out indexes for them first */
	if (!initial_write && m_riffbase != 0)
	{
		for (std::uint64_t currentbase = 0; currentbase < m_writeoffs; currentbase += FOUR_GB)
		{
			std::uint64_t const currentend = currentbase + FOUR_GB;
			std::uint32_t chunks_this_index = 0;
			std::uint32_t bytes_this_index = 0;

			/* count chunks that are in this region */
			for (std::uint32_t 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;

			if (master_entries >= MAX_AVI_SIZE_IN_GB / 4)
				return error::WRITE_ERROR;

			/* allocate memory */
			std::unique_ptr<std::uint8_t []> tempbuf;
			try { tempbuf.reset(new std::uint8_t[24 + 8 * chunks_this_index]); }
			catch (...) { return error::NO_MEMORY; }
			std::memset(&tempbuf[0], 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 (std::uint32_t 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], m_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.bytes_per_sample());
			master_entries++;

			/* write the index */
			error const avierr = chunk_write(indexchunkid, &tempbuf[0], 24 + 8 * chunks_this_index);
			if (avierr != error::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((master_entries == 0) ? CHUNKTYPE_JUNK : CHUNKTYPE_INDX, buffer, sizeof(buffer), stream.saved_indx_offset(), initial_write);
}


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

/**
 * @fn  static avi_error write_idx1_chunk(avi_file *file)
 *
 * @brief   Writes an index 1 chunk.
 *
 * @param [in,out]  file    If non-null, the file.
 *
 * @return  An avi_error.
 */

avi_file::error avi_file_impl::write_idx1_chunk()
{
	std::uint32_t const tempbuflength = compute_idx1_size() - 8;
	std::uint32_t curchunk[2] = { 0 };

	/* allocate a temporary buffer */
	std::unique_ptr<std::uint8_t []> tempbuf;
	try { tempbuf.reset(new std::uint8_t[tempbuflength]); }
	catch (...) { return error::NO_MEMORY; }

	/* fill it in */
	for (std::uint32_t curoffset = 0; curoffset < tempbuflength; curoffset += 16)
	{
		std::uint64_t minoffset = ~std::uint64_t(0);
		int minstr = 0;

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

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

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

	/* write the chunk */
	return chunk_write(CHUNKTYPE_IDX1, &tempbuf[0], tempbuflength);
}


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

/**
 * @fn  static avi_error soundbuf_initialize(avi_file *file)
 *
 * @brief   Soundbuf initialize.
 *
 * @param [in,out]  file    If non-null, the file.
 *
 * @return  An avi_error.
 */

avi_file::error avi_file_impl::soundbuf_initialize()
{
	int offset;
	avi_stream *const audio = get_audio_stream(0, offset);
	avi_stream *const video = get_video_stream();

	/* we require a video stream */
	if (video == nullptr)
		return error::UNSUPPORTED_VIDEO_FORMAT;

	/* skip if no audio stream */
	if (audio == nullptr)
		return error::NONE;

	/* determine the number of samples we want in our buffer; 2 seconds should be enough */
	m_soundbuf_samples = m_info.audio_samplerate * SOUND_BUFFER_MSEC / 1000;

	/* allocate a buffer */
	m_soundbuf.clear();
	try { m_soundbuf.resize(m_soundbuf_samples * m_info.audio_channels, 0); }
	catch (...) { return error::NO_MEMORY; }

	/* determine the number of frames to be ahead (0.75secs) */
	m_soundbuf_frames = (std::uint64_t(video->rate()) * 75) / (std::uint64_t(video->scale()) * 100) + 1;
	return error::NONE;
}


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

/**
 * @fn  static avi_error soundbuf_write_chunk(avi_file *file, std::uint32_t framenum)
 *
 * @brief   Soundbuf write chunk.
 *
 * @param [in,out]  file    If non-null, the file.
 * @param   framenum        The framenum.
 *
 * @return  An avi_error.
 */

avi_file::error avi_file_impl::soundbuf_write_chunk(std::uint32_t framenum)
{
	int offset;
	avi_stream *const stream = get_audio_stream(0, offset);
	std::uint32_t length;

	/* skip if no audio stream */
	if (stream == nullptr)
		return error::NONE;

	/* determine the length of this chunk */
	if (framenum == 0)
		length = framenum_to_samplenum(m_soundbuf_frames);
	else
		length = framenum_to_samplenum(framenum + 1 + m_soundbuf_frames) - framenum_to_samplenum(framenum + m_soundbuf_frames);
	length *= stream->channels() * sizeof(std::int16_t);

	/* then do the initial write */
	error const avierr = chunk_write(get_chunkid_for_stream(stream), &m_soundbuf[0], length);
	if (avierr != error::NONE)
		return avierr;

	/* set the info for this new chunk */
	return stream->set_chunk_info(stream->chunks(), m_writeoffs - length - 8, length + 8);
}


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

/**
 * @fn  static avi_error soundbuf_flush(avi_file *file, int only_flush_full)
 *
 * @brief   Soundbuf flush.
 *
 * @param [in,out]  file    If non-null, the file.
 * @param   only_flush_full The only flush full.
 *
 * @return  An avi_error.
 */

avi_file::error avi_file_impl::soundbuf_flush(bool only_flush_full)
{
	int offset;
	avi_stream *const stream = get_audio_stream(0, offset);
	std::int32_t channelsamples = m_soundbuf_samples;
	std::int32_t processedsamples = 0;
	std::uint32_t finalchunks;
	error avierr;
	std::uint32_t chunknum;
	std::uint32_t chunkid;

	/* skip if no stream */
	if (stream == nullptr)
		return error::NONE;

	/* get the chunk ID for this stream */
	chunkid = get_chunkid_for_stream(stream);
	std::uint32_t const bytes_per_sample = stream->channels() * sizeof(std::int16_t);
	finalchunks = stream->chunks();

	/* find out how many samples we've accumulated */
	for (int channel = 0; channel < stream->channels(); channel++)
		channelsamples = (std::min<std::int32_t>)(channelsamples, m_soundbuf_chansamples[channel]);

	/* loop over pending sound chunks */
	for (chunknum = m_soundbuf_chunks; chunknum < stream->chunks(); chunknum++)
	{
		avi_chunk_list &chunk = stream->chunk(chunknum);
		std::uint32_t const 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 > m_soundbuf_samples)
				return error::EXCEEDED_SOUND_BUFFER;
			std::memset(&m_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 = m_soundbuf_samples - chunksamples;
			std::memset(&m_soundbuf[processedsamples * stream->channels()], 0, chunksamples * bytes_per_sample);
			chunkid = CHUNKTYPE_JUNK;
			finalchunks--;
		}

		/* copy the sample data in */
		avierr = chunk_overwrite(chunkid, &m_soundbuf[processedsamples * stream->channels()], chunk.length - 8, chunk.offset, false);
		if (avierr != error::NONE)
			return avierr;

		/* add up the samples */
		if (channelsamples > chunksamples)
			m_info.audio_numsamples = stream->add_samples(chunksamples);
		else if (channelsamples > 0)
			m_info.audio_numsamples = stream->add_samples(channelsamples);

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

	/* if we have a non-zero offset, shift everything down */
	if (processedsamples > 0)
	{
		/* first account for the samples we processed */
		if (m_soundbuf_samples > processedsamples)
			std::memmove(&m_soundbuf[0], &m_soundbuf[processedsamples * stream->channels()], (m_soundbuf_samples - processedsamples) * bytes_per_sample);
		for (int channel = 0; channel < stream->channels(); channel++)
			m_soundbuf_chansamples[channel] -= processedsamples;
	}

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

	/* account for flushed chunks */
	m_soundbuf_chunks = chunknum;
	return error::NONE;
}


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

/**
 * @fn  static void printf_chunk_recursive(avi_file *file, avi_chunk *container, int indent)
 *
 * @brief   Printf chunk recursive.
 *
 * @param [in,out]  file        If non-null, the file.
 * @param [in,out]  container   If non-null, the container.
 * @param   indent              The indent.
 */

void avi_file_impl::printf_chunk_recursive(avi_chunk const *container, int indent)
{
	char size[20], offset[20];
	avi_chunk curchunk;
	error avierr;

	/* iterate over chunks in this container */
	for (avierr = get_first_chunk(container, curchunk); avierr == error::NONE; avierr = get_next_chunk(container, curchunk))
	{
		std::uint32_t chunksize = curchunk.size;
		bool recurse = false;

		u64toa(curchunk.size, size);
		u64toa(curchunk.offset, offset);
		printf("%*schunk = %c%c%c%c, size=%s (%s)\n", indent, "",
				std::uint8_t(curchunk.type >> 0),
				std::uint8_t(curchunk.type >> 8),
				std::uint8_t(curchunk.type >> 16),
				std::uint8_t(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, "",
						std::uint8_t(curchunk.listtype >> 0),
						std::uint8_t(curchunk.listtype >> 8),
						std::uint8_t(curchunk.listtype >> 16),
						std::uint8_t(curchunk.listtype >> 24));
				recurse = true;
				chunksize = 0;
				break;
		}

		/* print data within the chunk */
		if (chunksize > 0 && curchunk.size < 1024 * 1024)
		{
			std::unique_ptr<std::uint8_t []> data;

			/* read the data for a chunk */
			avierr = read_chunk_data(curchunk, data);
			if (avierr == error::NONE)
			{
				std::uint32_t const bytes = (std::min)(std::uint32_t(512), chunksize);
				for (std::uint32_t 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");
			}
		}

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

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

} // anonymous namespace



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

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

/**
 * @fn  avi_error avi_open(const char *filename, avi_file **file)
 *
 * @brief   Queries if a given avi open.
 *
 * @param   filename        Filename of the file.
 * @param [in,out]  file    If non-null, the file.
 *
 * @return  An avi_error.
 */

avi_file::error avi_file::open(std::string const &filename, ptr &file)
{
	/* open the file */
	osd_file::ptr f;
	std::uint64_t length;
	if (osd_file::open(filename, OPEN_FLAG_READ, f, length))
		return error::CANT_OPEN_FILE;

	/* allocate the file */
	std::unique_ptr<avi_file_impl> newfile;
	try { newfile = std::make_unique<avi_file_impl>(std::move(f), length); }
	catch (...) { return error::NO_MEMORY; }

	/* parse the data */
	error const avierr = newfile->read_movie_data();
	if (avierr != error::NONE)
		return avierr;

	file = std::move(newfile);
	return error::NONE;
}


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

/**
 * @fn  avi_error avi_create(const char *filename, const avi_movie_info *info, avi_file **file)
 *
 * @brief   Avi create.
 *
 * @param   filename        Filename of the file.
 * @param   info            The information.
 * @param [in,out]  file    If non-null, the file.
 *
 * @return  An avi_error.
 */

avi_file::error avi_file::create(std::string const &filename, movie_info const &info, ptr &file)
{
	/* 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 error::UNSUPPORTED_VIDEO_FORMAT;

	/* validate audio info */
	if (info.audio_format != 0 ||
		info.audio_channels > MAX_SOUND_CHANNELS ||
		info.audio_samplebits != 16)
		return error::UNSUPPORTED_AUDIO_FORMAT;

	/* open the file */
	osd_file::ptr f;
	std::uint64_t length;
	std::error_condition const filerr = osd_file::open(filename, OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS, f, length);
	if (filerr)
		return error::CANT_OPEN_FILE;

	/* allocate the file */
	error avierr;
	std::unique_ptr<avi_file_impl> newfile;
	try { newfile = std::make_unique<avi_file_impl>(std::move(f), info); }
	catch (...)
	{
		avierr = error::NO_MEMORY;
		goto error;
	}

	/* initialize the sound buffering */
	avierr = newfile->soundbuf_initialize();
	if (avierr != error::NONE)
		goto error;

	/* write the initial headers */
	avierr = newfile->write_initial_headers();
	if (avierr != error::NONE)
		goto error;

	file = std::move(newfile);
	return error::NONE;

error:
	f.reset();
	newfile.reset();
	osd_file::remove(filename);
	return avierr;
}


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

/**
 * @fn  const char *avi_error_string(avi_error err)
 *
 * @brief   Avi error string.
 *
 * @param   err The error.
 *
 * @return  null if it fails, else a char*.
 */

const char *avi_file::error_string(error err)
{
	switch (err)
	{
		case error::NONE:                       return "success";
		case error::END:                        return "hit end of file";
		case error::INVALID_DATA:               return "invalid data";
		case error::NO_MEMORY:                  return "out of memory";
		case error::READ_ERROR:                 return "read error";
		case error::WRITE_ERROR:                return "write error";
		case error::STACK_TOO_DEEP:             return "stack overflow";
		case error::UNSUPPORTED_FEATURE:        return "unsupported feature";
		case error::CANT_OPEN_FILE:             return "unable to open file";
		case error::INCOMPATIBLE_AUDIO_STREAMS: return "found incompatible audio streams";
		case error::INVALID_SAMPLERATE:         return "found invalid sample rate";
		case error::INVALID_STREAM:             return "invalid stream";
		case error::INVALID_FRAME:              return "invalid frame index";
		case error::INVALID_BITMAP:             return "invalid bitmap";
		case error::UNSUPPORTED_VIDEO_FORMAT:   return "unsupported video format";
		case error::UNSUPPORTED_AUDIO_FORMAT:   return "unsupported audio format";
		case error::EXCEEDED_SOUND_BUFFER:      return "sound buffer overflow";
		default:                                return "undocumented error";
	}
}


avi_file::avi_file()
{
}


avi_file::~avi_file()
{
}
