From 0ae3c1f82702b07e5356a8e271e617c7dfe7135c Mon Sep 17 00:00:00 2001 From: dec05eba Date: Thu, 28 Nov 2024 11:42:39 +0100 Subject: Repurpose '/' in audio argument to set the audio track name, not the recording node name in pulseaudio/pipewire. This also now allows setting audio track name when recording application audio --- src/main.cpp | 156 ++++++++++++++++++++++++----------------------------------- 1 file changed, 64 insertions(+), 92 deletions(-) (limited to 'src/main.cpp') diff --git a/src/main.cpp b/src/main.cpp index a675bab..1375fa1 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1103,10 +1103,10 @@ static void usage_full() { printf(" Content frame rate is similar to variable frame rate mode, except the frame rate will match the frame rate of the captured content when possible, but not capturing above the frame rate set in this -f option.\n"); printf("\n"); printf(" -a Audio device or application 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 or application.\n"); - printf(" 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"); - printf(" Multiple audio sources can be merged into one audio track by using \"|\" as a separator into one -a argument, for example: -a \"alsa_output1|alsa_output2\".\n"); printf(" 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"); - printf(" The audio name can also be prefixed with \"device:\", for example: -a \"device:alsa_output.pci-0000_00_1b.0.analog-stereo.monitor\".\n"); + printf(" Multiple audio sources can be merged into one audio track by using \"|\" as a separator into one -a argument, for example: -a \"default_output|default_input\".\n"); + printf(" A name can be given to the audio track by prefixing the audio with /, for example \"track name/default_output\" or \"track name/default_output|default_input\".\n"); + printf(" The audio name can also be prefixed with \"device:\", for example: -a \"device:default_output\".\n"); printf(" To record audio from an application then prefix the audio name with \"app:\", for example: -a \"app:Brave\".\n"); printf(" To record audio from all applications except the provided use prefix the audio name with \"app-inverse:\", for example: -a \"app-inverse:Brave\".\n"); printf(" \"app:\" and \"app-inverse:\" can't be mixed in one audio track.\n"); @@ -1397,7 +1397,7 @@ static double audio_codec_get_desired_delay(AudioCodec audio_codec, int fps) { return std::max(0.0, base - fps_inv); } -struct AudioDevice { +struct AudioDeviceData { SoundDevice sound_device; AudioInput audio_input; AVFilterContext *src_filter_ctx = nullptr; @@ -1408,10 +1408,11 @@ struct AudioDevice { // TODO: Cleanup struct AudioTrack { + std::string name; AVCodecContext *codec_context = nullptr; AVStream *stream = nullptr; - std::vector audio_devices; + std::vector audio_devices; AVFilterGraph *graph = nullptr; AVFilterContext *sink = nullptr; int stream_index = 0; @@ -1535,6 +1536,8 @@ static void save_replay_async(AVCodecContext *video_codec_context, int video_str for(AudioTrack &audio_track : audio_tracks) { stream_index_to_audio_track_map[audio_track.stream_index] = &audio_track; AVStream *audio_stream = create_stream(av_format_context, audio_track.codec_context); + if(!audio_track.name.empty()) + av_dict_set(&audio_stream->metadata, "title", audio_track.name.c_str(), 0); avcodec_parameters_from_context(audio_stream->codecpar, audio_track.codec_context); audio_track.stream = audio_stream; } @@ -1631,65 +1634,57 @@ static bool string_starts_with(const std::string &str, const char *substr) { return (int)str.size() >= len && memcmp(str.data(), substr, len) == 0; } -static const AudioInput* get_audio_device_by_name(const std::vector &audio_inputs, const std::string &name) { - for(const auto &audio_input : audio_inputs) { - if(audio_input.name == name) - return &audio_input; +static const AudioDevice* get_audio_device_by_name(const std::vector &audio_devices, const char *name) { + for(const auto &audio_device : audio_devices) { + if(strcmp(audio_device.name.c_str(), name) == 0) + return &audio_device; } return nullptr; } -static std::vector parse_audio_input_arg(const char *str, const AudioDevices &audio_devices) { - std::vector audio_inputs; +static MergedAudioInputs parse_audio_input_arg(const char *str, const AudioDevices &audio_devices) { + MergedAudioInputs result; + const bool name_is_existing_audio_device = get_audio_device_by_name(audio_devices.audio_inputs, str) != nullptr; + if(name_is_existing_audio_device) { + result.audio_inputs.push_back({str, AudioInputType::DEVICE, false}); + return result; + } + + const char *track_name_sep_ptr = strchr(str, '/'); + if(track_name_sep_ptr) { + result.track_name.assign(str, track_name_sep_ptr - str); + str = track_name_sep_ptr + 1; + } + split_string(str, '|', [&](const char *sub, size_t size) { AudioInput audio_input; audio_input.name.assign(sub, size); if(string_starts_with(audio_input.name.c_str(), "app:")) { audio_input.name.erase(audio_input.name.begin(), audio_input.name.begin() + 4); - audio_input.description = audio_input.name; audio_input.type = AudioInputType::APPLICATION; audio_input.inverted = false; - audio_inputs.push_back(std::move(audio_input)); + result.audio_inputs.push_back(std::move(audio_input)); return true; } else if(string_starts_with(audio_input.name.c_str(), "app-inverse:")) { audio_input.name.erase(audio_input.name.begin(), audio_input.name.begin() + 12); - audio_input.description = audio_input.name; audio_input.type = AudioInputType::APPLICATION; audio_input.inverted = true; - audio_inputs.push_back(std::move(audio_input)); + result.audio_inputs.push_back(std::move(audio_input)); return true; } else if(string_starts_with(audio_input.name.c_str(), "device:")) { audio_input.name.erase(audio_input.name.begin(), audio_input.name.begin() + 7); audio_input.type = AudioInputType::DEVICE; - audio_inputs.push_back(std::move(audio_input)); + result.audio_inputs.push_back(std::move(audio_input)); return true; } else { - const bool name_is_existing_audio_device = get_audio_device_by_name(audio_devices.audio_inputs, audio_input.name); - const size_t index = audio_input.name.find('/'); - if(!name_is_existing_audio_device && index != std::string::npos) { - audio_input.description = audio_input.name.substr(0, index); - audio_input.name.erase(audio_input.name.begin(), audio_input.name.begin() + index + 1); - } audio_input.type = AudioInputType::DEVICE; - audio_inputs.push_back(std::move(audio_input)); + result.audio_inputs.push_back(std::move(audio_input)); return true; } }); - return audio_inputs; -} -static std::vector parse_app_audio_input_arg(const char *str) { - std::vector audio_inputs; - split_string(str, '|', [&](const char *sub, size_t size) { - AudioInput audio_input; - audio_input.name.assign(sub, size); - audio_input.description = audio_input.name; - audio_input.type = AudioInputType::APPLICATION; - audio_inputs.push_back(std::move(audio_input)); - return true; - }); - return audio_inputs; + return result; } // TODO: Does this match all livestreaming cases? @@ -2453,43 +2448,42 @@ static void match_app_audio_input_to_available_apps(const std::vector parse_audio_inputs(const AudioDevices &audio_devices, const Arg &audio_input_arg, const Arg &app_audio_input_arg, const Arg &app_audio_input_inverted_arg) { +static std::vector parse_audio_inputs(const AudioDevices &audio_devices, const Arg &audio_input_arg) { std::vector requested_audio_inputs; 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, audio_devices)}); + requested_audio_inputs.push_back(parse_audio_input_arg(audio_input, audio_devices)); for(AudioInput &request_audio_input : requested_audio_inputs.back().audio_inputs) { if(request_audio_input.type != AudioInputType::DEVICE) continue; bool match = false; - if(!audio_devices.default_output.empty() && request_audio_input.name == "default_output") { + if(request_audio_input.name == "default_output") { + if(audio_devices.default_output.empty()) { + fprintf(stderr, "Error: -a default_output was specified but no default audio output is specified in the audio server\n"); + _exit(2); + } 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") { + } else if(request_audio_input.name == "default_input") { + if(audio_devices.default_input.empty()) { + fprintf(stderr, "Error: -a default_input was specified but no default audio input is specified in the audio server\n"); + _exit(2); + } request_audio_input.name = audio_devices.default_input; - if(request_audio_input.description.empty()) - request_audio_input.description = "gsr-Default input"; - match = true; - } - - const AudioInput* existing_audio_input = get_audio_device_by_name(audio_devices.audio_inputs, request_audio_input.name); - if(existing_audio_input) { - if(request_audio_input.description.empty()) - request_audio_input.description = "gsr-" + existing_audio_input->description; match = true; + } else { + const bool name_is_existing_audio_device = get_audio_device_by_name(audio_devices.audio_inputs, request_audio_input.name.c_str()) != nullptr; + if(name_is_existing_audio_device) + match = true; } 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()); + fprintf(stderr, "Error: Audio 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()) @@ -2502,23 +2496,6 @@ static std::vector parse_audio_inputs(const AudioDevices &aud } } - for(const char *app_audio_input : app_audio_input_arg.values) { - if(!app_audio_input || app_audio_input[0] == '\0') - continue; - - requested_audio_inputs.push_back({parse_app_audio_input_arg(app_audio_input)}); - } - - for(const char *app_audio_input : app_audio_input_inverted_arg.values) { - if(!app_audio_input || app_audio_input[0] == '\0') - continue; - - requested_audio_inputs.push_back({parse_app_audio_input_arg(app_audio_input)}); - for(auto &audio_input : requested_audio_inputs.back().audio_inputs) { - audio_input.inverted = true; - } - } - return requested_audio_inputs; } @@ -2869,15 +2846,15 @@ static const AVCodec* select_video_codec_with_fallback(VideoCodec *video_codec, return pick_video_codec(video_codec, egl, use_software_video_encoder, video_codec_auto, video_codec_to_use, is_flv, low_power); } -static std::vector create_device_audio_inputs(const std::vector &audio_inputs, AVCodecContext *audio_codec_context, int num_channels, double num_audio_frames_shift, std::vector &src_filter_ctx, bool use_amix) { - std::vector audio_track_audio_devices; +static std::vector create_device_audio_inputs(const std::vector &audio_inputs, AVCodecContext *audio_codec_context, int num_channels, double num_audio_frames_shift, std::vector &src_filter_ctx, bool use_amix) { + std::vector audio_track_audio_devices; for(size_t i = 0; i < audio_inputs.size(); ++i) { const auto &audio_input = audio_inputs[i]; AVFilterContext *src_ctx = nullptr; if(use_amix) src_ctx = src_filter_ctx[i]; - AudioDevice audio_device; + AudioDeviceData audio_device; audio_device.audio_input = audio_input; audio_device.src_filter_ctx = src_ctx; @@ -2885,7 +2862,8 @@ static std::vector create_device_audio_inputs(const std::vectorframe_size, audio_codec_context_get_audio_format(audio_codec_context)) != 0) { + const std::string description = "gsr-" + audio_input.name; + if(sound_device_get_by_name(&audio_device.sound_device, audio_input.name.c_str(), description.c_str(), num_channels, audio_codec_context->frame_size, audio_codec_context_get_audio_format(audio_codec_context)) != 0) { fprintf(stderr, "Error: failed to get \"%s\" audio device\n", audio_input.name.c_str()); _exit(1); } @@ -2900,8 +2878,8 @@ static std::vector create_device_audio_inputs(const std::vectorpts = -audio_codec_context->frame_size * num_audio_frames_shift; @@ -3028,8 +3006,6 @@ int main(int argc, char **argv) { { "-f", Arg { {}, false, false } }, { "-s", Arg { {}, true, false } }, { "-a", Arg { {}, true, true } }, - { "-aa", Arg { {}, true, true } }, // TODO: Remove soon since this is deprecated. User should use -a with app: instead - { "-aai", Arg { {}, true, true } }, // TODO: Remove soon since this is deprecated. User should use -a with app-inverse: instead { "-q", Arg { {}, true, false } }, { "-o", Arg { {}, true, false } }, { "-r", Arg { {}, true, false } }, @@ -3288,20 +3264,12 @@ int main(int argc, char **argv) { } const Arg &audio_input_arg = args["-a"]; - const Arg &app_audio_input_arg = args["-aa"]; - const Arg &app_audio_input_inverted_arg = args["-aai"]; AudioDevices audio_devices; if(!audio_input_arg.values.empty()) audio_devices = get_pulseaudio_inputs(); - - if(!app_audio_input_arg.values.empty()) - fprintf(stderr, "gsr warning: argument -aa is deprecated, use -a with app: prefix instead, for example: -a \"app:Brave\"\n"); - if(!app_audio_input_inverted_arg.values.empty()) - fprintf(stderr, "gsr warning: argument -aai is deprecated, use -a with app-inverse: prefix instead, for example: -a \"app-inverse:Brave\"\n"); - - std::vector requested_audio_inputs = parse_audio_inputs(audio_devices, audio_input_arg, app_audio_input_arg, app_audio_input_inverted_arg); + std::vector requested_audio_inputs = parse_audio_inputs(audio_devices, audio_input_arg); const bool uses_app_audio = merged_audio_inputs_has_app_audio(requested_audio_inputs); std::vector app_audio_names; @@ -3652,7 +3620,7 @@ int main(int argc, char **argv) { if(is_livestream && requested_audio_inputs.empty()) { fprintf(stderr, "Info: live streaming but no audio track was added. Adding a silent audio track\n"); MergedAudioInputs mai; - mai.audio_inputs.push_back({ "", "gsr-silent" }); + mai.audio_inputs.push_back({""}); requested_audio_inputs.push_back(std::move(mai)); } @@ -3735,6 +3703,9 @@ int main(int argc, char **argv) { if(replay_buffer_size_secs == -1) audio_stream = create_stream(av_format_context, audio_codec_context); + if(audio_stream && !merged_audio_inputs.track_name.empty()) + av_dict_set(&audio_stream->metadata, "title", merged_audio_inputs.track_name.c_str(), 0); + open_audio(audio_codec_context); if(audio_stream) avcodec_parameters_from_context(audio_stream->codecpar, audio_codec_context); @@ -3766,7 +3737,7 @@ int main(int argc, char **argv) { const double audio_startup_time_seconds = force_no_audio_offset ? 0 : audio_codec_get_desired_delay(audio_codec, fps);// * ((double)audio_codec_context->frame_size / 1024.0); const double num_audio_frames_shift = audio_startup_time_seconds / timeout_sec; - std::vector audio_track_audio_devices; + std::vector audio_track_audio_devices; if(audio_inputs_has_app_audio(merged_audio_inputs.audio_inputs)) { assert(!use_amix); #ifdef GSR_APP_AUDIO @@ -3777,6 +3748,7 @@ int main(int argc, char **argv) { } AudioTrack audio_track; + audio_track.name = merged_audio_inputs.track_name; audio_track.codec_context = audio_codec_context; audio_track.stream = audio_stream; audio_track.audio_devices = std::move(audio_track_audio_devices); @@ -3839,7 +3811,7 @@ int main(int argc, char **argv) { memset(empty_audio, 0, audio_buffer_size); for(AudioTrack &audio_track : audio_tracks) { - for(AudioDevice &audio_device : audio_track.audio_devices) { + for(AudioDeviceData &audio_device : audio_track.audio_devices) { audio_device.thread = std::thread([&]() mutable { const AVSampleFormat sound_device_sample_format = audio_format_to_sample_format(audio_codec_context_get_audio_format(audio_track.codec_context)); // TODO: Always do conversion for now. This fixes issue with stuttering audio on pulseaudio with opus + multiple audio sources merged @@ -4234,7 +4206,7 @@ int main(int argc, char **argv) { } for(AudioTrack &audio_track : audio_tracks) { - for(AudioDevice &audio_device : audio_track.audio_devices) { + for(auto &audio_device : audio_track.audio_devices) { audio_device.thread.join(); sound_device_close(&audio_device.sound_device); } -- cgit v1.2.3