recording: open output after startup state has settled

Defer recording until the first core frame after pending startup state loading. This lets the core publish its resulting AV information before the output is opened, preserving the requested filename instead of creating a header-only file and restarting into a dated recording. Cancel pending startup on stop, content close and quit. Exercise the actual startup functions and retain the auto-state-load harness link.
This commit is contained in:
Codex committed 2026-10-27 05:11:10 +01:00
1 parent ac57896750
commit 43580f5b9e
8 files changed
+124 -3

No files matched your search

+2
View File
@@ -387,3 +387,5 @@ samples/gfx/d3d12_upscaler/Zdir/
samples/gfx/d3d12_upscaler/Zdir_fsr/
samples/gfx/d3d12_upscaler/Zdir_xess/
samples/gfx/d3d12_upscaler/Zdir_none/
samples/record/ffmpeg_pipeline/record_finalize_fragment.c
samples/record/ffmpeg_pipeline/record_start_fragment.c
+32
View File
@@ -22,6 +22,7 @@
#include <retro_math.h>
#include "../configuration.h"
#include "../content.h"
#include "../list_special.h"
#include "../gfx/video_driver.h"
#include "../audio/audio_driver.h"
@@ -81,10 +82,26 @@ void recording_gpu_frame_info(struct video_frame_info *info,bool capture)
}
void recording_gpu_poll(void)
{
if (recording_state.start_pending)
{
runloop_state_t *loop = runloop_state_get_ptr();
if (loop->quit_pending || loop->content_closing)
command_event(CMD_EVENT_RECORD_DEINIT, NULL);
}
record_nvenc_poll();
record_metal_poll();
recording_gpu_reinit_poll();
}
void recording_start_frame(void)
{
if (!recording_state.start_pending || runloop_is_content_switching()
|| content_load_state_in_progress(NULL)
|| runloop_state_get_ptr()->auto_state_load_pending)
return;
recording_state.start_pending = false;
if (!recording_init())
command_event(CMD_EVENT_RECORD_DEINIT, NULL);
}
void recording_gpu_stop(void) { record_nvenc_stop(); record_metal_stop(); }
bool recording_gpu_pending(void) { return record_nvenc_pending() || record_metal_pending(); }
@@ -181,6 +198,13 @@ bool recording_deinit(void)
bool history_list_enable = config_get_ptr()->bools.history_list_enable;
#endif
if (recording_st->start_pending)
{
recording_st->start_pending = false;
recording_st->path[0] = '\0';
return true;
}
if (recording_gpu_cancel_start())
{
recording_st->driver = NULL;
@@ -262,6 +286,14 @@ bool recording_init(void)
if (!recording_enable)
return false;
if (content_load_state_in_progress(NULL)
|| runloop_st->auto_state_load_pending)
{
recording_st->start_pending = true;
return true;
}
recording_st->start_pending = false;
#ifndef HAVE_RECORD_NVENC
if (nvenc_native)
{
+2
View File
@@ -170,6 +170,7 @@ struct recording
bool enable;
bool streaming_enable;
bool use_output_dir;
bool start_pending;
};
typedef struct recording recording_state_t;
@@ -210,6 +211,7 @@ extern const record_driver_t record_metal;
struct video_frame_info;
void recording_gpu_frame_info(struct video_frame_info *info,bool capture);
void recording_gpu_poll(void);
void recording_start_frame(void);
void recording_gpu_stop(void);
bool recording_gpu_pending(void);
/* Main owner: coalesce reinit flags while native users retire. The existing
+2
View File
@@ -10579,6 +10579,8 @@ static bool retroarch_main_quit_begin(void)
if (runloop_st->flags & RUNLOOP_FLAG_SHUTDOWN_INITIATED)
return false;
recording_gpu_stop();
if (recording_state_get_ptr()->start_pending)
command_event(CMD_EVENT_RECORD_DEINIT, NULL);
if ( settings->bools.savestate_auto_save
&& runloop_st->current_core_type != CORE_TYPE_DUMMY)
command_event_save_auto_state();
+1
View File
@@ -11155,6 +11155,7 @@ bool core_run(void)
if (runloop_st->auto_state_load_pending)
runloop_st->auto_state_load_core_ran = true;
audio_driver_frame_end();
recording_start_frame();
}
#ifdef HAVE_GAME_AI
+5 -2
View File
@@ -79,7 +79,10 @@ endif
record_finalize_fragment.c: $(CORE_DIR)/record/record_driver.c
sed -n '/^bool recording_deinit(void)/,/^void streaming_set_state/p' $< | sed '$$d' > $@
record_finalize_test.o: record_finalize_fragment.c
record_start_fragment.c: $(CORE_DIR)/record/record_driver.c
sed -n '/^void recording_gpu_poll(void)/,/^void recording_gpu_stop(void)/p' $< | sed '$$d' > $@
record_finalize_test.o: record_finalize_fragment.c record_start_fragment.c
record_audio_growth_test.o: $(CORE_DIR)/record/drivers/record_ffmpeg.c
record_video_stride_test.o: $(CORE_DIR)/record/drivers/record_ffmpeg.c
@@ -97,6 +100,6 @@ $(SAMPLE_OBJDIR)/%.o: $$(call sample_src,$$*).c
clean:
rm -rf $(SAMPLE_OBJDIR)
rm -f $(TARGET) $(TARGET).exe $(OBJS) ffmpeg_pipeline_test.mkv record_finalize_fragment.c
rm -f $(TARGET) $(TARGET).exe $(OBJS) ffmpeg_pipeline_test.mkv record_finalize_fragment.c record_start_fragment.c
.PHONY: clean
@@ -7,11 +7,15 @@
#include "../../../defaults.h"
#include "../../../playlist.h"
#include "../../../verbosity.h"
#include "../../../runloop.h"
#include "../../../command.h"
static recording_state_t recording_state;
static settings_t test_settings;
static unsigned freed, histories, gpu_cleanup;
static bool finalize_result, gpu_start_pending;
static bool load_pending, switching, start_success;
static unsigned starts;
struct defaults g_defaults;
/* The native GPU encoders, which this test does not build: only their
* addresses are compared */
@@ -39,10 +43,38 @@ static void release(void *data) { (void)data; freed++; }
#include "record_finalize_fragment.c"
bool content_load_state_in_progress(void *data)
{
(void)data;
return load_pending;
}
bool runloop_is_content_switching(void) { return switching; }
bool recording_init(void)
{
if (load_pending || switching
|| runloop_state_get_ptr()->auto_state_load_pending
|| strcmp(recording_state.path, "requested.mkv"))
return false;
starts++;
return start_success;
}
bool command_event(enum event_command cmd, void *data)
{
(void)data;
if (cmd != CMD_EVENT_RECORD_DEINIT) return false;
recording_state.enable = false;
return recording_deinit();
}
void record_nvenc_poll(void) { }
void record_metal_poll(void) { }
void record_mediacodec_poll(void) { }
void recording_gpu_reinit_poll(void) { }
#include "record_start_fragment.c"
int record_finalize_test_run(void)
{
record_driver_t driver = {0};
unsigned lane;
unsigned lane, mode;
driver.free = release;
driver.ident = "test";
test_settings.bools.history_list_enable = true;
@@ -80,6 +112,52 @@ int record_finalize_test_run(void)
return 1;
}
gpu_start_pending = false;
recording_state.start_pending = true;
recording_state.driver = NULL;
recording_state.data = NULL;
recording_state.path[0] = 'x';
if (!recording_deinit() || recording_state.start_pending
|| recording_state.path[0] || freed || gpu_cleanup || histories)
{
fprintf(stderr, "frontend finalize: state-load start was not cancelled\n");
return 1;
}
for (mode = 0; mode < 4; mode++)
{
runloop_state_t *loop = runloop_state_get_ptr();
recording_state.start_pending = true;
recording_state.enable = true;
strcpy(recording_state.path, "requested.mkv");
starts = 0;
start_success = mode != 1;
load_pending = true;
recording_gpu_poll();
recording_start_frame();
if (starts || !recording_state.start_pending) return 1;
load_pending = false;
loop->auto_state_load_pending = true;
recording_gpu_poll();
recording_start_frame();
if (starts || !recording_state.start_pending) return 1;
loop->auto_state_load_pending = false;
switching = true;
recording_gpu_poll();
recording_start_frame();
if (starts || !recording_state.start_pending) return 1;
switching = false;
loop->content_closing = mode == 2;
loop->quit_pending = mode == 3;
recording_gpu_poll();
if (starts) return 1;
recording_start_frame();
recording_gpu_poll();
recording_start_frame();
if (recording_state.start_pending || starts != (unsigned)(mode < 2)
|| (mode && recording_state.enable)
|| (mode >= 2 && recording_state.path[0])) return 1;
loop->content_closing = loop->quit_pending = false;
}
puts("[pass] recording start waits for state completion and cancels safely");
puts("[pass] frontend finalization propagates failures, cleans up, and gates history");
return 0;
}
@@ -30,6 +30,7 @@ void input_driver_poll(void) {}
void input_driver_poll_between_frames(void (*poll)(void)) { poll(); }
void video_driver_cached_frame(void) {}
void audio_driver_frame_end(void) {}
void recording_start_frame(void) {}
void audio_driver_publish_runloop(void) {}
void RARCH_LOG(const char *fmt, ...) { (void)fmt; }
bool verbosity_is_enabled(void) { return false; }