OSSIA
Open Scenario System for Interactive Application
Loading...
Searching...
No Matches
pipewire_protocol.hpp
1#pragma once
2#include <ossia/detail/config.hpp>
3
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>
12
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>
18
19#include <cmath>
20
21#include <cassert>
22#include <cerrno>
23#include <cstdio>
24#include <stdexcept>
25
26#include <spa/param/latency-utils.h>
27
28namespace ossia
29{
30class libpipewire
31{
32public:
33 decltype(&::pw_init) init{};
34 decltype(&::pw_deinit) deinit{};
35
36 decltype(&::pw_context_new) context_new{};
37 decltype(&::pw_context_connect) context_connect{};
38 decltype(&::pw_context_destroy) context_destroy{};
39
40 decltype(&::pw_core_disconnect) core_disconnect{};
41
42 decltype(&::pw_proxy_add_listener) proxy_add_listener{};
43 decltype(&::pw_proxy_destroy) proxy_destroy{};
44
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{};
50
51 decltype(&::pw_properties_new) properties_new{};
52 decltype(&::pw_properties_free) properties_free{};
53 decltype(&::pw_properties_get) properties_get{};
54
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{};
62
63 static const libpipewire& instance()
64 {
65 static const libpipewire self;
66 return self;
67 }
68
69private:
70 dylib_loader library;
71
72 libpipewire()
73 : library("libpipewire-0.3.so.0")
74 {
75 // in terms of regex:
76 // decltype\‍(&::([a-z_]+)\‍) [a-z_]+{};
77 // \1 = library.symbol<decltype(&::\1)>("\1");
78 init = library.symbol<decltype(&::pw_init)>("pw_init");
79 deinit = library.symbol<decltype(&::pw_deinit)>("pw_deinit");
80
81 context_new = library.symbol<decltype(&::pw_context_new)>("pw_context_new");
82 context_connect
83 = library.symbol<decltype(&::pw_context_connect)>("pw_context_connect");
84 context_destroy
85 = library.symbol<decltype(&::pw_context_destroy)>("pw_context_destroy");
86
87 core_disconnect
88 = library.symbol<decltype(&::pw_core_disconnect)>("pw_core_disconnect");
89
90 proxy_add_listener
91 = library.symbol<decltype(&::pw_proxy_add_listener)>("pw_proxy_add_listener");
92 proxy_destroy = library.symbol<decltype(&::pw_proxy_destroy)>("pw_proxy_destroy");
93
94 main_loop_new = library.symbol<decltype(&::pw_main_loop_new)>("pw_main_loop_new");
95 main_loop_destroy
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");
99 main_loop_get_loop
100 = library.symbol<decltype(&::pw_main_loop_get_loop)>("pw_main_loop_get_loop");
101
102 properties_new = library.symbol<decltype(&::pw_properties_new)>("pw_properties_new");
103 properties_free
104 = library.symbol<decltype(&::pw_properties_free)>("pw_properties_free");
105 properties_get = library.symbol<decltype(&::pw_properties_get)>("pw_properties_get");
106
107 filter_new_simple
108 = library.symbol<decltype(&::pw_filter_new_simple)>("pw_filter_new_simple");
109 filter_get_node_id
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");
113 filter_add_port
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");
119
120 assert(init);
121 assert(deinit);
122
123 assert(context_new);
124 assert(context_connect);
125 assert(context_destroy);
126
127 assert(core_disconnect);
128
129 assert(proxy_destroy);
130
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);
136
137 assert(properties_new);
138 assert(properties_free);
139 assert(properties_get);
140
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);
148 }
149};
150
151struct pipewire_context
152{
153 pw_main_loop* main_loop{};
154 pw_loop* lp{};
155
156 pw_context* context{};
157 pw_core* core{};
158
159 pw_registry* registry{};
160 spa_hook registry_listener{};
161
162 struct listened_port
163 {
164 uint32_t id{};
165 pw_port* port{};
166 std::unique_ptr<spa_hook> listener;
167 };
168 std::vector<listened_port> port_listener{};
169
170 struct port_info
171 {
172 uint32_t id{};
173
174 std::string format;
175 std::string port_name;
176 std::string port_alias;
177 std::string object_path;
178 std::string node_id;
179 std::string port_id;
180
181 bool physical{};
182 bool terminal{};
183 bool monitor{};
184 pw_direction direction{};
185 };
186
187 struct node
188 {
189 std::vector<port_info> inputs;
190 std::vector<port_info> outputs;
191 };
192
193 struct graph
194 {
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;
199
200 void for_each_port(auto func)
201 {
202 for(auto& map : {physical_audio, physical_midi, software_audio, software_midi})
203 {
204 for(auto& [id, node] : map)
205 {
206 for(auto& port : node.inputs)
207 func(port);
208 for(auto& port : node.outputs)
209 func(port);
210 }
211 }
212 }
213
214 void remove_port(uint32_t id)
215 {
216 for(auto map : {&physical_audio, &physical_midi, &software_audio, &software_midi})
217 {
218 for(auto& [_, node] : *map)
219 {
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; });
224 }
225 }
226 }
227 } current_graph;
228
229 int sync{};
230
231 const libpipewire& pw = libpipewire::instance();
232 explicit pipewire_context()
233 {
235 int argc = 0;
236 char* argv[] = {NULL};
237 char** aa = argv;
238 pw.init(&argc, &aa);
239
240 this->main_loop = pw.main_loop_new(nullptr);
241 if(!this->main_loop)
242 {
243 ossia::logger().error("PipeWire: main_loop_new failed!");
244 return;
245 }
246
247 this->lp = pw.main_loop_get_loop(this->main_loop);
248 if(!lp)
249 {
250 ossia::logger().error("PipeWire: main_loop_get_loop failed!");
251 return;
252 }
253
254 this->context = pw.context_new(lp, nullptr, 0);
255 if(!this->context)
256 {
257 ossia::logger().error("PipeWire: context_new failed!");
258 return;
259 }
260
261 this->core = pw.context_connect(this->context, nullptr, 0);
262 if(!this->core)
263 {
264 ossia::logger().error("PipeWire: context_connect failed!");
265 return;
266 }
267
268 this->registry = pw_core_get_registry(this->core, PW_VERSION_REGISTRY, 0);
269 if(!this->registry)
270 {
271 ossia::logger().error("PipeWire: core_get_registry failed!");
272 return;
273 }
274
275 // Register a listener which will listen on when ports are added / removed
276 spa_zero(registry_listener);
277 static constexpr const struct pw_port_events port_events
278 = {.version = PW_VERSION_PORT_EVENTS,
279 .info =
280 [](void* object, const pw_port_info* info) {
281 ((pipewire_context*)object)->register_port(info);
282 },
283 .param = {}};
284
285 static constexpr const struct pw_registry_events registry_events = {
286 .version = PW_VERSION_REGISTRY_EVENTS,
287 .global =
288 [](void* object, uint32_t id, uint32_t /*permissions*/, const char* type,
289 uint32_t /*version*/, const struct spa_dict* /*props*/) {
290 pipewire_context& self = *(pipewire_context*)object;
291
292 // When a port is added:
293 if(strcmp(type, PW_TYPE_INTERFACE_Port) == 0)
294 {
295 auto port
296 = (pw_port*)pw_registry_bind(self.registry, id, type, PW_VERSION_PORT, 0);
297 self.port_listener.push_back({id, port, std::make_unique<spa_hook>()});
298 auto& l = self.port_listener.back();
299
300 pw_port_add_listener(l.port, l.listener.get(), &port_events, &self);
301 }
302 },
303 .global_remove =
304 [](void* object, uint32_t id) {
305 pipewire_context& self = *(pipewire_context*)object;
306
307 // When a port is removed:
308 // Remove from the graph
309 self.current_graph.remove_port(id);
310
311 // Remove from the listeners
312 auto it = ossia::find_if(
313 self.port_listener, [&](const listened_port& l) { return l.id == id; });
314 if(it != self.port_listener.end())
315 {
316 libpipewire::instance().proxy_destroy((pw_proxy*)it->port);
317 self.port_listener.erase(it);
318 }
319 },
320 };
321
322 // Start listening
323 pw_registry_add_listener(
324 this->registry, &this->registry_listener, &registry_events, this);
325
326 synchronize();
327 }
328
329 int pending{};
330 int done{};
331 void synchronize()
332 {
333 pending = 0;
334 done = 0;
335
336 if(!core)
337 return;
338
339 spa_hook core_listener;
340
341 static constexpr struct pw_core_events core_events = {
342 .version = PW_VERSION_CORE_EVENTS,
343 .info = {},
344 .done =
345 [](void* object, uint32_t id, int seq) {
346 auto& self = *(pipewire_context*)object;
347 if(id == PW_ID_CORE && seq == self.pending)
348 {
349 self.done = 1;
350 libpipewire::instance().main_loop_quit(self.main_loop);
351 }
352 },
353 .ping = {},
354 .error = {},
355 .remove_id = {},
356 .bound_id = {},
357 .add_mem = {},
358 .remove_mem = {},
359#if defined(PW_CORE_EVENT_BOUND_PROPS)
360 .bound_props = {},
361#endif
362 };
363
364 spa_zero(core_listener);
365 pw_core_add_listener(core, &core_listener, &core_events, this);
366
367 pending = pw_core_sync(core, PW_ID_CORE, 0);
368 while(!done)
369 {
370 pw.main_loop_run(this->main_loop);
371 }
372 spa_hook_remove(&core_listener);
373 }
374
375 pw_proxy* link_ports(uint32_t out_port, uint32_t in_port)
376 {
377 auto props = pw.properties_new(
378 PW_KEY_LINK_OUTPUT_PORT, std::to_string(out_port).c_str(),
379 PW_KEY_LINK_INPUT_PORT, std::to_string(in_port).c_str(), nullptr);
380
381 auto proxy = (pw_proxy*)pw_core_create_object(
382 this->core, "link-factory", PW_TYPE_INTERFACE_Link, PW_VERSION_LINK,
383 &props->dict, 0);
384
385 if(!proxy)
386 {
387 ossia::logger().error("PipeWire: could not allocate link");
388 pw.properties_free(props);
389 return nullptr;
390 }
391
392 synchronize();
393 pw.properties_free(props);
394 return proxy;
395 }
396
397 void register_port(const pw_port_info* info)
398 {
399 const spa_dict_item* item{};
400
401 port_info p;
402 p.id = info->id;
403
404 spa_dict_for_each(item, info->props)
405 {
406 std::string_view k{item->key}, v{item->value};
407 if(k == "format.dsp")
408 p.format = v;
409 else if(k == "port.name")
410 p.port_name = v;
411 else if(k == "port.alias")
412 p.port_alias = v;
413 else if(k == "object.path")
414 p.object_path = v;
415 else if(k == "port.id")
416 p.port_id = v;
417 else if(k == "node.id")
418 p.node_id = v;
419 else if(k == "port.physical" && v == "true")
420 p.physical = true;
421 else if(k == "port.terminal" && v == "true")
422 p.terminal = true;
423 else if(k == "port.monitor" && v == "true")
424 p.monitor = true;
425 else if(k == "port.direction")
426 {
427 if(v == "out")
428 {
429 p.direction = pw_direction::SPA_DIRECTION_OUTPUT;
430 }
431 else
432 {
433 p.direction = pw_direction::SPA_DIRECTION_INPUT;
434 }
435 }
436 }
437
438 if(p.node_id.empty())
439 return;
440
441 const auto nid = std::stoul(p.node_id);
442 if(p.physical)
443 {
444 if(p.format.find("audio") != p.format.npos)
445 {
446 if(p.direction == pw_direction::SPA_DIRECTION_OUTPUT)
447 this->current_graph.physical_audio[nid].outputs.push_back(std::move(p));
448 else
449 this->current_graph.physical_audio[nid].inputs.push_back(std::move(p));
450 }
451 else if(p.format.find("midi") != p.format.npos)
452 {
453 if(p.direction == pw_direction::SPA_DIRECTION_OUTPUT)
454 this->current_graph.physical_midi[nid].outputs.push_back(std::move(p));
455 else
456 this->current_graph.physical_midi[nid].inputs.push_back(std::move(p));
457 }
458 else
459 {
460 // TODO, video ?
461 }
462 }
463 else
464 {
465 if(p.format.find("audio") != p.format.npos)
466 {
467 if(p.direction == pw_direction::SPA_DIRECTION_OUTPUT)
468 this->current_graph.software_audio[nid].outputs.push_back(std::move(p));
469 else
470 this->current_graph.software_audio[nid].inputs.push_back(std::move(p));
471 }
472 else if(p.format.find("midi") != p.format.npos)
473 {
474 if(p.direction == pw_direction::SPA_DIRECTION_OUTPUT)
475 this->current_graph.software_midi[nid].outputs.push_back(std::move(p));
476 else
477 this->current_graph.software_midi[nid].inputs.push_back(std::move(p));
478 }
479 else
480 {
481 // TODO, video ?
482 }
483 }
484 }
485
486 int get_fd() const noexcept
487 {
488 if(!this->lp)
489 return -1;
490
491 auto spa_callbacks = this->lp->control->iface.cb;
492 auto spa_loop_methods = (const spa_loop_control_methods*)spa_callbacks.funcs;
493 if(spa_loop_methods->get_fd)
494 return spa_loop_methods->get_fd(spa_callbacks.data);
495 else
496 return -1;
497 }
498
499 ~pipewire_context()
500 {
501 if(this->registry)
502 pw.proxy_destroy((pw_proxy*)this->registry);
503 for(auto& [id, p, l] : this->port_listener)
504 if(l)
505 pw.proxy_destroy((pw_proxy*)p);
506 if(this->core)
507 pw.core_disconnect(this->core);
508 if(this->context)
509 pw.context_destroy(this->context);
510 if(this->main_loop)
511 pw.main_loop_destroy(this->main_loop);
512
513 pw.deinit();
514 }
515};
516
517struct audio_setup
518{
519 std::string name;
520 std::string card_in;
521 std::string card_out;
522
523 std::vector<std::string> inputs;
524 std::vector<std::string> outputs;
525
526 int rate{};
527 int buffer_size{};
528};
529
530class pipewire_audio_protocol : public audio_engine
531{
532public:
533 struct port
534 {
535 };
536
537 std::shared_ptr<pipewire_context> loop{};
538 pw_filter* filter{};
539 std::vector<pw_proxy*> links{};
540
541 explicit pipewire_audio_protocol(
542 std::shared_ptr<pipewire_context> loop, const audio_setup& setup)
543 {
544 auto& pw = libpipewire::instance();
545
546 static constexpr const struct pw_filter_events filter_events = {
547 .version = PW_VERSION_FILTER_EVENTS,
548 .destroy = {},
549 .state_changed = {},
550 .io_changed = {},
551 .param_changed = {},
552 .add_buffer = {},
553 .remove_buffer = {},
554 .process = on_process,
555 .drained = {},
556#if PW_VERSION_CORE > 3
557 .command = {},
558#endif
559 };
560
561 this->loop = loop;
562
563 auto lp = loop->lp;
564 // clang-format off
565 // Create the filter (the main pipewire object which will represent the
566 // software)
567 auto filter_props{
568 pw.properties_new(
569 PW_KEY_MEDIA_TYPE, "Audio",
570 PW_KEY_MEDIA_CATEGORY, "Duplex",
571 PW_KEY_MEDIA_ROLE, "DSP",
572 PW_KEY_MEDIA_NAME, "ossia",
573 PW_KEY_NODE_NAME, "ossia",
574 PW_KEY_NODE_GROUP, "group.dsp.0",
575 PW_KEY_NODE_DESCRIPTION, "ossia score",
576 PW_KEY_NODE_LATENCY, fmt::format("{}/{}", setup.buffer_size, setup.rate).c_str(),
577 PW_KEY_NODE_FORCE_QUANTUM, fmt::format("{}", setup.buffer_size).c_str(),
578 PW_KEY_NODE_LOCK_QUANTUM, "true",
579 PW_KEY_NODE_TRANSPORT_SYNC, "true",
580 //PW_KEY_NODE_RATE, fmt::format("{}/{}", 1, setup.rate).c_str(),
581 PW_KEY_NODE_FORCE_RATE, fmt::format("{}", setup.rate).c_str(),
582 //PW_KEY_NODE_LOCK_RATE, "true",
583 //PW_KEY_NODE_ALWAYS_PROCESS, "true",
584 PW_KEY_NODE_PAUSE_ON_IDLE, "false",
585 PW_KEY_NODE_SUSPEND_ON_IDLE, "false",
586 nullptr)};
587
588 // clang-format on
589 this->filter = pw.filter_new_simple(
590 lp, setup.name.c_str(), filter_props, &filter_events, this);
591 if(!this->filter)
592 {
593 throw std::runtime_error("PipeWire: could not create filter instance");
594 }
595
596 // Create the request ports
597 for(std::size_t i = 0; i < setup.inputs.size(); i++)
598 {
599 auto p = (port*)pw.filter_add_port(
600 this->filter, PW_DIRECTION_INPUT, PW_FILTER_PORT_FLAG_MAP_BUFFERS,
601 sizeof(struct port),
602 pw.properties_new(
603 PW_KEY_FORMAT_DSP, "32 bit float mono audio", PW_KEY_PORT_NAME,
604 setup.inputs[i].c_str(), NULL),
605 NULL, 0);
606 input_ports.push_back(p);
607 }
608
609 for(std::size_t i = 0; i < setup.outputs.size(); i++)
610 {
611 auto p = (port*)pw.filter_add_port(
612 this->filter, PW_DIRECTION_OUTPUT, PW_FILTER_PORT_FLAG_MAP_BUFFERS,
613 sizeof(struct port),
614 pw.properties_new(
615 PW_KEY_FORMAT_DSP, "32 bit float mono audio", PW_KEY_PORT_NAME,
616 setup.outputs[i].c_str(), NULL),
617 NULL, 0);
618 output_ports.push_back(p);
619 }
620
621 if(pw.filter_connect(this->filter, PW_FILTER_FLAG_RT_PROCESS, nullptr, 0) < 0)
622 {
623 throw std::runtime_error("PipeWire: cannot connect");
624 }
625
626 // Wait until everything is registered with PipeWire
627 this->loop->synchronize();
628 {
629 int k = 0;
630 auto node_id = filter_node_id();
631 while(node_id == 4294967295)
632 {
633 this->loop->synchronize();
634 node_id = filter_node_id();
635
636 if(k++; k > 100)
637 return;
638 }
639
640 // Leave some time to resolve the ports
641 k = 0;
642 const auto num_local_ins = this->input_ports.size();
643 const auto num_local_outs = this->output_ports.size();
644 auto& this_node = this->loop->current_graph.software_audio[node_id];
645 while(this_node.inputs.size() < num_local_ins
646 || this_node.outputs.size() < num_local_outs)
647 {
648 this->loop->synchronize();
649 if(k++; k > 100)
650 return;
651 }
652 }
653
654 activated = true;
655 this->effective_buffer_size = setup.buffer_size;
656 this->effective_sample_rate = setup.rate;
657 this->effective_inputs = setup.inputs.size();
658 this->effective_outputs = setup.outputs.size();
659 }
660
661 uint32_t filter_node_id() { return this->loop->pw.filter_get_node_id(this->filter); }
662
663 void autoconnect()
664 {
665 auto node_id = filter_node_id();
666
667 std::vector<std::pair<std::optional<uint32_t>, std::optional<uint32_t>>>
668 phys_in_to_ossia;
669 std::vector<std::pair<std::optional<uint32_t>, std::optional<uint32_t>>>
670 ossia_to_phys_out;
671
672 // Link to the first physical soundcard we see
673 for(auto& [node, ports] : loop->current_graph.physical_audio)
674 {
675 auto& [out, in] = ports;
676
677 // The soundcard outputs are input ports
678 for(auto& port : in)
679 {
680 phys_in_to_ossia.emplace_back(port.id, std::nullopt);
681 }
682
683 // The soundcard inputs are output ports
684 for(auto& port : out)
685 {
686 ossia_to_phys_out.emplace_back(std::nullopt, port.id);
687 }
688 }
689
690 // Enumerate our matching local ports
691 for(auto& [node, ports] : loop->current_graph.software_audio)
692 {
693 if(node == node_id)
694 {
695 auto& [in, out] = ports;
696
697 // Connect our inputs to the soundcard inputs
698 for(std::size_t i = 0; i < in.size(); i++)
699 {
700 if(i >= phys_in_to_ossia.size())
701 break;
702
703 phys_in_to_ossia[i].second = in[i].id;
704 }
705
706 // Connect our outputs to the soundcard inputs
707 for(std::size_t i = 0; i < out.size(); i++)
708 {
709 if(i >= ossia_to_phys_out.size())
710 break;
711
712 ossia_to_phys_out[i].first = out[i].id;
713 }
714 break;
715 }
716 }
717
718 // Connect as much as we can
719 for(auto [phys, self] : phys_in_to_ossia)
720 {
721 if(phys && self)
722 {
723 if(auto link = this->loop->link_ports(*phys, *self))
724 this->links.push_back(link);
725 }
726 else
727 {
728 break;
729 }
730 }
731
732 for(auto [self, phys] : ossia_to_phys_out)
733 {
734 if(self && phys)
735 {
736 if(auto link = this->loop->link_ports(*self, *phys))
737 this->links.push_back(link);
738 }
739 else
740 {
741 break;
742 }
743 }
744 }
745
746 void wait(int ms) override
747 {
748 if(!loop)
749 return;
750
751 using namespace std::chrono;
752 using clk = high_resolution_clock;
753
754 auto t0 = clk::now();
755 auto t1 = clk::now();
756 while(duration_cast<milliseconds>(t1 - t0).count() < ms)
757 {
758 pw_loop_iterate(loop->lp, ms);
759 t1 = clk::now();
760 }
761 }
762
763 bool running() const override
764 {
765 if(!this->loop)
766 return false;
767 return activated;
768 }
769
770 ~pipewire_audio_protocol()
771 {
772 auto& pw = libpipewire::instance();
773
774 for(auto link : this->links)
775 pw.proxy_destroy(link);
776
777 pw.filter_destroy(this->filter);
778
779 loop->synchronize();
780 }
781
782 static void
783 clear_buffers(pipewire_audio_protocol& self, uint32_t nframes, std::size_t outputs)
784 {
785 auto& pw = libpipewire::instance();
786 for(std::size_t i = 0; i < outputs; i++)
787 {
788 auto chan = (float*)pw.filter_get_dsp_buffer(self.output_ports[i], nframes);
789 if(chan)
790 for(std::size_t j = 0; j < nframes; j++)
791 chan[j] = 0.f;
792 }
793
794 return;
795 }
796
797 static void on_process(void* userdata, struct spa_io_position* position)
798 {
799 [[maybe_unused]]
800 static const thread_local auto _
801 = [] {
802 ossia::set_thread_name("ossia audio 0");
803 ossia::set_thread_pinned(thread_type::Audio, 0);
804 return 0;
805 }();
806
807 if(!userdata)
808 return;
809
810 const auto& pw = libpipewire::instance();
811 auto& self = *(pipewire_audio_protocol*)userdata;
812 const uint32_t nframes = position->clock.duration;
813
814 self.tick_start();
815
816 const auto inputs = self.input_ports.size();
817 const auto outputs = self.output_ports.size();
818 if(self.stop_processing)
819 {
820 self.tick_clear();
821 clear_buffers(self, nframes, outputs);
822 return;
823 }
824
825 auto dummy = (float*)alloca(sizeof(float) * nframes);
826 memset(dummy, 0, sizeof(float) * nframes);
827
828 auto float_input = (float**)alloca(sizeof(float*) * inputs);
829 auto float_output = (float**)alloca(sizeof(float*) * outputs);
830 for(std::size_t i = 0; i < inputs; i++)
831 {
832 float_input[i] = (float*)pw.filter_get_dsp_buffer(self.input_ports[i], nframes);
833 if(float_input[i] == nullptr)
834 float_input[i] = dummy;
835 }
836 for(std::size_t i = 0; i < outputs; i++)
837 {
838 float_output[i] = (float*)pw.filter_get_dsp_buffer(self.output_ports[i], nframes);
839 if(float_output[i] == nullptr)
840 float_output[i] = dummy;
841 }
842
843 // Actual execution
844 ossia::audio_tick_state ts{float_input, float_output, (int)inputs,
845 (int)outputs, nframes, position->clock.nsec * 1e-9};
846 self.audio_tick(ts);
847 self.tick_end();
848 }
849
850 std::vector<port*> input_ports;
851 std::vector<port*> output_ports;
852 bool activated{};
853};
854
855}
856#endif
857#endif
Definition git_info.h:7
spdlog::logger & logger() noexcept
Where the errors will be logged. Default is stderr.
Definition context.cpp:118