mirror of
https://github.com/holub/mame
synced 2025-04-22 08:22:15 +03:00
Added uncompressed AVI recording. Extended aviio to be able
to write RGB bitmaps. Unfortunately, the only option is fully uncompressed, which means the resulting AVIs are *HUGE* and may not play correctly in realtime due to high data rate. The intention is that these uncompressed AVIs are post-processed by other utilities to compress the video and produce a realtime playable result. Added new command-line option -aviwrite which works just like -mngwrite, except it produces AVIs and streams sound to them. Updated documentation accordingly. Shift+F12 still produces MNGs for now, though this might change in the future. Modified fileio.c to retain the full pathname to the file so that it can be queried while the file is open.
This commit is contained in:
parent
5c918815e4
commit
86dd599aa8
@ -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 <filename>
|
||||
|
||||
Stream video and sound data to the given <filename> in AVI format,
|
||||
producing an animation of the game session complete with sound. The
|
||||
default is NULL (no recording).
|
||||
|
||||
-wavwrite <filename>
|
||||
|
||||
Writes the final mixer output to the given <filename> in WAV format,
|
||||
|
@ -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 */
|
||||
|
@ -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 */
|
||||
|
@ -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
|
||||
-------------------------------------------------*/
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
236
src/emu/video.c
236
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
|
||||
***************************************************************************/
|
||||
|
@ -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__ */
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user