diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/recorder/recorder.c | 766 |
1 files changed, 407 insertions, 359 deletions
diff --git a/src/recorder/recorder.c b/src/recorder/recorder.c index 9eb819f..7ace350 100644 --- a/src/recorder/recorder.c +++ b/src/recorder/recorder.c @@ -76,6 +76,15 @@ struct gsr_recorder { char replay_recording_filepath[PATH_MAX]; bool replay_recording; + double fps_start_time; + int fps_counter; + int damage_fps_counter; + bool paused; + double record_start_time; + int64_t video_pts_counter; + int64_t video_prev_pts; + bool hdr_metadata_set; + volatile sig_atomic_t running; volatile sig_atomic_t toggle_pause; volatile sig_atomic_t toggle_replay_recording; @@ -94,34 +103,7 @@ struct gsr_recorder { static void gsr_recorder_stop_recording(gsr_recorder *self); -gsr_recorder* gsr_recorder_create(const gsr_recorder_params *params, const gsr_recorder_callbacks *callbacks, int *error) { - *error = GSR_ERROR_GENERIC; - - gsr_recorder *self = calloc(1, sizeof(gsr_recorder)); - if(!self) { - gsr_log(GSR_LOG_LEVEL_ERROR, "gsr_recorder_create: failed to allocate recorder"); - return NULL; - } - - self->settings = *params->settings; - if(callbacks) - self->callbacks = *callbacks; - self->windowing = params->windowing; - self->egl = ¶ms->windowing->egl; - self->window = params->windowing->window; - self->capture_deps = params->capture_deps; - self->capture_sources = params->capture_sources; - self->audio_input_tracks = params->audio_input_tracks; - self->running = 1; - self->audio_max_frame_size = 1024; - int error_code = GSR_ERROR_GENERIC; - self->hdr = video_codec_is_hdr(params->settings->video_codec); - self->plugin_filepaths = params->plugin_filepaths; - self->num_plugin_filepaths = params->num_plugin_filepaths; -#ifdef GSR_APP_AUDIO - self->pipewire_audio = params->pipewire_audio; -#endif - +static int recorder_setup_container(gsr_recorder *self) { // The output format is automatically guessed by the file extension avformat_alloc_output_context2(&self->av_format_context, NULL, self->settings.container_format, self->settings.filename); if (!self->av_format_context) { @@ -130,11 +112,9 @@ gsr_recorder* gsr_recorder_create(const gsr_recorder_params *params, const gsr_r } else { gsr_log(GSR_LOG_LEVEL_ERROR, "Failed to deduce container format from file extension. Use the '-c' option to specify container format"); args_parser_print_usage(); - error_code = GSR_ERROR_GENERIC; - goto fail; + return GSR_ERROR_GENERIC; } - error_code = GSR_ERROR_GENERIC; - goto fail; + return GSR_ERROR_GENERIC; } set_format_context_options(self->av_format_context); @@ -157,11 +137,14 @@ gsr_recorder* gsr_recorder_create(const gsr_recorder_params *params, const gsr_r self->uses_amix = gsr_audio_input_tracks_should_use_amix(self->audio_input_tracks); self->settings.audio_codec = select_audio_codec_with_fallback(self->settings.audio_codec, self->file_extension, self->uses_amix); + return GSR_ERROR_OK; +} + +static int recorder_setup_video_sources(gsr_recorder *self) { self->video_size = (vec2i){0, 0}; const int video_sources_result = gsr_video_sources_create(&self->video_sources_data, &self->settings, self->egl, self->capture_deps, false, self->capture_sources, &self->video_size); if(video_sources_result != GSR_ERROR_OK) { - error_code = video_sources_result; - goto fail; + return video_sources_result; } self->video_sources = &self->video_sources_data; @@ -175,25 +158,26 @@ gsr_recorder* gsr_recorder_create(const gsr_recorder_params *params, const gsr_r gsr_audio_input silent_audio_input; memset(&silent_audio_input, 0, sizeof(silent_audio_input)); if(!gsr_merged_audio_inputs_add(&silent_audio_track, &silent_audio_input) || !gsr_audio_input_tracks_add(self->audio_input_tracks, &silent_audio_track)) { - error_code = GSR_ERROR_GENERIC; - goto fail; + return GSR_ERROR_GENERIC; } } self->video_stream = NULL; + return GSR_ERROR_OK; +} + +static int recorder_setup_video_encoder(gsr_recorder *self) { if(self->settings.video_encoder == GSR_VIDEO_ENCODER_HW_CPU && self->settings.video_codec != (gsr_video_codec)GSR_VIDEO_CODEC_AUTO && self->settings.video_codec != GSR_VIDEO_CODEC_H264) { gsr_log(GSR_LOG_LEVEL_ERROR, "-encoder cpu was specified but a codec other than h264 was specified. -encoder cpu supports only h264 at the moment"); - error_code = GSR_ERROR_GENERIC; - goto fail; + return GSR_ERROR_GENERIC; } self->low_power = false; const AVCodec *video_codec_f = NULL; const int select_video_codec_result = select_video_codec_with_fallback(self->video_size, &self->settings, self->file_extension, self->egl, &self->low_power, &video_codec_f); if(select_video_codec_result != GSR_ERROR_OK) { - error_code = select_video_codec_result; - goto fail; + return select_video_codec_result; } const enum AVPixelFormat video_pix_fmt = get_pixel_format(self->settings.video_codec, self->egl->gpu_info.vendor, self->settings.video_encoder == GSR_VIDEO_ENCODER_HW_CPU); @@ -204,8 +188,7 @@ gsr_recorder* gsr_recorder_create(const gsr_recorder_params *params, const gsr_r self->video_frame = av_frame_alloc(); if(!self->video_frame) { gsr_log(GSR_LOG_LEVEL_ERROR, "Failed to allocate video frame"); - error_code = GSR_ERROR_GENERIC; - goto fail; + return GSR_ERROR_GENERIC; } self->video_frame->format = self->video_codec_context->pix_fmt; self->video_frame->width = self->video_size.x; @@ -219,28 +202,24 @@ gsr_recorder* gsr_recorder_create(const gsr_recorder_params *params, const gsr_r const size_t estimated_replay_buffer_packets = calculate_estimated_replay_buffer_packets(self->settings.replay_buffer_size_secs, self->settings.fps, self->settings.audio_codec, self->audio_input_tracks); self->recording_clock = gsr_recording_clock_create(); if(!self->recording_clock) { - error_code = GSR_ERROR_GENERIC; - goto fail; + return GSR_ERROR_GENERIC; } self->encoder_initialized = true; if(!gsr_encoder_init(&self->encoder, self->settings.replay_storage, estimated_replay_buffer_packets, self->settings.replay_buffer_size_secs, self->settings.filename)) { gsr_log(GSR_LOG_LEVEL_ERROR, "failed to create encoder"); - error_code = GSR_ERROR_GENERIC; - goto fail; + return GSR_ERROR_GENERIC; } self->video_encoder = create_video_encoder(self->egl, &self->settings); if(!self->video_encoder) { gsr_log(GSR_LOG_LEVEL_ERROR, "failed to create video encoder"); - error_code = GSR_ERROR_GENERIC; - goto fail; + return GSR_ERROR_GENERIC; } if(!gsr_video_encoder_start(self->video_encoder, self->video_codec_context, self->video_frame)) { gsr_log(GSR_LOG_LEVEL_ERROR, "failed to start video encoder"); - error_code = GSR_ERROR_GENERIC; - goto fail; + return GSR_ERROR_GENERIC; } self->video_size.x = self->video_codec_context->width; @@ -250,8 +229,7 @@ gsr_recorder* gsr_recorder_create(const gsr_recorder_params *params, const gsr_r memset(&self->plugins, 0, sizeof(self->plugins)); if(gsr_load_plugins(&self->plugins, self->plugin_filepaths, self->num_plugin_filepaths, &self->settings, self->egl, self->video_size) != GSR_ERROR_OK) { - error_code = GSR_ERROR_GENERIC; - goto fail; + return GSR_ERROR_GENERIC; } @@ -265,8 +243,7 @@ gsr_recorder* gsr_recorder_create(const gsr_recorder_params *params, const gsr_r self->color_conversion_initialized = true; if(gsr_color_conversion_init(&self->color_conversion, &color_conversion_params) != 0) { gsr_log(GSR_LOG_LEVEL_ERROR, "main: failed to create color conversion"); - error_code = GSR_ERROR_GENERIC; - goto fail; + return GSR_ERROR_GENERIC; } gsr_color_conversion_clear(&self->color_conversion); @@ -275,13 +252,11 @@ gsr_recorder* gsr_recorder_create(const gsr_recorder_params *params, const gsr_r if(self->settings.video_encoder == GSR_VIDEO_ENCODER_HW_CPU) { if(!open_video_software(self->video_codec_context, &self->settings)) { - error_code = GSR_ERROR_GENERIC; - goto fail; + return GSR_ERROR_GENERIC; } } else { if(!open_video_hardware(self->video_codec_context, self->low_power, self->egl, &self->settings)) { - error_code = GSR_ERROR_GENERIC; - goto fail; + return GSR_ERROR_GENERIC; } } @@ -295,9 +270,12 @@ gsr_recorder* gsr_recorder_create(const gsr_recorder_params *params, const gsr_r } } + return GSR_ERROR_OK; +} + +static int recorder_setup_audio(gsr_recorder *self) { if(gsr_audio_capture_init(&self->audio_capture, &self->encoder, self->recording_clock, &self->running) != GSR_ERROR_OK) { - error_code = GSR_ERROR_GENERIC; - goto fail; + return GSR_ERROR_GENERIC; } @@ -308,8 +286,7 @@ gsr_recorder* gsr_recorder_create(const gsr_recorder_params *params, const gsr_r const bool use_amix = gsr_audio_inputs_should_use_amix(merged_audio_inputs); AVCodecContext *audio_codec_context = create_audio_codec_context(self->settings.fps, self->settings.audio_codec, use_amix, self->settings.audio_bitrate); if(!audio_codec_context) { - error_code = GSR_ERROR_GENERIC; - goto fail; + return GSR_ERROR_GENERIC; } AVStream *audio_stream = NULL; @@ -323,8 +300,7 @@ gsr_recorder* gsr_recorder_create(const gsr_recorder_params *params, const gsr_r av_dict_set(&audio_stream->metadata, "title", merged_audio_inputs->track_name, 0); if(!open_audio(audio_codec_context, self->settings.ffmpeg_audio_opts)) { - error_code = GSR_ERROR_GENERIC; - goto fail; + return GSR_ERROR_GENERIC; } if(audio_stream) @@ -344,14 +320,12 @@ gsr_recorder* gsr_recorder_create(const gsr_recorder_params *params, const gsr_r if(use_amix) { if(merged_audio_inputs->num_items > GSR_MAX_AUDIO_SOURCES_PER_TRACK) { gsr_log(GSR_LOG_LEVEL_ERROR, "too many audio sources for one audio track, the maximum is %d", GSR_MAX_AUDIO_SOURCES_PER_TRACK); - error_code = GSR_ERROR_GENERIC; - goto fail; + return GSR_ERROR_GENERIC; } if(gsr_audio_init_filter_graph(audio_codec_context, &graph, &sink, src_filter_ctx, merged_audio_inputs->num_items) < 0) { gsr_log(GSR_LOG_LEVEL_ERROR, "failed to create audio filter"); - error_code = GSR_ERROR_GENERIC; - goto fail; + return GSR_ERROR_GENERIC; } } @@ -383,14 +357,12 @@ gsr_recorder* gsr_recorder_create(const gsr_recorder_params *params, const gsr_r } if(audio_track_result != GSR_ERROR_OK) { - error_code = audio_track_result; - goto fail; + return audio_track_result; } if(!gsr_audio_capture_add_track(&self->audio_capture, &audio_track)) { - error_code = GSR_ERROR_GENERIC; - goto fail; + return GSR_ERROR_GENERIC; } ++audio_stream_index; @@ -401,18 +373,68 @@ gsr_recorder* gsr_recorder_create(const gsr_recorder_params *params, const gsr_r //av_dump_format(self->av_format_context, 0, filename, 1); + return GSR_ERROR_OK; +} + +static int recorder_open_output(gsr_recorder *self) { if(!self->settings.is_replaying && !(self->av_format_context->oformat->flags & AVFMT_NOFILE)) { const int ret = avio_open(&self->av_format_context->pb, self->settings.filename, AVIO_FLAG_WRITE); if(ret < 0) { gsr_log(GSR_LOG_LEVEL_ERROR, "Could not open '%s': %s", self->settings.filename, gsr_av_error_to_string(ret)); - error_code = GSR_ERROR_GENERIC; - goto fail; + return GSR_ERROR_GENERIC; } } if(!self->settings.is_replaying) av_write_header(self->av_format_context, self->settings.ffmpeg_opts); + return GSR_ERROR_OK; +} + +gsr_recorder* gsr_recorder_create(const gsr_recorder_params *params, const gsr_recorder_callbacks *callbacks, int *error) { + *error = GSR_ERROR_GENERIC; + + gsr_recorder *self = calloc(1, sizeof(gsr_recorder)); + if(!self) { + gsr_log(GSR_LOG_LEVEL_ERROR, "gsr_recorder_create: failed to allocate recorder"); + return NULL; + } + + self->settings = *params->settings; + if(callbacks) + self->callbacks = *callbacks; + self->windowing = params->windowing; + self->egl = ¶ms->windowing->egl; + self->window = params->windowing->window; + self->capture_deps = params->capture_deps; + self->capture_sources = params->capture_sources; + self->audio_input_tracks = params->audio_input_tracks; + self->running = 1; + self->audio_max_frame_size = 1024; + int error_code = GSR_ERROR_GENERIC; + self->hdr = video_codec_is_hdr(params->settings->video_codec); + self->plugin_filepaths = params->plugin_filepaths; + self->num_plugin_filepaths = params->num_plugin_filepaths; +#ifdef GSR_APP_AUDIO + self->pipewire_audio = params->pipewire_audio; +#endif + + const struct { + int (*setup)(gsr_recorder *self); + } setup_phases[] = { + { recorder_setup_container }, + { recorder_setup_video_sources }, + { recorder_setup_video_encoder }, + { recorder_setup_audio }, + { recorder_open_output }, + }; + + for(size_t i = 0; i < sizeof(setup_phases)/sizeof(setup_phases[0]); ++i) { + error_code = setup_phases[i].setup(self); + if(error_code != GSR_ERROR_OK) + goto fail; + } + *error = GSR_ERROR_OK; return self; @@ -421,354 +443,380 @@ gsr_recorder* gsr_recorder_create(const gsr_recorder_params *params, const gsr_r gsr_recorder_destroy(self); return NULL; } +static void recorder_process_events(gsr_recorder *self) { +while(gsr_window_process_event(self->window)) { + if(self->capture_deps->x11_cursor_display && self->settings.record_cursor) + gsr_cursor_on_event(&self->capture_deps->x11_cursor, gsr_window_get_event_data(self->window)); + + gsr_damage_on_event(&self->damage, gsr_window_get_event_data(self->window)); + for(size_t video_source_index = 0; video_source_index < self->video_sources->num_items; ++video_source_index) { +gsr_video_source *video_source = &self->video_sources->items[video_source_index]; + gsr_capture_on_event(video_source->capture, self->egl); + } +} -int gsr_recorder_run(gsr_recorder *self) { - double fps_start_time = clock_get_monotonic_seconds(); - //double frame_timer_start = fps_start_time; - int fps_counter = 0; - int damage_fps_counter = 0; - - bool paused = false; - self->replay_recording = false; - - memset(&self->replay_recording_output, 0, sizeof(self->replay_recording_output)); - - gsr_replay_save_init(&self->replay_save); - - self->force_iframe_frame = false; - - gsr_recording_clock_start(self->recording_clock); - const double record_start_time = gsr_recording_clock_get_start_time(self->recording_clock); - - if(gsr_audio_capture_start(&self->audio_capture, self->audio_max_frame_size, self->uses_amix) != GSR_ERROR_OK) - return GSR_ERROR_GENERIC; +if(self->capture_deps->x11_cursor_display && self->settings.record_cursor) + gsr_cursor_tick(&self->capture_deps->x11_cursor, DefaultRootWindow(self->capture_deps->x11_cursor_display)); +} - // Set update_fps to 24 to test if duplicate/delayed frames cause video/audio desync or too fast/slow video. - //const double update_fps = fps + 190; - self->should_stop_error = false; +static bool recorder_tick_video_sources(gsr_recorder *self) { +gsr_damage_tick(&self->damage); - int64_t video_pts_counter = 0; - int64_t video_prev_pts = 0; +self->should_stop_error = false; +bool damaged = false; - bool hdr_metadata_set = false; - self->hdr = video_codec_is_hdr(self->settings.video_codec); +if(self->use_damage_tracking) + damaged = gsr_damage_is_damaged(&self->damage); - - memset(&self->damage, 0, sizeof(self->damage)); - if(self->settings.framerate_mode == GSR_FRAMERATE_MODE_CONTENT && gsr_capture_sources_has_damage_tracked_target(self->capture_sources)) { - if(gsr_window_get_display_server(self->window) == GSR_DISPLAY_SERVER_X11) { - gsr_damage_init(&self->damage, self->egl, &self->capture_deps->x11_cursor, self->settings.record_cursor); - self->use_damage_tracking = true; +for(size_t video_source_index = 0; video_source_index < self->video_sources->num_items; ++video_source_index) { +gsr_video_source *video_source = &self->video_sources->items[video_source_index]; + gsr_capture_tick(video_source->capture); - for(size_t i = 0; i < self->capture_sources->num_items; ++i) { - const gsr_capture_source *capture_source = &self->capture_sources->items[i]; - switch(capture_source->type) { - case GSR_CAPTURE_SOURCE_TYPE_WINDOW: - gsr_damage_start_tracking_window(&self->damage, capture_source->window_id); - break; - case GSR_CAPTURE_SOURCE_TYPE_MONITOR: - case GSR_CAPTURE_SOURCE_TYPE_REGION: - // TODO: When capturing a region only track damage in that region - gsr_damage_start_tracking_monitor(&self->damage, capture_source->name); - break; - default: - break; - } - } - } else if(gsr_capture_sources_has_monitor_or_region(self->capture_sources)) { - gsr_log(GSR_LOG_LEVEL_WARNING, "\"-fm content\" has no effect on Wayland when recording a monitor. Either record a monitor on X11 or capture with desktop portal instead (-w portal)"); - } + if(gsr_capture_should_stop(video_source->capture, &self->should_stop_error)) { + self->running = 0; + break; } - while(self->running) { - while(gsr_window_process_event(self->window)) { - if(self->capture_deps->x11_cursor_display && self->settings.record_cursor) - gsr_cursor_on_event(&self->capture_deps->x11_cursor, gsr_window_get_event_data(self->window)); - - gsr_damage_on_event(&self->damage, gsr_window_get_event_data(self->window)); - for(size_t video_source_index = 0; video_source_index < self->video_sources->num_items; ++video_source_index) { - gsr_video_source *video_source = &self->video_sources->items[video_source_index]; - gsr_capture_on_event(video_source->capture, self->egl); - } - } + if(video_source->capture_source->type == GSR_CAPTURE_SOURCE_TYPE_FOCUSED_WINDOW) { + assert(video_source->capture->get_window_id); + const Window damage_target_window = video_source->capture->get_window_id(video_source->capture); - if(self->capture_deps->x11_cursor_display && self->settings.record_cursor) - gsr_cursor_tick(&self->capture_deps->x11_cursor, DefaultRootWindow(self->capture_deps->x11_cursor_display)); + if((int64_t)damage_target_window != video_source->capture_source->window_id) { + gsr_damage_stop_tracking_window(&self->damage, video_source->capture_source->window_id); + if(damage_target_window != 0) + gsr_damage_start_tracking_window(&self->damage, damage_target_window); + } - gsr_damage_tick(&self->damage); + video_source->capture_source->window_id = damage_target_window; + } - self->should_stop_error = false; - bool damaged = false; + if(video_source->capture->is_damaged) + damaged |= video_source->capture->is_damaged(video_source->capture); + else if(!self->use_damage_tracking) + damaged = true; +} - if(self->use_damage_tracking) - damaged = gsr_damage_is_damaged(&self->damage); +damaged |= gsr_plugins_is_damaged(&self->plugins); - for(size_t video_source_index = 0; video_source_index < self->video_sources->num_items; ++video_source_index) { - gsr_video_source *video_source = &self->video_sources->items[video_source_index]; - gsr_capture_tick(video_source->capture); +// TODO: Readd wayland sync warning when removing this +if(self->settings.framerate_mode != GSR_FRAMERATE_MODE_CONTENT) + damaged = true; - if(gsr_capture_should_stop(video_source->capture, &self->should_stop_error)) { - self->running = 0; - break; - } +if(damaged) + ++self->damage_fps_counter; - if(video_source->capture_source->type == GSR_CAPTURE_SOURCE_TYPE_FOCUSED_WINDOW) { - assert(video_source->capture->get_window_id); - const Window damage_target_window = video_source->capture->get_window_id(video_source->capture); + return damaged; +} - if((int64_t)damage_target_window != video_source->capture_source->window_id) { - gsr_damage_stop_tracking_window(&self->damage, video_source->capture_source->window_id); - if(damage_target_window != 0) - gsr_damage_start_tracking_window(&self->damage, damage_target_window); - } +static void recorder_update_fps_counters(gsr_recorder *self, bool damaged) { +++self->fps_counter; +const double time_now = clock_get_monotonic_seconds(); +//const double frame_timer_elapsed = time_now - frame_timer_start; +const double elapsed = time_now - self->fps_start_time; +if (elapsed >= 1.0) { + if(self->settings.verbose) { + fprintf(stderr, "update fps: %d, damage fps: %d\n", self->fps_counter, self->damage_fps_counter); + } + self->fps_start_time = time_now; + self->fps_counter = 0; + self->damage_fps_counter = 0; +} +} - video_source->capture_source->window_id = damage_target_window; - } +static void recorder_capture_and_encode_frame(gsr_recorder *self, bool damaged) { +const double this_video_frame_time = gsr_recording_clock_get_time(self->recording_clock); +const int64_t expected_frames = floor((this_video_frame_time - self->record_start_time) / self->target_fps); +const int64_t num_missed_frames = expected_frames - self->video_pts_counter; - if(video_source->capture->is_damaged) - damaged |= video_source->capture->is_damaged(video_source->capture); - else if(!self->use_damage_tracking) - damaged = true; - } +if(damaged && num_missed_frames >= 1 && !self->paused) { + // TODO: Dont do this if no damage? + self->egl->glClear(0); - damaged |= gsr_plugins_is_damaged(&self->plugins); + gsr_damage_clear(&self->damage); + gsr_plugins_clear_damage(&self->plugins); + gsr_capture_deps_cleanup_kms_fds(self->capture_deps); - // TODO: Readd wayland sync warning when removing this - if(self->settings.framerate_mode != GSR_FRAMERATE_MODE_CONTENT) - damaged = true; + gsr_capture_deps_update_kms(self->capture_deps); - if(damaged) - ++damage_fps_counter; + bool capture_has_synchronous_task = false; + for(size_t video_source_index = 0; video_source_index < self->video_sources->num_items; ++video_source_index) { +gsr_video_source *video_source = &self->video_sources->items[video_source_index]; + if(video_source->capture->clear_damage) + video_source->capture->clear_damage(video_source->capture); - ++fps_counter; - const double time_now = clock_get_monotonic_seconds(); - //const double frame_timer_elapsed = time_now - frame_timer_start; - const double elapsed = time_now - fps_start_time; - if (elapsed >= 1.0) { - if(self->settings.verbose) { - fprintf(stderr, "update fps: %d, damage fps: %d\n", fps_counter, damage_fps_counter); + if(video_source->capture->capture_has_synchronous_task) { + capture_has_synchronous_task = video_source->capture->capture_has_synchronous_task(video_source->capture); + if(capture_has_synchronous_task) { + self->paused = true; + gsr_recording_clock_set_paused(self->recording_clock, true); } - fps_start_time = time_now; - fps_counter = 0; - damage_fps_counter = 0; } + } - const double this_video_frame_time = gsr_recording_clock_get_time(self->recording_clock); - const int64_t expected_frames = floor((this_video_frame_time - record_start_time) / self->target_fps); - const int64_t num_missed_frames = expected_frames - video_pts_counter; + for(size_t video_source_index = 0; video_source_index < self->video_sources->num_items; ++video_source_index) { +gsr_video_source *video_source = &self->video_sources->items[video_source_index]; + if(video_source->capture->pre_capture) + video_source->capture->pre_capture(video_source->capture, &video_source->metadata, self->output_color_conversion); + } - if(damaged && num_missed_frames >= 1 && !paused) { - // TODO: Dont do this if no damage? - self->egl->glClear(0); + if(self->output_color_conversion->schedule_clear) { + self->output_color_conversion->schedule_clear = false; + gsr_color_conversion_clear(self->output_color_conversion); + } - gsr_damage_clear(&self->damage); - gsr_plugins_clear_damage(&self->plugins); - gsr_capture_deps_cleanup_kms_fds(self->capture_deps); + for(size_t video_source_index = 0; video_source_index < self->video_sources->num_items; ++video_source_index) { +gsr_video_source *video_source = &self->video_sources->items[video_source_index]; + gsr_capture_capture(video_source->capture, &video_source->metadata, self->output_color_conversion); + } - gsr_capture_deps_update_kms(self->capture_deps); + gsr_capture_deps_cleanup_kms_fds(self->capture_deps); - bool capture_has_synchronous_task = false; - for(size_t video_source_index = 0; video_source_index < self->video_sources->num_items; ++video_source_index) { - gsr_video_source *video_source = &self->video_sources->items[video_source_index]; - if(video_source->capture->clear_damage) - video_source->capture->clear_damage(video_source->capture); + if(self->plugins.num_plugins > 0) { + gsr_plugins_draw(&self->plugins); + gsr_color_conversion_draw(&self->color_conversion, self->plugins.texture, + (vec2i){0, 0}, self->video_size, + (vec2i){0, 0}, self->video_size, + self->video_size, GSR_ROT_0, GSR_FLIP_NONE, GSR_SOURCE_COLOR_RGB, false); + } - if(video_source->capture->capture_has_synchronous_task) { - capture_has_synchronous_task = video_source->capture->capture_has_synchronous_task(video_source->capture); - if(capture_has_synchronous_task) { - paused = true; - gsr_recording_clock_set_paused(self->recording_clock, true); - } - } - } + if(capture_has_synchronous_task) { + self->paused = false; + gsr_recording_clock_set_paused(self->recording_clock, false); + } - for(size_t video_source_index = 0; video_source_index < self->video_sources->num_items; ++video_source_index) { - gsr_video_source *video_source = &self->video_sources->items[video_source_index]; - if(video_source->capture->pre_capture) - video_source->capture->pre_capture(video_source->capture, &video_source->metadata, self->output_color_conversion); - } + gsr_egl_swap_buffers(self->egl); + gsr_video_encoder_copy_textures_to_frame(self->video_encoder, self->video_frame, self->output_color_conversion); - if(self->output_color_conversion->schedule_clear) { - self->output_color_conversion->schedule_clear = false; - gsr_color_conversion_clear(self->output_color_conversion); - } + for(size_t video_source_index = 0; video_source_index < self->video_sources->num_items; ++video_source_index) { +gsr_video_source *video_source = &self->video_sources->items[video_source_index]; + if(self->hdr && !self->hdr_metadata_set && !self->settings.is_replaying && add_hdr_metadata_to_video_stream(video_source->capture, self->video_stream)) + self->hdr_metadata_set = true; + } - for(size_t video_source_index = 0; video_source_index < self->video_sources->num_items; ++video_source_index) { - gsr_video_source *video_source = &self->video_sources->items[video_source_index]; - gsr_capture_capture(video_source->capture, &video_source->metadata, self->output_color_conversion); - } + // TODO: Check if duplicate frame can be saved just by writing it with a different pts instead of sending it again + const int num_frames_to_encode = self->settings.framerate_mode == GSR_FRAMERATE_MODE_CONSTANT ? num_missed_frames : 1; + for(int i = 0; i < num_frames_to_encode; ++i) { + if(self->settings.framerate_mode == GSR_FRAMERATE_MODE_CONSTANT) { + self->video_frame->pts = self->video_pts_counter + i; + } else { + self->video_frame->pts = (this_video_frame_time - self->record_start_time) * (double)AV_TIME_BASE; + const bool same_pts = self->video_frame->pts == self->video_prev_pts; + self->video_prev_pts = self->video_frame->pts; + if(same_pts) + continue; + } - gsr_capture_deps_cleanup_kms_fds(self->capture_deps); + if(self->force_iframe_frame) { + self->video_frame->pict_type = AV_PICTURE_TYPE_I; + } - if(self->plugins.num_plugins > 0) { - gsr_plugins_draw(&self->plugins); - gsr_color_conversion_draw(&self->color_conversion, self->plugins.texture, - (vec2i){0, 0}, self->video_size, - (vec2i){0, 0}, self->video_size, - self->video_size, GSR_ROT_0, GSR_FLIP_NONE, GSR_SOURCE_COLOR_RGB, false); - } + int ret = avcodec_send_frame(self->video_codec_context, self->video_frame); + if(ret == 0) { + // TODO: Move to separate thread because this could write to network (for example when livestreaming) + gsr_encoder_receive_packets(&self->encoder, self->video_codec_context, self->video_frame->pts, GSR_VIDEO_STREAM_INDEX); + } else { + gsr_log(GSR_LOG_LEVEL_ERROR, "avcodec_send_frame failed, error: %s", gsr_av_error_to_string(ret)); + } - if(capture_has_synchronous_task) { - paused = false; - gsr_recording_clock_set_paused(self->recording_clock, false); - } + if(self->force_iframe_frame) { + self->force_iframe_frame = false; + self->video_frame->pict_type = AV_PICTURE_TYPE_NONE; + } + } - gsr_egl_swap_buffers(self->egl); - gsr_video_encoder_copy_textures_to_frame(self->video_encoder, self->video_frame, self->output_color_conversion); + self->video_pts_counter += num_missed_frames; +} +} - for(size_t video_source_index = 0; video_source_index < self->video_sources->num_items; ++video_source_index) { - gsr_video_source *video_source = &self->video_sources->items[video_source_index]; - if(self->hdr && !hdr_metadata_set && !self->settings.is_replaying && add_hdr_metadata_to_video_stream(video_source->capture, self->video_stream)) - hdr_metadata_set = true; - } +static void recorder_apply_pause_toggle(gsr_recorder *self) { + if(self->toggle_pause == 1 && !self->settings.is_replaying) { + self->paused = !self->paused; + gsr_recording_clock_set_paused(self->recording_clock, self->paused); + gsr_log(GSR_LOG_LEVEL_INFO, self->paused ? "Paused" : "Unpaused"); + self->toggle_pause = 0; + } +} - // TODO: Check if duplicate frame can be saved just by writing it with a different pts instead of sending it again - const int num_frames_to_encode = self->settings.framerate_mode == GSR_FRAMERATE_MODE_CONSTANT ? num_missed_frames : 1; - for(int i = 0; i < num_frames_to_encode; ++i) { - if(self->settings.framerate_mode == GSR_FRAMERATE_MODE_CONSTANT) { - self->video_frame->pts = video_pts_counter + i; - } else { - self->video_frame->pts = (this_video_frame_time - record_start_time) * (double)AV_TIME_BASE; - const bool same_pts = self->video_frame->pts == video_prev_pts; - video_prev_pts = self->video_frame->pts; - if(same_pts) - continue; - } +static void recorder_apply_replay_recording_toggle(gsr_recorder *self) { + if(self->toggle_replay_recording && !self->settings.replay_recording_directory) { + self->toggle_replay_recording = 0; + if(self->callbacks.recording_started) + self->callbacks.recording_started(NULL, self->callbacks.userdata); + } - if(self->force_iframe_frame) { - self->video_frame->pict_type = AV_PICTURE_TYPE_I; + if(self->toggle_replay_recording && self->settings.replay_recording_directory) { + self->toggle_replay_recording = 0; + const bool new_replay_recording_state = !self->replay_recording; + if(new_replay_recording_state) { + gsr_audio_capture_lock_filter(&self->audio_capture); + self->num_replay_recording_items = 0; + const bool filepath_created = gsr_create_new_recording_filepath_from_timestamp(self->replay_recording_filepath, sizeof(self->replay_recording_filepath), self->settings.replay_recording_directory, "Video", self->file_extension, self->settings.date_folders); + if(filepath_created && gsr_recording_output_start(&self->replay_recording_output, self->replay_recording_filepath, &self->settings, self->video_codec_context, &self->audio_capture, self->hdr, self->video_sources)) { + const size_t video_recording_destination_id = gsr_encoder_add_recording_destination(&self->encoder, self->video_codec_context, self->replay_recording_output.av_format_context, self->replay_recording_output.video_stream, self->video_frame->pts); + if(self->settings.write_first_frame_ts && video_recording_destination_id != (size_t)-1) { + char ts_filepath[PATH_MAX + 4]; + snprintf(ts_filepath, sizeof(ts_filepath), "%s.ts", self->replay_recording_filepath); + gsr_encoder_set_recording_destination_first_frame_ts_filepath(&self->encoder, video_recording_destination_id, ts_filepath); } - int ret = avcodec_send_frame(self->video_codec_context, self->video_frame); - if(ret == 0) { - // TODO: Move to separate thread because this could write to network (for example when livestreaming) - gsr_encoder_receive_packets(&self->encoder, self->video_codec_context, self->video_frame->pts, GSR_VIDEO_STREAM_INDEX); - } else { - gsr_log(GSR_LOG_LEVEL_ERROR, "avcodec_send_frame failed, error: %s", gsr_av_error_to_string(ret)); + if(video_recording_destination_id != (size_t)-1 && self->num_replay_recording_items < GSR_MAX_RECORDING_DESTINATIONS) { + self->replay_recording_items[self->num_replay_recording_items] = video_recording_destination_id; + ++self->num_replay_recording_items; } - if(self->force_iframe_frame) { - self->force_iframe_frame = false; - self->video_frame->pict_type = AV_PICTURE_TYPE_NONE; + for(size_t i = 0; i < self->replay_recording_output.num_audio_streams; ++i) { + const gsr_recording_audio_stream *audio_stream = &self->replay_recording_output.audio_streams[i]; + const size_t audio_recording_destination_id = gsr_encoder_add_recording_destination(&self->encoder, audio_stream->audio_track->codec_context, self->replay_recording_output.av_format_context, audio_stream->stream, audio_stream->audio_track->pts); + if(audio_recording_destination_id != (size_t)-1 && self->num_replay_recording_items < GSR_MAX_RECORDING_DESTINATIONS) { + self->replay_recording_items[self->num_replay_recording_items] = audio_recording_destination_id; + ++self->num_replay_recording_items; + } } + + self->replay_recording = true; + self->force_iframe_frame = true; + gsr_log(GSR_LOG_LEVEL_INFO, "Started recording"); + if(self->callbacks.recording_started) + self->callbacks.recording_started(self->replay_recording_filepath, self->callbacks.userdata); + } else { + if(self->callbacks.recording_started) + self->callbacks.recording_started(NULL, self->callbacks.userdata); + } + gsr_audio_capture_unlock_filter(&self->audio_capture); + } else if(self->replay_recording_output.av_format_context) { + for(size_t i = 0; i < self->num_replay_recording_items; ++i) { + gsr_encoder_remove_recording_destination(&self->encoder, self->replay_recording_items[i]); + } + self->num_replay_recording_items = 0; + + if(gsr_recording_output_stop(&self->replay_recording_output)) { + gsr_log(GSR_LOG_LEVEL_INFO, "Stopped recording"); + if(self->callbacks.recording_stopped) + self->callbacks.recording_stopped(self->replay_recording_filepath, self->callbacks.userdata); + } else { + if(self->callbacks.recording_stopped) + self->callbacks.recording_stopped(NULL, self->callbacks.userdata); } - video_pts_counter += num_missed_frames; + self->replay_recording = false; + self->replay_recording_filepath[0] = '\0'; } + } +} - if(self->toggle_pause == 1 && !self->settings.is_replaying) { - paused = !paused; - gsr_recording_clock_set_paused(self->recording_clock, paused); - gsr_log(GSR_LOG_LEVEL_INFO, paused ? "Paused" : "Unpaused"); - self->toggle_pause = 0; - } +static void recorder_poll_replay_save(gsr_recorder *self) { + bool replay_save_result = false; + const char *replay_save_output_filepath = NULL; + if(gsr_replay_save_poll(&self->replay_save, &replay_save_result, &replay_save_output_filepath)) { + if(self->callbacks.replay_saved) + self->callbacks.replay_saved(replay_save_output_filepath[0] == '\0' || !replay_save_result ? NULL : replay_save_output_filepath, self->callbacks.userdata); + } - if(self->toggle_replay_recording && !self->settings.replay_recording_directory) { - self->toggle_replay_recording = 0; - if(self->callbacks.recording_started) - self->callbacks.recording_started(NULL, self->callbacks.userdata); + if(self->save_replay_seconds != 0 && !gsr_replay_save_is_running(&self->replay_save) && self->settings.is_replaying) { + int current_save_replay_seconds = self->save_replay_seconds; + if(current_save_replay_seconds > 0) + current_save_replay_seconds += self->settings.keyint; + + self->save_replay_seconds = 0; + const bool replay_start_result = gsr_replay_save_start(&self->replay_save, self->video_codec_context, GSR_VIDEO_STREAM_INDEX, &self->audio_capture, &self->encoder, &self->settings, self->file_extension, self->hdr, self->video_sources, current_save_replay_seconds); + if(!replay_start_result && self->callbacks.replay_saved) + self->callbacks.replay_saved(NULL, self->callbacks.userdata); + + if(self->settings.restart_replay_on_save && current_save_replay_seconds == GSR_SAVE_REPLAY_SECONDS_FULL) { + pthread_mutex_lock(&self->encoder.replay_mutex); + gsr_replay_buffer_clear(self->encoder.replay_buffer); + pthread_mutex_unlock(&self->encoder.replay_mutex); } + } +} - if(self->toggle_replay_recording && self->settings.replay_recording_directory) { - self->toggle_replay_recording = 0; - const bool new_replay_recording_state = !self->replay_recording; - if(new_replay_recording_state) { - gsr_audio_capture_lock_filter(&self->audio_capture); - self->num_replay_recording_items = 0; - const bool filepath_created = gsr_create_new_recording_filepath_from_timestamp(self->replay_recording_filepath, sizeof(self->replay_recording_filepath), self->settings.replay_recording_directory, "Video", self->file_extension, self->settings.date_folders); - if(filepath_created && gsr_recording_output_start(&self->replay_recording_output, self->replay_recording_filepath, &self->settings, self->video_codec_context, &self->audio_capture, self->hdr, self->video_sources)) { - const size_t video_recording_destination_id = gsr_encoder_add_recording_destination(&self->encoder, self->video_codec_context, self->replay_recording_output.av_format_context, self->replay_recording_output.video_stream, self->video_frame->pts); - if(self->settings.write_first_frame_ts && video_recording_destination_id != (size_t)-1) { - char ts_filepath[PATH_MAX + 4]; - snprintf(ts_filepath, sizeof(ts_filepath), "%s.ts", self->replay_recording_filepath); - gsr_encoder_set_recording_destination_first_frame_ts_filepath(&self->encoder, video_recording_destination_id, ts_filepath); - } +static void recorder_sleep_until_next_frame(gsr_recorder *self) { + const double time_at_frame_end = gsr_recording_clock_get_time(self->recording_clock); + const double time_elapsed_total = time_at_frame_end - self->record_start_time; + const int64_t frames_elapsed = floor(time_elapsed_total / self->target_fps); + const double time_at_next_frame = (frames_elapsed + 1) * self->target_fps; + double time_to_next_frame = time_at_next_frame - time_elapsed_total; + if(time_to_next_frame > self->target_fps) + time_to_next_frame = self->target_fps; + const int64_t end_num_missed_frames = frames_elapsed - self->video_pts_counter; + + if(time_to_next_frame > 0.0 && end_num_missed_frames <= 0) + av_usleep(time_to_next_frame * 1000.0 * 1000.0); + else { + if(self->paused) + av_usleep(20.0 * 1000.0); // 20 milliseconds + else if(self->settings.framerate_mode == GSR_FRAMERATE_MODE_CONTENT) + av_usleep(2.8 * 1000.0); // 2.8 milliseconds + } +} - if(video_recording_destination_id != (size_t)-1 && self->num_replay_recording_items < GSR_MAX_RECORDING_DESTINATIONS) { - self->replay_recording_items[self->num_replay_recording_items] = video_recording_destination_id; - ++self->num_replay_recording_items; - } +int gsr_recorder_run(gsr_recorder *self) { + self->fps_start_time = clock_get_monotonic_seconds(); + //double frame_timer_start = self->fps_start_time; + self->fps_counter = 0; + self->damage_fps_counter = 0; - for(size_t i = 0; i < self->replay_recording_output.num_audio_streams; ++i) { - const gsr_recording_audio_stream *audio_stream = &self->replay_recording_output.audio_streams[i]; - const size_t audio_recording_destination_id = gsr_encoder_add_recording_destination(&self->encoder, audio_stream->audio_track->codec_context, self->replay_recording_output.av_format_context, audio_stream->stream, audio_stream->audio_track->pts); - if(audio_recording_destination_id != (size_t)-1 && self->num_replay_recording_items < GSR_MAX_RECORDING_DESTINATIONS) { - self->replay_recording_items[self->num_replay_recording_items] = audio_recording_destination_id; - ++self->num_replay_recording_items; - } - } + self->paused = false; + self->replay_recording = false; - self->replay_recording = true; - self->force_iframe_frame = true; - gsr_log(GSR_LOG_LEVEL_INFO, "Started recording"); - if(self->callbacks.recording_started) - self->callbacks.recording_started(self->replay_recording_filepath, self->callbacks.userdata); - } else { - if(self->callbacks.recording_started) - self->callbacks.recording_started(NULL, self->callbacks.userdata); - } - gsr_audio_capture_unlock_filter(&self->audio_capture); - } else if(self->replay_recording_output.av_format_context) { - for(size_t i = 0; i < self->num_replay_recording_items; ++i) { - gsr_encoder_remove_recording_destination(&self->encoder, self->replay_recording_items[i]); - } - self->num_replay_recording_items = 0; - - if(gsr_recording_output_stop(&self->replay_recording_output)) { - gsr_log(GSR_LOG_LEVEL_INFO, "Stopped recording"); - if(self->callbacks.recording_stopped) - self->callbacks.recording_stopped(self->replay_recording_filepath, self->callbacks.userdata); - } else { - if(self->callbacks.recording_stopped) - self->callbacks.recording_stopped(NULL, self->callbacks.userdata); - } + memset(&self->replay_recording_output, 0, sizeof(self->replay_recording_output)); - self->replay_recording = false; - self->replay_recording_filepath[0] = '\0'; - } - } + gsr_replay_save_init(&self->replay_save); - bool replay_save_result = false; - const char *replay_save_output_filepath = NULL; - if(gsr_replay_save_poll(&self->replay_save, &replay_save_result, &replay_save_output_filepath)) { - if(self->callbacks.replay_saved) - self->callbacks.replay_saved(replay_save_output_filepath[0] == '\0' || !replay_save_result ? NULL : replay_save_output_filepath, self->callbacks.userdata); - } + self->force_iframe_frame = false; - if(self->save_replay_seconds != 0 && !gsr_replay_save_is_running(&self->replay_save) && self->settings.is_replaying) { - int current_save_replay_seconds = self->save_replay_seconds; - if(current_save_replay_seconds > 0) - current_save_replay_seconds += self->settings.keyint; + gsr_recording_clock_start(self->recording_clock); + self->record_start_time = gsr_recording_clock_get_start_time(self->recording_clock); - self->save_replay_seconds = 0; - const bool replay_start_result = gsr_replay_save_start(&self->replay_save, self->video_codec_context, GSR_VIDEO_STREAM_INDEX, &self->audio_capture, &self->encoder, &self->settings, self->file_extension, self->hdr, self->video_sources, current_save_replay_seconds); - if(!replay_start_result && self->callbacks.replay_saved) - self->callbacks.replay_saved(NULL, self->callbacks.userdata); + if(gsr_audio_capture_start(&self->audio_capture, self->audio_max_frame_size, self->uses_amix) != GSR_ERROR_OK) + return GSR_ERROR_GENERIC; - if(self->settings.restart_replay_on_save && current_save_replay_seconds == GSR_SAVE_REPLAY_SECONDS_FULL) { - pthread_mutex_lock(&self->encoder.replay_mutex); - gsr_replay_buffer_clear(self->encoder.replay_buffer); - pthread_mutex_unlock(&self->encoder.replay_mutex); + // Set update_fps to 24 to test if duplicate/delayed frames cause video/audio desync or too fast/slow video. + //const double update_fps = fps + 190; + self->should_stop_error = false; + + self->video_pts_counter = 0; + self->video_prev_pts = 0; + + self->hdr_metadata_set = false; + self->hdr = video_codec_is_hdr(self->settings.video_codec); + + + memset(&self->damage, 0, sizeof(self->damage)); + if(self->settings.framerate_mode == GSR_FRAMERATE_MODE_CONTENT && gsr_capture_sources_has_damage_tracked_target(self->capture_sources)) { + if(gsr_window_get_display_server(self->window) == GSR_DISPLAY_SERVER_X11) { + gsr_damage_init(&self->damage, self->egl, &self->capture_deps->x11_cursor, self->settings.record_cursor); + self->use_damage_tracking = true; + + for(size_t i = 0; i < self->capture_sources->num_items; ++i) { + const gsr_capture_source *capture_source = &self->capture_sources->items[i]; + switch(capture_source->type) { + case GSR_CAPTURE_SOURCE_TYPE_WINDOW: + gsr_damage_start_tracking_window(&self->damage, capture_source->window_id); + break; + case GSR_CAPTURE_SOURCE_TYPE_MONITOR: + case GSR_CAPTURE_SOURCE_TYPE_REGION: + // TODO: When capturing a region only track damage in that region + gsr_damage_start_tracking_monitor(&self->damage, capture_source->name); + break; + default: + break; + } } + } else if(gsr_capture_sources_has_monitor_or_region(self->capture_sources)) { + gsr_log(GSR_LOG_LEVEL_WARNING, "\"-fm content\" has no effect on Wayland when recording a monitor. Either record a monitor on X11 or capture with desktop portal instead (-w portal)"); } + } - const double time_at_frame_end = gsr_recording_clock_get_time(self->recording_clock); - const double time_elapsed_total = time_at_frame_end - record_start_time; - const int64_t frames_elapsed = floor(time_elapsed_total / self->target_fps); - const double time_at_next_frame = (frames_elapsed + 1) * self->target_fps; - double time_to_next_frame = time_at_next_frame - time_elapsed_total; - if(time_to_next_frame > self->target_fps) - time_to_next_frame = self->target_fps; - const int64_t end_num_missed_frames = frames_elapsed - video_pts_counter; - - if(time_to_next_frame > 0.0 && end_num_missed_frames <= 0) - av_usleep(time_to_next_frame * 1000.0 * 1000.0); - else { - if(paused) - av_usleep(20.0 * 1000.0); // 20 milliseconds - else if(self->settings.framerate_mode == GSR_FRAMERATE_MODE_CONTENT) - av_usleep(2.8 * 1000.0); // 2.8 milliseconds - } + while(self->running) { + recorder_process_events(self); + const bool damaged = recorder_tick_video_sources(self); + recorder_update_fps_counters(self, damaged); + recorder_capture_and_encode_frame(self, damaged); + recorder_apply_pause_toggle(self); + recorder_apply_replay_recording_toggle(self); + recorder_poll_replay_save(self); + recorder_sleep_until_next_frame(self); } gsr_recorder_stop_recording(self); |
