From 844a54b43b3330711e5f3c5e4680f960538f8eb5 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Fri, 2 Aug 2024 00:12:39 +0200 Subject: Allow using 'default_output' / 'default_input' as audio devices (-a option), add --list-audio-devices option --- src/main.cpp | 129 ++++++++++++++++++++++++++++++++++++++++++---------------- src/sound.cpp | 66 +++++++++++++++++++++++++++--- 2 files changed, 153 insertions(+), 42 deletions(-) (limited to 'src') diff --git a/src/main.cpp b/src/main.cpp index 50fa7f8..48c6223 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1036,6 +1036,7 @@ static void usage_full() { fprintf(stderr, " -a Audio device to record from (pulse audio device). Can be specified multiple times. Each time this is specified a new audio track is added for the specified audio device.\n"); fprintf(stderr, " A name can be given to the audio input device by prefixing the audio input with /, for example \"dummy/alsa_output.pci-0000_00_1b.0.analog-stereo.monitor\".\n"); fprintf(stderr, " Multiple audio devices can be merged into one audio track by using \"|\" as a separator into one -a argument, for example: -a \"alsa_output1|alsa_output2\".\n"); + fprintf(stderr, " The audio device can also be \"default_output\" in which case the default output device is used, or \"default_input\" in which case the default input device is used.\n"); fprintf(stderr, " If the audio device is an empty string then the audio device is ignored.\n"); fprintf(stderr, " Optional, no audio track is added by default.\n"); fprintf(stderr, "\n"); @@ -1097,6 +1098,13 @@ static void usage_full() { fprintf(stderr, " Supported capture options (window, focused, screen, monitors and portal, if supported by the system).\n"); fprintf(stderr, " If opengl initialization fails then the program exits with 22, if no usable drm device is found then it exits with 23. On success it exits with 0.\n"); fprintf(stderr, "\n"); + fprintf(stderr, " --list-audio-devices\n"); + fprintf(stderr, " List audio devices (for use by GPU Screen Recorder UI). Lists audio devices in the following format (prints them to stdout and exits):\n"); + fprintf(stderr, " \n"); + fprintf(stderr, " For example:\n"); + fprintf(stderr, " bluez_input.88:C9:E8:66:A2:27 WH-1000XM4\n"); + fprintf(stderr, " The is the name to pass to GPU Screen Recorder in a -a option.\n"); + fprintf(stderr, "\n"); //fprintf(stderr, " -pixfmt The pixel format to use for the output video. yuv420 is the most common format and is best supported, but the color is compressed, so colors can look washed out and certain colors of text can look bad. Use yuv444 for no color compression, but the video may not work everywhere and it may not work with hardware video decoding. Optional, set to 'yuv420' by default\n"); fprintf(stderr, " -o The output file path. If omitted then the encoded data is sent to stdout. Required in replay mode (when using -r).\n"); fprintf(stderr, " In replay mode this has to be a directory instead of a file.\n"); @@ -1782,6 +1790,23 @@ static void info_command() { _exit(0); } +static void list_audio_devices_command() { + const AudioDevices audio_devices = get_pulseaudio_inputs(); + + if(!audio_devices.default_output.empty()) + puts("default_output Default output"); + + if(!audio_devices.default_input.empty()) + puts("default_input Default input"); + + for(const auto &audio_input : audio_devices.audio_inputs) { + printf("%s %s\n", audio_input.name.c_str(), audio_input.description.c_str()); + } + + fflush(stdout); + _exit(0); +} + static gsr_capture* create_capture_impl(const char *window_str, const char *screen_region, bool wayland, gsr_egl *egl, int fps, bool overclock, VideoCodec video_codec, gsr_color_range color_range, bool record_cursor, bool track_damage, bool use_software_video_encoder, bool restore_portal_session) { vec2i region_size = { 0, 0 }; Window src_window_id = None; @@ -1998,6 +2023,65 @@ struct Arg { } }; +// Manually check if the audio inputs we give exist. This is only needed for pipewire, not pulseaudio. +// Pipewire instead DEFAULTS TO THE DEFAULT AUDIO INPUT. THAT'S RETARDED. +// OH, YOU MISSPELLED THE AUDIO INPUT? FUCK YOU +static std::vector parse_audio_inputs(const AudioDevices &audio_devices, const Arg &audio_input_arg, bool &uses_amix) { + std::vector requested_audio_inputs; + uses_amix = false; + + for(const char *audio_input : audio_input_arg.values) { + if(!audio_input || audio_input[0] == '\0') + continue; + + requested_audio_inputs.push_back({parse_audio_input_arg(audio_input)}); + if(requested_audio_inputs.back().audio_inputs.size() > 1) + uses_amix = true; + + for(AudioInput &request_audio_input : requested_audio_inputs.back().audio_inputs) { + bool match = false; + + if(!audio_devices.default_output.empty() && request_audio_input.name == "default_output") { + request_audio_input.name = audio_devices.default_output; + if(request_audio_input.description.empty()) + request_audio_input.description = "gsr-Default output"; + match = true; + } + + if(!audio_devices.default_input.empty() && request_audio_input.name == "default_input") { + request_audio_input.name = audio_devices.default_input; + if(request_audio_input.description.empty()) + request_audio_input.description = "gsr-Default input"; + match = true; + } + + for(const auto &existing_audio_input : audio_devices.audio_inputs) { + if(request_audio_input.name == existing_audio_input.name) { + if(request_audio_input.description.empty()) + request_audio_input.description = "gsr-" + existing_audio_input.description; + + match = true; + break; + } + } + + if(!match) { + fprintf(stderr, "Error: Audio input device '%s' is not a valid audio device, expected one of:\n", request_audio_input.name.c_str()); + if(!audio_devices.default_output.empty()) + fprintf(stderr, " default_output (Default output)\n"); + if(!audio_devices.default_input.empty()) + fprintf(stderr, " default_input (Default input)\n"); + for(const auto &existing_audio_input : audio_devices.audio_inputs) { + fprintf(stderr, " %s (%s)\n", existing_audio_input.name.c_str(), existing_audio_input.description.c_str()); + } + _exit(2); + } + } + } + + return requested_audio_inputs; +} + int main(int argc, char **argv) { signal(SIGINT, stop_handler); signal(SIGUSR1, save_replay_handler); @@ -2033,6 +2117,11 @@ int main(int argc, char **argv) { _exit(0); } + if(argc == 2 && strcmp(argv[1], "--list-audio-devices") == 0) { + list_audio_devices_command(); + _exit(0); + } + //av_log_set_level(AV_LOG_TRACE); std::map args = { @@ -2274,44 +2363,12 @@ int main(int argc, char **argv) { } const Arg &audio_input_arg = args["-a"]; - std::vector audio_inputs; + AudioDevices audio_devices; if(!audio_input_arg.values.empty()) - audio_inputs = get_pulseaudio_inputs(); - std::vector requested_audio_inputs; - bool uses_amix = false; - - // Manually check if the audio inputs we give exist. This is only needed for pipewire, not pulseaudio. - // Pipewire instead DEFAULTS TO THE DEFAULT AUDIO INPUT. THAT'S RETARDED. - // OH, YOU MISSPELLED THE AUDIO INPUT? FUCK YOU - for(const char *audio_input : audio_input_arg.values) { - if(!audio_input || audio_input[0] == '\0') - continue; - - requested_audio_inputs.push_back({parse_audio_input_arg(audio_input)}); - if(requested_audio_inputs.back().audio_inputs.size() > 1) - uses_amix = true; + audio_devices = get_pulseaudio_inputs(); - for(AudioInput &request_audio_input : requested_audio_inputs.back().audio_inputs) { - bool match = false; - for(const auto &existing_audio_input : audio_inputs) { - if(strcmp(request_audio_input.name.c_str(), existing_audio_input.name.c_str()) == 0) { - if(request_audio_input.description.empty()) - request_audio_input.description = "gsr-" + existing_audio_input.description; - - match = true; - break; - } - } - - if(!match) { - fprintf(stderr, "Error: Audio input device '%s' is not a valid audio device, expected one of:\n", request_audio_input.name.c_str()); - for(const auto &existing_audio_input : audio_inputs) { - fprintf(stderr, " %s (%s)\n", existing_audio_input.name.c_str(), existing_audio_input.description.c_str()); - } - _exit(2); - } - } - } + bool uses_amix = false; + std::vector requested_audio_inputs = parse_audio_inputs(audio_devices, audio_input_arg, uses_amix); const char *container_format = args["-c"].value(); if(container_format && strcmp(container_format, "mkv") == 0) diff --git a/src/sound.cpp b/src/sound.cpp index 53000bd..d0f2a80 100644 --- a/src/sound.cpp +++ b/src/sound.cpp @@ -327,12 +327,66 @@ static void pa_sourcelist_cb(pa_context *ctx, const pa_source_info *source_info, if(eol > 0) return; - std::vector *inputs = (std::vector*)userdata; - inputs->push_back({ source_info->name, source_info->description }); + AudioDevices *audio_devices = (AudioDevices*)userdata; + audio_devices->audio_inputs.push_back({ source_info->name, source_info->description }); } -std::vector get_pulseaudio_inputs() { - std::vector inputs; +static void pa_server_info_cb(pa_context*, const pa_server_info *server_info, void *userdata) { + AudioDevices *audio_devices = (AudioDevices*)userdata; + if(server_info->default_sink_name) + audio_devices->default_output = std::string(server_info->default_sink_name) + ".monitor"; + if(server_info->default_source_name) + audio_devices->default_input = server_info->default_source_name; +} + +static void get_pulseaudio_default_inputs(AudioDevices &audio_devices) { + pa_mainloop *main_loop = pa_mainloop_new(); + + pa_context *ctx = pa_context_new(pa_mainloop_get_api(main_loop), "gpu-screen-recorder-gtk"); + pa_context_connect(ctx, NULL, PA_CONTEXT_NOFLAGS, NULL); + int state = 0; + int pa_ready = 0; + pa_context_set_state_callback(ctx, pa_state_cb, &pa_ready); + + pa_operation *pa_op = NULL; + + for(;;) { + // Not ready + if(pa_ready == 0) { + pa_mainloop_iterate(main_loop, 1, NULL); + continue; + } + + switch(state) { + case 0: { + pa_op = pa_context_get_server_info(ctx, pa_server_info_cb, &audio_devices); + ++state; + break; + } + } + + // Couldn't get connection to the server + if(pa_ready == 2 || (state == 1 && pa_op && pa_operation_get_state(pa_op) == PA_OPERATION_DONE)) { + if(pa_op) + pa_operation_unref(pa_op); + pa_context_disconnect(ctx); + pa_context_unref(ctx); + pa_mainloop_free(main_loop); + return; + } + + pa_mainloop_iterate(main_loop, 1, NULL); + } + + pa_mainloop_free(main_loop); +} + +AudioDevices get_pulseaudio_inputs() { + AudioDevices audio_devices; + + // TODO: Do this in the same connection below instead of two separate connections + get_pulseaudio_default_inputs(audio_devices); + pa_mainloop *main_loop = pa_mainloop_new(); pa_context *ctx = pa_context_new(pa_mainloop_get_api(main_loop), "gpu-screen-recorder"); @@ -352,7 +406,7 @@ std::vector get_pulseaudio_inputs() { switch(state) { case 0: { - pa_op = pa_context_get_source_info_list(ctx, pa_sourcelist_cb, &inputs); + pa_op = pa_context_get_source_info_list(ctx, pa_sourcelist_cb, &audio_devices); ++state; break; } @@ -371,5 +425,5 @@ std::vector get_pulseaudio_inputs() { } pa_mainloop_free(main_loop); - return inputs; + return audio_devices; } -- cgit v1.2.3