diff options
-rw-r--r-- | include/pipewire_audio.h | 6 | ||||
-rw-r--r-- | include/sound.hpp | 5 | ||||
-rw-r--r-- | src/main.cpp | 28 | ||||
-rw-r--r-- | src/pipewire_audio.c | 67 | ||||
-rw-r--r-- | src/sound.cpp | 66 |
5 files changed, 89 insertions, 83 deletions
diff --git a/include/pipewire_audio.h b/include/pipewire_audio.h index 33fc9eb..e2ffd49 100644 --- a/include/pipewire_audio.h +++ b/include/pipewire_audio.h @@ -11,6 +11,7 @@ #define GSR_PIPEWIRE_AUDIO_MAX_STREAM_NODES 128 #define GSR_PIPEWIRE_AUDIO_MAX_PORTS 256 #define GSR_PIPEWIRE_AUDIO_MAX_REQUESTED_LINKS 32 +#define GSR_PIPEWIRE_AUDIO_MAX_VIRTUAL_SINKS 32 typedef enum { GSR_PIPEWIRE_AUDIO_NODE_TYPE_STREAM_OUTPUT, /* Application audio */ @@ -67,11 +68,16 @@ typedef struct { gsr_pipewire_audio_requested_link requested_links[GSR_PIPEWIRE_AUDIO_MAX_REQUESTED_LINKS]; int num_requested_links; + + struct pw_proxy *virtual_sink_proxies[GSR_PIPEWIRE_AUDIO_MAX_VIRTUAL_SINKS]; + int num_virtual_sink_proxies; } gsr_pipewire_audio; bool gsr_pipewire_audio_init(gsr_pipewire_audio *self); void gsr_pipewire_audio_deinit(gsr_pipewire_audio *self); +bool gsr_pipewire_audio_create_virtual_sink(gsr_pipewire_audio *self, const char *name); + /* This function links audio source outputs from applications that match the name |app_names| to the input that matches the name |stream_name_input|. diff --git a/include/sound.hpp b/include/sound.hpp index 21bf0bf..215e167 100644 --- a/include/sound.hpp +++ b/include/sound.hpp @@ -64,11 +64,6 @@ typedef enum { Returns 0 on success, or a negative value on failure. */ int sound_device_get_by_name(SoundDevice *device, const char *device_name, const char *description, unsigned int num_channels, unsigned int period_frame_size, AudioFormat audio_format); -/* - Creates a module-combine-sink and connects to it for recording, returning the device into the |device| parameter. - Returns 0 on success, or a negative value on failure. -*/ -int sound_device_create_combined_sink_connect(SoundDevice *device, const char *combined_sink_name, unsigned int num_channels, unsigned int period_frame_size, AudioFormat audio_format); void sound_device_close(SoundDevice *device); diff --git a/src/main.cpp b/src/main.cpp index dd7e0f1..e70646a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1403,7 +1403,6 @@ struct AudioDeviceData { AVFilterContext *src_filter_ctx = nullptr; AVFrame *frame = nullptr; std::thread thread; // TODO: Instead of having a thread for each track, have one thread for all threads and read the data with non-blocking read - std::string combined_sink_name; }; // TODO: Cleanup @@ -2885,21 +2884,28 @@ static AudioDeviceData create_application_audio_audio_input(const MergedAudioInp char random_str[8]; if(!generate_random_characters_standard_alphabet(random_str, sizeof(random_str))) { - fprintf(stderr, "gsr error: ailed to generate random string\n"); + fprintf(stderr, "gsr error: failed to generate random string\n"); _exit(1); } - audio_device.combined_sink_name = "gsr-combined-"; - audio_device.combined_sink_name.append(random_str, sizeof(random_str)); + std::string combined_sink_name = "gsr-combined-"; + combined_sink_name.append(random_str, sizeof(random_str)); - if(sound_device_create_combined_sink_connect(&audio_device.sound_device, audio_device.combined_sink_name.c_str(), num_channels, audio_codec_context->frame_size, audio_codec_context_get_audio_format(audio_codec_context)) != 0) { + if(!gsr_pipewire_audio_create_virtual_sink(pipewire_audio, combined_sink_name.c_str())) { + fprintf(stderr, "gsr error: failed to create virtual sink for application audio\n"); + _exit(1); + } + + combined_sink_name += ".monitor"; + + if(sound_device_get_by_name(&audio_device.sound_device, combined_sink_name.c_str(), "gpu-screen-recorder", num_channels, audio_codec_context->frame_size, audio_codec_context_get_audio_format(audio_codec_context)) != 0) { fprintf(stderr, "Error: failed to setup audio recording to combined sink\n"); _exit(1); } - std::vector<const char*> audio_sources; + std::vector<const char*> audio_devices_sources; for(const auto &audio_input : merged_audio_inputs.audio_inputs) { if(audio_input.type == AudioInputType::DEVICE) - audio_sources.push_back(audio_input.name.c_str()); + audio_devices_sources.push_back(audio_input.name.c_str()); } bool app_audio_inverted = false; @@ -2911,20 +2917,20 @@ static AudioDeviceData create_application_audio_audio_input(const MergedAudioInp } } - if(!audio_sources.empty()) { - if(!gsr_pipewire_audio_add_link_from_sources_to_sink(pipewire_audio, audio_sources.data(), audio_sources.size(), audio_device.combined_sink_name.c_str())) { + if(!audio_devices_sources.empty()) { + if(!gsr_pipewire_audio_add_link_from_sources_to_sink(pipewire_audio, audio_devices_sources.data(), audio_devices_sources.size(), combined_sink_name.c_str())) { fprintf(stderr, "gsr error: failed to add application audio link\n"); _exit(1); } } if(app_audio_inverted) { - if(!gsr_pipewire_audio_add_link_from_apps_to_sink_inverted(pipewire_audio, app_names.data(), app_names.size(), audio_device.combined_sink_name.c_str())) { + if(!gsr_pipewire_audio_add_link_from_apps_to_sink_inverted(pipewire_audio, app_names.data(), app_names.size(), combined_sink_name.c_str())) { fprintf(stderr, "gsr error: failed to add application audio link\n"); _exit(1); } } else { - if(!gsr_pipewire_audio_add_link_from_apps_to_sink(pipewire_audio, app_names.data(), app_names.size(), audio_device.combined_sink_name.c_str())) { + if(!gsr_pipewire_audio_add_link_from_apps_to_sink(pipewire_audio, app_names.data(), app_names.size(), combined_sink_name.c_str())) { fprintf(stderr, "gsr error: failed to add application audio link\n"); _exit(1); } diff --git a/src/pipewire_audio.c b/src/pipewire_audio.c index 0af96bb..152f374 100644 --- a/src/pipewire_audio.c +++ b/src/pipewire_audio.c @@ -277,20 +277,20 @@ bool gsr_pipewire_audio_init(gsr_pipewire_audio *self) { self->thread_loop = pw_thread_loop_new("gsr screen capture", NULL); if(!self->thread_loop) { - fprintf(stderr, "gsr error: gsr_pipewire_video_setup_stream: failed to create pipewire thread\n"); + fprintf(stderr, "gsr error: gsr_pipewire_audio_init: failed to create pipewire thread\n"); gsr_pipewire_audio_deinit(self); return false; } self->context = pw_context_new(pw_thread_loop_get_loop(self->thread_loop), NULL, 0); if(!self->context) { - fprintf(stderr, "gsr error: gsr_pipewire_video_setup_stream: failed to create pipewire context\n"); + fprintf(stderr, "gsr error: gsr_pipewire_audio_init: failed to create pipewire context\n"); gsr_pipewire_audio_deinit(self); return false; } if(pw_thread_loop_start(self->thread_loop) < 0) { - fprintf(stderr, "gsr error: gsr_pipewire_video_setup_stream: failed to start thread\n"); + fprintf(stderr, "gsr error: gsr_pipewire_audio_init: failed to start thread\n"); gsr_pipewire_audio_deinit(self); return false; } @@ -312,7 +312,6 @@ bool gsr_pipewire_audio_init(gsr_pipewire_audio *self) { self->server_version_sync = pw_core_sync(self->core, PW_ID_CORE, 0); pw_thread_loop_wait(self->thread_loop); - pw_thread_loop_unlock(self->thread_loop); return true; } @@ -323,6 +322,14 @@ void gsr_pipewire_audio_deinit(gsr_pipewire_audio *self) { pw_thread_loop_stop(self->thread_loop); } + for(int i = 0; i < self->num_virtual_sink_proxies; ++i) { + if(self->virtual_sink_proxies[i]) { + pw_proxy_destroy(self->virtual_sink_proxies[i]); + self->virtual_sink_proxies[i] = NULL; + } + } + self->num_virtual_sink_proxies = 0; + if(self->core) { pw_core_disconnect(self->core); self->core = NULL; @@ -362,6 +369,55 @@ void gsr_pipewire_audio_deinit(gsr_pipewire_audio *self) { #endif } +static struct pw_properties* gsr_pipewire_create_null_audio_sink(const char *name) { + struct spa_error_location err_loc; + char props_str[512]; + snprintf(props_str, sizeof(props_str), "{ factory.name=support.null-audio-sink node.name=\"%s\" media.class=Audio/Sink object.linger=false audio.position=[FL FR] monitor.channel-volumes=true monitor.passthrough=true adjust_time=0 slaves=\"\" }", name); + struct pw_properties *props = pw_properties_new_string_checked(props_str, strlen(props_str), &err_loc); + if(!props) { + fprintf(stderr, "gsr error: gsr_pipewire_create_null_audio_sink: failed to create virtual sink properties, error: %d:%d: %s\n", err_loc.line, err_loc.col, err_loc.reason); + return NULL; + } + return props; +} + +bool gsr_pipewire_audio_create_virtual_sink(gsr_pipewire_audio *self, const char *name) { + if(self->num_virtual_sink_proxies == GSR_PIPEWIRE_AUDIO_MAX_VIRTUAL_SINKS) { + fprintf(stderr, "gsr error: gsr_pipewire_audio_create_virtual_sink: reached max number of virtual sinks\n"); + return false; + } + + pw_thread_loop_lock(self->thread_loop); + + struct pw_properties *virtual_sink_props = gsr_pipewire_create_null_audio_sink(name); + if(!virtual_sink_props) { + pw_thread_loop_unlock(self->thread_loop); + return false; + } + + struct pw_proxy *virtual_sink_proxy = pw_core_create_object(self->core, "adapter", PW_TYPE_INTERFACE_Node, PW_VERSION_NODE, &virtual_sink_props->dict, 0); + // TODO: + // If these are done then the above needs sizeof(*self) as the last argument + //pw_proxy_add_object_listener(virtual_sink_proxy, &pd->object_listener, &node_events, self); + //pw_proxy_add_listener(virtual_sink_proxy, &pd->proxy_listener, &proxy_events, self); + // TODO: proxy + pw_properties_free(virtual_sink_props); + if(!virtual_sink_proxy) { + fprintf(stderr, "gsr error: gsr_pipewire_audio_create_virtual_sink: failed to create virtual sink\n"); + pw_thread_loop_unlock(self->thread_loop); + return false; + } + + self->server_version_sync = pw_core_sync(self->core, PW_ID_CORE, self->server_version_sync); + pw_thread_loop_wait(self->thread_loop); + pw_thread_loop_unlock(self->thread_loop); + + self->virtual_sink_proxies[self->num_virtual_sink_proxies] = virtual_sink_proxy; + ++self->num_virtual_sink_proxies; + + return true; +} + static bool string_remove_suffix(char *str, const char *suffix) { int str_len = strlen(str); int suffix_len = strlen(suffix); @@ -387,6 +443,9 @@ static bool gsr_pipewire_audio_add_link_from_apps_to_output(gsr_pipewire_audio * if(!input_name_copy) goto error; + if(input_type == GSR_PIPEWIRE_AUDIO_LINK_INPUT_TYPE_SINK) + string_remove_suffix(input_name_copy, ".monitor"); + for(int i = 0; i < num_output_names; ++i) { output_names_copy[i] = strdup(output_names[i]); if(!output_names_copy[i]) diff --git a/src/sound.cpp b/src/sound.cpp index b500a57..d72187c 100644 --- a/src/sound.cpp +++ b/src/sound.cpp @@ -42,30 +42,11 @@ struct pa_handle { int operation_success; double latency_seconds; - - uint32_t combined_sink_module_index; }; -static void destroy_combined_sink(pa_handle *p) { - // TODO: error handling - pa_operation *module_pa = pa_context_unload_module(p->context, p->combined_sink_module_index, NULL, NULL); - for(;;) { - if(pa_operation_get_state(module_pa) == PA_OPERATION_DONE) { - pa_operation_unref(module_pa); - break; - } - pa_mainloop_iterate(p->mainloop, 1, NULL); - } -} - static void pa_sound_device_free(pa_handle *p) { assert(p); - if(p->combined_sink_module_index != PA_INVALID_INDEX) { - destroy_combined_sink(p); - p->combined_sink_module_index = PA_INVALID_INDEX; - } - if (p->stream) { pa_stream_unref(p->stream); p->stream = NULL; @@ -90,31 +71,10 @@ static void pa_sound_device_free(pa_handle *p) { pa_xfree(p); } -static void module_index_callback(pa_context*, uint32_t idx, void *userdata) { - pa_handle *p = (pa_handle*)userdata; - p->combined_sink_module_index = idx; -} - -static bool create_null_sink(pa_handle *p, const char *null_sink_name) { - // TODO: Error handling - char module_argument[256]; - snprintf(module_argument, sizeof(module_argument), "sink_name=\"%s\" slaves= adjust_time=0", null_sink_name); - pa_operation *module_pa = pa_context_load_module(p->context, "module-null-sink", module_argument, module_index_callback, p); - for(;;) { - if(pa_operation_get_state(module_pa) == PA_OPERATION_DONE) { - pa_operation_unref(module_pa); - break; - } - pa_mainloop_iterate(p->mainloop, 1, NULL); - } - return p->combined_sink_module_index != PA_INVALID_INDEX; -} - static pa_handle* pa_sound_device_new(const char *server, const char *name, const char *dev, const char *stream_name, - const char *combined_sink_name, const pa_sample_spec *ss, const pa_buffer_attr *attr, int *rerror) { @@ -122,7 +82,6 @@ static pa_handle* pa_sound_device_new(const char *server, int error = PA_ERR_INTERNAL, r; p = pa_xnew0(pa_handle, 1); - p->combined_sink_module_index = PA_INVALID_INDEX; const int buffer_size = attr->fragsize; void *buffer = malloc(buffer_size); @@ -161,23 +120,12 @@ static pa_handle* pa_sound_device_new(const char *server, pa_mainloop_iterate(p->mainloop, 1, NULL); } - char device_to_record[256]; - if(combined_sink_name) { - if(!create_null_sink(p, combined_sink_name)) { - fprintf(stderr, "gsr error: pa_sound_device_new: failed to create module-combine-sink\n"); - goto fail; - } - snprintf(device_to_record, sizeof(device_to_record), "%s.monitor", combined_sink_name); - } else { - snprintf(device_to_record, sizeof(device_to_record), "%s", dev); - } - if (!(p->stream = pa_stream_new(p->context, stream_name, ss, NULL))) { error = pa_context_errno(p->context); goto fail; } - r = pa_stream_connect_record(p->stream, device_to_record, attr, + r = pa_stream_connect_record(p->stream, dev, attr, (pa_stream_flags_t)(PA_STREAM_INTERPOLATE_TIMING|PA_STREAM_ADJUST_LATENCY|PA_STREAM_AUTO_TIMING_UPDATE)); if (r < 0) { @@ -312,7 +260,7 @@ static int audio_format_to_get_bytes_per_sample(AudioFormat audio_format) { return 2; } -static int sound_device_setup_record(SoundDevice *device, const char *device_name, const char *description, unsigned int num_channels, unsigned int period_frame_size, AudioFormat audio_format, const char *combined_sink_name) { +int sound_device_get_by_name(SoundDevice *device, const char *device_name, const char *description, unsigned int num_channels, unsigned int period_frame_size, AudioFormat audio_format) { pa_sample_spec ss; ss.format = audio_format_to_pulse_audio_format(audio_format); ss.rate = 48000; @@ -326,7 +274,7 @@ static int sound_device_setup_record(SoundDevice *device, const char *device_nam buffer_attr.maxlength = buffer_attr.fragsize; int error = 0; - pa_handle *handle = pa_sound_device_new(nullptr, description, device_name, description, combined_sink_name, &ss, &buffer_attr, &error); + pa_handle *handle = pa_sound_device_new(nullptr, description, device_name, description, &ss, &buffer_attr, &error); if(!handle) { fprintf(stderr, "pa_sound_device_new() failed: %s. Audio input device %s might not be valid\n", pa_strerror(error), description); return -1; @@ -337,14 +285,6 @@ static int sound_device_setup_record(SoundDevice *device, const char *device_nam return 0; } -int sound_device_get_by_name(SoundDevice *device, const char *device_name, const char *description, unsigned int num_channels, unsigned int period_frame_size, AudioFormat audio_format) { - return sound_device_setup_record(device, device_name, description, num_channels, period_frame_size, audio_format, NULL); -} - -int sound_device_create_combined_sink_connect(SoundDevice *device, const char *combined_sink_name, unsigned int num_channels, unsigned int period_frame_size, AudioFormat audio_format) { - return sound_device_setup_record(device, "gpu-screen-recorder", "gpu-screen-recorder", num_channels, period_frame_size, audio_format, combined_sink_name); -} - void sound_device_close(SoundDevice *device) { if(device->handle) pa_sound_device_free((pa_handle*)device->handle); |