diff --git a/docs/config.txt b/docs/config.txt index dedb7502bb1..b71b46a3ea9 100644 --- a/docs/config.txt +++ b/docs/config.txt @@ -461,6 +461,12 @@ Core state/playback options for that, and reassemble the audio/video using offline tools. The default is NULL (no recording). +-aviwrite + + Stream video and sound data to the given in AVI format, + producing an animation of the game session complete with sound. The + default is NULL (no recording). + -wavwrite Writes the final mixer output to the given in WAV format, diff --git a/src/emu/emuopts.c b/src/emu/emuopts.c index 21a39282d57..1564d851a56 100644 --- a/src/emu/emuopts.c +++ b/src/emu/emuopts.c @@ -65,6 +65,7 @@ const options_entry mame_core_options[] = { "playback;pb", NULL, 0, "playback an input file" }, { "record;rec", NULL, 0, "record an input file" }, { "mngwrite", NULL, 0, "optional filename to write a MNG movie of the current session" }, + { "aviwrite", NULL, 0, "optional filename to write an AVI movie of the current session" }, { "wavwrite", NULL, 0, "optional filename to write a WAV file of the current session" }, /* performance options */ diff --git a/src/emu/emuopts.h b/src/emu/emuopts.h index 11af383f003..44d49880050 100644 --- a/src/emu/emuopts.h +++ b/src/emu/emuopts.h @@ -64,6 +64,7 @@ #define OPTION_PLAYBACK "playback" #define OPTION_RECORD "record" #define OPTION_MNGWRITE "mngwrite" +#define OPTION_AVIWRITE "aviwrite" #define OPTION_WAVWRITE "wavwrite" /* core performance options */ diff --git a/src/emu/fileio.c b/src/emu/fileio.c index 1566bab96d3..3c07d7f867a 100644 --- a/src/emu/fileio.c +++ b/src/emu/fileio.c @@ -44,6 +44,7 @@ struct _mame_file #ifdef DEBUG_COOKIE UINT32 debug_cookie; /* sanity checking for debugging */ #endif + astring * filename; /* full filename */ core_file * file; /* core file pointer */ UINT32 openflags; /* flags we used for the open */ char hash[HASH_BUF_SIZE]; /* hash data for the file */ @@ -214,7 +215,6 @@ static file_error fopen_internal(core_options *opts, const char *searchpath, con { file_error filerr = FILERR_NOT_FOUND; path_iterator iterator; - astring *fullname; /* can either have a hash or open for write, but not both */ if ((openflags & OPEN_FLAG_HAS_CRC) && (openflags & OPEN_FLAG_WRITE)) @@ -240,28 +240,27 @@ static file_error fopen_internal(core_options *opts, const char *searchpath, con path_iterator_init(&iterator, opts, searchpath); /* loop over paths */ - fullname = astring_alloc(); - while (path_iterator_get_next(&iterator, fullname)) + (*file)->filename = astring_alloc(); + while (path_iterator_get_next(&iterator, (*file)->filename)) { /* compute the full pathname */ - if (astring_len(fullname) > 0) - astring_catc(fullname, PATH_SEPARATOR); - astring_catc(fullname, filename); + if (astring_len((*file)->filename) > 0) + astring_catc((*file)->filename, PATH_SEPARATOR); + astring_catc((*file)->filename, filename); /* attempt to open the file directly */ - filerr = core_fopen(astring_c(fullname), openflags, &(*file)->file); + filerr = core_fopen(astring_c((*file)->filename), openflags, &(*file)->file); if (filerr == FILERR_NONE) break; /* if we're opening for read-only we have other options */ if ((openflags & (OPEN_FLAG_READ | OPEN_FLAG_WRITE)) == OPEN_FLAG_READ) { - filerr = fopen_attempt_zipped(fullname, crc, openflags, *file); + filerr = fopen_attempt_zipped((*file)->filename, crc, openflags, *file); if (filerr == FILERR_NONE) break; } } - astring_free(fullname); /* handle errors and return */ if (filerr != FILERR_NONE) @@ -379,6 +378,8 @@ void mame_fclose(mame_file *file) core_fclose(file->file); if (file->zipdata != NULL) free(file->zipdata); + if (file->filename != NULL) + astring_free(file->filename); free(file); } @@ -701,6 +702,20 @@ core_file *mame_core_file(mame_file *file) } +/*------------------------------------------------- + mame_file_full_name - return the full filename + for a given mame_file +-------------------------------------------------*/ + +const char *mame_file_full_name(mame_file *file) +{ + /* return a pointer to the string if we have one; otherwise, return NULL */ + if (file->filename != NULL) + return astring_c(file->filename); + return NULL; +} + + /*------------------------------------------------- mame_fhash - returns the hash for a file -------------------------------------------------*/ diff --git a/src/emu/fileio.h b/src/emu/fileio.h index 3272ee2ede4..2d79f46a8a7 100644 --- a/src/emu/fileio.h +++ b/src/emu/fileio.h @@ -163,6 +163,9 @@ void mame_closepath(mame_path *path); /* return the core_file underneath the mame_file */ core_file *mame_core_file(mame_file *file); +/* return the full filename for a given mame_file */ +const char *mame_file_full_name(mame_file *file); + /* return a hash string for the file with the given functions */ const char *mame_fhash(mame_file *file, UINT32 functions); diff --git a/src/emu/sound.c b/src/emu/sound.c index 7deabf91b7c..d57a8d82bab 100644 --- a/src/emu/sound.c +++ b/src/emu/sound.c @@ -753,6 +753,7 @@ static TIMER_CALLBACK( sound_update ) if (finalmix_offset > 0) { osd_update_audio_stream(machine, finalmix, finalmix_offset / 2); + video_avi_add_sound(machine, finalmix, finalmix_offset / 2); if (wavfile != NULL) wav_add_data_16(wavfile, finalmix, finalmix_offset); } diff --git a/src/emu/ui.c b/src/emu/ui.c index 07644dfc311..901ee29e8fe 100644 --- a/src/emu/ui.c +++ b/src/emu/ui.c @@ -1314,14 +1314,14 @@ static UINT32 handler_ingame(running_machine *machine, UINT32 state) /* toggle movie recording */ if (input_ui_pressed(IPT_UI_RECORD_MOVIE)) { - if (!video_is_movie_active(machine->primary_screen)) + if (!video_mng_is_movie_active(machine->primary_screen)) { - video_movie_begin_recording(machine->primary_screen, NULL); + video_mng_begin_recording(machine->primary_screen, NULL); popmessage("REC START"); } else { - video_movie_end_recording(machine->primary_screen); + video_mng_end_recording(machine->primary_screen); popmessage("REC STOP"); } } diff --git a/src/emu/video.c b/src/emu/video.c index 9aea274d024..09b0c83ffb0 100644 --- a/src/emu/video.c +++ b/src/emu/video.c @@ -15,6 +15,7 @@ #include "debugger.h" #include "rendutil.h" #include "ui.h" +#include "aviio.h" #include "snap.lh" @@ -79,7 +80,8 @@ struct _screen_state vblank_state_changed_func vblank_callback[MAX_VBLANK_CALLBACKS]; /* the array of callbacks */ /* movie recording */ - mame_file * movie_file; /* handle to the open movie file */ + mame_file * mng_file; /* handle to the open movie file */ + avi_file * avi_file; /* handle to the open movie file */ UINT32 movie_frame; /* current movie frame number */ }; @@ -188,7 +190,8 @@ static void create_snapshot_bitmap(const device_config *screen); static file_error mame_fopen_next(running_machine *machine, const char *pathoption, const char *extension, mame_file **file); /* movie recording */ -static void movie_record_frame(const device_config *screen); +static void video_mng_record_frame(const device_config *screen); +static void video_avi_record_frame(const device_config *screen); /* software rendering */ static void rgb888_draw_primitives(const render_primitive *primlist, void *dstdata, UINT32 width, UINT32 height, UINT32 pitch); @@ -345,8 +348,12 @@ void video_init(running_machine *machine) /* start recording movie if specified */ filename = options_get_string(mame_options(), OPTION_MNGWRITE); - if ((filename[0] != 0) && (machine->primary_screen != NULL)) - video_movie_begin_recording(machine->primary_screen, filename); + if (filename[0] != 0 && machine->primary_screen != NULL) + video_mng_begin_recording(machine->primary_screen, filename); + + filename = options_get_string(mame_options(), OPTION_AVIWRITE); + if (filename[0] != 0 && machine->primary_screen != NULL) + video_avi_begin_recording(machine->primary_screen, filename); /* if no screens, create a periodic timer to drive updates */ if (machine->primary_screen == NULL) @@ -371,7 +378,10 @@ static void video_exit(running_machine *machine) /* stop recording any movie */ if (machine->primary_screen != NULL) - video_movie_end_recording(machine->primary_screen); + { + video_mng_end_recording(machine->primary_screen); + video_avi_end_recording(machine->primary_screen); + } /* free all the graphics elements */ for (i = 0; i < MAX_GFX_ELEMENTS; i++) @@ -696,7 +706,7 @@ void video_screen_configure(const device_config *screen, int width, int height, /* if there has been no VBLANK time specified in the MACHINE_DRIVER, compute it now from the visible area, otherwise just used the supplied value */ - if ((config->vblank == 0) && !config->oldstyle_vblank_supplied) + if (config->vblank == 0 && !config->oldstyle_vblank_supplied) state->vblank_period = state->scantime * (height - (visarea->max_y + 1 - visarea->min_y)); else state->vblank_period = config->vblank; @@ -833,7 +843,7 @@ void video_screen_update_now(const device_config *screen) we only update up to the previous scanline. This minimizes the number of pixels that might be drawn incorrectly until we support a pixel level granularity */ - if ((current_hpos < (state->width / 2)) && (current_vpos > 0)) + if (current_hpos < (state->width / 2) && current_vpos > 0) current_vpos = current_vpos - 1; video_screen_update_partial(screen, current_vpos); @@ -1157,10 +1167,10 @@ static DEVICE_START( video_screen ) assert(config->height > 0); assert(config->refresh > 0); assert(config->visarea.min_x >= 0); - assert((config->visarea.max_x < config->width) || (config->type == SCREEN_TYPE_VECTOR)); + assert(config->visarea.max_x < config->width || config->type == SCREEN_TYPE_VECTOR); assert(config->visarea.max_x > config->visarea.min_x); assert(config->visarea.min_y >= 0); - assert((config->visarea.max_y < config->height) || (config->type == SCREEN_TYPE_VECTOR)); + assert(config->visarea.max_y < config->height || config->type == SCREEN_TYPE_VECTOR); assert(config->visarea.max_y > config->visarea.min_y); /* allocate the VBLANK timers */ @@ -1297,7 +1307,7 @@ static TIMER_CALLBACK( vblank_begin_callback ) (*state->vblank_callback[i])(screen, TRUE); /* if this is the primary screen and we need to update now */ - if ((screen == machine->primary_screen) && !(machine->config->video_attributes & VIDEO_UPDATE_AFTER_VBLANK)) + if (screen == machine->primary_screen && !(machine->config->video_attributes & VIDEO_UPDATE_AFTER_VBLANK)) video_frame_update(machine, FALSE); /* reset the VBLANK start timer for the next frame */ @@ -1327,7 +1337,7 @@ static TIMER_CALLBACK( vblank_end_callback ) (*state->vblank_callback[i])(screen, FALSE); /* if this is the primary screen and we need to update now */ - if ((screen == machine->primary_screen) && (machine->config->video_attributes & VIDEO_UPDATE_AFTER_VBLANK)) + if (screen == machine->primary_screen && (machine->config->video_attributes & VIDEO_UPDATE_AFTER_VBLANK)) video_frame_update(machine, FALSE); /* increment the frame number counter */ @@ -1445,7 +1455,7 @@ void video_frame_update(running_machine *machine, int debug) if (phase == MAME_PHASE_RUNNING) { /* reset partial updates if we're paused or if the debugger is active */ - if ((machine->primary_screen != NULL) && (mame_is_paused(machine) || debug || mame_debug_is_active())) + if (machine->primary_screen != NULL && (mame_is_paused(machine) || debug || mame_debug_is_active())) { void *param = (void *)machine->primary_screen; scanline0_callback(machine, param, 0); @@ -1508,7 +1518,10 @@ static int finish_screen_updates(running_machine *machine) /* update our movie recording state */ if (!mame_is_paused(machine)) - movie_record_frame(screen); + { + video_mng_record_frame(screen); + video_avi_record_frame(screen); + } } /* reset the screen changed flags */ @@ -2158,7 +2171,7 @@ void video_save_active_screen_snapshots(running_machine *machine) /*------------------------------------------------- creare_snapshot_bitmap - creates a bitmap containing the screenshot for the - given screen number + given screen -------------------------------------------------*/ static void create_snapshot_bitmap(const device_config *screen) @@ -2177,7 +2190,7 @@ static void create_snapshot_bitmap(const device_config *screen) render_target_set_bounds(global.snap_target, width, height, 0); /* if we don't have a bitmap, or if it's not the right size, allocate a new one */ - if ((global.snap_bitmap == NULL) || (width != global.snap_bitmap->width) || (height != global.snap_bitmap->height)) + if (global.snap_bitmap == NULL || width != global.snap_bitmap->width || height != global.snap_bitmap->height) { if (global.snap_bitmap != NULL) bitmap_free(global.snap_bitmap); @@ -2233,72 +2246,72 @@ static file_error mame_fopen_next(running_machine *machine, const char *pathopti ***************************************************************************/ /*------------------------------------------------- - video_is_movie_active - return true if a movie - is currently being recorded + video_mng_is_movie_active - return true if a + MNG movie is currently being recorded -------------------------------------------------*/ -int video_is_movie_active(const device_config *screen) +int video_mng_is_movie_active(const device_config *screen) { screen_state *state = get_safe_token(screen); - return (state->movie_file != NULL); + return (state->mng_file != NULL); } /*------------------------------------------------- - video_movie_begin_recording - begin recording + video_mng_begin_recording - begin recording of a MNG movie -------------------------------------------------*/ -void video_movie_begin_recording(const device_config *screen, const char *name) +void video_mng_begin_recording(const device_config *screen, const char *name) { screen_state *state = get_safe_token(screen); file_error filerr; /* close any existing movie file */ - if (state->movie_file != NULL) - video_movie_end_recording(screen); + if (state->mng_file != NULL) + video_mng_end_recording(screen); /* create a new movie file and start recording */ if (name != NULL) - filerr = mame_fopen(SEARCHPATH_MOVIE, name, OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS, &state->movie_file); + filerr = mame_fopen(SEARCHPATH_MOVIE, name, OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS, &state->mng_file); else - filerr = mame_fopen_next(screen->machine, SEARCHPATH_MOVIE, "mng", &state->movie_file); + filerr = mame_fopen_next(screen->machine, SEARCHPATH_MOVIE, "mng", &state->mng_file); state->movie_frame = 0; } /*------------------------------------------------- - video_movie_end_recording - stop recording of + video_mng_end_recording - stop recording of a MNG movie -------------------------------------------------*/ -void video_movie_end_recording(const device_config *screen) +void video_mng_end_recording(const device_config *screen) { screen_state *state = get_safe_token(screen); /* close the file if it exists */ - if (state->movie_file != NULL) + if (state->mng_file != NULL) { - mng_capture_stop(mame_core_file(state->movie_file)); - mame_fclose(state->movie_file); - state->movie_file = NULL; + mng_capture_stop(mame_core_file(state->mng_file)); + mame_fclose(state->mng_file); + state->mng_file = NULL; state->movie_frame = 0; } } /*------------------------------------------------- - movie_record_frame - record a frame of a + video_mng_record_frame - record a frame of a movie -------------------------------------------------*/ -static void movie_record_frame(const device_config *screen) +static void video_mng_record_frame(const device_config *screen) { screen_state *state = get_safe_token(screen); const rgb_t *palette; /* only record if we have a file */ - if (state->movie_file != NULL) + if (state->mng_file != NULL) { png_info pnginfo = { 0 }; png_error error; @@ -2320,24 +2333,21 @@ static void movie_record_frame(const device_config *screen) png_add_text(&pnginfo, "System", text); /* start the capture */ - error = mng_capture_start(mame_core_file(state->movie_file), global.snap_bitmap, ATTOSECONDS_TO_HZ(state->frame_period)); + error = mng_capture_start(mame_core_file(state->mng_file), global.snap_bitmap, ATTOSECONDS_TO_HZ(state->frame_period)); if (error != PNGERR_NONE) { png_free(&pnginfo); - video_movie_end_recording(screen); + video_mng_end_recording(screen); return; } } /* write the next frame */ palette = (screen->machine->palette != NULL) ? palette_entry_list_adjusted(screen->machine->palette) : NULL; - error = mng_capture_frame(mame_core_file(state->movie_file), &pnginfo, global.snap_bitmap, screen->machine->config->total_colors, palette); + error = mng_capture_frame(mame_core_file(state->mng_file), &pnginfo, global.snap_bitmap, screen->machine->config->total_colors, palette); png_free(&pnginfo); if (error != PNGERR_NONE) - { - video_movie_end_recording(screen); - return; - } + video_mng_end_recording(screen); profiler_mark(PROFILER_END); } @@ -2345,6 +2355,150 @@ static void movie_record_frame(const device_config *screen) +/*************************************************************************** + AVI MOVIE RECORDING +***************************************************************************/ + +/*------------------------------------------------- + video_avi_begin_recording - begin recording + of an AVI movie +-------------------------------------------------*/ + +void video_avi_begin_recording(const device_config *screen, const char *name) +{ + screen_state *state = get_safe_token(screen); + avi_movie_info info; + mame_file *tempfile; + file_error filerr; + avi_error avierr; + + /* close any existing movie file */ + if (state->avi_file != NULL) + video_avi_end_recording(screen); + + /* create a snapshot bitmap so we know what the target size is */ + create_snapshot_bitmap(screen); + + /* build up information about this new movie */ + info.video_format = 0; + info.video_timescale = 1000 * ATTOSECONDS_TO_HZ(state->frame_period); + info.video_sampletime = 1000; + info.video_numsamples = 0; + info.video_width = global.snap_bitmap->width; + info.video_height = global.snap_bitmap->height; + info.video_depth = 24; + + info.audio_format = 0; + info.audio_timescale = 1; + info.audio_sampletime = screen->machine->sample_rate; + info.audio_numsamples = 0; + info.audio_channels = 2; + info.audio_samplebits = 16; + info.audio_samplerate = screen->machine->sample_rate; + + /* create a new temporary movie file */ + if (name != NULL) + filerr = mame_fopen(SEARCHPATH_MOVIE, name, OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS, &tempfile); + else + filerr = mame_fopen_next(screen->machine, SEARCHPATH_MOVIE, "avi", &tempfile); + state->movie_frame = 0; + + /* if we succeeded, make a copy of the name and create the real file over top */ + if (filerr == FILERR_NONE) + { + astring *fullname = astring_dupc(mame_file_full_name(tempfile)); + mame_fclose(tempfile); + + /* create the file and free the string */ + avierr = avi_create(astring_c(fullname), &info, &state->avi_file); + astring_free(fullname); + } +} + + +/*------------------------------------------------- + video_avi_end_recording - stop recording of + a avi movie +-------------------------------------------------*/ + +void video_avi_end_recording(const device_config *screen) +{ + screen_state *state = get_safe_token(screen); + + /* close the file if it exists */ + if (state->avi_file != NULL) + { + avi_close(state->avi_file); + state->avi_file = NULL; + state->movie_frame = 0; + } +} + + +/*------------------------------------------------- + video_avi_record_frame - record a frame of a + movie +-------------------------------------------------*/ + +static void video_avi_record_frame(const device_config *screen) +{ + screen_state *state = get_safe_token(screen); + + /* only record if we have a file */ + if (state->avi_file != NULL) + { + avi_error avierr; + + profiler_mark(PROFILER_MOVIE_REC); + + /* create the bitmap */ + create_snapshot_bitmap(screen); + + /* write the next frame */ + avierr = avi_append_video_frame_rgb32(state->avi_file, global.snap_bitmap); + if (avierr != AVIERR_NONE) + video_avi_end_recording(screen); + + profiler_mark(PROFILER_END); + } +} + + +/*------------------------------------------------- + video_avi_add_sound - add sound to an AVI + recording +-------------------------------------------------*/ + +void video_avi_add_sound(running_machine *machine, const INT16 *sound, int numsamples) +{ + const device_config *screen; + + /* loop over screens that might be recording */ + for (screen = video_screen_first(machine->config); screen != NULL; screen = video_screen_next(screen)) + { + screen_state *state = get_safe_token(screen); + + /* only record if we have a file */ + if (state->avi_file != NULL) + { + avi_error avierr; + + profiler_mark(PROFILER_MOVIE_REC); + + /* write the next frame */ + avierr = avi_append_sound_samples(state->avi_file, 0, sound + 0, numsamples, 1); + if (avierr == AVIERR_NONE) + avierr = avi_append_sound_samples(state->avi_file, 1, sound + 1, numsamples, 1); + if (avierr != AVIERR_NONE) + video_avi_end_recording(screen); + + profiler_mark(PROFILER_END); + } + } +} + + + /*************************************************************************** SOFTWARE RENDERING ***************************************************************************/ diff --git a/src/emu/video.h b/src/emu/video.h index 3ebcbf4053d..68a7bf55fd3 100644 --- a/src/emu/video.h +++ b/src/emu/video.h @@ -263,9 +263,13 @@ void video_save_active_screen_snapshots(running_machine *machine); /* ----- movie recording ----- */ -int video_is_movie_active(const device_config *screen); -void video_movie_begin_recording(const device_config *screen, const char *name); -void video_movie_end_recording(const device_config *screen); +int video_mng_is_movie_active(const device_config *screen); +void video_mng_begin_recording(const device_config *screen, const char *name); +void video_mng_end_recording(const device_config *screen); +int video_avi_is_movie_active(const device_config *screen); +void video_avi_begin_recording(const device_config *screen, const char *name); +void video_avi_end_recording(const device_config *screen); +void video_avi_add_sound(running_machine *machine, const INT16 *sound, int numsamples); #endif /* __VIDEO_H__ */ diff --git a/src/lib/util/aviio.c b/src/lib/util/aviio.c index c4edafd9b3c..80484524f5c 100644 --- a/src/lib/util/aviio.c +++ b/src/lib/util/aviio.c @@ -52,11 +52,6 @@ #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 @@ -224,6 +219,9 @@ static avi_error soundbuf_initialize(avi_file *file); static avi_error soundbuf_write_chunk(avi_file *file, UINT32 framenum); static avi_error soundbuf_flush(avi_file *file, int only_flush_full); +/* RGB helpers */ +static avi_error rgb32_compress_to_rgb(avi_stream *stream, const bitmap_t *bitmap, UINT8 *data, UINT32 numbytes); + /* YUY helpers */ static avi_error yuv_decompress_to_yuy16(avi_stream *stream, const UINT8 *data, UINT32 numbytes, bitmap_t *bitmap); static avi_error yuy16_compress_to_yuy(avi_stream *stream, const bitmap_t *bitmap, UINT8 *data, UINT32 numbytes); @@ -928,6 +926,10 @@ avi_error avi_append_video_frame_yuy16(avi_file *file, const bitmap_t *bitmap) avi_error avierr; UINT32 maxlength; + /* validate our ability to handle the data */ + if (stream->format != FORMAT_UYVY && stream->format != FORMAT_VYUY && stream->format != FORMAT_YUY2 && stream->format != FORMAT_HFYU) + return AVIERR_UNSUPPORTED_VIDEO_FORMAT; + /* double check bitmap format */ if (bitmap->format != BITMAP_FORMAT_YUY16) return AVIERR_INVALID_BITMAP; @@ -959,12 +961,62 @@ avi_error avi_append_video_frame_yuy16(avi_file *file, const bitmap_t *bitmap) } +/*------------------------------------------------- + avi_append_video_frame_rgb32 - append a frame + of video in RGB32 format +-------------------------------------------------*/ + +avi_error avi_append_video_frame_rgb32(avi_file *file, const bitmap_t *bitmap) +{ + avi_stream *stream = get_video_stream(file); + avi_error avierr; + UINT32 maxlength; + + /* validate our ability to handle the data */ + if (stream->format != 0) + return AVIERR_UNSUPPORTED_VIDEO_FORMAT; + + /* depth must be 24 */ + if (stream->depth != 24) + return AVIERR_UNSUPPORTED_VIDEO_FORMAT; + + /* double check bitmap format */ + if (bitmap->format != BITMAP_FORMAT_RGB32) + return AVIERR_INVALID_BITMAP; + + /* write out any sound data first */ + avierr = soundbuf_write_chunk(file, stream->chunks); + if (avierr != AVIERR_NONE) + return avierr; + + /* make sure we have enough room */ + maxlength = 3 * stream->width * stream->height; + avierr = expand_tempbuffer(file, maxlength); + if (avierr != AVIERR_NONE) + return avierr; + + /* copy the RGB data to the destination */ + avierr = rgb32_compress_to_rgb(stream, bitmap, file->tempbuffer, maxlength); + if (avierr != AVIERR_NONE) + return avierr; + + /* set the info for this new chunk */ + avierr = set_stream_chunk_info(stream, stream->chunks, file->writeoffs, maxlength + 8); + if (avierr != AVIERR_NONE) + return avierr; + stream->samples = file->info.video_numsamples = stream->chunks; + + /* write the data */ + return chunk_write(file, get_chunkid_for_stream(file, stream), file->tempbuffer, maxlength); +} + + /*------------------------------------------------- avi_append_sound_samples - append sound samples -------------------------------------------------*/ -avi_error avi_append_sound_samples(avi_file *file, int channel, const INT16 *samples, UINT32 numsamples) +avi_error avi_append_sound_samples(avi_file *file, int channel, const INT16 *samples, UINT32 numsamples, UINT32 sampleskip) { UINT32 sampoffset = file->soundbuf_chansamples[channel]; UINT32 sampnum; @@ -977,6 +1029,7 @@ avi_error avi_append_sound_samples(avi_file *file, int channel, const INT16 *sam for (sampnum = 0; sampnum < numsamples; sampnum++) { INT16 data = *samples++; + samples += sampleskip; data = LITTLE_ENDIANIZE_INT16(data); file->soundbuf[sampoffset++ * file->info.audio_channels + channel] = data; } @@ -2239,6 +2292,57 @@ static avi_error soundbuf_flush(avi_file *file, int only_flush_full) } +/*------------------------------------------------- + rgb32_compress_to_rgb - "compress" an RGB32 + bitmap to an RGB encoded frame +-------------------------------------------------*/ + +static avi_error rgb32_compress_to_rgb(avi_stream *stream, const bitmap_t *bitmap, UINT8 *data, UINT32 numbytes) +{ + int height = MIN(stream->height, bitmap->height); + int width = MIN(stream->width, bitmap->width); + UINT8 *dataend = data + numbytes; + int x, y; + + /* compressed video */ + for (y = 0; y < height; y++) + { + const UINT32 *source = (UINT32 *)bitmap->base + y * bitmap->rowpixels; + UINT8 *dest = data + (stream->height - 1 - y) * stream->width * 3; + + for (x = 0; x < width && dest < dataend; x++) + { + UINT32 pix = *source++; + *dest++ = RGB_BLUE(pix); + *dest++ = RGB_GREEN(pix); + *dest++ = RGB_RED(pix); + } + + /* fill in any blank space on the right */ + for ( ; x < stream->width && dest < dataend; x++) + { + *dest++ = 0; + *dest++ = 0; + *dest++ = 0; + } + } + + /* fill in any blank space on the bottom */ + for ( ; y < stream->height; y++) + { + UINT8 *dest = data + (stream->height - 1 - y) * stream->width * 3; + for (x = 0; x < stream->width && dest < dataend; x++) + { + *dest++ = 0; + *dest++ = 0; + *dest++ = 0; + } + } + + return AVIERR_NONE; +} + + /*------------------------------------------------- yuv_decompress_to_yuy16 - decompress a YUV encoded frame to a YUY16 bitmap diff --git a/src/lib/util/aviio.h b/src/lib/util/aviio.h index 37e949e6961..9c9e938893e 100644 --- a/src/lib/util/aviio.h +++ b/src/lib/util/aviio.h @@ -118,6 +118,7 @@ avi_error avi_read_video_frame_yuy16(avi_file *file, UINT32 framenum, bitmap_t * avi_error avi_read_sound_samples(avi_file *file, int channel, UINT32 firstsample, UINT32 numsamples, INT16 *output); avi_error avi_append_video_frame_yuy16(avi_file *file, const bitmap_t *bitmap); -avi_error avi_append_sound_samples(avi_file *file, int channel, const INT16 *samples, UINT32 numsamples); +avi_error avi_append_video_frame_rgb32(avi_file *file, const bitmap_t *bitmap); +avi_error avi_append_sound_samples(avi_file *file, int channel, const INT16 *samples, UINT32 numsamples, UINT32 sampleskip); #endif