diff options
Diffstat (limited to 'include')
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 */ |
