aboutsummaryrefslogtreecommitdiffhomepage
path: root/include
diff options
context:
space:
mode:
Diffstat (limited to 'include')
-rw-r--r--include/args_parser.h72
-rw-r--r--include/cli/commands.h15
-rw-r--r--include/cli/ipc.h105
-rw-r--r--include/defs.h1
-rw-r--r--include/encoder/encoder.h6
-rw-r--r--include/ffmpeg_utils.h19
-rw-r--r--include/json.h15
-rw-r--r--include/log.h20
-rw-r--r--include/recorder/audio_capture.h92
-rw-r--r--include/recorder/audio_codec.h23
-rw-r--r--include/recorder/audio_input.h66
-rw-r--r--include/recorder/capture_setup.h49
-rw-r--r--include/recorder/capture_source.h60
-rw-r--r--include/recorder/codec_select.h30
-rw-r--r--include/recorder/error.h23
-rw-r--r--include/recorder/muxer.h40
-rw-r--r--include/recorder/recorder.h69
-rw-r--r--include/recorder/recording_clock.h21
-rw-r--r--include/recorder/replay_save.h53
-rw-r--r--include/recorder/screenshot.h36
-rw-r--r--include/recorder/settings.h55
-rw-r--r--include/recorder/video_codec.h18
-rw-r--r--include/recorder/windowing.h35
-rw-r--r--include/replay_buffer/replay_buffer.h8
-rw-r--r--include/sound.h59
-rw-r--r--include/sound.hpp91
-rw-r--r--include/utils.h18
27 files changed, 954 insertions, 145 deletions
diff --git a/include/args_parser.h b/include/args_parser.h
index e8c68d8..2954dde 100644
--- a/include/args_parser.h
+++ b/include/args_parser.h
@@ -5,10 +5,11 @@
#include <stdint.h>
#include "defs.h"
#include "vec2.h"
+#include "recorder/settings.h"
typedef struct gsr_egl gsr_egl;
-#define NUM_ARGS 38
+#define NUM_ARGS 39
typedef enum {
GSR_CAPTURE_SOURCE_TYPE_WINDOW,
@@ -56,66 +57,31 @@ typedef struct {
} typed_value;
} Arg;
+/* These return the exit code that gpu-screen-recorder should exit with */
typedef struct {
- void (*version)(void *userdata);
- void (*info)(void *userdata);
- void (*list_audio_devices)(void *userdata);
- void (*list_application_audio)(void *userdata);
- void (*list_v4l2_devices)(void *userdata);
- void (*list_capture_options)(const char *card_path, void *userdata);
- void (*list_monitors)(void *userdata);
+ int (*version)(void *userdata);
+ int (*info)(void *userdata);
+ int (*list_audio_devices)(void *userdata);
+ int (*list_application_audio)(void *userdata);
+ int (*list_v4l2_devices)(void *userdata);
+ int (*list_capture_options)(const char *card_path, void *userdata);
+ int (*list_monitors)(void *userdata);
} args_handlers;
+typedef enum {
+ ARGS_PARSE_RESULT_ERROR,
+ ARGS_PARSE_RESULT_OK,
+ /* One of the |args_handlers| ran and |command_exit_code| is set */
+ ARGS_PARSE_RESULT_COMMAND_HANDLED
+} args_parse_result;
+
typedef struct {
Arg args[NUM_ARGS];
-
- gsr_video_encoder_hardware video_encoder;
- gsr_pixel_format pixel_format;
- gsr_framerate_mode framerate_mode;
- gsr_color_range color_range;
- gsr_tune tune;
- gsr_video_codec video_codec;
- gsr_audio_codec audio_codec;
- gsr_bitrate_mode bitrate_mode;
- gsr_video_quality video_quality;
- gsr_replay_storage replay_storage;
-
- const char *capture_source;
- const char *container_format;
- const char *filename;
- const char *replay_recording_directory;
- const char *portal_session_token_filepath;
- const char *recording_saved_script;
- const char *ffmpeg_opts;
- const char *ffmpeg_video_opts;
- const char *ffmpeg_audio_opts;
- bool verbose;
- bool gl_debug;
- bool fallback_cpu_encoding;
- bool low_power;
- bool exclude_metadata;
- bool record_cursor;
- bool date_folders;
- bool restore_portal_session;
- bool restart_replay_on_save;
- bool write_first_frame_ts;
- bool is_replaying;
- bool is_livestream;
- bool is_output_piped;
- bool low_latency_recording;
- bool very_old_gpu;
- int64_t video_bitrate;
- int64_t audio_bitrate;
- int64_t fps;
- int64_t replay_buffer_size_secs;
- double keyint;
- vec2i output_resolution;
- vec2i region_size;
- vec2i region_position;
+ gsr_recorder_settings settings;
} args_parser;
/* |argv| is stored as a reference */
-bool args_parser_parse(args_parser *self, int argc, char **argv, const args_handlers *args_handlers, void *userdata);
+args_parse_result args_parser_parse(args_parser *self, int argc, char **argv, const args_handlers *args_handlers, void *userdata, int *command_exit_code);
void args_parser_deinit(args_parser *self);
bool args_parser_validate_with_gl_info(args_parser *self, gsr_egl *egl);
diff --git a/include/cli/commands.h b/include/cli/commands.h
new file mode 100644
index 0000000..9ab00f0
--- /dev/null
+++ b/include/cli/commands.h
@@ -0,0 +1,15 @@
+#ifndef GSR_CLI_COMMANDS_H
+#define GSR_CLI_COMMANDS_H
+
+/* These are the |args_handlers| of the argument parser. They return the exit code that gpu-screen-recorder should exit with */
+int version_command(void *userdata);
+int info_command(void *userdata);
+int list_audio_devices_command(void *userdata);
+int list_application_audio_command(void *userdata);
+int list_v4l2_devices(void *userdata);
+int list_capture_options_command(const char *card_path, void *userdata);
+int list_monitors_command(void *userdata);
+
+void run_recording_saved_script_async(const char *script_file, const char *video_file, const char *type);
+
+#endif /* GSR_CLI_COMMANDS_H */
diff --git a/include/cli/ipc.h b/include/cli/ipc.h
new file mode 100644
index 0000000..0c4c076
--- /dev/null
+++ b/include/cli/ipc.h
@@ -0,0 +1,105 @@
+#ifndef GSR_CLI_IPC_H
+#define GSR_CLI_IPC_H
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <limits.h>
+#include <pthread.h>
+
+#define GSR_IPC_MAX_CLIENTS 8
+#define GSR_IPC_MAX_REQUEST_SIZE 4096
+#define GSR_IPC_MAX_ERROR_MESSAGE_SIZE 256
+#define GSR_IPC_MAX_ESCAPED_ERROR_MESSAGE_SIZE (GSR_IPC_MAX_ERROR_MESSAGE_SIZE*6)
+#define GSR_IPC_MAX_ESCAPED_DATA_SIZE PATH_MAX
+#define GSR_IPC_MAX_REPLY_SIZE (GSR_IPC_MAX_ESCAPED_ERROR_MESSAGE_SIZE + GSR_IPC_MAX_ESCAPED_DATA_SIZE + 128)
+#define GSR_IPC_CLIENT_SEND_BUFFER_SIZE (GSR_IPC_MAX_REPLY_SIZE*2)
+
+/*
+ These are called from the ipc thread while the recording is running.
+ Return false and write a message to |error_message| to reply to the request with an error.
+ The reply to the requests that match a |gsr_ipc_deferred_request_type| is not sent when the handler
+ succeeds. It's sent when the matching gsr_ipc_complete_request is called.
+*/
+typedef struct {
+ bool (*stop)(char *error_message, size_t error_message_size, void *userdata);
+ bool (*toggle_pause)(char *error_message, size_t error_message_size, void *userdata);
+ bool (*set_paused)(bool paused, char *error_message, size_t error_message_size, void *userdata);
+ bool (*toggle_replay_recording)(char *error_message, size_t error_message_size, void *userdata);
+ bool (*start_replay_recording)(char *error_message, size_t error_message_size, void *userdata);
+ bool (*stop_replay_recording)(char *error_message, size_t error_message_size, void *userdata);
+ /*
+ |seconds| is GSR_SAVE_REPLAY_SECONDS_FULL when the whole replay buffer should be saved.
+ |restart_replay| overrides the -restart-replay-on-save option for this save when |has_restart_replay| is set.
+ */
+ bool (*save_replay)(int seconds, bool has_restart_replay, bool restart_replay, char *error_message, size_t error_message_size, void *userdata);
+ void *userdata;
+} gsr_ipc_handlers;
+
+/* Requests that are replied to when the file they save has been saved (see gsr_ipc_complete_request) */
+typedef enum {
+ GSR_IPC_DEFERRED_REQUEST_STOP,
+ GSR_IPC_DEFERRED_REQUEST_SAVE_REPLAY,
+ GSR_IPC_DEFERRED_REQUEST_STOP_REPLAY_RECORDING,
+ GSR_IPC_DEFERRED_REQUEST_TYPE_COUNT
+} gsr_ipc_deferred_request_type;
+
+typedef enum {
+ GSR_IPC_DEFERRED_REQUEST_STATE_EMPTY,
+ GSR_IPC_DEFERRED_REQUEST_STATE_PENDING,
+ GSR_IPC_DEFERRED_REQUEST_STATE_COMPLETED
+} gsr_ipc_deferred_request_state;
+
+typedef struct {
+ gsr_ipc_deferred_request_state state;
+ int client_fd;
+ int64_t request_id;
+ bool success;
+ bool has_filepath;
+ char filepath[PATH_MAX];
+} gsr_ipc_deferred_request;
+
+typedef struct {
+ int fd;
+ char request[GSR_IPC_MAX_REQUEST_SIZE];
+ size_t request_size;
+ bool request_too_large;
+ char send_buffer[GSR_IPC_CLIENT_SEND_BUFFER_SIZE];
+ size_t send_buffer_size;
+} gsr_ipc_client;
+
+/* Receives newline terminated json requests on a unix domain socket and replies to every request */
+typedef struct {
+ bool initialized;
+ int socket_fd;
+ int poll_fd;
+ bool socket_bound;
+ char socket_filepath[PATH_MAX];
+ int wakeup_pipe[2];
+ gsr_ipc_client clients[GSR_IPC_MAX_CLIENTS];
+ int num_clients;
+ gsr_ipc_handlers handlers;
+ gsr_ipc_deferred_request deferred_requests[GSR_IPC_DEFERRED_REQUEST_TYPE_COUNT];
+ pthread_mutex_t deferred_requests_mutex;
+ bool deferred_requests_mutex_created;
+ pthread_t thread;
+ bool thread_running;
+} gsr_ipc;
+
+/* Returns a |gsr_error| value. Creates the socket, requests are not handled until gsr_ipc_start is called */
+int gsr_ipc_init(gsr_ipc *self, const char *socket_filepath);
+void gsr_ipc_deinit(gsr_ipc *self);
+
+/* Returns a |gsr_error| value. Starts handling requests. Does nothing if |self| hasn't been initialized */
+int gsr_ipc_start(gsr_ipc *self, const gsr_ipc_handlers *handlers);
+/* Stops handling requests. Does nothing if the ipc hasn't been started */
+void gsr_ipc_stop(gsr_ipc *self);
+
+/*
+ Sends the reply to the pending request of |type|, or does nothing when there is no pending request of that type.
+ |filepath| is the path of the saved file and it can be NULL when nothing was saved.
+ This is safe to call from any thread. Does nothing if |self| hasn't been initialized.
+*/
+void gsr_ipc_complete_request(gsr_ipc *self, gsr_ipc_deferred_request_type type, bool success, const char *filepath);
+
+#endif /* GSR_CLI_IPC_H */
diff --git a/include/defs.h b/include/defs.h
index 4bb10d8..5ffb546 100644
--- a/include/defs.h
+++ b/include/defs.h
@@ -11,6 +11,7 @@ typedef enum {
GSR_GPU_VENDOR_INTEL,
GSR_GPU_VENDOR_NVIDIA,
GSR_GPU_VENDOR_BROADCOM,
+ GSR_GPU_VENDOR_APPLE
} gsr_gpu_vendor;
typedef struct {
diff --git a/include/encoder/encoder.h b/include/encoder/encoder.h
index b46deb5..15009f1 100644
--- a/include/encoder/encoder.h
+++ b/include/encoder/encoder.h
@@ -41,7 +41,11 @@ typedef struct {
} gsr_encoder;
bool gsr_encoder_init(gsr_encoder *self, gsr_replay_storage replay_storage, size_t replay_buffer_num_packets, double replay_buffer_time, const char *replay_directory);
-void gsr_encoder_deinit(gsr_encoder *self);
+/*
+ When |exiting| is set the memory of the replay buffer is left to the operating system to free, which makes
+ this much faster when the replay buffer holds several gigabytes of data. See gsr_replay_buffer_destroy_at_exit.
+*/
+void gsr_encoder_deinit(gsr_encoder *self, bool exiting);
void gsr_encoder_receive_packets(gsr_encoder *self, AVCodecContext *codec_context, int64_t pts, int stream_index);
/* Returns the id to the recording destination, or -1 on error */
diff --git a/include/ffmpeg_utils.h b/include/ffmpeg_utils.h
new file mode 100644
index 0000000..6d7481b
--- /dev/null
+++ b/include/ffmpeg_utils.h
@@ -0,0 +1,19 @@
+#ifndef GSR_FFMPEG_UTILS_H
+#define GSR_FFMPEG_UTILS_H
+
+#include <stdbool.h>
+
+typedef struct AVFormatContext AVFormatContext;
+
+const char* gsr_av_error_to_string(int err);
+
+/* Marks that a packet has been successfully written to |av_format_context|, see gsr_av_format_context_write_trailer */
+void gsr_av_format_context_mark_packet_written(AVFormatContext *av_format_context);
+/*
+ The same as av_write_trailer, except that the trailer is not written when no packet has been written to a muxer
+ that uses the hybrid_fragmented movflags option (see set_format_context_options), because the mov muxer in FFmpeg
+ crashes when it finalizes a hybrid_fragmented file that has no packets. Returns 0 on success, just like av_write_trailer.
+*/
+int gsr_av_format_context_write_trailer(AVFormatContext *av_format_context);
+
+#endif /* GSR_FFMPEG_UTILS_H */
diff --git a/include/json.h b/include/json.h
new file mode 100644
index 0000000..3bf8a9e
--- /dev/null
+++ b/include/json.h
@@ -0,0 +1,15 @@
+#ifndef GSR_JSON_H
+#define GSR_JSON_H
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include "../external/sj.h"
+
+bool gsr_json_string_equals(const sj_Value *value, const char *str);
+/* Fails if |value| is not a json number without a fractional part */
+bool gsr_json_number_to_int64(const sj_Value *value, int64_t *result);
+/* An escaped string can become 6 times as large as |str|. The result is truncated when it doesn't fit in |buffer| */
+void gsr_json_escape_string(char *buffer, size_t buffer_size, const char *str);
+
+#endif /* GSR_JSON_H */
diff --git a/include/log.h b/include/log.h
new file mode 100644
index 0000000..e92ac99
--- /dev/null
+++ b/include/log.h
@@ -0,0 +1,20 @@
+#ifndef GSR_LOG_H
+#define GSR_LOG_H
+
+typedef enum {
+ GSR_LOG_LEVEL_DEBUG,
+ GSR_LOG_LEVEL_INFO,
+ GSR_LOG_LEVEL_WARNING,
+ GSR_LOG_LEVEL_ERROR
+} gsr_log_level;
+
+/* The handler may be called from any thread. |message| has no prefix, level name or trailing newline */
+typedef void (*gsr_log_handler)(gsr_log_level level, const char *message, void *userdata);
+
+void gsr_log(gsr_log_level level, const char *fmt, ...) __attribute__((format(printf, 2, 3)));
+/* Messages below |level| are discarded */
+void gsr_log_set_level(gsr_log_level level);
+/* A NULL |handler| restores the default handler which prints "gsr <level>: <message>" to stderr */
+void gsr_log_set_handler(gsr_log_handler handler, void *userdata);
+
+#endif /* GSR_LOG_H */
diff --git a/include/recorder/audio_capture.h b/include/recorder/audio_capture.h
new file mode 100644
index 0000000..4f757c9
--- /dev/null
+++ b/include/recorder/audio_capture.h
@@ -0,0 +1,92 @@
+#ifndef GSR_RECORDER_AUDIO_CAPTURE_H
+#define GSR_RECORDER_AUDIO_CAPTURE_H
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdatomic.h>
+#include <pthread.h>
+#include "../sound.h"
+#include "recording_clock.h"
+#include "../encoder/encoder.h"
+#include "audio_input.h"
+
+#ifdef GSR_APP_AUDIO
+#include "../pipewire_audio.h"
+#endif
+
+#include <libavcodec/avcodec.h>
+#include <libavfilter/avfilter.h>
+
+#define GSR_MAX_AUDIO_SOURCES_PER_TRACK 32
+
+typedef struct gsr_audio_capture gsr_audio_capture;
+typedef struct gsr_audio_track gsr_audio_track;
+typedef struct gsr_audio_device_capture gsr_audio_device_capture;
+
+typedef struct {
+ gsr_audio_capture *audio_capture;
+ gsr_audio_track *track;
+ gsr_audio_device_capture *device;
+} gsr_audio_device_thread_userdata;
+
+struct gsr_audio_device_capture {
+ SoundDevice sound_device;
+ gsr_audio_input audio_input;
+ AVFilterContext *src_filter_ctx;
+ AVFrame *frame;
+ pthread_t thread;
+ bool thread_created;
+ gsr_audio_device_thread_userdata thread_userdata;
+};
+
+/* TODO: Instead of having a thread for each audio device, have one thread for all of them and read the data with non-blocking read */
+struct gsr_audio_track {
+ char name[GSR_AUDIO_TRACK_NAME_MAX_SIZE];
+ AVCodecContext *codec_context;
+ gsr_audio_device_capture *audio_devices;
+ size_t num_audio_devices;
+ AVFilterGraph *graph;
+ AVFilterContext *sink;
+ int stream_index;
+ int64_t pts;
+};
+
+struct gsr_audio_capture {
+ gsr_audio_track *tracks;
+ size_t num_tracks;
+ size_t capacity_tracks;
+
+ pthread_mutex_t filter_mutex;
+ bool filter_mutex_initialized;
+ pthread_t amix_thread;
+ bool amix_thread_created;
+ uint8_t *empty_audio;
+
+ gsr_encoder *encoder;
+ gsr_recording_clock *clock;
+ const atomic_int *running;
+};
+
+/* Returns a |gsr_error| value */
+int gsr_audio_capture_init(gsr_audio_capture *self, gsr_encoder *encoder, gsr_recording_clock *clock, const atomic_int *running);
+void gsr_audio_capture_deinit(gsr_audio_capture *self);
+
+bool gsr_audio_capture_add_track(gsr_audio_capture *self, const gsr_audio_track *track);
+/* Allocates the silence buffer and starts one thread per audio device, plus the amix thread when |uses_amix| is set */
+int gsr_audio_capture_start(gsr_audio_capture *self, int audio_max_frame_size, bool uses_amix);
+void gsr_audio_capture_join_threads(gsr_audio_capture *self);
+void gsr_audio_capture_lock_filter(gsr_audio_capture *self);
+void gsr_audio_capture_unlock_filter(gsr_audio_capture *self);
+
+/* Returns 0 on success, or a negative value on failure. |src_filter_ctx| must have room for |num_sources| items */
+int gsr_audio_init_filter_graph(AVCodecContext *audio_codec_context, AVFilterGraph **graph, AVFilterContext **sink, AVFilterContext **src_filter_ctx, size_t num_sources);
+
+/* Returns a |gsr_error| value. Opens the sound devices of one audio track */
+int gsr_audio_track_init_device_inputs(gsr_audio_track *self, const gsr_merged_audio_inputs *merged_audio_inputs, AVCodecContext *audio_codec_context, int num_channels, double num_audio_frames_shift, AVFilterContext **src_filter_ctx, bool use_amix);
+#ifdef GSR_APP_AUDIO
+/* Returns a |gsr_error| value. Creates a combined sink that application audio is routed to */
+int gsr_audio_track_init_application_input(gsr_audio_track *self, const gsr_merged_audio_inputs *merged_audio_inputs, AVCodecContext *audio_codec_context, int num_channels, double num_audio_frames_shift, gsr_pipewire_audio *pipewire_audio);
+#endif
+void gsr_audio_track_deinit(gsr_audio_track *self);
+
+#endif /* GSR_RECORDER_AUDIO_CAPTURE_H */
diff --git a/include/recorder/audio_codec.h b/include/recorder/audio_codec.h
new file mode 100644
index 0000000..d432abd
--- /dev/null
+++ b/include/recorder/audio_codec.h
@@ -0,0 +1,23 @@
+#ifndef GSR_RECORDER_AUDIO_CODEC_H
+#define GSR_RECORDER_AUDIO_CODEC_H
+
+#include <stdbool.h>
+#include <stdint.h>
+#include "../defs.h"
+#include "../sound.h"
+
+#include <libavcodec/avcodec.h>
+
+#define GSR_AUDIO_SAMPLE_RATE 48000
+
+gsr_audio_format audio_codec_context_get_audio_format(const AVCodecContext *audio_codec_context);
+enum AVSampleFormat audio_format_to_sample_format(const gsr_audio_format audio_format);
+
+AVCodecContext* create_audio_codec_context(int fps, gsr_audio_codec audio_codec, bool mix_audio, int64_t audio_bitrate);
+bool open_audio(AVCodecContext *audio_codec_context, const char *ffmpeg_audio_opts);
+AVFrame* create_audio_frame(AVCodecContext *audio_codec_context);
+
+double audio_codec_get_desired_delay(gsr_audio_codec audio_codec, int fps);
+int audio_codec_get_frame_size(gsr_audio_codec audio_codec);
+
+#endif /* GSR_RECORDER_AUDIO_CODEC_H */
diff --git a/include/recorder/audio_input.h b/include/recorder/audio_input.h
new file mode 100644
index 0000000..f6cb87e
--- /dev/null
+++ b/include/recorder/audio_input.h
@@ -0,0 +1,66 @@
+#ifndef GSR_RECORDER_AUDIO_INPUT_H
+#define GSR_RECORDER_AUDIO_INPUT_H
+
+#include <stdbool.h>
+#include <stddef.h>
+#include "../sound.h"
+
+#define GSR_AUDIO_INPUT_NAME_MAX_SIZE 256
+#define GSR_AUDIO_TRACK_NAME_MAX_SIZE 512
+
+typedef enum {
+ GSR_AUDIO_INPUT_TYPE_DEVICE,
+ GSR_AUDIO_INPUT_TYPE_APPLICATION
+} gsr_audio_input_type;
+
+typedef struct {
+ char name[GSR_AUDIO_INPUT_NAME_MAX_SIZE];
+ gsr_audio_input_type type;
+ bool inverted;
+} gsr_audio_input;
+
+typedef struct {
+ char track_name[GSR_AUDIO_TRACK_NAME_MAX_SIZE];
+ bool has_custom_name;
+ gsr_audio_input *items;
+ size_t num_items;
+ size_t capacity_items;
+} gsr_merged_audio_inputs;
+
+typedef struct {
+ gsr_merged_audio_inputs *items;
+ size_t num_items;
+ size_t capacity_items;
+} gsr_audio_input_tracks;
+
+typedef struct {
+ char name[GSR_AUDIO_INPUT_NAME_MAX_SIZE];
+} gsr_app_audio_name;
+
+typedef struct {
+ gsr_app_audio_name *items;
+ size_t num_items;
+ size_t capacity_items;
+} gsr_app_audio_names;
+
+bool gsr_app_audio_names_add(gsr_app_audio_names *self, const char *name);
+void gsr_app_audio_names_deinit(gsr_app_audio_names *self);
+
+/* Returns a |gsr_error| value. Parses one -a option value, which may contain multiple audio inputs separated by | */
+int gsr_merged_audio_inputs_parse(gsr_merged_audio_inputs *self, const char *str);
+bool gsr_merged_audio_inputs_add(gsr_merged_audio_inputs *self, const gsr_audio_input *audio_input);
+void gsr_merged_audio_inputs_deinit(gsr_merged_audio_inputs *self);
+
+/* Returns a |gsr_error| value. Parses every -a option value and validates that the audio devices exist */
+int gsr_audio_input_tracks_parse(gsr_audio_input_tracks *self, const char **audio_input_args, int num_audio_input_args, const gsr_audio_devices *audio_devices);
+bool gsr_audio_input_tracks_add(gsr_audio_input_tracks *self, const gsr_merged_audio_inputs *merged_audio_inputs);
+void gsr_audio_input_tracks_deinit(gsr_audio_input_tracks *self);
+
+bool gsr_audio_inputs_has_app_audio(const gsr_merged_audio_inputs *self);
+bool gsr_audio_inputs_should_use_amix(const gsr_merged_audio_inputs *self);
+bool gsr_audio_input_tracks_has_app_audio(const gsr_audio_input_tracks *self);
+bool gsr_audio_input_tracks_should_use_amix(const gsr_audio_input_tracks *self);
+/* Returns a |gsr_error| value. Warns about application audio names that don't match any running application */
+int gsr_audio_input_tracks_validate_app_audio(const gsr_audio_input_tracks *self, const gsr_app_audio_names *app_audio_names);
+
+#endif /* GSR_RECORDER_AUDIO_INPUT_H */
diff --git a/include/recorder/capture_setup.h b/include/recorder/capture_setup.h
new file mode 100644
index 0000000..fd4b7e7
--- /dev/null
+++ b/include/recorder/capture_setup.h
@@ -0,0 +1,49 @@
+#ifndef GSR_RECORDER_CAPTURE_SETUP_H
+#define GSR_RECORDER_CAPTURE_SETUP_H
+
+#include <stdbool.h>
+#include <stddef.h>
+#include "../egl.h"
+#include "../cursor.h"
+#include "../kde_night_light.h"
+#include "../capture/capture.h"
+#include "../../kms/client/kms_client.h"
+#include "capture_source.h"
+#include "settings.h"
+
+#include <X11/Xlib.h>
+
+typedef struct {
+ gsr_kms_client kms_client;
+ bool kms_client_initialized;
+ gsr_kms_response kms_response;
+ gsr_kde_night_light *kde_night_light;
+ bool kde_night_light_initialized;
+ gsr_cursor x11_cursor;
+ Display *x11_cursor_display;
+} gsr_capture_deps;
+
+typedef struct {
+ gsr_capture *capture;
+ gsr_capture_metadata metadata;
+ gsr_capture_source *capture_source;
+} gsr_video_source;
+
+typedef struct {
+ gsr_video_source *items;
+ size_t num_items;
+} gsr_video_sources;
+
+void gsr_capture_deps_init(gsr_capture_deps *self);
+void gsr_capture_deps_init_cursor(gsr_capture_deps *self, gsr_egl *egl, bool record_cursor);
+void gsr_capture_deps_deinit(gsr_capture_deps *self);
+void gsr_capture_deps_cleanup_kms_fds(gsr_capture_deps *self);
+void gsr_capture_deps_update_kms(gsr_capture_deps *self);
+
+/* Returns a |gsr_error| value, or the error returned by gsr_capture_start. |video_size| is set to the size of all capture sources combined */
+int gsr_video_sources_create(gsr_video_sources *self, const gsr_recorder_settings *settings, gsr_egl *egl, gsr_capture_deps *deps, bool prefer_ximage, gsr_capture_sources *capture_sources, vec2i *video_size);
+void gsr_video_sources_update_with_real_video_size(gsr_video_sources *self, vec2i video_size);
+bool gsr_video_sources_uses_external_image(const gsr_video_sources *self);
+void gsr_video_sources_deinit(gsr_video_sources *self);
+
+#endif /* GSR_RECORDER_CAPTURE_SETUP_H */
diff --git a/include/recorder/capture_source.h b/include/recorder/capture_source.h
new file mode 100644
index 0000000..503c620
--- /dev/null
+++ b/include/recorder/capture_source.h
@@ -0,0 +1,60 @@
+#ifndef GSR_RECORDER_CAPTURE_SOURCE_H
+#define GSR_RECORDER_CAPTURE_SOURCE_H
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stddef.h>
+#include "../vec2.h"
+#include "../args_parser.h"
+#include "../capture/capture.h"
+#include "../capture/v4l2.h"
+
+#define GSR_CAPTURE_SOURCE_NAME_MAX_SIZE 256
+
+typedef enum {
+ VVEC2I_TYPE_PIXELS,
+ VVEC2I_TYPE_SCALAR
+} vvec2i_type;
+
+typedef struct {
+ int x;
+ int y;
+ vvec2i_type x_type;
+ vvec2i_type y_type;
+} vvec2i;
+
+typedef struct {
+ char name[GSR_CAPTURE_SOURCE_NAME_MAX_SIZE];
+ CaptureSourceType type;
+ gsr_capture_alignment halign;
+ gsr_capture_alignment valign;
+ gsr_capture_v4l2_pixfmt v4l2_pixfmt;
+ uint32_t flip;
+ vvec2i pos;
+ vvec2i size;
+ vec2i region_pos;
+ vec2i region_size;
+ bool region_set;
+ int64_t window_id;
+ int camera_fps;
+ vec2i camera_resolution;
+} gsr_capture_source;
+
+typedef struct {
+ gsr_capture_source *items;
+ size_t num_items;
+ size_t capacity_items;
+} gsr_capture_sources;
+
+void gsr_capture_source_init(gsr_capture_source *self, vec2i region_position, vec2i region_size);
+
+/* Returns a |gsr_error| value. Parses the -w option value, which may contain multiple capture sources separated by | */
+int gsr_capture_sources_parse(gsr_capture_sources *self, const char *capture_source_arg, vec2i region_position, vec2i region_size);
+void gsr_capture_sources_deinit(gsr_capture_sources *self);
+
+bool gsr_capture_sources_has_type(const gsr_capture_sources *self, CaptureSourceType type);
+bool gsr_capture_sources_has_damage_tracked_target(const gsr_capture_sources *self);
+bool gsr_capture_sources_has_region_set(const gsr_capture_sources *self);
+bool gsr_capture_sources_has_monitor_or_region(const gsr_capture_sources *self);
+
+#endif /* GSR_RECORDER_CAPTURE_SOURCE_H */
diff --git a/include/recorder/codec_select.h b/include/recorder/codec_select.h
new file mode 100644
index 0000000..ff4b064
--- /dev/null
+++ b/include/recorder/codec_select.h
@@ -0,0 +1,30 @@
+#ifndef GSR_RECORDER_CODEC_SELECT_H
+#define GSR_RECORDER_CODEC_SELECT_H
+
+#include <stdbool.h>
+#include "../defs.h"
+#include "../egl.h"
+#include "../vec2.h"
+#include "../codec_query/codec_query.h"
+#include "settings.h"
+
+#include <libavcodec/avcodec.h>
+
+typedef struct gsr_video_encoder gsr_video_encoder;
+
+gsr_video_encoder* create_video_encoder(gsr_egl *egl, const gsr_recorder_settings *settings);
+
+bool get_supported_video_codecs(gsr_egl *egl, gsr_video_codec video_codec, bool use_software_video_encoder, bool cleanup, gsr_supported_video_codecs *video_codecs);
+void set_supported_video_codecs_ffmpeg(gsr_supported_video_codecs *supported_video_codecs, gsr_supported_video_codecs *supported_video_codecs_vulkan, gsr_gpu_vendor vendor);
+
+vec2i codec_get_max_resolution(gsr_video_codec video_codec, bool use_software_video_encoder, const gsr_supported_video_codecs *supported_video_codecs);
+bool codec_supports_resolution(vec2i codec_max_resolution, vec2i capture_resolution);
+/* Returns -1 if none is available */
+gsr_video_codec select_appropriate_video_codec_automatically(vec2i video_size, const gsr_supported_video_codecs *supported_video_codecs);
+void force_cpu_encoding(gsr_recorder_settings *settings);
+
+gsr_audio_codec select_audio_codec_with_fallback(gsr_audio_codec audio_codec, const char *file_extension, bool uses_amix);
+/* Returns a |gsr_error| value. |settings| video codec and encoder are updated to the codec that was selected */
+int select_video_codec_with_fallback(vec2i video_size, gsr_recorder_settings *settings, const char *file_extension, gsr_egl *egl, bool *low_power, const AVCodec **video_codec);
+
+#endif /* GSR_RECORDER_CODEC_SELECT_H */
diff --git a/include/recorder/error.h b/include/recorder/error.h
new file mode 100644
index 0000000..ba2ae1d
--- /dev/null
+++ b/include/recorder/error.h
@@ -0,0 +1,23 @@
+#ifndef GSR_RECORDER_ERROR_H
+#define GSR_RECORDER_ERROR_H
+
+/* The negated value of each error is the exit code that gpu-screen-recorder exits with */
+typedef enum {
+ GSR_ERROR_OK = 0,
+ GSR_ERROR_GENERIC = -1,
+ GSR_ERROR_UNSUPPORTED = -2,
+ GSR_ERROR_CAPTURE_FAILED = -3,
+ GSR_ERROR_VIDEO_CODEC_QUERY_FAILED = -11,
+ GSR_ERROR_OPENGL_LOAD_FAILED = -22,
+ GSR_ERROR_AUDIO_DEVICE_NOT_FOUND = -50,
+ GSR_ERROR_MONITOR_NOT_FOUND = -51,
+ GSR_ERROR_NO_VIDEO_CODEC_AVAILABLE = -52,
+ GSR_ERROR_VIDEO_CODEC_RESOLUTION_UNSUPPORTED = -53,
+ GSR_ERROR_VIDEO_CODEC_UNSUPPORTED = -54
+} gsr_error;
+
+static inline int gsr_error_to_exit_code(int error) {
+ return error < 0 ? -error : 0;
+}
+
+#endif /* GSR_RECORDER_ERROR_H */
diff --git a/include/recorder/muxer.h b/include/recorder/muxer.h
new file mode 100644
index 0000000..e47cdde
--- /dev/null
+++ b/include/recorder/muxer.h
@@ -0,0 +1,40 @@
+#ifndef GSR_RECORDER_MUXER_H
+#define GSR_RECORDER_MUXER_H
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <limits.h>
+#include "audio_capture.h"
+#include "capture_setup.h"
+#include "settings.h"
+
+#include <libavformat/avformat.h>
+
+typedef struct {
+ const gsr_audio_track *audio_track;
+ AVStream *stream;
+} gsr_recording_audio_stream;
+
+typedef struct {
+ AVFormatContext *av_format_context;
+ AVStream *video_stream;
+ gsr_recording_audio_stream *audio_streams;
+ size_t num_audio_streams;
+} gsr_recording_output;
+
+AVStream* create_stream(AVFormatContext *av_format_context, AVCodecContext *codec_context);
+bool add_hdr_metadata_to_video_stream(gsr_capture *cap, AVStream *video_stream);
+void set_format_context_options(AVFormatContext *av_format_context);
+void av_write_header(AVFormatContext *av_format_context, const char *ffmpeg_opts);
+
+/* Returns false on failure. Creates the output file and its video and audio streams */
+bool gsr_recording_output_start(gsr_recording_output *self, const char *filename, const gsr_recorder_settings *settings, AVCodecContext *video_codec_context, const gsr_audio_capture *audio_capture, bool hdr, gsr_video_sources *video_sources);
+bool gsr_recording_output_stop(gsr_recording_output *self);
+gsr_recording_audio_stream* gsr_recording_output_get_audio_stream_by_index(gsr_recording_output *self, int stream_index);
+
+/* |filepath| should be at least PATH_MAX bytes in size. Creates the directories of the filepath */
+bool gsr_create_new_recording_filepath_from_timestamp(char *filepath, size_t filepath_size, const char *directory, const char *filename_prefix, const char *file_extension, bool date_folders);
+
+size_t calculate_estimated_replay_buffer_packets(int64_t replay_buffer_size_secs, int fps, gsr_audio_codec audio_codec, const gsr_audio_input_tracks *audio_inputs);
+
+#endif /* GSR_RECORDER_MUXER_H */
diff --git a/include/recorder/recorder.h b/include/recorder/recorder.h
new file mode 100644
index 0000000..9ff13bd
--- /dev/null
+++ b/include/recorder/recorder.h
@@ -0,0 +1,69 @@
+#ifndef GSR_RECORDER_RECORDER_H
+#define GSR_RECORDER_RECORDER_H
+
+#include <stdbool.h>
+#include "settings.h"
+#include "capture_source.h"
+#include "capture_setup.h"
+#include "audio_input.h"
+#include "windowing.h"
+#ifdef GSR_APP_AUDIO
+#include "../pipewire_audio.h"
+#endif
+
+/* Records video and audio to a file, or to a replay buffer that can be saved to a file at any time */
+typedef struct gsr_recorder gsr_recorder;
+
+typedef struct {
+ /* |filepath| is NULL when the recording failed to save */
+ void (*replay_saved)(const char *filepath, void *userdata);
+ void (*recording_started)(const char *filepath, void *userdata);
+ /* |filepath| is NULL when the recording failed to save */
+ void (*recording_stopped)(const char *filepath, void *userdata);
+ void (*paused_changed)(bool paused, void *userdata);
+ void *userdata;
+} gsr_recorder_callbacks;
+
+typedef struct {
+ const gsr_recorder_settings *settings;
+ gsr_windowing *windowing;
+ gsr_capture_deps *capture_deps;
+ gsr_capture_sources *capture_sources;
+ gsr_audio_input_tracks *audio_input_tracks;
+ const char **plugin_filepaths;
+ int num_plugin_filepaths;
+#ifdef GSR_APP_AUDIO
+ gsr_pipewire_audio *pipewire_audio;
+#endif
+} gsr_recorder_params;
+
+/* Returns NULL on failure and sets |error| to a |gsr_error| value */
+gsr_recorder* gsr_recorder_create(const gsr_recorder_params *params, const gsr_recorder_callbacks *callbacks, int *error);
+/* This is only called when the program is exiting, so memory that the operating system frees on exit isn't free'd. Only set exiting to true if the program is exiting */
+void gsr_recorder_destroy(gsr_recorder *self, bool exiting);
+
+/* Returns a |gsr_error| value. Records until gsr_recorder_stop is called or until the capture target is gone */
+int gsr_recorder_run(gsr_recorder *self);
+
+/* These are safe to call from a signal handler or from another thread */
+void gsr_recorder_stop(gsr_recorder *self);
+void gsr_recorder_toggle_pause(gsr_recorder *self);
+/* Does nothing when the recording is already paused/unpaused */
+void gsr_recorder_set_paused(gsr_recorder *self, bool paused);
+void gsr_recorder_toggle_replay_recording(gsr_recorder *self);
+/* Does nothing when a recording is already running */
+void gsr_recorder_start_replay_recording(gsr_recorder *self);
+/* Does nothing when no recording is running */
+void gsr_recorder_stop_replay_recording(gsr_recorder *self);
+bool gsr_recorder_is_replay_recording(const gsr_recorder *self);
+#define GSR_RESTART_REPLAY_USE_OPTION -1
+#define GSR_RESTART_REPLAY_DISABLE 0
+#define GSR_RESTART_REPLAY_ENABLE 1
+
+/*
+ |seconds| can be GSR_SAVE_REPLAY_SECONDS_FULL to save the whole replay buffer.
+ |restart_replay| overrides the -restart-replay-on-save option for this save when it's not GSR_RESTART_REPLAY_USE_OPTION.
+*/
+void gsr_recorder_save_replay(gsr_recorder *self, int seconds, int restart_replay);
+
+#endif /* GSR_RECORDER_RECORDER_H */
diff --git a/include/recorder/recording_clock.h b/include/recorder/recording_clock.h
new file mode 100644
index 0000000..b99e936
--- /dev/null
+++ b/include/recorder/recording_clock.h
@@ -0,0 +1,21 @@
+#ifndef GSR_RECORDER_RECORDING_CLOCK_H
+#define GSR_RECORDER_RECORDING_CLOCK_H
+
+#include <stdbool.h>
+
+/* Monotonic clock that excludes the time the recording has been paused. Safe to use from multiple threads */
+typedef struct gsr_recording_clock gsr_recording_clock;
+
+gsr_recording_clock* gsr_recording_clock_create(void);
+void gsr_recording_clock_destroy(gsr_recording_clock *self);
+
+/* Sets the time the recording started to now */
+void gsr_recording_clock_start(gsr_recording_clock *self);
+double gsr_recording_clock_get_start_time(const gsr_recording_clock *self);
+/* Returns the current time, excluding the time the recording has been paused */
+double gsr_recording_clock_get_time(const gsr_recording_clock *self);
+
+void gsr_recording_clock_set_paused(gsr_recording_clock *self, bool paused);
+bool gsr_recording_clock_is_paused(const gsr_recording_clock *self);
+
+#endif /* GSR_RECORDER_RECORDING_CLOCK_H */
diff --git a/include/recorder/replay_save.h b/include/recorder/replay_save.h
new file mode 100644
index 0000000..7f48851
--- /dev/null
+++ b/include/recorder/replay_save.h
@@ -0,0 +1,53 @@
+#ifndef GSR_RECORDER_REPLAY_SAVE_H
+#define GSR_RECORDER_REPLAY_SAVE_H
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <limits.h>
+#include <pthread.h>
+#include <stdatomic.h>
+#include "muxer.h"
+#include "audio_capture.h"
+#include "capture_setup.h"
+#include "settings.h"
+
+#include "../encoder/encoder.h"
+#include "../replay_buffer/replay_buffer.h"
+
+#define GSR_SAVE_REPLAY_SECONDS_FULL -1
+
+typedef struct {
+ int64_t pts_offset;
+ int stream_index;
+} gsr_audio_pts_offset;
+
+/* Saves the replay buffer to a file on a separate thread */
+typedef struct {
+ pthread_t thread;
+ bool thread_created;
+ atomic_int finished;
+ bool success;
+ char output_filepath[PATH_MAX];
+
+ AVCodecContext *video_codec_context;
+ int video_stream_index;
+ gsr_recording_output recording_output;
+ gsr_replay_buffer_iterator video_start_iterator;
+ int64_t video_pts_offset;
+ gsr_audio_pts_offset *audio_pts_offsets;
+ size_t num_audio_pts_offsets;
+ gsr_replay_buffer *cloned_replay_buffer;
+ gsr_encoder *encoder;
+} gsr_replay_save;
+
+void gsr_replay_save_init(gsr_replay_save *self);
+bool gsr_replay_save_is_running(const gsr_replay_save *self);
+
+/* Returns false if the replay failed to start. |current_save_replay_seconds| can be GSR_SAVE_REPLAY_SECONDS_FULL to save the whole replay buffer */
+bool gsr_replay_save_start(gsr_replay_save *self, AVCodecContext *video_codec_context, int video_stream_index, const gsr_audio_capture *audio_capture, gsr_encoder *encoder, const gsr_recorder_settings *settings, const char *file_extension, bool hdr, gsr_video_sources *video_sources, int current_save_replay_seconds);
+/* Returns true when the replay finished saving, in which case |success| and |output_filepath| are set. |output_filepath| is empty when nothing was saved */
+bool gsr_replay_save_poll(gsr_replay_save *self, bool *success, const char **output_filepath);
+/* Waits for an ongoing replay save to finish. Returns the same values as gsr_replay_save_poll */
+bool gsr_replay_save_join(gsr_replay_save *self, bool *success, const char **output_filepath);
+
+#endif /* GSR_RECORDER_REPLAY_SAVE_H */
diff --git a/include/recorder/screenshot.h b/include/recorder/screenshot.h
new file mode 100644
index 0000000..00a7889
--- /dev/null
+++ b/include/recorder/screenshot.h
@@ -0,0 +1,36 @@
+#ifndef GSR_RECORDER_SCREENSHOT_H
+#define GSR_RECORDER_SCREENSHOT_H
+
+#include <stdbool.h>
+#include <stdatomic.h>
+#include "../egl.h"
+#include "../image_writer.h"
+#include "../plugins.h"
+#include "capture_source.h"
+#include "capture_setup.h"
+#include "settings.h"
+
+typedef struct {
+ const gsr_recorder_settings *settings;
+ gsr_egl *egl;
+ gsr_window *window;
+ gsr_capture_deps *capture_deps;
+ gsr_capture_sources *capture_sources;
+ gsr_image_format image_format;
+ const char **plugin_filepaths;
+ int num_plugin_filepaths;
+ const atomic_int *running;
+ void (*screenshot_saved)(const char *filepath, void *userdata);
+ void *userdata;
+} gsr_screenshot_params;
+
+bool get_image_format_from_filename(const char *filename, gsr_image_format *image_format);
+gsr_color_range image_format_to_color_range(gsr_image_format image_format, int image_quality);
+int video_quality_to_image_quality_value(gsr_video_quality video_quality);
+
+/* Returns a |gsr_error| value. Captures one frame and writes it to |settings->filename| */
+int gsr_screenshot_take(const gsr_screenshot_params *params);
+
+int gsr_load_plugins(gsr_plugins *plugins, const char **plugin_filepaths, int num_plugin_filepaths, const gsr_recorder_settings *settings, gsr_egl *egl, vec2i video_size);
+
+#endif /* GSR_RECORDER_SCREENSHOT_H */
diff --git a/include/recorder/settings.h b/include/recorder/settings.h
new file mode 100644
index 0000000..164e082
--- /dev/null
+++ b/include/recorder/settings.h
@@ -0,0 +1,55 @@
+#ifndef GSR_RECORDER_SETTINGS_H
+#define GSR_RECORDER_SETTINGS_H
+
+#include <stdbool.h>
+#include <stdint.h>
+#include "../defs.h"
+#include "../vec2.h"
+
+typedef struct {
+ gsr_video_encoder_hardware video_encoder;
+ gsr_pixel_format pixel_format;
+ gsr_framerate_mode framerate_mode;
+ gsr_color_range color_range;
+ gsr_tune tune;
+ gsr_video_codec video_codec;
+ gsr_audio_codec audio_codec;
+ gsr_bitrate_mode bitrate_mode;
+ gsr_video_quality video_quality;
+ gsr_replay_storage replay_storage;
+
+ const char *capture_source;
+ const char *container_format;
+ const char *filename;
+ const char *replay_recording_directory;
+ const char *portal_session_token_filepath;
+ const char *recording_saved_script;
+ const char *ffmpeg_opts;
+ const char *ffmpeg_video_opts;
+ const char *ffmpeg_audio_opts;
+ bool verbose;
+ bool gl_debug;
+ bool fallback_cpu_encoding;
+ bool low_power;
+ bool exclude_metadata;
+ bool record_cursor;
+ bool date_folders;
+ bool restore_portal_session;
+ bool restart_replay_on_save;
+ bool write_first_frame_ts;
+ bool is_replaying;
+ bool is_livestream;
+ bool is_output_piped;
+ bool low_latency_recording;
+ bool very_old_gpu;
+ int64_t video_bitrate;
+ int64_t audio_bitrate;
+ int64_t fps;
+ int64_t replay_buffer_size_secs;
+ double keyint;
+ vec2i output_resolution;
+ vec2i region_size;
+ vec2i region_position;
+} gsr_recorder_settings;
+
+#endif /* GSR_RECORDER_SETTINGS_H */
diff --git a/include/recorder/video_codec.h b/include/recorder/video_codec.h
new file mode 100644
index 0000000..9347a9e
--- /dev/null
+++ b/include/recorder/video_codec.h
@@ -0,0 +1,18 @@
+#ifndef GSR_RECORDER_VIDEO_CODEC_H
+#define GSR_RECORDER_VIDEO_CODEC_H
+
+#include <stdbool.h>
+#include "../defs.h"
+#include "../egl.h"
+#include "settings.h"
+
+#include <libavcodec/avcodec.h>
+
+int video_quality_to_h264_equivalent_qp(gsr_video_quality video_quality);
+enum AVPixelFormat get_pixel_format(gsr_video_codec video_codec, gsr_gpu_vendor vendor, bool use_software_video_encoder);
+
+AVCodecContext* create_video_codec_context(enum AVPixelFormat pix_fmt, const AVCodec *codec, const gsr_egl *egl, const gsr_recorder_settings *settings, int width, int height);
+bool open_video_software(AVCodecContext *codec_context, const gsr_recorder_settings *settings);
+bool open_video_hardware(AVCodecContext *codec_context, bool low_power, const gsr_egl *egl, const gsr_recorder_settings *settings);
+
+#endif /* GSR_RECORDER_VIDEO_CODEC_H */
diff --git a/include/recorder/windowing.h b/include/recorder/windowing.h
new file mode 100644
index 0000000..630f3fe
--- /dev/null
+++ b/include/recorder/windowing.h
@@ -0,0 +1,35 @@
+#ifndef GSR_RECORDER_WINDOWING_H
+#define GSR_RECORDER_WINDOWING_H
+
+#include <stdbool.h>
+#include "../defs.h"
+#include "../egl.h"
+
+#include <X11/Xlib.h>
+
+typedef struct {
+ bool monitor_capture;
+ bool gl_debug;
+ bool listen_to_x11_events;
+} gsr_windowing_params;
+
+typedef struct {
+ Display *display;
+ gsr_window *window;
+ gsr_egl egl;
+ bool egl_loaded;
+ bool card_path_found;
+} gsr_windowing;
+
+/* Returns a |gsr_error| value. Connects to the display server and creates a window */
+int gsr_windowing_init(gsr_windowing *self, const gsr_windowing_params *params);
+/* Returns a |gsr_error| value. Loads opengl and finds the drm card to use */
+int gsr_windowing_load_egl(gsr_windowing *self, const gsr_windowing_params *params);
+void gsr_windowing_deinit(gsr_windowing *self);
+bool gsr_windowing_is_wayland(const gsr_windowing *self);
+
+bool gsr_windowing_is_using_prime_run(void);
+void gsr_windowing_disable_prime_run(void);
+bool monitor_capture_use_drm(const gsr_window *window, gsr_gpu_vendor vendor);
+
+#endif /* GSR_RECORDER_WINDOWING_H */
diff --git a/include/replay_buffer/replay_buffer.h b/include/replay_buffer/replay_buffer.h
index 6e91103..bf2e4e6 100644
--- a/include/replay_buffer/replay_buffer.h
+++ b/include/replay_buffer/replay_buffer.h
@@ -14,6 +14,7 @@ typedef struct {
struct gsr_replay_buffer {
void (*destroy)(gsr_replay_buffer *self);
+ void (*destroy_at_exit)(gsr_replay_buffer *self);
bool (*append)(gsr_replay_buffer *self, const AVPacket *av_packet, double timestamp);
void (*clear)(gsr_replay_buffer *self);
AVPacket* (*iterator_get_packet)(gsr_replay_buffer *self, gsr_replay_buffer_iterator iterator);
@@ -30,6 +31,13 @@ struct gsr_replay_buffer {
gsr_replay_buffer* gsr_replay_buffer_create(gsr_replay_storage replay_storage, const char *replay_directory, double replay_buffer_time, size_t replay_buffer_num_packets);
void gsr_replay_buffer_destroy(gsr_replay_buffer *self);
+/*
+ Only frees the resources that the operating system doesn't free when the process exits, such as files,
+ and leaves the memory to the operating system to free. Freeing a replay buffer that holds several gigabytes
+ of data takes seconds, which is time wasted when the process is exiting anyway.
+ |self| can't be used after this.
+*/
+void gsr_replay_buffer_destroy_at_exit(gsr_replay_buffer *self);
bool gsr_replay_buffer_append(gsr_replay_buffer *self, const AVPacket *av_packet, double timestamp);
void gsr_replay_buffer_clear(gsr_replay_buffer *self);
diff --git a/include/sound.h b/include/sound.h
new file mode 100644
index 0000000..31c7d94
--- /dev/null
+++ b/include/sound.h
@@ -0,0 +1,59 @@
+#ifndef GSR_SOUND_H
+#define GSR_SOUND_H
+
+#include <stdbool.h>
+#include <stddef.h>
+
+typedef struct {
+ void *handle;
+ unsigned int frames;
+} SoundDevice;
+
+typedef struct {
+ char name[256];
+ char description[256];
+} gsr_audio_device;
+
+typedef struct {
+ char default_output[256];
+ char default_input[256];
+ gsr_audio_device *items;
+ size_t num_items;
+ size_t capacity_items;
+} gsr_audio_devices;
+
+typedef enum {
+ GSR_AUDIO_FORMAT_S16,
+ GSR_AUDIO_FORMAT_S32,
+ GSR_AUDIO_FORMAT_F32
+} gsr_audio_format;
+
+/*
+ Get a sound device by name, returning the device into the |device| parameter.
+ |device_name| can be a device name or "default_output", "default_input" or "" to not connect to any device (used for app audio for example).
+ If the device name is "default_output" or "default_input" then it will automatically switch which
+ device is records from when the default output/input is changed in the system audio settings.
+ Returns 0 on success, or a negative value on failure.
+*/
+int sound_device_get_by_name(SoundDevice *device, const char *node_name, const char *device_name, const char *description, unsigned int num_channels, unsigned int period_frame_size, gsr_audio_format audio_format);
+
+void sound_device_close(SoundDevice *device);
+
+/*
+ Discards the audio that has been captured so far.
+ Call this before the first call to sound_device_read_next_chunk to not get audio that was captured before that point,
+ since the sound device can be created a while before audio capture starts.
+*/
+void sound_device_flush(SoundDevice *device);
+
+/*
+ Returns the next chunk of audio into @buffer.
+ Returns the number of frames read, or a negative value on failure.
+*/
+int sound_device_read_next_chunk(SoundDevice *device, void **buffer, double timeout_sec, double *latency_seconds);
+
+void get_pulseaudio_inputs(gsr_audio_devices *audio_devices);
+void gsr_audio_devices_deinit(gsr_audio_devices *self);
+bool pulseaudio_server_is_pipewire(void);
+
+#endif /* GSR_SOUND_H */
diff --git a/include/sound.hpp b/include/sound.hpp
deleted file mode 100644
index d78e4b6..0000000
--- a/include/sound.hpp
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- Copyright (C) 2020 dec05eba
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see <https://www.gnu.org/licenses/>.
-*/
-
-#ifndef GPU_SCREEN_RECORDER_H
-#define GPU_SCREEN_RECORDER_H
-
-#include <optional>
-#include <vector>
-#include <string>
-
-typedef struct {
- void *handle;
- unsigned int frames;
-} SoundDevice;
-
-struct AudioDevice {
- std::string name;
- std::string description;
-};
-
-struct AudioDevices {
- std::string default_output;
- std::string default_input;
- std::vector<AudioDevice> audio_inputs;
-};
-
-enum class AudioInputType {
- DEVICE,
- APPLICATION
-};
-
-struct AudioInput {
- std::string name;
- AudioInputType type = AudioInputType::DEVICE;
- bool inverted = false;
-};
-
-struct MergedAudioInputs {
- std::optional<std::string> custom_name;
- std::string track_name;
- std::vector<AudioInput> audio_inputs;
-};
-
-typedef enum {
- S16,
- S32,
- F32
-} AudioFormat;
-
-/*
- Get a sound device by name, returning the device into the |device| parameter.
- |device_name| can be a device name or "default_output", "default_input" or "" to not connect to any device (used for app audio for example).
- If the device name is "default_output" or "default_input" then it will automatically switch which
- device is records from when the default output/input is changed in the system audio settings.
- Returns 0 on success, or a negative value on failure.
-*/
-int sound_device_get_by_name(SoundDevice *device, const char *node_name, const char *device_name, const char *description, unsigned int num_channels, unsigned int period_frame_size, AudioFormat audio_format);
-
-void sound_device_close(SoundDevice *device);
-
-/*
- Discards the audio that has been captured so far.
- Call this before the first call to sound_device_read_next_chunk to not get audio that was captured before that point,
- since the sound device can be created a while before audio capture starts.
-*/
-void sound_device_flush(SoundDevice *device);
-
-/*
- Returns the next chunk of audio into @buffer.
- Returns the number of frames read, or a negative value on failure.
-*/
-int sound_device_read_next_chunk(SoundDevice *device, void **buffer, double timeout_sec, double *latency_seconds);
-
-AudioDevices get_pulseaudio_inputs();
-bool pulseaudio_server_is_pipewire();
-
-#endif /* GPU_SCREEN_RECORDER_H */
diff --git a/include/utils.h b/include/utils.h
index d2c3b7d..d1a2a3c 100644
--- a/include/utils.h
+++ b/include/utils.h
@@ -6,6 +6,7 @@
#include "../include/defs.h"
#include <stdbool.h>
#include <stdint.h>
+#include <stddef.h>
typedef struct AVCodecContext AVCodecContext;
typedef struct AVFrame AVFrame;
@@ -64,4 +65,21 @@ unsigned int gl_create_texture(gsr_egl *egl, int width, int height, int internal
bool get_nvidia_driver_version(int *major, int *minor);
+/* Return false from the callback to stop splitting */
+typedef bool (*gsr_string_split_callback)(const char *str, size_t size, void *userdata);
+void gsr_string_split(const char *str, char delimiter, gsr_string_split_callback callback, void *userdata);
+
+bool gsr_string_starts_with(const char *str, size_t str_size, const char *substr);
+bool gsr_string_ends_with(const char *str, const char *substr);
+
+bool gsr_string_to_int(const char *str, size_t size, int *number);
+bool gsr_string_to_int64(const char *str, size_t size, int64_t *number);
+
+bool gsr_array_ensure_capacity(void **array, size_t num_items, size_t *capacity_items, size_t item_size);
+
+/* These write a formatted local date/time into |str|, which should be at least 128 bytes in size */
+void gsr_get_date_str(char *str, size_t size);
+void gsr_get_date_only_str(char *str, size_t size);
+void gsr_get_time_only_str(char *str, size_t size);
+
#endif /* GSR_UTILS_H */