mame/src/lib/util/aviio.c
2007-12-17 15:19:59 +00:00

2745 lines
80 KiB
C

/***************************************************************************
aviio.c
AVI movie format parsing helpers.
Copyright (c) 1996-2007, Nicola Salmoria and the MAME Team.
Visit http://mamedev.org for licensing and usage restrictions.
***************************************************************************/
#include "aviio.h"
/***************************************************************************
CONSTANTS
***************************************************************************/
#define FILETYPE_READ 1
#define FILETYPE_CREATE 2
#define MAX_RIFF_SIZE (2UL * 1024 * 1024 * 1024 - 1024) /* just under 2GB */
#define MAX_AVI_SIZE_IN_GB (256)
#define FOUR_GB ((UINT64)1 << 32)
#define MAX_SOUND_CHANNELS 2
#define SOUND_BUFFER_MSEC 2000 /* microseconds of sond buffering */
#define CHUNKTYPE_RIFF AVI_FOURCC('R','I','F','F')
#define CHUNKTYPE_LIST AVI_FOURCC('L','I','S','T')
#define CHUNKTYPE_JUNK AVI_FOURCC('J','U','N','K')
#define CHUNKTYPE_AVIH AVI_FOURCC('a','v','i','h')
#define CHUNKTYPE_STRH AVI_FOURCC('s','t','r','h')
#define CHUNKTYPE_STRF AVI_FOURCC('s','t','r','f')
#define CHUNKTYPE_IDX1 AVI_FOURCC('i','d','x','1')
#define CHUNKTYPE_INDX AVI_FOURCC('i','n','d','x')
#define CHUNKTYPE_XXDB AVI_FOURCC(0x00,0x00,'d','b')
#define CHUNKTYPE_XXDC AVI_FOURCC(0x00,0x00,'d','c')
#define CHUNKTYPE_XXWB AVI_FOURCC(0x00,0x00,'w','b')
#define CHUNKTYPE_IXXX AVI_FOURCC('i','x',0x00,0x00)
#define CHUNKTYPE_XX_MASK AVI_FOURCC(0x00,0x00,0xff,0xff)
#define LISTTYPE_AVI AVI_FOURCC('A','V','I',' ')
#define LISTTYPE_AVIX AVI_FOURCC('A','V','I','X')
#define LISTTYPE_HDRL AVI_FOURCC('h','d','r','l')
#define LISTTYPE_STRL AVI_FOURCC('s','t','r','l')
#define LISTTYPE_MOVI AVI_FOURCC('m','o','v','i')
#define STREAMTYPE_VIDS AVI_FOURCC('v','i','d','s')
#define STREAMTYPE_AUDS AVI_FOURCC('a','u','d','s')
#define HANDLER_DIB AVI_FOURCC('D','I','B',' ')
#define HANDLER_HFYU AVI_FOURCC('h','f','y','u')
#define FORMAT_UYVY AVI_FOURCC('U','Y','V','Y')
#define FORMAT_VYUY AVI_FOURCC('V','Y','U','Y')
#define FORMAT_YUY2 AVI_FOURCC('Y','U','Y','2')
#define FORMAT_HFYU AVI_FOURCC('H','F','Y','U')
/* main AVI header files */
#define AVIF_HASINDEX 0x00000010
#define AVIF_MUSTUSEINDEX 0x00000020
#define AVIF_ISINTERLEAVED 0x00000100
#define AVIF_COPYRIGHTED 0x00010000
#define AVIF_WASCAPTUREFILE 0x00020000
/* index definitions */
#define AVI_INDEX_OF_INDEXES 0x00
#define AVI_INDEX_OF_CHUNKS 0x01
#define AVI_INDEX_IS_DATA 0x80
#define AVI_INDEX_2FIELD 0x01
/* HuffYUV definitions */
#define HUFFYUV_PREDICT_LEFT 0
#define HUFFYUV_PREDICT_GRADIENT 1
#define HUFFYUV_PREDICT_MEDIAN 2
#define HUFFYUV_PREDICT_DECORR 0x40
/***************************************************************************
TYPE DEFINITIONS
***************************************************************************/
typedef struct _avi_chunk avi_chunk;
struct _avi_chunk
{
UINT64 offset; /* file offset of chunk header */
UINT64 size; /* size of this chunk */
UINT32 type; /* type of this chunk */
UINT32 listtype; /* type of this list (if we are a list) */
};
typedef struct _avi_chunk_list avi_chunk_list;
struct _avi_chunk_list
{
UINT64 offset; /* offset in the file of header */
UINT32 length; /* length of the chunk including header */
};
typedef struct _huffyuv_table huffyuv_table;
struct _huffyuv_table
{
UINT8 shift[256]; /* bit shift amounts */
UINT32 bits[256]; /* bit match values */
UINT32 mask[256]; /* bit mask values */
UINT16 baselookup[65536]; /* base lookup table */
UINT16 * extralookup; /* extra lookup tables */
};
typedef struct _huffyuv_data huffyuv_data;
struct _huffyuv_data
{
UINT8 predictor; /* predictor */
huffyuv_table table[3]; /* array of tables */
};
typedef struct _avi_stream avi_stream;
struct _avi_stream
{
UINT32 type; /* subtype of stream */
UINT32 format; /* format of stream data */
UINT32 rate; /* timescale for stream */
UINT32 scale; /* duration of one sample in the stream */
UINT32 samples; /* number of samples */
avi_chunk_list * chunk; /* list of chunks */
UINT32 chunks; /* chunks currently known */
UINT32 chunksalloc; /* number of chunks allocated */
UINT32 width; /* width of video */
UINT32 height; /* height of video */
UINT32 depth; /* depth of video */
UINT8 interlace; /* interlace parameters */
huffyuv_data * huffyuv; /* huffyuv decompression data */
UINT16 channels; /* audio channels */
UINT16 samplebits; /* audio bits per sample */
UINT32 samplerate; /* audio sample rate */
/* only used when creating */
UINT64 saved_strh_offset; /* writeoffset of strh chunk */
UINT64 saved_indx_offset; /* writeoffset of indx chunk */
};
struct _avi_file
{
/* shared data */
osd_file * file; /* pointer to open file */
int type; /* type of access (read/create) */
avi_movie_info info; /* movie info structure */
UINT8 * tempbuffer; /* temporary buffer */
UINT32 tempbuffersize; /* size of the temporary buffer */
/* only used when reading */
int streams; /* number of streams */
avi_stream * stream; /* allocated array of stream information */
avi_chunk rootchunk; /* dummy root chunk that wraps the whole file */
/* only used when creating */
UINT64 writeoffs; /* current file write offset */
UINT64 riffbase; /* base of the current RIFF */
avi_chunk chunkstack[8]; /* stack of chunks we are writing */
int chunksp; /* stack pointer for the current chunk */
UINT64 saved_movi_offset; /* writeoffset of movi list */
UINT64 saved_avih_offset; /* writeoffset of avih chunk */
INT16 * soundbuf; /* buffer for sound data */
UINT32 soundbuf_samples; /* length of sound buffer in samples */
UINT32 soundbuf_chansamples[MAX_SOUND_CHANNELS]; /* samples in buffer for each channel */
UINT32 soundbuf_chunks; /* number of chunks completed so far */
UINT32 soundbuf_frames; /* number of frames ahead of the video */
};
/***************************************************************************
PROTOTYPES
***************************************************************************/
/* stream helpers */
/* core chunk read routines */
static avi_error get_first_chunk(avi_file *file, const avi_chunk *parent, avi_chunk *newchunk);
static avi_error get_next_chunk(avi_file *file, const avi_chunk *parent, avi_chunk *newchunk);
static avi_error find_first_chunk(avi_file *file, UINT32 findme, const avi_chunk *container, avi_chunk *result);
static avi_error find_next_chunk(avi_file *file, UINT32 findme, const avi_chunk *container, avi_chunk *result);
static avi_error get_next_chunk_internal(avi_file *file, const avi_chunk *parent, avi_chunk *newchunk, UINT64 offset);
static avi_error read_chunk_data(avi_file *file, const avi_chunk *chunk, UINT8 **buffer);
/* chunk read helpers */
static avi_error read_movie_data(avi_file *file);
static avi_error extract_movie_info(avi_file *file);
static avi_error parse_avih_chunk(avi_file *file, avi_chunk *avih);
static avi_error parse_strh_chunk(avi_file *file, avi_stream *stream, avi_chunk *strh);
static avi_error parse_strf_chunk(avi_file *file, avi_stream *stream, avi_chunk *strf);
static avi_error parse_indx_chunk(avi_file *file, avi_stream *stream, avi_chunk *strf);
static avi_error parse_idx1_chunk(avi_file *file, UINT64 baseoffset, avi_chunk *idx1);
/* core chunk write routines */
static avi_error chunk_open(avi_file *file, UINT32 type, UINT32 listtype, UINT32 estlength);
static avi_error chunk_close(avi_file *file);
static avi_error chunk_write(avi_file *file, UINT32 type, const void *data, UINT32 length);
static avi_error chunk_overwrite(avi_file *file, UINT32 type, const void *data, UINT32 length, UINT64 *offset, int initial_write);
/* chunk write helpers */
static avi_error write_initial_headers(avi_file *file);
static avi_error write_avih_chunk(avi_file *file, int initial_write);
static avi_error write_strh_chunk(avi_file *file, avi_stream *stream, int initial_write);
static avi_error write_strf_chunk(avi_file *file, avi_stream *stream);
static avi_error write_indx_chunk(avi_file *file, avi_stream *stream, int initial_write);
static avi_error write_idx1_chunk(avi_file *file);
/* sound buffering helpers */
static avi_error soundbuf_initialize(avi_file *file);
static avi_error soundbuf_write_chunk(avi_file *file, UINT32 framenum);
static avi_error soundbuf_flush(avi_file *file, int only_flush_full);
/* YUY helpers */
static avi_error yuv_decompress_to_yuy16(avi_stream *stream, const UINT8 *data, UINT32 numbytes, bitmap_t *bitmap);
static avi_error yuy16_compress_to_yuy(avi_stream *stream, const bitmap_t *bitmap, UINT8 *data, UINT32 numbytes);
/* HuffYUV helpers */
static avi_error huffyuv_extract_tables(avi_stream *stream, const UINT8 *chunkdata, UINT32 size);
static avi_error huffyuv_decompress_to_yuy16(avi_stream *stream, const UINT8 *data, UINT32 numbytes, bitmap_t *bitmap);
/* debugging */
static void printf_chunk_recursive(avi_file *file, avi_chunk *chunk, int indent);
/***************************************************************************
INLINE FUNCTIONS
***************************************************************************/
/*-------------------------------------------------
fetch_16bits - read 16 bits in LSB order
from the given buffer
-------------------------------------------------*/
INLINE UINT16 fetch_16bits(const UINT8 *data)
{
return data[0] | (data[1] << 8);
}
/*-------------------------------------------------
fetch_32bits - read 32 bits in LSB order
from the given buffer
-------------------------------------------------*/
INLINE UINT32 fetch_32bits(const UINT8 *data)
{
return data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24);
}
/*-------------------------------------------------
fetch_64bits - read 64 bits in LSB order
from the given buffer
-------------------------------------------------*/
INLINE UINT64 fetch_64bits(const UINT8 *data)
{
return (UINT64)data[0] | ((UINT64)data[1] << 8) |
((UINT64)data[2] << 16) | ((UINT64)data[3] << 24) |
((UINT64)data[4] << 32) | ((UINT64)data[5] << 40) |
((UINT64)data[6] << 48) | ((UINT64)data[7] << 56);
}
/*-------------------------------------------------
put_16bits - write 16 bits in LSB order
to the given buffer
-------------------------------------------------*/
INLINE void put_16bits(UINT8 *data, UINT16 value)
{
data[0] = value >> 0;
data[1] = value >> 8;
}
/*-------------------------------------------------
put_32bits - write 32 bits in LSB order
to the given buffer
-------------------------------------------------*/
INLINE void put_32bits(UINT8 *data, UINT32 value)
{
data[0] = value >> 0;
data[1] = value >> 8;
data[2] = value >> 16;
data[3] = value >> 24;
}
/*-------------------------------------------------
put_64bits - write 64 bits in LSB order
to the given buffer
-------------------------------------------------*/
INLINE void put_64bits(UINT8 *data, UINT64 value)
{
data[0] = value >> 0;
data[1] = value >> 8;
data[2] = value >> 16;
data[3] = value >> 24;
data[4] = value >> 32;
data[5] = value >> 40;
data[6] = value >> 48;
data[7] = value >> 56;
}
/*-------------------------------------------------
get_video_stream - return a pointer to the
video stream
-------------------------------------------------*/
INLINE avi_stream *get_video_stream(avi_file *file)
{
int streamnum;
/* find the video stream */
for (streamnum = 0; streamnum < file->streams; streamnum++)
if (file->stream[streamnum].type == STREAMTYPE_VIDS)
return &file->stream[streamnum];
return NULL;
}
/*-------------------------------------------------
get_audio_stream - return a pointer to the
audio stream for the 'n'th channel
-------------------------------------------------*/
INLINE avi_stream *get_audio_stream(avi_file *file, int channel, int *offset)
{
int streamnum;
/* find the audios stream */
for (streamnum = 0; streamnum < file->streams; streamnum++)
if (file->stream[streamnum].type == STREAMTYPE_AUDS)
{
if (channel < file->stream[streamnum].channels)
{
if (offset != NULL)
*offset = channel;
return &file->stream[streamnum];
}
channel -= file->stream[streamnum].channels;
}
return NULL;
}
/*-------------------------------------------------
set_stream_chunk_info - set the chunk info
for a given chunk within a stream
-------------------------------------------------*/
INLINE avi_error set_stream_chunk_info(avi_stream *stream, UINT32 index, UINT64 offset, UINT32 length)
{
/* if we need to allocate more, allocate more */
if (index >= stream->chunksalloc)
{
UINT32 newcount = MAX(index, stream->chunksalloc + 1000);
stream->chunk = realloc(stream->chunk, newcount * sizeof(stream->chunk[0]));
if (stream->chunk == NULL)
return AVIERR_NO_MEMORY;
stream->chunksalloc = newcount;
}
/* set the data */
stream->chunk[index].offset = offset;
stream->chunk[index].length = length;
/* update the number of chunks */
stream->chunks = MAX(stream->chunks, index + 1);
return AVIERR_NONE;
}
/*-------------------------------------------------
compute_idx1_size - compute the size of the
idx1 chunk
-------------------------------------------------*/
INLINE UINT32 compute_idx1_size(avi_file *file)
{
int chunks = 0;
int strnum;
/* count chunks in streams */
for (strnum = 0; strnum < file->streams; strnum++)
chunks += file->stream[strnum].chunks;
return chunks * 16 + 8;
}
/*-------------------------------------------------
get_chunkid_for_stream - make a chunk id for
a given stream
-------------------------------------------------*/
INLINE UINT32 get_chunkid_for_stream(avi_file *file, avi_stream *stream)
{
UINT32 chunkid;
chunkid = AVI_FOURCC('0' + (stream - file->stream) / 10, '0' + (stream - file->stream) % 10, 0, 0);
if (stream->type == STREAMTYPE_VIDS)
chunkid |= (stream->format == 0) ? CHUNKTYPE_XXDB : CHUNKTYPE_XXDC;
else if (stream->type == STREAMTYPE_AUDS)
chunkid |= CHUNKTYPE_XXWB;
return chunkid;
}
/*-------------------------------------------------
framenum_to_samplenum - given a video frame
number, get the first sample number
-------------------------------------------------*/
INLINE UINT32 framenum_to_samplenum(avi_file *file, UINT32 framenum)
{
return ((UINT64)file->info.audio_samplerate * (UINT64)framenum * (UINT64)file->info.video_sampletime + file->info.video_timescale - 1) / (UINT64)file->info.video_timescale;
}
/*-------------------------------------------------
expand_tempbuffer - expand the file's
tempbuffer if necessary to contain the
requested amount of data
-------------------------------------------------*/
INLINE avi_error expand_tempbuffer(avi_file *file, UINT32 length)
{
/* expand the tempbuffer to hold the data if necessary */
if (length > file->tempbuffersize)
{
file->tempbuffersize = 2 * length;
file->tempbuffer = realloc(file->tempbuffer, file->tempbuffersize);
if (file->tempbuffer == NULL)
return AVIERR_NO_MEMORY;
}
return AVIERR_NONE;
}
/***************************************************************************
IMPLEMENTATION
***************************************************************************/
/*-------------------------------------------------
avi_open - open an AVI movie file for read
-------------------------------------------------*/
avi_error avi_open(const char *filename, avi_file **file)
{
avi_file *newfile = NULL;
file_error filerr;
avi_error avierr;
UINT64 length;
/* allocate the file */
newfile = malloc(sizeof(*newfile));
if (newfile == NULL)
return AVIERR_NO_MEMORY;
memset(newfile, 0, sizeof(*newfile));
newfile->type = FILETYPE_READ;
/* open the file */
filerr = osd_open(filename, OPEN_FLAG_READ, &newfile->file, &length);
if (filerr != FILERR_NONE)
{
avierr = AVIERR_CANT_OPEN_FILE;
goto error;
}
/* make a root atom */
newfile->rootchunk.offset = 0;
newfile->rootchunk.size = length;
newfile->rootchunk.type = 0;
newfile->rootchunk.listtype = 0;
/* parse the data */
avierr = read_movie_data(newfile);
if (avierr != AVIERR_NONE)
goto error;
*file = newfile;
return AVIERR_NONE;
error:
/* clean up after an error */
if (newfile != NULL)
{
if (newfile->file != NULL)
osd_close(newfile->file);
free(newfile);
}
return avierr;
}
/*-------------------------------------------------
avi_create - create a new QuickTime movie file
-------------------------------------------------*/
avi_error avi_create(const char *filename, const avi_movie_info *info, avi_file **file)
{
avi_file *newfile = NULL;
file_error filerr;
avi_stream *stream;
avi_error avierr;
UINT64 length;
/* validate video info */
if ((info->video_format != 0 && info->video_format != FORMAT_UYVY && info->video_format != FORMAT_VYUY && info->video_format != FORMAT_YUY2) ||
info->video_width == 0 ||
info->video_height == 0 ||
info->video_depth == 0 || info->video_depth % 8 != 0)
return AVIERR_UNSUPPORTED_VIDEO_FORMAT;
/* validate audio info */
if (info->audio_format != 0 ||
info->audio_channels > MAX_SOUND_CHANNELS ||
info->audio_samplebits != 16)
return AVIERR_UNSUPPORTED_AUDIO_FORMAT;
/* allocate the file */
newfile = malloc(sizeof(*newfile));
if (newfile == NULL)
return AVIERR_NO_MEMORY;
memset(newfile, 0, sizeof(*newfile));
newfile->type = FILETYPE_CREATE;
/* open the file */
filerr = osd_open(filename, OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS, &newfile->file, &length);
if (filerr != FILERR_NONE)
{
avierr = AVIERR_CANT_OPEN_FILE;
goto error;
}
/* copy the movie info */
newfile->info = *info;
newfile->info.video_numsamples = 0;
newfile->info.audio_numsamples = 0;
/* allocate two streams */
newfile->stream = malloc(2 * sizeof(newfile->stream[0]));
if (newfile->stream == NULL)
{
avierr = AVIERR_NO_MEMORY;
goto error;
}
memset(newfile->stream, 0, 2 * sizeof(newfile->stream[0]));
/* initialize the video track */
stream = &newfile->stream[newfile->streams++];
stream->type = STREAMTYPE_VIDS;
stream->format = newfile->info.video_format;
stream->rate = newfile->info.video_timescale;
stream->scale = newfile->info.video_sampletime;
stream->width = newfile->info.video_width;
stream->height = newfile->info.video_height;
stream->depth = newfile->info.video_depth;
/* initialize the audio track */
if (newfile->info.audio_channels > 0)
{
stream = &newfile->stream[newfile->streams++];
stream->type = STREAMTYPE_AUDS;
stream->format = newfile->info.audio_format;
stream->rate = newfile->info.audio_timescale;
stream->scale = newfile->info.audio_sampletime;
stream->channels = newfile->info.audio_channels;
stream->samplebits = newfile->info.audio_samplebits;
stream->samplerate = newfile->info.audio_samplerate;
/* initialize the sound buffering */
avierr = soundbuf_initialize(newfile);
if (avierr != AVIERR_NONE)
goto error;
}
/* write the initial headers */
avierr = write_initial_headers(newfile);
*file = newfile;
return AVIERR_NONE;
error:
/* clean up after an error */
if (newfile != NULL)
{
if (newfile->stream != NULL)
free(newfile->stream);
if (newfile->file != NULL)
{
osd_close(newfile->file);
osd_rmfile(filename);
}
free(newfile);
}
return avierr;
}
/*-------------------------------------------------
avi_close - close a QuickTime movie file
-------------------------------------------------*/
avi_error avi_close(avi_file *file)
{
avi_error avierr = AVIERR_NONE;
int strnum;
/* if we're creating a new file, finalize it by writing out the non-media chunks */
if (file->type == FILETYPE_CREATE)
{
/* flush any pending sound data */
avierr = soundbuf_flush(file, FALSE);
/* close the movi chunk */
if (avierr == AVIERR_NONE)
avierr = chunk_close(file);
/* if this is the first RIFF chunk, write an idx1 */
if (avierr == AVIERR_NONE && file->riffbase == 0)
avierr = write_idx1_chunk(file);
/* close the RIFF chunk */
if (avierr == AVIERR_NONE)
avierr = chunk_close(file);
/* update the avih chunk */
if (avierr == AVIERR_NONE)
avierr = write_avih_chunk(file, FALSE);
/* update the strh and indx chunks for each stream */
for (strnum = 0; strnum < file->streams; strnum++)
{
if (avierr == AVIERR_NONE)
avierr = write_strh_chunk(file, &file->stream[strnum], FALSE);
if (avierr == AVIERR_NONE)
avierr = write_indx_chunk(file, &file->stream[strnum], FALSE);
}
}
/* close the file */
osd_close(file->file);
/* free the stream-specific data */
for (strnum = 0; strnum < file->streams; strnum++)
{
avi_stream *stream = &file->stream[strnum];
if (stream->huffyuv != NULL)
{
huffyuv_data *huffyuv = stream->huffyuv;
int table;
for (table = 0; table < ARRAY_LENGTH(huffyuv->table); table++)
if (huffyuv->table[table].extralookup != NULL)
free(huffyuv->table[table].extralookup);
free(huffyuv);
}
if (stream->chunk != NULL)
free(stream->chunk);
}
/* free the file itself */
if (file->soundbuf != NULL)
free(file->soundbuf);
if (file->stream != NULL)
free(file->stream);
if (file->tempbuffer != NULL)
free(file->tempbuffer);
free(file);
return avierr;
}
/*-------------------------------------------------
avi_printf_chunks - print the chunks in a file
-------------------------------------------------*/
void avi_printf_chunks(avi_file *file)
{
printf_chunk_recursive(file, &file->rootchunk, 0);
}
/*-------------------------------------------------
avi_error_string - get the error string for
an avi_error
-------------------------------------------------*/
const char *avi_error_string(avi_error err)
{
switch (err)
{
case AVIERR_NONE: return "success";
case AVIERR_END: return "hit end of file";
case AVIERR_INVALID_DATA: return "invalid data";
case AVIERR_NO_MEMORY: return "out of memory";
case AVIERR_READ_ERROR: return "read error";
case AVIERR_WRITE_ERROR: return "write error";
case AVIERR_STACK_TOO_DEEP: return "stack overflow";
case AVIERR_UNSUPPORTED_FEATURE: return "unsupported feature";
case AVIERR_CANT_OPEN_FILE: return "unable to open file";
case AVIERR_INCOMPATIBLE_AUDIO_STREAMS: return "found incompatible audio streams";
case AVIERR_INVALID_SAMPLERATE: return "found invalid sample rate";
case AVIERR_INVALID_STREAM: return "invalid stream";
case AVIERR_INVALID_FRAME: return "invalid frame index";
case AVIERR_INVALID_BITMAP: return "invalid bitmap";
case AVIERR_UNSUPPORTED_VIDEO_FORMAT: return "unsupported video format";
case AVIERR_UNSUPPORTED_AUDIO_FORMAT: return "unsupported audio format";
case AVIERR_EXCEEDED_SOUND_BUFFER: return "sound buffer overflow";
default: return "undocumented error";
}
}
/*-------------------------------------------------
avi_get_movie_info - return a pointer to the
movie info
-------------------------------------------------*/
const avi_movie_info *avi_get_movie_info(avi_file *file)
{
return &file->info;
}
/*-------------------------------------------------
avi_frame_to_sample - convert a frame index
to a sample index
-------------------------------------------------*/
UINT32 avi_first_sample_in_frame(avi_file *file, UINT32 framenum)
{
return framenum_to_samplenum(file, framenum);
}
/*-------------------------------------------------
avi_read_video_frame_yuy16 - read video data
for a particular frame from the AVI file,
converting to YUY16 format
-------------------------------------------------*/
avi_error avi_read_video_frame_yuy16(avi_file *file, UINT32 framenum, bitmap_t *bitmap)
{
avi_error avierr = AVIERR_NONE;
UINT32 bytes_read, chunkid;
file_error filerr;
avi_stream *stream;
/* get the video stream */
stream = get_video_stream(file);
if (stream == NULL)
return AVIERR_INVALID_STREAM;
/* validate our ability to handle the data */
if (stream->format != FORMAT_UYVY && stream->format != FORMAT_VYUY && stream->format != FORMAT_YUY2 && stream->format != FORMAT_HFYU)
return AVIERR_UNSUPPORTED_VIDEO_FORMAT;
/* assume one chunk == one frame */
if (framenum >= stream->chunks)
return AVIERR_INVALID_FRAME;
/* we only support YUY-style bitmaps (16bpp) */
if (bitmap->format != BITMAP_FORMAT_YUY16 || bitmap->width < stream->width || bitmap->height < stream->height)
return AVIERR_INVALID_BITMAP;
/* expand the tempbuffer to hold the data if necessary */
avierr = expand_tempbuffer(file, stream->chunk[framenum].length);
if (avierr != AVIERR_NONE)
return avierr;
/* read in the data */
filerr = osd_read(file->file, file->tempbuffer, stream->chunk[framenum].offset, stream->chunk[framenum].length, &bytes_read);
if (filerr != FILERR_NONE || bytes_read != stream->chunk[framenum].length)
return AVIERR_READ_ERROR;
/* validate this is good data */
chunkid = fetch_32bits(&file->tempbuffer[0]);
if (chunkid == get_chunkid_for_stream(file, stream))
{
/* HuffYUV-compressed */
if (stream->format == FORMAT_HFYU)
avierr = huffyuv_decompress_to_yuy16(stream, file->tempbuffer + 8, stream->chunk[framenum].length - 8, bitmap);
/* other YUV-compressed */
else
avierr = yuv_decompress_to_yuy16(stream, file->tempbuffer + 8, stream->chunk[framenum].length - 8, bitmap);
}
else
avierr = AVIERR_INVALID_DATA;
return avierr;
}
/*-------------------------------------------------
avi_read_sound_samples - read sound sample
data from an AVI file
-------------------------------------------------*/
avi_error avi_read_sound_samples(avi_file *file, int channel, UINT32 firstsample, UINT32 numsamples, INT16 *output)
{
avi_error avierr = AVIERR_NONE;
UINT32 bytes_per_sample;
file_error filerr;
avi_stream *stream;
int offset;
/* get the audio stream */
stream = get_audio_stream(file, channel, &offset);
if (stream == NULL)
return AVIERR_INVALID_STREAM;
/* validate our ability to handle the data */
if (stream->format != 0 || (stream->samplebits != 8 && stream->samplebits != 16))
return AVIERR_UNSUPPORTED_AUDIO_FORMAT;
/* verify we are in range */
if (firstsample >= stream->samples)
return AVIERR_INVALID_FRAME;
if (firstsample + numsamples > stream->samples)
numsamples = stream->samples - firstsample;
/* determine bytes per sample */
bytes_per_sample = (stream->samplebits / 8) * stream->channels;
/* loop until all samples have been extracted */
while (numsamples > 0)
{
UINT32 chunkbase = 0, chunkend = 0, chunkid;
UINT32 bytes_read, samples_this_chunk;
int chunknum, sampnum;
/* locate the chunk with the first sample */
for (chunknum = 0; chunknum < stream->chunks; chunknum++)
{
chunkend = chunkbase + (stream->chunk[chunknum].length - 8) / bytes_per_sample;
if (firstsample < chunkend)
break;
chunkbase = chunkend;
}
/* expand the tempbuffer to hold the data if necessary */
avierr = expand_tempbuffer(file, stream->chunk[chunknum].length);
if (avierr != AVIERR_NONE)
return avierr;
/* read in the data */
filerr = osd_read(file->file, file->tempbuffer, stream->chunk[chunknum].offset, stream->chunk[chunknum].length, &bytes_read);
if (filerr != FILERR_NONE || bytes_read != stream->chunk[chunknum].length)
return AVIERR_READ_ERROR;
/* validate this is good data */
chunkid = fetch_32bits(&file->tempbuffer[0]);
if (chunkid != get_chunkid_for_stream(file, stream))
return AVIERR_INVALID_DATA;
/* determine how many samples to copy */
samples_this_chunk = chunkend - firstsample;
samples_this_chunk = MIN(samples_this_chunk, numsamples);
/* extract 16-bit samples from the chunk */
if (stream->samplebits == 16)
{
const INT16 *base = (const INT16 *)(file->tempbuffer + 8);
base += stream->channels * (firstsample - chunkbase) + offset;
for (sampnum = 0; sampnum < samples_this_chunk; sampnum++)
{
*output++ = LITTLE_ENDIANIZE_INT16(*base);
base += stream->channels;
}
}
/* extract 8-bit samples from the chunk */
else if (stream->samplebits == 8)
{
const UINT8 *base = (const UINT8 *)(file->tempbuffer + 8);
base += stream->channels * (firstsample - chunkbase) + offset;
for (sampnum = 0; sampnum < samples_this_chunk; sampnum++)
{
*output++ = (*base << 8) - 0x8000;
base += stream->channels;
}
}
/* update our counters */
firstsample += samples_this_chunk;
numsamples -= samples_this_chunk;
}
return avierr;
}
/*-------------------------------------------------
avi_append_video_frame_yuy16 - append a frame
of video in YUY16 format
-------------------------------------------------*/
avi_error avi_append_video_frame_yuy16(avi_file *file, const bitmap_t *bitmap)
{
avi_stream *stream = get_video_stream(file);
avi_error avierr;
UINT32 maxlength;
/* double check bitmap format */
if (bitmap->format != BITMAP_FORMAT_YUY16)
return AVIERR_INVALID_BITMAP;
/* write out any sound data first */
avierr = soundbuf_write_chunk(file, stream->chunks);
if (avierr != AVIERR_NONE)
return avierr;
/* make sure we have enough room */
maxlength = 2 * stream->width * stream->height;
avierr = expand_tempbuffer(file, maxlength);
if (avierr != AVIERR_NONE)
return avierr;
/* now compress the data */
avierr = yuy16_compress_to_yuy(stream, bitmap, file->tempbuffer, maxlength);
if (avierr != AVIERR_NONE)
return avierr;
/* set the info for this new chunk */
avierr = set_stream_chunk_info(stream, stream->chunks, file->writeoffs, maxlength + 8);
if (avierr != AVIERR_NONE)
return avierr;
stream->samples = file->info.video_numsamples = stream->chunks;
/* write the data */
return chunk_write(file, get_chunkid_for_stream(file, stream), file->tempbuffer, maxlength);
}
/*-------------------------------------------------
avi_append_sound_samples - append sound
samples
-------------------------------------------------*/
avi_error avi_append_sound_samples(avi_file *file, int channel, const INT16 *samples, UINT32 numsamples)
{
UINT32 sampoffset = file->soundbuf_chansamples[channel];
UINT32 sampnum;
/* see if we have enough room in the buffer */
if (sampoffset + numsamples > file->soundbuf_samples)
return AVIERR_EXCEEDED_SOUND_BUFFER;
/* append samples to the buffer in little-endian format */
for (sampnum = 0; sampnum < numsamples; sampnum++)
{
INT16 data = *samples++;
data = LITTLE_ENDIANIZE_INT16(data);
file->soundbuf[sampoffset++ * file->info.audio_channels + channel] = data;
}
file->soundbuf_chansamples[channel] = sampoffset;
/* flush any full sound chunks to disk */
return soundbuf_flush(file, TRUE);
}
/*-------------------------------------------------
read_chunk_data - read a chunk's data into
memory
-------------------------------------------------*/
static avi_error read_chunk_data(avi_file *file, const avi_chunk *chunk, UINT8 **buffer)
{
file_error filerr;
UINT32 bytes_read;
/* allocate memory for the data */
*buffer = malloc(chunk->size);
if (*buffer == NULL)
return AVIERR_NO_MEMORY;
/* read from the file */
filerr = osd_read(file->file, *buffer, chunk->offset + 8, chunk->size, &bytes_read);
if (filerr != FILERR_NONE || bytes_read != chunk->size)
{
free(*buffer);
*buffer = NULL;
return AVIERR_READ_ERROR;
}
return AVIERR_NONE;
}
/*-------------------------------------------------
get_first_chunk - get information about the
first chunk in a container
-------------------------------------------------*/
static avi_error get_first_chunk(avi_file *file, const avi_chunk *parent, avi_chunk *newchunk)
{
UINT64 startoffset = (parent != NULL && parent->type != 0) ? parent->offset + 12 : 0;
if (parent != NULL && parent->type != CHUNKTYPE_LIST && parent->type != CHUNKTYPE_RIFF && parent->type != 0)
return AVIERR_INVALID_DATA;
return get_next_chunk_internal(file, parent, newchunk, startoffset);
}
/*-------------------------------------------------
get_next_chunk - get information about the
next chunk in a container
-------------------------------------------------*/
static avi_error get_next_chunk(avi_file *file, const avi_chunk *parent, avi_chunk *newchunk)
{
UINT64 nextoffset = newchunk->offset + 8 + newchunk->size + (newchunk->size & 1);
return get_next_chunk_internal(file, parent, newchunk, nextoffset);
}
/*-------------------------------------------------
find_first_chunk - get information about the
first chunk of a particular type in a container
-------------------------------------------------*/
static avi_error find_first_chunk(avi_file *file, UINT32 findme, const avi_chunk *container, avi_chunk *result)
{
avi_error avierr;
for (avierr = get_first_chunk(file, container, result); avierr == AVIERR_NONE; avierr = get_next_chunk(file, container, result))
if (result->type == findme)
return AVIERR_NONE;
return avierr;
}
/*-------------------------------------------------
find_next_chunk - get information about the
next chunk of a particular type in a container
-------------------------------------------------*/
static avi_error find_next_chunk(avi_file *file, UINT32 findme, const avi_chunk *container, avi_chunk *result)
{
avi_error avierr;
for (avierr = get_next_chunk(file, container, result); avierr == AVIERR_NONE; avierr = get_next_chunk(file, container, result))
if (result->type == findme)
return AVIERR_NONE;
return avierr;
}
/*-------------------------------------------------
find_first_list - get information about the
first list of a particular type in a container
-------------------------------------------------*/
static avi_error find_first_list(avi_file *file, UINT32 findme, const avi_chunk *container, avi_chunk *result)
{
avi_error avierr;
for (avierr = find_first_chunk(file, CHUNKTYPE_LIST, container, result); avierr == AVIERR_NONE; avierr = find_next_chunk(file, CHUNKTYPE_LIST, container, result))
if (result->listtype == findme)
return AVIERR_NONE;
return avierr;
}
/*-------------------------------------------------
find_next_list - get information about the
next list of a particular type in a container
-------------------------------------------------*/
static avi_error find_next_list(avi_file *file, UINT32 findme, const avi_chunk *container, avi_chunk *result)
{
avi_error avierr;
for (avierr = find_next_chunk(file, CHUNKTYPE_LIST, container, result); avierr == AVIERR_NONE; avierr = find_next_chunk(file, CHUNKTYPE_LIST, container, result))
if (result->listtype == findme)
return AVIERR_NONE;
return avierr;
}
/*-------------------------------------------------
get_next_chunk_internal - fetch the next
chunk relative to the current one
-------------------------------------------------*/
static avi_error get_next_chunk_internal(avi_file *file, const avi_chunk *parent, avi_chunk *newchunk, UINT64 offset)
{
file_error filerr;
UINT8 buffer[12];
UINT32 bytesread;
/* NULL parent implies the root */
if (parent == NULL)
parent = &file->rootchunk;
/* start at the current offset */
newchunk->offset = offset;
/* if we're past the bounds of the parent, bail */
if (newchunk->offset + 8 >= parent->offset + 8 + parent->size)
return AVIERR_END;
/* read the header */
filerr = osd_read(file->file, buffer, newchunk->offset, 8, &bytesread);
if (filerr != FILERR_NONE || bytesread != 8)
return AVIERR_INVALID_DATA;
/* fill in the new chunk */
newchunk->type = fetch_32bits(&buffer[0]);
newchunk->size = fetch_32bits(&buffer[4]);
/* if we are a list, fetch the list type */
if (newchunk->type == CHUNKTYPE_LIST || newchunk->type == CHUNKTYPE_RIFF)
{
filerr = osd_read(file->file, &buffer[8], newchunk->offset + 8, 4, &bytesread);
if (filerr != FILERR_NONE || bytesread != 4)
return AVIERR_INVALID_DATA;
newchunk->listtype = fetch_32bits(&buffer[8]);
}
return AVIERR_NONE;
}
/*-------------------------------------------------
read_movie_data - get data about a movie
-------------------------------------------------*/
static avi_error read_movie_data(avi_file *file)
{
avi_chunk riff, hdrl, avih, strl, strh, strf, indx, movi, idx1;
avi_error avierr;
int strindex;
/* find the RIFF chunk */
avierr = find_first_chunk(file, CHUNKTYPE_RIFF, NULL, &riff);
if (avierr != AVIERR_NONE)
goto error;
/* verify that the RIFF type is AVI */
if (riff.listtype != LISTTYPE_AVI)
{
avierr = AVIERR_INVALID_DATA;
goto error;
}
/* find the hdrl LIST chunk within the RIFF */
avierr = find_first_list(file, LISTTYPE_HDRL, &riff, &hdrl);
if (avierr != AVIERR_NONE)
goto error;
/* find the avih chunk */
avierr = find_first_chunk(file, CHUNKTYPE_AVIH, &hdrl, &avih);
if (avierr != AVIERR_NONE)
goto error;
/* parse the avih chunk */
avierr = parse_avih_chunk(file, &avih);
if (avierr != AVIERR_NONE)
goto error;
/* loop over strl LIST chunks */
strindex = 0;
for (avierr = find_first_list(file, LISTTYPE_STRL, &hdrl, &strl); avierr == AVIERR_NONE; avierr = find_next_list(file, LISTTYPE_STRL, &hdrl, &strl))
{
/* if we have too many, it's a bad file */
if (strindex >= file->streams)
goto error;
/* find the strh chunk */
avierr = find_first_chunk(file, CHUNKTYPE_STRH, &strl, &strh);
if (avierr != AVIERR_NONE)
goto error;
/* parse the data */
avierr = parse_strh_chunk(file, &file->stream[strindex], &strh);
if (avierr != AVIERR_NONE)
goto error;
/* find the strf chunk */
avierr = find_first_chunk(file, CHUNKTYPE_STRF, &strl, &strf);
if (avierr != AVIERR_NONE)
goto error;
/* parse the data */
avierr = parse_strf_chunk(file, &file->stream[strindex], &strf);
if (avierr != AVIERR_NONE)
goto error;
/* find the indx chunk, if present */
avierr = find_first_chunk(file, CHUNKTYPE_INDX, &strl, &indx);
if (avierr == AVIERR_NONE)
avierr = parse_indx_chunk(file, &file->stream[strindex], &indx);
/* next stream */
strindex++;
}
/* normalize the error after parsing the stream headers */
if (avierr == AVIERR_END)
avierr = AVIERR_NONE;
if (avierr != AVIERR_NONE)
goto error;
/* find the base of the movi data */
avierr = find_first_list(file, LISTTYPE_MOVI, &riff, &movi);
if (avierr != AVIERR_NONE)
goto error;
/* find and parse the idx1 chunk within the RIFF (if present) */
avierr = find_first_chunk(file, CHUNKTYPE_IDX1, &riff, &idx1);
if (avierr == AVIERR_NONE)
avierr = parse_idx1_chunk(file, movi.offset + 8, &idx1);
avierr = AVIERR_NONE;
/* now extract the movie info */
avierr = extract_movie_info(file);
error:
return avierr;
}
/*-------------------------------------------------
extract_movie_info - extract the movie info
from the streams we've read
-------------------------------------------------*/
static avi_error extract_movie_info(avi_file *file)
{
avi_stream *audiostream;
avi_stream *stream;
/* get the video stream */
stream = get_video_stream(file);
if (stream != NULL)
{
/* fill in the info */
file->info.video_format = stream->format;
file->info.video_timescale = stream->rate;
file->info.video_sampletime = stream->scale;
file->info.video_numsamples = stream->samples;
file->info.video_width = stream->width;
file->info.video_height = stream->height;
}
/* get the first audio stream */
stream = get_audio_stream(file, 0, NULL);
if (stream != NULL)
{
/* fill in the info */
file->info.audio_format = stream->format;
file->info.audio_timescale = stream->rate;
file->info.audio_sampletime = stream->scale;
file->info.audio_numsamples = stream->samples;
file->info.audio_channels = 1;
file->info.audio_samplebits = stream->samplebits;
file->info.audio_samplerate = stream->samplerate;
}
/* now make sure all other audio streams are valid */
audiostream = stream;
while (1)
{
/* get the stream info */
stream = get_audio_stream(file, file->info.audio_channels, NULL);
if (stream == NULL)
break;
file->info.audio_channels++;
/* verify compatibility */
if (file->info.audio_format != stream->format ||
file->info.audio_timescale != stream->rate ||
file->info.audio_sampletime != stream->scale ||
file->info.audio_numsamples != stream->samples ||
file->info.audio_samplebits != stream->samplebits ||
file->info.audio_samplerate != stream->samplerate)
return AVIERR_INCOMPATIBLE_AUDIO_STREAMS;
}
return AVIERR_NONE;
}
/*-------------------------------------------------
parse_avih_chunk - parse an avih header
chunk
-------------------------------------------------*/
static avi_error parse_avih_chunk(avi_file *file, avi_chunk *avih)
{
UINT8 *chunkdata = NULL;
avi_error avierr;
/* read the data */
avierr = read_chunk_data(file, avih, &chunkdata);
if (avierr != AVIERR_NONE)
goto error;
/* extract the data */
file->streams = fetch_32bits(&chunkdata[24]);
/* allocate memory for the streams */
file->stream = malloc(sizeof(*file->stream) * file->streams);
if (file->stream == NULL)
goto error;
memset(file->stream, 0, sizeof(*file->stream) * file->streams);
error:
if (chunkdata != NULL)
free(chunkdata);
return avierr;
}
/*-------------------------------------------------
parse_strh_chunk - parse a strh header
chunk
-------------------------------------------------*/
static avi_error parse_strh_chunk(avi_file *file, avi_stream *stream, avi_chunk *strh)
{
UINT8 *chunkdata = NULL;
avi_error avierr;
/* read the data */
avierr = read_chunk_data(file, strh, &chunkdata);
if (avierr != AVIERR_NONE)
goto error;
/* extract the data */
stream->type = fetch_32bits(&chunkdata[0]);
stream->scale = fetch_32bits(&chunkdata[20]);
stream->rate = fetch_32bits(&chunkdata[24]);
stream->samples = fetch_32bits(&chunkdata[32]);
error:
if (chunkdata != NULL)
free(chunkdata);
return avierr;
}
/*-------------------------------------------------
parse_strf_chunk - parse a strf header
chunk
-------------------------------------------------*/
static avi_error parse_strf_chunk(avi_file *file, avi_stream *stream, avi_chunk *strf)
{
UINT8 *chunkdata = NULL;
avi_error avierr;
/* read the data */
avierr = read_chunk_data(file, strf, &chunkdata);
if (avierr != AVIERR_NONE)
goto error;
/* audio and video streams have differing headers */
if (stream->type == STREAMTYPE_VIDS)
{
stream->width = fetch_32bits(&chunkdata[4]);
stream->height = fetch_32bits(&chunkdata[8]);
stream->depth = fetch_16bits(&chunkdata[14]);
stream->format = fetch_32bits(&chunkdata[16]);
/* extra extraction for HuffYUV data */
if (stream->format == FORMAT_HFYU && strf->size >= 56)
{
avierr = huffyuv_extract_tables(stream, chunkdata, strf->size);
if (avierr != AVIERR_NONE)
goto error;
}
}
else if (stream->type == STREAMTYPE_AUDS)
{
stream->channels = fetch_16bits(&chunkdata[2]);
stream->samplebits = fetch_16bits(&chunkdata[14]);
stream->samplerate = fetch_32bits(&chunkdata[4]);
}
error:
if (chunkdata != NULL)
free(chunkdata);
return avierr;
}
/*-------------------------------------------------
parse_indx_chunk - parse an indx chunk
-------------------------------------------------*/
static avi_error parse_indx_chunk(avi_file *file, avi_stream *stream, avi_chunk *strf)
{
UINT32 entries, entry, id;
UINT8 *chunkdata = NULL;
UINT16 longs_per_entry;
UINT8 subtype, type;
UINT64 baseoffset;
avi_error avierr;
/* read the data */
avierr = read_chunk_data(file, strf, &chunkdata);
if (avierr != AVIERR_NONE)
goto error;
/* extract the data */
longs_per_entry = fetch_16bits(&chunkdata[0]);
subtype = chunkdata[2];
type = chunkdata[3];
entries = fetch_32bits(&chunkdata[4]);
id = fetch_32bits(&chunkdata[8]);
baseoffset = fetch_64bits(&chunkdata[12]);
/* if this is a superindex, loop over entries and call ourselves recursively */
if (type == AVI_INDEX_OF_INDEXES)
{
/* validate the size of each entry */
if (longs_per_entry != 4)
return AVIERR_INVALID_DATA;
/* loop over entries and create subchunks for each */
for (entry = 0; entry < entries; entry++)
{
const UINT8 *base = &chunkdata[24 + entry * 16];
file_error filerr;
avi_chunk subchunk;
UINT32 bytes_read;
UINT8 buffer[8];
/* go read the subchunk */
subchunk.offset = fetch_64bits(&base[0]);
filerr = osd_read(file->file, buffer, subchunk.offset, sizeof(buffer), &bytes_read);
if (filerr != FILERR_NONE || bytes_read != sizeof(buffer))
{
avierr = AVIERR_READ_ERROR;
break;
}
/* parse the data */
subchunk.type = fetch_32bits(&buffer[0]);
subchunk.size = fetch_32bits(&buffer[4]);
/* recursively parse each referenced chunk; stop if we hit an error */
avierr = parse_indx_chunk(file, stream, &subchunk);
if (avierr != AVIERR_NONE)
break;
}
}
/* otherwise, this is a standard index */
else if (type == AVI_INDEX_OF_CHUNKS)
{
/* validate the size of each entry */
if (longs_per_entry != 2 && longs_per_entry != 3)
return AVIERR_INVALID_DATA;
/* loop over entries and parse out the data */
for (entry = 0; entry < entries; entry++)
{
const UINT8 *base = &chunkdata[24 + entry * 4 * longs_per_entry];
UINT32 offset = fetch_32bits(&base[0]);
UINT32 size = fetch_32bits(&base[4]);
/* set the info for this chunk and advance */
avierr = set_stream_chunk_info(stream, stream->chunks++, baseoffset + offset - 8, size + 8);
if (avierr != AVIERR_NONE)
break;
}
}
error:
if (chunkdata != NULL)
free(chunkdata);
return avierr;
}
/*-------------------------------------------------
parse_idx1_chunk - parse an idx1 chunk
-------------------------------------------------*/
static avi_error parse_idx1_chunk(avi_file *file, UINT64 baseoffset, avi_chunk *idx1)
{
UINT8 *chunkdata = NULL;
avi_error avierr;
UINT32 entries;
UINT32 entry;
/* read the data */
avierr = read_chunk_data(file, idx1, &chunkdata);
if (avierr != AVIERR_NONE)
goto error;
/* loop over entries */
entries = idx1->size / 16;
for (entry = 0; entry < entries; entry++)
{
const UINT8 *base = &chunkdata[entry * 16];
UINT32 chunkid = fetch_32bits(&base[0]);
UINT32 offset = fetch_32bits(&base[8]);
UINT32 size = fetch_32bits(&base[12]);
avi_stream *stream;
int streamnum;
/* determine the stream index */
streamnum = ((chunkid >> 8) & 0xff) - '0';
streamnum += 10 * ((chunkid & 0xff) - '0');
if (streamnum >= file->streams)
{
avierr = AVIERR_INVALID_DATA;
goto error;
}
stream = &file->stream[streamnum];
/* set the appropriate entry */
avierr = set_stream_chunk_info(stream, stream->chunks++, baseoffset + offset, size + 8);
if (avierr != AVIERR_NONE)
goto error;
}
error:
if (chunkdata != NULL)
free(chunkdata);
return avierr;
}
/*-------------------------------------------------
chunk_open - open a new chunk for writing
-------------------------------------------------*/
static avi_error chunk_open(avi_file *file, UINT32 type, UINT32 listtype, UINT32 estlength)
{
file_error filerr;
avi_chunk *chunk;
UINT32 written;
/* if we're out of stack entries, bail */
if (file->chunksp >= ARRAY_LENGTH(file->chunkstack))
return AVIERR_STACK_TOO_DEEP;
chunk = &file->chunkstack[file->chunksp++];
/* set up the chunk information */
chunk->offset = file->writeoffs;
chunk->size = estlength;
chunk->type = type;
chunk->listtype = listtype;
/* non-list types */
if (type != CHUNKTYPE_RIFF && type != CHUNKTYPE_LIST)
{
UINT8 buffer[8];
/* populate the header */
put_32bits(&buffer[0], chunk->type);
put_32bits(&buffer[4], chunk->size);
/* write the header */
filerr = osd_write(file->file, buffer, file->writeoffs, sizeof(buffer), &written);
if (filerr != FILERR_NONE || written != sizeof(buffer))
return AVIERR_WRITE_ERROR;
file->writeoffs += written;
}
/* list types */
else
{
UINT8 buffer[12];
/* populate the header */
put_32bits(&buffer[0], chunk->type);
put_32bits(&buffer[4], chunk->size);
put_32bits(&buffer[8], chunk->listtype);
/* write the header */
filerr = osd_write(file->file, buffer, file->writeoffs, sizeof(buffer), &written);
if (filerr != FILERR_NONE || written != sizeof(buffer))
return AVIERR_WRITE_ERROR;
file->writeoffs += written;
}
return AVIERR_NONE;
}
/*-------------------------------------------------
chunk_close - finish writing an chunk
-------------------------------------------------*/
static avi_error chunk_close(avi_file *file)
{
avi_chunk *chunk = &file->chunkstack[--file->chunksp];
UINT64 chunksize = file->writeoffs - (chunk->offset + 8);
UINT32 written;
/* error if we don't fit into 32 bits */
if (chunksize != (UINT32)chunksize)
return AVIERR_INVALID_DATA;
/* write the final size if it is different from the guess */
if (chunk->size != chunksize)
{
file_error filerr;
UINT8 buffer[4];
put_32bits(&buffer[0], (UINT32)chunksize);
filerr = osd_write(file->file, buffer, chunk->offset + 4, sizeof(buffer), &written);
if (filerr != FILERR_NONE || written != sizeof(buffer))
return AVIERR_WRITE_ERROR;
}
/* round up to the next word */
file->writeoffs += chunksize & 1;
return AVIERR_NONE;
}
/*-------------------------------------------------
chunk_write - write an chunk and its data
-------------------------------------------------*/
static avi_error chunk_write(avi_file *file, UINT32 type, const void *data, UINT32 length)
{
file_error filerr;
avi_error avierr;
UINT32 written;
/* if we are getting too big, split the RIFF */
if ((file->riffbase == 0 && file->writeoffs + length + compute_idx1_size(file) >= MAX_RIFF_SIZE) ||
file->writeoffs + length - file->riffbase >= MAX_RIFF_SIZE)
{
/* close the movi list */
avierr = chunk_close(file);
if (avierr != AVIERR_NONE)
return avierr;
/* write the idx1 chunk if this is the first */
if (file->riffbase == 0)
{
avierr = write_idx1_chunk(file);
if (avierr != AVIERR_NONE)
return avierr;
}
/* close the RIFF */
avierr = chunk_close(file);
if (avierr != AVIERR_NONE)
return avierr;
/* open a new RIFF */
file->riffbase = file->writeoffs;
avierr = chunk_open(file, CHUNKTYPE_RIFF, LISTTYPE_AVIX, 0);
if (avierr != AVIERR_NONE)
return avierr;
/* open a nested movi list */
file->saved_movi_offset = file->writeoffs;
avierr = chunk_open(file, CHUNKTYPE_LIST, LISTTYPE_MOVI, 0);
if (avierr != AVIERR_NONE)
return avierr;
}
/* open the chunk */
avierr = chunk_open(file, type, 0, length);
if (avierr != AVIERR_NONE)
return avierr;
/* write the data */
filerr = osd_write(file->file, data, file->writeoffs, length, &written);
if (filerr != FILERR_NONE || written != length)
return AVIERR_WRITE_ERROR;
file->writeoffs += written;
/* close the chunk */
return chunk_close(file);
}
/*-------------------------------------------------
chunk_overwrite - write a chunk in two passes;
first pass writes to the end of file and
saves the offset; second pass overwrites the
original
-------------------------------------------------*/
static avi_error chunk_overwrite(avi_file *file, UINT32 type, const void *data, UINT32 length, UINT64 *offset, int initial_write)
{
UINT64 savedoffset = 0;
avi_error avierr;
/* if this is our initial write, save the offset */
if (initial_write)
*offset = file->writeoffs;
/* otherwise, remember the current write offset and replace it with the original */
else
{
savedoffset = file->writeoffs;
file->writeoffs = *offset;
}
/* write the chunk */
avierr = chunk_write(file, type, data, length);
/* if this isn't the initial write, restore the previous offset */
if (!initial_write)
file->writeoffs = savedoffset;
return avierr;
}
/*-------------------------------------------------
write_initial_headers - write out the inital
set of AVI and stream headers
-------------------------------------------------*/
static avi_error write_initial_headers(avi_file *file)
{
avi_error avierr;
int strnum;
/* reset the write pointer */
file->writeoffs = 0;
/* open a RIFF chunk */
avierr = chunk_open(file, CHUNKTYPE_RIFF, LISTTYPE_AVI, 0);
if (avierr != AVIERR_NONE)
return avierr;
/* open a hdlr LIST */
avierr = chunk_open(file, CHUNKTYPE_LIST, LISTTYPE_HDRL, 0);
if (avierr != AVIERR_NONE)
return avierr;
/* write an avih chunk */
avierr = write_avih_chunk(file, TRUE);
if (avierr != AVIERR_NONE)
return avierr;
/* for each stream, write a strl LIST */
for (strnum = 0; strnum < file->streams; strnum++)
{
/* open a strl LIST */
avierr = chunk_open(file, CHUNKTYPE_LIST, LISTTYPE_STRL, 0);
if (avierr != AVIERR_NONE)
return avierr;
/* write the strh chunk */
avierr = write_strh_chunk(file, &file->stream[strnum], TRUE);
if (avierr != AVIERR_NONE)
return avierr;
/* write the strf chunk */
avierr = write_strf_chunk(file, &file->stream[strnum]);
if (avierr != AVIERR_NONE)
return avierr;
/* write the indx chunk */
avierr = write_indx_chunk(file, &file->stream[strnum], TRUE);
if (avierr != AVIERR_NONE)
return avierr;
/* close the strl LIST */
avierr = chunk_close(file);
if (avierr != AVIERR_NONE)
return avierr;
}
/* close the hdlr LIST */
avierr = chunk_close(file);
if (avierr != AVIERR_NONE)
return avierr;
/* open a movi LIST */
file->saved_movi_offset = file->writeoffs;
avierr = chunk_open(file, CHUNKTYPE_LIST, LISTTYPE_MOVI, 0);
if (avierr != AVIERR_NONE)
return avierr;
return avierr;
}
/*-------------------------------------------------
write_avih_chunk - write the avih header
chunk
-------------------------------------------------*/
static avi_error write_avih_chunk(avi_file *file, int initial_write)
{
avi_stream *video = get_video_stream(file);
UINT8 buffer[56];
/* reset the buffer */
memset(buffer, 0, sizeof(buffer));
put_32bits(&buffer[0], 1000000 * (INT64)video->scale / video->rate); /* dwMicroSecPerFrame */
put_32bits(&buffer[12], AVIF_HASINDEX | AVIF_ISINTERLEAVED); /* dwFlags */
put_32bits(&buffer[16], video->samples); /* dwTotalFrames */
put_32bits(&buffer[24], file->streams); /* dwStreams */
put_32bits(&buffer[32], video->width); /* dwWidth */
put_32bits(&buffer[36], video->height); /* dwHeight */
/* (over)write the chunk */
return chunk_overwrite(file, CHUNKTYPE_AVIH, buffer, sizeof(buffer), &file->saved_avih_offset, initial_write);
}
/*-------------------------------------------------
write_strh_chunk - write the strh header
chunk
-------------------------------------------------*/
static avi_error write_strh_chunk(avi_file *file, avi_stream *stream, int initial_write)
{
UINT8 buffer[56];
/* reset the buffer */
memset(buffer, 0, sizeof(buffer));
put_32bits(&buffer[0], stream->type); /* fccType */
put_32bits(&buffer[20], stream->scale); /* dwScale */
put_32bits(&buffer[24], stream->rate); /* dwRate */
put_32bits(&buffer[32], stream->samples); /* dwLength */
put_32bits(&buffer[40], 10000); /* dwQuality */
/* video-stream specific data */
if (stream->type == STREAMTYPE_VIDS)
{
put_32bits(&buffer[4], /* fccHandler */
(stream->format == FORMAT_HFYU) ? HANDLER_HFYU : HANDLER_DIB);
put_32bits(&buffer[36], /* dwSuggestedBufferSize */
stream->width * stream->height * 4);
put_16bits(&buffer[52], stream->width); /* rcFrame.right */
put_16bits(&buffer[54], stream->height); /* rcFrame.bottom */
}
/* audio-stream specific data */
if (stream->type == STREAMTYPE_AUDS)
{
put_32bits(&buffer[36], /* dwSuggestedBufferSize */
stream->samplerate * stream->channels * (stream->samplebits / 8));
put_32bits(&buffer[44], /* dwSampleSize */
stream->channels * (stream->samplebits / 8));
}
/* write the chunk */
return chunk_overwrite(file, CHUNKTYPE_STRH, buffer, sizeof(buffer), &stream->saved_strh_offset, initial_write);
}
/*-------------------------------------------------
write_strf_chunk - write the strf header
chunk
-------------------------------------------------*/
static avi_error write_strf_chunk(avi_file *file, avi_stream *stream)
{
/* video stream */
if (stream->type == STREAMTYPE_VIDS)
{
UINT8 buffer[40];
/* reset the buffer */
memset(buffer, 0, sizeof(buffer));
put_32bits(&buffer[0], sizeof(buffer)); /* biSize */
put_32bits(&buffer[4], stream->width); /* biWidth */
put_32bits(&buffer[8], stream->height); /* biHeight */
put_16bits(&buffer[12], 1); /* biPlanes */
put_16bits(&buffer[14], stream->depth); /* biBitCount */
put_32bits(&buffer[16], stream->format); /* biCompression */
put_32bits(&buffer[20], /* biSizeImage */
stream->width * stream->height * (stream->depth + 7) / 8);
/* write the chunk */
return chunk_write(file, CHUNKTYPE_STRF, buffer, sizeof(buffer));
}
/* audio stream */
if (stream->type == STREAMTYPE_AUDS)
{
UINT8 buffer[16];
/* reset the buffer */
memset(buffer, 0, sizeof(buffer));
put_16bits(&buffer[0], 1); /* wFormatTag */
put_16bits(&buffer[2], stream->channels); /* nChannels */
put_32bits(&buffer[4], stream->samplerate); /* nSamplesPerSec */
put_32bits(&buffer[8], /* nAvgBytesPerSec */
stream->samplerate * stream->channels * (stream->samplebits / 8));
put_16bits(&buffer[12], /* nBlockAlign */
stream->channels * (stream->samplebits / 8));
put_16bits(&buffer[14], stream->samplebits); /* wBitsPerSample */
/* write the chunk */
return chunk_write(file, CHUNKTYPE_STRF, buffer, sizeof(buffer));
}
return AVIERR_INVALID_DATA;
}
/*-------------------------------------------------
write_indx_chunk - write the indx header
chunk
-------------------------------------------------*/
static avi_error write_indx_chunk(avi_file *file, avi_stream *stream, int initial_write)
{
UINT8 buffer[24 + 16 * MAX_AVI_SIZE_IN_GB / 4];
UINT32 chunkid, indexchunkid;
UINT32 master_entries = 0;
/* reset the buffer */
memset(buffer, 0, sizeof(buffer));
/* construct the chunk ID and index chunk ID */
chunkid = get_chunkid_for_stream(file, stream);
indexchunkid = AVI_FOURCC('i', 'x', '0' + (stream - file->stream) / 10, '0' + (stream - file->stream) % 10);
/* loop over chunks of 4GB and write out indexes for them first */
if (!initial_write && file->riffbase != 0)
{
UINT64 currentbase;
for (currentbase = 0; currentbase < file->writeoffs; currentbase += FOUR_GB)
{
UINT64 currentend = currentbase + FOUR_GB;
UINT32 chunks_this_index = 0;
UINT32 bytes_this_index = 0;
avi_error avierr;
UINT32 chunknum;
UINT8 *tempbuf;
/* count chunks that are in this region */
for (chunknum = 0; chunknum < stream->chunks; chunknum++)
if (stream->chunk[chunknum].offset >= currentbase && stream->chunk[chunknum].offset < currentend)
chunks_this_index++;
/* if no chunks, skip */
if (chunks_this_index == 0)
continue;
/* allocate memory */
tempbuf = malloc(24 + 8 * chunks_this_index);
if (tempbuf == NULL)
return AVIERR_NO_MEMORY;
memset(tempbuf, 0, 24 + 8 * chunks_this_index);
/* make a regular index */
put_16bits(&tempbuf[0], 2); /* wLongsPerEntry */
tempbuf[2] = 0; /* bIndexSubType */
tempbuf[3] = AVI_INDEX_OF_CHUNKS; /* bIndexType */
put_32bits(&tempbuf[4], chunks_this_index); /* nEntriesInUse */
put_32bits(&tempbuf[8], chunkid); /* dwChunkId */
put_64bits(&tempbuf[12], currentbase); /* qwBaseOffset */
/* now fill in the indexes */
chunks_this_index = 0;
for (chunknum = 0; chunknum < stream->chunks; chunknum++)
if (stream->chunk[chunknum].offset >= currentbase && stream->chunk[chunknum].offset < currentend)
{
put_32bits(&tempbuf[24 + 8 * chunks_this_index + 0], stream->chunk[chunknum].offset + 8 - currentbase);
put_32bits(&tempbuf[24 + 8 * chunks_this_index + 4], stream->chunk[chunknum].length - 8);
bytes_this_index += stream->chunk[chunknum].length;
chunks_this_index++;
}
/* write the offset of this index to the master table */
put_64bits(&buffer[24 + 16 * master_entries + 0], file->writeoffs);
put_32bits(&buffer[24 + 16 * master_entries + 8], 24 + 8 * chunks_this_index + 8);
if (stream->type == STREAMTYPE_VIDS)
put_32bits(&buffer[24 + 16 * master_entries + 12], chunks_this_index);
else if (stream->type == STREAMTYPE_AUDS)
put_32bits(&buffer[24 + 16 * master_entries + 12], bytes_this_index / ((stream->samplebits / 8) * stream->channels));
master_entries++;
/* write the index */
avierr = chunk_write(file, indexchunkid, tempbuf, 24 + 8 * chunks_this_index);
free(tempbuf);
if (avierr != AVIERR_NONE)
return avierr;
}
}
/* build up the master index */
if (master_entries != 0)
{
put_16bits(&buffer[0], 4); /* wLongsPerEntry */
buffer[2] = 0; /* bIndexSubType */
buffer[3] = AVI_INDEX_OF_INDEXES; /* bIndexType */
put_32bits(&buffer[4], master_entries); /* nEntriesInUse */
put_32bits(&buffer[8], chunkid); /* dwChunkId */
}
/* (over)write the chunk */
return chunk_overwrite(file, (master_entries == 0) ? CHUNKTYPE_JUNK : CHUNKTYPE_INDX, buffer, sizeof(buffer), &stream->saved_indx_offset, initial_write);
}
/*-------------------------------------------------
write_idx1_chunk - write the idx1 chunk
-------------------------------------------------*/
static avi_error write_idx1_chunk(avi_file *file)
{
UINT32 tempbuflength = compute_idx1_size(file) - 8;
UINT32 curchunk[2] = { 0 };
UINT32 curoffset;
avi_error avierr;
UINT8 *tempbuf;
/* allocate a temporary buffer */
tempbuf = malloc(tempbuflength);
if (tempbuf == NULL)
return AVIERR_NO_MEMORY;
/* fill it in */
for (curoffset = 0; curoffset < tempbuflength; curoffset += 16)
{
UINT64 minoffset = ~(UINT64)0;
int strnum, minstr = 0;
/* determine which stream has the next chunk */
for (strnum = 0; strnum < file->streams; strnum++)
if (curchunk[strnum] < file->stream[strnum].chunks && file->stream[strnum].chunk[curchunk[strnum]].offset < minoffset)
{
minoffset = file->stream[strnum].chunk[curchunk[strnum]].offset;
minstr = strnum;
}
/* make an entry for this index */
put_32bits(&tempbuf[curoffset + 0], get_chunkid_for_stream(file, &file->stream[minstr]));
put_32bits(&tempbuf[curoffset + 4], 0x0010 /* AVIIF_KEYFRAME */);
put_32bits(&tempbuf[curoffset + 8], minoffset - (file->saved_movi_offset + 8));
put_32bits(&tempbuf[curoffset + 12], file->stream[minstr].chunk[curchunk[minstr]].length - 8);
/* advance the chunk counter for this stream */
curchunk[minstr]++;
}
/* write the chunk */
avierr = chunk_write(file, CHUNKTYPE_IDX1, tempbuf, tempbuflength);
free(tempbuf);
return avierr;
}
/*-------------------------------------------------
soundbuf_initialize - initialize the sound
buffering system
-------------------------------------------------*/
static avi_error soundbuf_initialize(avi_file *file)
{
avi_stream *audio = get_audio_stream(file, 0, NULL);
avi_stream *video = get_video_stream(file);
/* we require a video stream */
if (video == NULL)
return AVIERR_UNSUPPORTED_VIDEO_FORMAT;
/* skip if no audio stream */
if (audio == NULL)
return AVIERR_NONE;
/* determine the number of samples we want in our buffer; 2 seconds should be enough */
file->soundbuf_samples = file->info.audio_samplerate * SOUND_BUFFER_MSEC / 1000;
/* allocate a buffer */
file->soundbuf = malloc(file->soundbuf_samples * file->info.audio_channels * sizeof(file->soundbuf[0]));
if (file->soundbuf == NULL)
return AVIERR_NO_MEMORY;
memset(file->soundbuf, 0, file->soundbuf_samples * file->info.audio_channels * sizeof(file->soundbuf[0]));
/* determine the number of frames to be ahead (0.75secs) */
file->soundbuf_frames = ((UINT64)video->rate * 75) / ((UINT64)video->scale * 100) + 1;
return AVIERR_NONE;
}
/*-------------------------------------------------
soundbuf_write_chunk - write the initial
chunk data
-------------------------------------------------*/
static avi_error soundbuf_write_chunk(avi_file *file, UINT32 framenum)
{
avi_stream *stream = get_audio_stream(file, 0, NULL);
avi_error avierr;
UINT32 chunknum;
UINT32 length;
/* skip if no audio stream */
if (stream == NULL)
return AVIERR_NONE;
/* determine the length of this chunk */
if (framenum == 0)
length = framenum_to_samplenum(file, file->soundbuf_frames);
else
length = framenum_to_samplenum(file, framenum + 1 + file->soundbuf_frames) - framenum_to_samplenum(file, framenum + file->soundbuf_frames);
length *= stream->channels * sizeof(INT16);
/* set the info for this new chunk */
chunknum = stream->chunks;
avierr = set_stream_chunk_info(stream, chunknum, file->writeoffs, length + 8);
if (avierr != AVIERR_NONE)
return avierr;
/* then do the initial write */
return chunk_overwrite(file, get_chunkid_for_stream(file, stream), file->soundbuf, length, &stream->chunk[chunknum].offset, TRUE);
}
/*-------------------------------------------------
soundbuf_flush - flush data from the sound
buffers
-------------------------------------------------*/
static avi_error soundbuf_flush(avi_file *file, int only_flush_full)
{
avi_stream *stream = get_audio_stream(file, 0, NULL);
INT32 channelsamples = file->soundbuf_samples;
INT32 processedsamples = 0;
UINT32 bytes_per_sample;
UINT32 finalchunks;
avi_error avierr;
UINT32 chunknum;
UINT32 chunkid;
int channel;
/* skip if no stream */
if (stream == NULL)
return AVIERR_NONE;
/* get the chunk ID for this stream */
chunkid = get_chunkid_for_stream(file, stream);
bytes_per_sample = stream->channels * sizeof(INT16);
finalchunks = stream->chunks;
/* find out how many samples we've accumulated */
for (channel = 0; channel < stream->channels; channel++)
channelsamples = MIN(channelsamples, file->soundbuf_chansamples[channel]);
/* loop over pending sound chunks */
for (chunknum = file->soundbuf_chunks; chunknum < stream->chunks; chunknum++)
{
avi_chunk_list *chunk = &stream->chunk[chunknum];
UINT32 chunksamples = (chunk->length - 8) / bytes_per_sample;
/* stop if we don't have enough to satisfy this chunk */
if (only_flush_full && channelsamples < chunksamples)
break;
/* if we don't have all the samples we need, pad with 0's */
if (channelsamples > 0 && channelsamples < chunksamples)
{
if (processedsamples + chunksamples > file->soundbuf_samples)
return AVIERR_EXCEEDED_SOUND_BUFFER;
memset(&file->soundbuf[(processedsamples + channelsamples) * stream->channels], 0, (chunksamples - channelsamples) * bytes_per_sample);
}
/* if we're completely out of samples, clear the buffer entirely and use the end */
else if (channelsamples <= 0)
{
processedsamples = file->soundbuf_samples - chunksamples;
memset(&file->soundbuf[processedsamples * stream->channels], 0, chunksamples * bytes_per_sample);
chunkid = CHUNKTYPE_JUNK;
finalchunks--;
}
/* copy the sample data in */
avierr = chunk_overwrite(file, chunkid, &file->soundbuf[processedsamples * stream->channels], chunk->length - 8, &chunk->offset, FALSE);
if (avierr != AVIERR_NONE)
return avierr;
/* add up the samples */
if (channelsamples > 0)
file->info.audio_numsamples = stream->samples += channelsamples;
/* advance past those */
processedsamples += chunksamples;
channelsamples -= chunksamples;
channelsamples = MAX(0, channelsamples);
}
/* if we have a non-zero offset, shift everything down */
if (processedsamples > 0)
{
/* first account for the samples we processed */
memmove(&file->soundbuf[0], &file->soundbuf[processedsamples * stream->channels], (file->soundbuf_samples - processedsamples) * bytes_per_sample);
for (channel = 0; channel < stream->channels; channel++)
file->soundbuf_chansamples[channel] -= processedsamples;
}
/* update the final chunk count */
if (!only_flush_full)
stream->chunks = finalchunks;
/* account for flushed chunks */
file->soundbuf_chunks = chunknum;
return AVIERR_NONE;
}
/*-------------------------------------------------
yuv_decompress_to_yuy16 - decompress a YUV
encoded frame to a YUY16 bitmap
-------------------------------------------------*/
static avi_error yuv_decompress_to_yuy16(avi_stream *stream, const UINT8 *data, UINT32 numbytes, bitmap_t *bitmap)
{
const UINT16 *dataend = (const UINT16 *)(data + numbytes);
int x, y;
/* compressed video */
for (y = 0; y < stream->height; y++)
{
const UINT16 *source = (const UINT16 *)data + y * stream->width;
UINT16 *dest = (UINT16 *)bitmap->base + y * bitmap->rowpixels;
/* switch off the compression */
switch (stream->format)
{
case FORMAT_UYVY:
for (x = 0; x < stream->width && source < dataend; x++)
*dest++ = *source++;
break;
case FORMAT_VYUY:
case FORMAT_YUY2:
for (x = 0; x < stream->width && source < dataend; x++)
{
UINT16 pix = *source++;
*dest++ = (pix >> 8) | (pix << 8);
}
break;
}
}
return AVIERR_NONE;
}
/*-------------------------------------------------
yuy16_compress_to_yuy - "compress" a YUY16
bitmap to a YUV encoded frame
-------------------------------------------------*/
static avi_error yuy16_compress_to_yuy(avi_stream *stream, const bitmap_t *bitmap, UINT8 *data, UINT32 numbytes)
{
const UINT16 *dataend = (const UINT16 *)(data + numbytes);
int x, y;
/* compressed video */
for (y = 0; y < stream->height; y++)
{
const UINT16 *source = (UINT16 *)bitmap->base + y * bitmap->rowpixels;
UINT16 *dest = (UINT16 *)data + y * stream->width;
/* switch off the compression */
switch (stream->format)
{
case FORMAT_UYVY:
for (x = 0; x < stream->width && dest < dataend; x++)
*dest++ = *source++;
break;
case FORMAT_VYUY:
case FORMAT_YUY2:
for (x = 0; x < stream->width && source < dataend; x++)
{
UINT16 pix = *source++;
*dest++ = (pix >> 8) | (pix << 8);
}
break;
}
}
return AVIERR_NONE;
}
/*-------------------------------------------------
huffyuv_extract_tables - extract HuffYUV
tables
-------------------------------------------------*/
static avi_error huffyuv_extract_tables(avi_stream *stream, const UINT8 *chunkdata, UINT32 size)
{
const UINT8 *chunkend = chunkdata + size;
avi_error avierr = AVIERR_NONE;
int tabnum;
/* allocate memory for the data */
stream->huffyuv = malloc(sizeof(*stream->huffyuv));
if (stream->huffyuv == NULL)
{
avierr = AVIERR_NO_MEMORY;
goto error;
}
/* extract predictor information */
if (&chunkdata[40] >= chunkend)
return AVIERR_INVALID_DATA;
stream->huffyuv->predictor = chunkdata[40];
/* make sure it's the left predictor */
if ((stream->huffyuv->predictor & ~HUFFYUV_PREDICT_DECORR) != HUFFYUV_PREDICT_LEFT)
return AVIERR_UNSUPPORTED_VIDEO_FORMAT;
/* make sure it's 16bpp YUV data */
if (chunkdata[41] != 16)
return AVIERR_UNSUPPORTED_VIDEO_FORMAT;
chunkdata += 44;
/* loop over tables */
for (tabnum = 0; tabnum < 3; tabnum++)
{
huffyuv_table *table = &stream->huffyuv->table[tabnum];
UINT32 curbits, bitadd;
UINT16 bitsat16 = 0;
int offset = 0, bits;
/* loop until we populate the whole table */
while (offset < 256)
{
int data, shift, count, i;
/* extract the next run */
if (chunkdata >= chunkend)
{
avierr = AVIERR_INVALID_DATA;
goto error;
}
data = *chunkdata++;
shift = data & 0x1f;
count = data >> 5;
/* zero count means next whole byte is a count */
if (count == 0)
{
if (chunkdata >= chunkend)
{
avierr = AVIERR_INVALID_DATA;
goto error;
}
count = *chunkdata++;
}
for (i = 0; i < count; i++)
table->shift[offset++] = shift;
}
/* now determine bit patterns and masks */
curbits = 0;
for (bits = 31; bits >= 0; bits--)
{
bitadd = 1 << (32 - bits);
/* make sure we've cleared out all the bits below */
if ((curbits & (bitadd - 1)) != 0)
{
avierr = AVIERR_INVALID_DATA;
goto error;
}
/* find all entries with this shift count and assign them */
for (offset = 0; offset < 256; offset++)
if (table->shift[offset] == bits)
{
table->bits[offset] = curbits;
table->mask[offset] = ~(bitadd - 1);
curbits += bitadd;
}
/* remember the bit pattern when we complete all the 17-bit codes */
if (bits == 17)
bitsat16 = curbits >> 16;
}
/* allocate the number of extra lookup tables we need */
if (bitsat16 > 0)
{
table->extralookup = malloc(bitsat16 * 65536 * sizeof(table->extralookup[0]));
if (table->extralookup == NULL)
{
avierr = AVIERR_NO_MEMORY;
goto error;
}
for (offset = 0; offset < bitsat16; offset++)
table->baselookup[offset] = (offset << 8) | 0;
}
/* then create lookup tables */
for (offset = 0; offset < 256; offset++)
if (table->shift[offset] > 16)
{
UINT16 *tablebase = table->extralookup + (table->bits[offset] >> 16) * 65536;
UINT32 start = table->bits[offset] & 0xffff;
UINT32 end = start + ((1 << (32 - table->shift[offset])) - 1);
while (start <= end)
tablebase[start++] = (offset << 8) | (table->shift[offset] - 16);
}
else if (table->shift[offset] > 0)
{
UINT32 start = table->bits[offset] >> 16;
UINT32 end = start + ((1 << (16 - table->shift[offset])) - 1);
while (start <= end)
table->baselookup[start++] = (offset << 8) | table->shift[offset];
}
}
error:
if (avierr != AVIERR_NONE && stream->huffyuv != NULL)
{
free(stream->huffyuv);
stream->huffyuv = NULL;
}
return avierr;
}
/*-------------------------------------------------
huffyuv_decompress_to_yuy16 - decompress a
HuffYUV-encoded frame to a YUY16 bitmap
-------------------------------------------------*/
static avi_error huffyuv_decompress_to_yuy16(avi_stream *stream, const UINT8 *data, UINT32 numbytes, bitmap_t *bitmap)
{
huffyuv_data *huffyuv = stream->huffyuv;
int prevlines = (stream->height > 288) ? 2 : 1;
UINT8 lastprevy = 0, lastprevcb = 0, lastprevcr = 0;
UINT8 lasty = 0, lastcb = 0, lastcr = 0;
UINT8 bitsinbuffer = 0;
UINT32 bitbuffer = 0;
UINT32 dataoffs = 0;
int x, y;
/* compressed video */
for (y = 0; y < stream->height; y++)
{
UINT16 *dest = (UINT16 *)bitmap->base + y * bitmap->rowpixels;
/* handle the first four bytes independently */
x = 0;
if (y == 0)
{
/* first DWORD is stored as YUY2 */
lasty = data[dataoffs++];
lastcb = data[dataoffs++];
*dest++ = (lasty << 8) | lastcb;
lasty = data[dataoffs++];
lastcr = data[dataoffs++];
*dest++ = (lasty << 8) | lastcr;
x = 2;
}
/* loop over pixels */
for ( ; x < stream->width; x++)
{
huffyuv_table *ytable = &huffyuv->table[0];
huffyuv_table *ctable = &huffyuv->table[1 + (x & 1)];
UINT16 pixel, huffdata;
int shift;
/* fill up the buffer; they store little-endian DWORDs, so we XOR with 3 */
while (bitsinbuffer <= 24 && dataoffs < numbytes)
{
bitbuffer |= data[dataoffs++ ^ 3] << (24 - bitsinbuffer);
bitsinbuffer += 8;
}
/* look up the Y component */
huffdata = ytable->baselookup[bitbuffer >> 16];
shift = huffdata & 0xff;
if (shift == 0)
{
bitsinbuffer -= 16;
bitbuffer <<= 16;
/* fill up the buffer; they store little-endian DWORDs, so we XOR with 3 */
while (bitsinbuffer <= 24 && dataoffs < numbytes)
{
bitbuffer |= data[dataoffs++ ^ 3] << (24 - bitsinbuffer);
bitsinbuffer += 8;
}
huffdata = ytable->extralookup[(huffdata >> 8) * 65536 + (bitbuffer >> 16)];
shift = huffdata & 0xff;
}
bitsinbuffer -= shift;
bitbuffer <<= shift;
pixel = huffdata & 0xff00;
/* fill up the buffer; they store little-endian DWORDs, so we XOR with 3 */
while (bitsinbuffer <= 24 && dataoffs < numbytes)
{
bitbuffer |= data[dataoffs++ ^ 3] << (24 - bitsinbuffer);
bitsinbuffer += 8;
}
/* look up the Cb/Cr component */
huffdata = ctable->baselookup[bitbuffer >> 16];
shift = huffdata & 0xff;
if (shift == 0)
{
bitsinbuffer -= 16;
bitbuffer <<= 16;
/* fill up the buffer; they store little-endian DWORDs, so we XOR with 3 */
while (bitsinbuffer <= 24 && dataoffs < numbytes)
{
bitbuffer |= data[dataoffs++ ^ 3] << (24 - bitsinbuffer);
bitsinbuffer += 8;
}
huffdata = ctable->extralookup[(huffdata >> 8) * 65536 + (bitbuffer >> 16)];
shift = huffdata & 0xff;
}
bitsinbuffer -= shift;
bitbuffer <<= shift;
*dest++ = pixel | (huffdata >> 8);
}
}
/* apply deltas */
lasty = lastcb = lastcr = 0;
lastprevy = lastprevcb = lastprevcr = 0;
for (y = 0; y < stream->height; y++)
{
UINT16 *dest = (UINT16 *)bitmap->base + y * bitmap->rowpixels;
UINT16 *prevrow = dest - prevlines * bitmap->rowpixels;
/* left predict or gradient predict */
if ((huffyuv->predictor & ~HUFFYUV_PREDICT_DECORR) == HUFFYUV_PREDICT_LEFT ||
((huffyuv->predictor & ~HUFFYUV_PREDICT_DECORR) == HUFFYUV_PREDICT_GRADIENT))
{
/* first do left deltas */
for (x = 0; x < stream->width; x += 2)
{
UINT16 pixel0 = dest[x + 0];
UINT16 pixel1 = dest[x + 1];
lasty += pixel0 >> 8;
lastcb += pixel0;
dest[x + 0] = (lasty << 8) | lastcb;
lasty += pixel1 >> 8;
lastcr += pixel1;
dest[x + 1] = (lasty << 8) | lastcr;
}
/* for gradient, we then add in the previous row */
if ((huffyuv->predictor & ~HUFFYUV_PREDICT_DECORR) == HUFFYUV_PREDICT_GRADIENT && y >= prevlines)
for (x = 0; x < stream->width; x++)
{
UINT16 curpix = dest[x];
UINT16 prevpix = prevrow[x];
UINT8 ysum = (curpix >> 8) + (prevpix >> 8);
UINT8 csum = curpix + prevpix;
dest[x] = (ysum << 8) | csum;
}
}
/* median predict on rows > 0 */
else if ((huffyuv->predictor & ~HUFFYUV_PREDICT_DECORR) == HUFFYUV_PREDICT_MEDIAN && y >= prevlines)
{
for (x = 0; x < stream->width; x += 2)
{
UINT16 prevpixel0 = prevrow[x + 0];
UINT16 prevpixel1 = prevrow[x + 1];
UINT16 pixel0 = dest[x + 0];
UINT16 pixel1 = dest[x + 1];
UINT8 a, b, c;
/* compute previous, above, and (prev + above - above-left) */
a = lasty;
b = prevpixel0 >> 8;
c = lastprevy;
lastprevy = b;
if (a > b) { UINT8 tmp = a; a = b; b = tmp; }
if (a > c) { UINT8 tmp = a; a = c; c = tmp; }
if (b > c) { UINT8 tmp = b; b = c; c = tmp; }
lasty = (pixel0 >> 8) + b;
/* compute previous, above, and (prev + above - above-left) */
a = lastcb;
b = prevpixel0 & 0xff;
c = lastprevcb;
lastprevcb = b;
if (a > b) { UINT8 tmp = a; a = b; b = tmp; }
if (a > c) { UINT8 tmp = a; a = c; c = tmp; }
if (b > c) { UINT8 tmp = b; b = c; c = tmp; }
lastcb = (pixel0 & 0xff) + b;
dest[x + 0] = (lasty << 8) | lastcb;
/* compute previous, above, and (prev + above - above-left) */
a = lasty;
b = prevpixel1 >> 8;
c = lastprevy;
lastprevy = b;
if (a > b) { UINT8 tmp = a; a = b; b = tmp; }
if (a > c) { UINT8 tmp = a; a = c; c = tmp; }
if (b > c) { UINT8 tmp = b; b = c; c = tmp; }
lasty = (pixel1 >> 8) + b;
/* compute previous, above, and (prev + above - above-left) */
a = lastcr;
b = prevpixel1 & 0xff;
c = lastprevcr;
lastprevcr = b;
if (a > b) { UINT8 tmp = a; a = b; b = tmp; }
if (a > c) { UINT8 tmp = a; a = c; c = tmp; }
if (b > c) { UINT8 tmp = b; b = c; c = tmp; }
lastcr = (pixel1 & 0xff) + b;
dest[x + 1] = (lasty << 8) | lastcr;
}
}
}
return AVIERR_NONE;
}
static void u64toa(UINT64 val, char *output)
{
UINT32 lo = (UINT32)(val & 0xffffffff);
UINT32 hi = (UINT32)(val >> 32);
if (hi != 0)
sprintf(output, "%X%08X", hi, lo);
else
sprintf(output, "%X", lo);
}
/*-------------------------------------------------
printf_chunk_recursive - print information
about a chunk recursively
-------------------------------------------------*/
static void printf_chunk_recursive(avi_file *file, avi_chunk *container, int indent)
{
char size[20], offset[20];
avi_chunk curchunk;
int avierr;
/* iterate over chunks in this container */
for (avierr = get_first_chunk(file, container, &curchunk); avierr == AVIERR_NONE; avierr = get_next_chunk(file, container, &curchunk))
{
UINT32 chunksize = curchunk.size;
int recurse = FALSE;
u64toa(curchunk.size, size);
u64toa(curchunk.offset, offset);
printf("%*schunk = %c%c%c%c, size=%s (%s)\n", indent, "",
(UINT8)(curchunk.type >> 0),
(UINT8)(curchunk.type >> 8),
(UINT8)(curchunk.type >> 16),
(UINT8)(curchunk.type >> 24),
size, offset);
/* certain chunks are just containers; recurse into them */
switch (curchunk.type)
{
/* basic containers */
case CHUNKTYPE_RIFF:
case CHUNKTYPE_LIST:
printf("%*stype = %c%c%c%c\n", indent, "",
(UINT8)(curchunk.listtype >> 0),
(UINT8)(curchunk.listtype >> 8),
(UINT8)(curchunk.listtype >> 16),
(UINT8)(curchunk.listtype >> 24));
recurse = TRUE;
chunksize = 0;
break;
}
/* print data within the chunk */
if (chunksize > 0 && curchunk.size < 1024 * 1024)
{
UINT8 *data = NULL;
int i;
/* read the data for a chunk */
avierr = read_chunk_data(file, &curchunk, &data);
if (avierr == AVIERR_NONE)
{
int bytes = MIN(512, chunksize);
for (i = 0; i < bytes; i++)
{
if (i % 16 == 0) printf("%*s ", indent, "");
printf("%02X ", data[i]);
if (i % 16 == 15) printf("\n");
}
if (chunksize % 16 != 0) printf("\n");
free(data);
}
}
/* if we're recursing, dive down */
if (recurse)
printf_chunk_recursive(file, &curchunk, indent + 4);
}
/* if we didn't get a legitimate error, indicate that */
if (avierr != AVIERR_END)
printf("[chunk error %d]\n", avierr);
}