2 #include <ossia/detail/config.hpp>
4 #if defined(OSSIA_ENABLE_PIPEWIRE)
5 #if __has_include(<pipewire/pipewire.h>) && __has_include(<spa/param/latency-utils.h>)
6 #define OSSIA_AUDIO_PIPEWIRE 1
7 #include <ossia/audio/audio_engine.hpp>
8 #include <ossia/detail/dylib_loader.hpp>
9 #include <ossia/detail/hash_map.hpp>
11 #include <ossia/detail/thread.hpp>
13 #include <pipewire/core.h>
14 #include <pipewire/filter.h>
15 #include <pipewire/pipewire.h>
16 #include <spa/pod/builder.h>
17 #include <spa/utils/result.h>
26 #include <spa/param/latency-utils.h>
33 decltype(&::pw_init) init{};
34 decltype(&::pw_deinit) deinit{};
36 decltype(&::pw_context_new) context_new{};
37 decltype(&::pw_context_connect) context_connect{};
38 decltype(&::pw_context_destroy) context_destroy{};
40 decltype(&::pw_core_disconnect) core_disconnect{};
42 decltype(&::pw_proxy_add_listener) proxy_add_listener{};
43 decltype(&::pw_proxy_destroy) proxy_destroy{};
45 decltype(&::pw_main_loop_new) main_loop_new{};
46 decltype(&::pw_main_loop_destroy) main_loop_destroy{};
47 decltype(&::pw_main_loop_quit) main_loop_quit{};
48 decltype(&::pw_main_loop_run) main_loop_run{};
49 decltype(&::pw_main_loop_get_loop) main_loop_get_loop{};
51 decltype(&::pw_properties_new) properties_new{};
52 decltype(&::pw_properties_free) properties_free{};
53 decltype(&::pw_properties_get) properties_get{};
55 decltype(&::pw_filter_new_simple) filter_new_simple{};
56 decltype(&::pw_filter_get_node_id) filter_get_node_id{};
57 decltype(&::pw_filter_get_properties) filter_get_properties{};
58 decltype(&::pw_filter_add_port) filter_add_port{};
59 decltype(&::pw_filter_destroy) filter_destroy{};
60 decltype(&::pw_filter_connect) filter_connect{};
61 decltype(&::pw_filter_get_dsp_buffer) filter_get_dsp_buffer{};
63 static const libpipewire& instance()
65 static const libpipewire
self;
73 : library(
"libpipewire-0.3.so.0")
78 init = library.symbol<decltype(&::pw_init)>(
"pw_init");
79 deinit = library.symbol<decltype(&::pw_deinit)>(
"pw_deinit");
81 context_new = library.symbol<decltype(&::pw_context_new)>(
"pw_context_new");
83 = library.symbol<decltype(&::pw_context_connect)>(
"pw_context_connect");
85 = library.symbol<decltype(&::pw_context_destroy)>(
"pw_context_destroy");
88 = library.symbol<decltype(&::pw_core_disconnect)>(
"pw_core_disconnect");
91 = library.symbol<decltype(&::pw_proxy_add_listener)>(
"pw_proxy_add_listener");
92 proxy_destroy = library.symbol<decltype(&::pw_proxy_destroy)>(
"pw_proxy_destroy");
94 main_loop_new = library.symbol<decltype(&::pw_main_loop_new)>(
"pw_main_loop_new");
96 = library.symbol<decltype(&::pw_main_loop_destroy)>(
"pw_main_loop_destroy");
97 main_loop_quit = library.symbol<decltype(&::pw_main_loop_quit)>(
"pw_main_loop_quit");
98 main_loop_run = library.symbol<decltype(&::pw_main_loop_run)>(
"pw_main_loop_run");
100 = library.symbol<decltype(&::pw_main_loop_get_loop)>(
"pw_main_loop_get_loop");
102 properties_new = library.symbol<decltype(&::pw_properties_new)>(
"pw_properties_new");
104 = library.symbol<decltype(&::pw_properties_free)>(
"pw_properties_free");
105 properties_get = library.symbol<decltype(&::pw_properties_get)>(
"pw_properties_get");
108 = library.symbol<decltype(&::pw_filter_new_simple)>(
"pw_filter_new_simple");
110 = library.symbol<decltype(&::pw_filter_get_node_id)>(
"pw_filter_get_node_id");
111 filter_get_properties = library.symbol<decltype(&::pw_filter_get_properties)>(
112 "pw_filter_get_properties");
114 = library.symbol<decltype(&::pw_filter_add_port)>(
"pw_filter_add_port");
115 filter_destroy = library.symbol<decltype(&::pw_filter_destroy)>(
"pw_filter_destroy");
116 filter_connect = library.symbol<decltype(&::pw_filter_connect)>(
"pw_filter_connect");
117 filter_get_dsp_buffer = library.symbol<decltype(&::pw_filter_get_dsp_buffer)>(
118 "pw_filter_get_dsp_buffer");
124 assert(context_connect);
125 assert(context_destroy);
127 assert(core_disconnect);
129 assert(proxy_destroy);
131 assert(main_loop_new);
132 assert(main_loop_destroy);
133 assert(main_loop_quit);
134 assert(main_loop_run);
135 assert(main_loop_get_loop);
137 assert(properties_new);
138 assert(properties_free);
139 assert(properties_get);
141 assert(filter_new_simple);
142 assert(filter_get_node_id);
143 assert(filter_get_properties);
144 assert(filter_add_port);
145 assert(filter_destroy);
146 assert(filter_connect);
147 assert(filter_get_dsp_buffer);
151 struct pipewire_context
153 pw_main_loop* main_loop{};
156 pw_context* context{};
159 pw_registry* registry{};
160 spa_hook registry_listener{};
166 std::unique_ptr<spa_hook> listener;
168 std::vector<listened_port> port_listener{};
175 std::string port_name;
176 std::string port_alias;
177 std::string object_path;
184 pw_direction direction{};
189 std::vector<port_info> inputs;
190 std::vector<port_info> outputs;
195 ossia::hash_map<uint32_t, node> physical_audio;
196 ossia::hash_map<uint32_t, node> physical_midi;
197 ossia::hash_map<uint32_t, node> software_audio;
198 ossia::hash_map<uint32_t, node> software_midi;
200 void for_each_port(
auto func)
202 for(
auto& map : {physical_audio, physical_midi, software_audio, software_midi})
204 for(
auto& [
id, node] : map)
206 for(
auto& port : node.inputs)
208 for(
auto& port : node.outputs)
214 void remove_port(uint32_t
id)
216 for(
auto map : {&physical_audio, &physical_midi, &software_audio, &software_midi})
218 for(
auto& [_, node] : *map)
220 ossia::remove_erase_if(
221 node.inputs, [
id](
const port_info& p) { return p.id == id; });
222 ossia::remove_erase_if(
223 node.outputs, [
id](
const port_info& p) { return p.id == id; });
231 const libpipewire& pw = libpipewire::instance();
232 explicit pipewire_context()
236 char* argv[] = {NULL};
240 this->main_loop = pw.main_loop_new(
nullptr);
247 this->lp = pw.main_loop_get_loop(this->main_loop);
250 ossia::logger().error(
"PipeWire: main_loop_get_loop failed!");
254 this->context = pw.context_new(lp,
nullptr, 0);
261 this->core = pw.context_connect(this->context,
nullptr, 0);
268 this->registry = pw_core_get_registry(this->core, PW_VERSION_REGISTRY, 0);
271 ossia::logger().error(
"PipeWire: core_get_registry failed!");
276 spa_zero(registry_listener);
277 static constexpr
const struct pw_port_events port_events = {
278 .version = PW_VERSION_PORT_EVENTS,
279 .info = [](
void* object,
281 info) { ((pipewire_context*)
object)->register_port(info); },
284 static constexpr
const struct pw_registry_events registry_events = {
285 .version = PW_VERSION_REGISTRY_EVENTS,
287 [](
void* object, uint32_t id, uint32_t ,
const char* type,
288 uint32_t ,
const struct spa_dict* ) {
289 pipewire_context&
self = *(pipewire_context*)
object;
292 if(strcmp(type, PW_TYPE_INTERFACE_Port) == 0)
295 = (pw_port*)pw_registry_bind(
self.registry,
id, type, PW_VERSION_PORT, 0);
296 self.port_listener.push_back({id, port, std::make_unique<spa_hook>()});
297 auto& l =
self.port_listener.back();
299 pw_port_add_listener(l.port, l.listener.get(), &port_events, &
self);
303 [](
void* object, uint32_t id) {
304 pipewire_context&
self = *(pipewire_context*)
object;
308 self.current_graph.remove_port(
id);
311 auto it = ossia::find_if(
312 self.port_listener, [&](
const listened_port& l) {
return l.id == id; });
313 if(it !=
self.port_listener.end())
315 libpipewire::instance().proxy_destroy((pw_proxy*)it->port);
316 self.port_listener.erase(it);
322 pw_registry_add_listener(
323 this->registry, &this->registry_listener, ®istry_events,
this);
338 spa_hook core_listener;
340 static constexpr
struct pw_core_events core_events = {
341 .version = PW_VERSION_CORE_EVENTS,
343 [](
void* object, uint32_t id,
int seq) {
344 auto&
self = *(pipewire_context*)
object;
345 if(
id == PW_ID_CORE && seq ==
self.pending)
348 libpipewire::instance().main_loop_quit(
self.main_loop);
353 spa_zero(core_listener);
354 pw_core_add_listener(core, &core_listener, &core_events,
this);
356 pending = pw_core_sync(core, PW_ID_CORE, 0);
359 pw.main_loop_run(this->main_loop);
361 spa_hook_remove(&core_listener);
364 pw_proxy* link_ports(uint32_t out_port, uint32_t in_port)
366 auto props = pw.properties_new(
367 PW_KEY_LINK_OUTPUT_PORT, std::to_string(out_port).c_str(),
368 PW_KEY_LINK_INPUT_PORT, std::to_string(in_port).c_str(),
nullptr);
370 auto proxy = (pw_proxy*)pw_core_create_object(
371 this->core,
"link-factory", PW_TYPE_INTERFACE_Link, PW_VERSION_LINK,
377 pw.properties_free(props);
382 pw.properties_free(props);
386 void register_port(
const pw_port_info* info)
388 const spa_dict_item* item{};
393 spa_dict_for_each(item, info->props)
395 std::string_view k{item->key}, v{item->value};
396 if(k ==
"format.dsp")
398 else if(k ==
"port.name")
400 else if(k ==
"port.alias")
402 else if(k ==
"object.path")
404 else if(k ==
"port.id")
406 else if(k ==
"node.id")
408 else if(k ==
"port.physical" && v ==
"true")
410 else if(k ==
"port.terminal" && v ==
"true")
412 else if(k ==
"port.monitor" && v ==
"true")
414 else if(k ==
"port.direction")
418 p.direction = pw_direction::SPA_DIRECTION_OUTPUT;
422 p.direction = pw_direction::SPA_DIRECTION_INPUT;
427 if(p.node_id.empty())
430 const auto nid = std::stoul(p.node_id);
433 if(p.format.find(
"audio") != p.format.npos)
435 if(p.direction == pw_direction::SPA_DIRECTION_OUTPUT)
436 this->current_graph.physical_audio[nid].outputs.push_back(std::move(p));
438 this->current_graph.physical_audio[nid].inputs.push_back(std::move(p));
440 else if(p.format.find(
"midi") != p.format.npos)
442 if(p.direction == pw_direction::SPA_DIRECTION_OUTPUT)
443 this->current_graph.physical_midi[nid].outputs.push_back(std::move(p));
445 this->current_graph.physical_midi[nid].inputs.push_back(std::move(p));
454 if(p.format.find(
"audio") != p.format.npos)
456 if(p.direction == pw_direction::SPA_DIRECTION_OUTPUT)
457 this->current_graph.software_audio[nid].outputs.push_back(std::move(p));
459 this->current_graph.software_audio[nid].inputs.push_back(std::move(p));
461 else if(p.format.find(
"midi") != p.format.npos)
463 if(p.direction == pw_direction::SPA_DIRECTION_OUTPUT)
464 this->current_graph.software_midi[nid].outputs.push_back(std::move(p));
466 this->current_graph.software_midi[nid].inputs.push_back(std::move(p));
475 int get_fd() const noexcept
480 auto spa_callbacks = this->lp->control->iface.cb;
481 auto spa_loop_methods = (
const spa_loop_control_methods*)spa_callbacks.funcs;
482 if(spa_loop_methods->get_fd)
483 return spa_loop_methods->get_fd(spa_callbacks.data);
491 pw.proxy_destroy((pw_proxy*)this->registry);
492 for(
auto& [
id, p, l] : this->port_listener)
494 pw.proxy_destroy((pw_proxy*)p);
496 pw.core_disconnect(this->core);
498 pw.context_destroy(this->context);
500 pw.main_loop_destroy(this->main_loop);
510 std::string card_out;
512 std::vector<std::string> inputs;
513 std::vector<std::string> outputs;
519 class pipewire_audio_protocol :
public audio_engine
526 std::shared_ptr<pipewire_context> loop{};
528 std::vector<pw_proxy*> links{};
530 explicit pipewire_audio_protocol(
531 std::shared_ptr<pipewire_context> loop,
const audio_setup& setup)
533 auto& pw = libpipewire::instance();
535 static constexpr
const struct pw_filter_events filter_events = {
536 .version = PW_VERSION_FILTER_EVENTS,
537 .process = on_process,
548 PW_KEY_MEDIA_TYPE,
"Audio",
549 PW_KEY_MEDIA_CATEGORY,
"Duplex",
550 PW_KEY_MEDIA_ROLE,
"DSP",
551 PW_KEY_MEDIA_NAME,
"ossia",
552 PW_KEY_NODE_LATENCY, fmt::format(
"{}/{}", setup.buffer_size, setup.rate).c_str(),
553 PW_KEY_NODE_FORCE_QUANTUM, fmt::format(
"{}", setup.buffer_size).c_str(),
554 PW_KEY_NODE_LOCK_QUANTUM,
"true",
555 PW_KEY_NODE_RATE, fmt::format(
"{}/{}", 1, setup.rate).c_str(),
556 PW_KEY_NODE_FORCE_RATE, fmt::format(
"{}/{}", 1, setup.rate).c_str(),
557 PW_KEY_NODE_LOCK_RATE,
"true",
558 PW_KEY_NODE_ALWAYS_PROCESS,
"true",
559 PW_KEY_NODE_PAUSE_ON_IDLE,
"false",
560 PW_KEY_NODE_SUSPEND_ON_IDLE,
"false",
564 this->filter = pw.filter_new_simple(
565 lp, setup.name.c_str(), filter_props, &filter_events,
this);
568 throw std::runtime_error(
"PipeWire: could not create filter instance");
572 for(std::size_t i = 0; i < setup.inputs.size(); i++)
574 auto p = (port*)pw.filter_add_port(
575 this->filter, PW_DIRECTION_INPUT, PW_FILTER_PORT_FLAG_MAP_BUFFERS,
578 PW_KEY_FORMAT_DSP,
"32 bit float mono audio", PW_KEY_PORT_NAME,
579 setup.inputs[i].c_str(), NULL),
581 input_ports.push_back(p);
584 for(std::size_t i = 0; i < setup.outputs.size(); i++)
586 auto p = (port*)pw.filter_add_port(
587 this->filter, PW_DIRECTION_OUTPUT, PW_FILTER_PORT_FLAG_MAP_BUFFERS,
590 PW_KEY_FORMAT_DSP,
"32 bit float mono audio", PW_KEY_PORT_NAME,
591 setup.outputs[i].c_str(), NULL),
593 output_ports.push_back(p);
596 if(pw.filter_connect(this->filter, PW_FILTER_FLAG_RT_PROCESS,
nullptr, 0) < 0)
598 throw std::runtime_error(
"PipeWire: cannot connect");
602 this->loop->synchronize();
605 auto node_id = filter_node_id();
606 while(node_id == 4294967295)
608 this->loop->synchronize();
609 node_id = filter_node_id();
617 const auto num_local_ins = this->input_ports.size();
618 const auto num_local_outs = this->output_ports.size();
619 auto& this_node = this->loop->current_graph.software_audio[node_id];
620 while(this_node.inputs.size() < num_local_ins
621 || this_node.outputs.size() < num_local_outs)
623 this->loop->synchronize();
630 this->effective_buffer_size = setup.buffer_size;
631 this->effective_sample_rate = setup.rate;
632 this->effective_inputs = setup.inputs.size();
633 this->effective_outputs = setup.outputs.size();
636 uint32_t filter_node_id() {
return this->loop->pw.filter_get_node_id(this->filter); }
640 auto node_id = filter_node_id();
642 std::vector<std::pair<std::optional<uint32_t>, std::optional<uint32_t>>>
644 std::vector<std::pair<std::optional<uint32_t>, std::optional<uint32_t>>>
648 for(
auto& [node, ports] : loop->current_graph.physical_audio)
650 auto& [out, in] = ports;
655 phys_in_to_ossia.emplace_back(port.id, std::nullopt);
659 for(
auto& port : out)
661 ossia_to_phys_out.emplace_back(std::nullopt, port.id);
666 for(
auto& [node, ports] : loop->current_graph.software_audio)
670 auto& [in, out] = ports;
673 for(std::size_t i = 0; i < in.size(); i++)
675 if(i >= phys_in_to_ossia.size())
678 phys_in_to_ossia[i].second = in[i].id;
682 for(std::size_t i = 0; i < out.size(); i++)
684 if(i >= ossia_to_phys_out.size())
687 ossia_to_phys_out[i].first = out[i].id;
694 for(
auto [phys,
self] : phys_in_to_ossia)
698 if(
auto link = this->loop->link_ports(*phys, *
self))
699 this->links.push_back(link);
707 for(
auto [
self, phys] : ossia_to_phys_out)
711 if(
auto link = this->loop->link_ports(*
self, *phys))
712 this->links.push_back(link);
721 void wait(
int ms)
override
726 using namespace std::chrono;
727 using clk = high_resolution_clock;
729 auto t0 = clk::now();
730 auto t1 = clk::now();
731 while(duration_cast<milliseconds>(t1 - t0).count() < ms)
733 pw_loop_iterate(loop->lp, ms);
738 bool running()
const override
745 ~pipewire_audio_protocol()
747 auto& pw = libpipewire::instance();
749 for(
auto link : this->links)
750 pw.proxy_destroy(link);
752 pw.filter_destroy(this->filter);
756 clear_buffers(pipewire_audio_protocol&
self, uint32_t nframes, std::size_t outputs)
758 auto& pw = libpipewire::instance();
759 for(std::size_t i = 0; i < outputs; i++)
761 auto chan = (
float*)pw.filter_get_dsp_buffer(
self.output_ports[i], nframes);
763 for(std::size_t j = 0; j < nframes; j++)
770 static void on_process(
void* userdata,
struct spa_io_position* position)
772 static const thread_local
auto _ = [] {
773 ossia::set_thread_name(
"ossia audio 0");
774 ossia::set_thread_pinned(thread_type::Audio, 0);
781 const auto& pw = libpipewire::instance();
782 auto&
self = *(pipewire_audio_protocol*)userdata;
783 const uint32_t nframes = position->clock.duration;
787 const auto inputs =
self.input_ports.size();
788 const auto outputs =
self.output_ports.size();
789 if(
self.stop_processing)
792 clear_buffers(
self, nframes, outputs);
796 auto dummy = (
float*)alloca(
sizeof(
float) * nframes);
797 memset(dummy, 0,
sizeof(
float) * nframes);
799 auto float_input = (
float**)alloca(
sizeof(
float*) * inputs);
800 auto float_output = (
float**)alloca(
sizeof(
float*) * outputs);
801 for(std::size_t i = 0; i < inputs; i++)
803 float_input[i] = (
float*)pw.filter_get_dsp_buffer(
self.input_ports[i], nframes);
804 if(float_input[i] ==
nullptr)
805 float_input[i] = dummy;
807 for(std::size_t i = 0; i < outputs; i++)
809 float_output[i] = (
float*)pw.filter_get_dsp_buffer(
self.output_ports[i], nframes);
810 if(float_output[i] ==
nullptr)
811 float_output[i] = dummy;
815 ossia::audio_tick_state ts{float_input, float_output, (int)inputs,
816 (
int)outputs, nframes, position->clock.nsec * 1e-9};
821 std::vector<port*> input_ports;
822 std::vector<port*> output_ports;
spdlog::logger & logger() noexcept
Where the errors will be logged. Default is stderr.
Definition: context.cpp:104