OSSIA
Open Scenario System for Interactive Application
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>
10 #include <ossia/detail/logger.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 
28 namespace ossia
29 {
30 class libpipewire
31 {
32 public:
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 
69 private:
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 
151 struct 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 = [](void* object,
280  const pw_port_info*
281  info) { ((pipewire_context*)object)->register_port(info); },
282  };
283 
284  static constexpr const struct pw_registry_events registry_events = {
285  .version = PW_VERSION_REGISTRY_EVENTS,
286  .global =
287  [](void* object, uint32_t id, uint32_t /*permissions*/, const char* type,
288  uint32_t /*version*/, const struct spa_dict* /*props*/) {
289  pipewire_context& self = *(pipewire_context*)object;
290 
291  // When a port is added:
292  if(strcmp(type, PW_TYPE_INTERFACE_Port) == 0)
293  {
294  auto port
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();
298 
299  pw_port_add_listener(l.port, l.listener.get(), &port_events, &self);
300  }
301  },
302  .global_remove =
303  [](void* object, uint32_t id) {
304  pipewire_context& self = *(pipewire_context*)object;
305 
306  // When a port is removed:
307  // Remove from the graph
308  self.current_graph.remove_port(id);
309 
310  // Remove from the listeners
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())
314  {
315  libpipewire::instance().proxy_destroy((pw_proxy*)it->port);
316  self.port_listener.erase(it);
317  }
318  },
319  };
320 
321  // Start listening
322  pw_registry_add_listener(
323  this->registry, &this->registry_listener, &registry_events, this);
324 
325  synchronize();
326  }
327 
328  int pending{};
329  int done{};
330  void synchronize()
331  {
332  pending = 0;
333  done = 0;
334 
335  if(!core)
336  return;
337 
338  spa_hook core_listener;
339 
340  static constexpr struct pw_core_events core_events = {
341  .version = PW_VERSION_CORE_EVENTS,
342  .done =
343  [](void* object, uint32_t id, int seq) {
344  auto& self = *(pipewire_context*)object;
345  if(id == PW_ID_CORE && seq == self.pending)
346  {
347  self.done = 1;
348  libpipewire::instance().main_loop_quit(self.main_loop);
349  }
350  },
351  };
352 
353  spa_zero(core_listener);
354  pw_core_add_listener(core, &core_listener, &core_events, this);
355 
356  pending = pw_core_sync(core, PW_ID_CORE, 0);
357  while(!done)
358  {
359  pw.main_loop_run(this->main_loop);
360  }
361  spa_hook_remove(&core_listener);
362  }
363 
364  pw_proxy* link_ports(uint32_t out_port, uint32_t in_port)
365  {
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);
369 
370  auto proxy = (pw_proxy*)pw_core_create_object(
371  this->core, "link-factory", PW_TYPE_INTERFACE_Link, PW_VERSION_LINK,
372  &props->dict, 0);
373 
374  if(!proxy)
375  {
376  ossia::logger().error("PipeWire: could not allocate link");
377  pw.properties_free(props);
378  return nullptr;
379  }
380 
381  synchronize();
382  pw.properties_free(props);
383  return proxy;
384  }
385 
386  void register_port(const pw_port_info* info)
387  {
388  const spa_dict_item* item{};
389 
390  port_info p;
391  p.id = info->id;
392 
393  spa_dict_for_each(item, info->props)
394  {
395  std::string_view k{item->key}, v{item->value};
396  if(k == "format.dsp")
397  p.format = v;
398  else if(k == "port.name")
399  p.port_name = v;
400  else if(k == "port.alias")
401  p.port_alias = v;
402  else if(k == "object.path")
403  p.object_path = v;
404  else if(k == "port.id")
405  p.port_id = v;
406  else if(k == "node.id")
407  p.node_id = v;
408  else if(k == "port.physical" && v == "true")
409  p.physical = true;
410  else if(k == "port.terminal" && v == "true")
411  p.terminal = true;
412  else if(k == "port.monitor" && v == "true")
413  p.monitor = true;
414  else if(k == "port.direction")
415  {
416  if(v == "out")
417  {
418  p.direction = pw_direction::SPA_DIRECTION_OUTPUT;
419  }
420  else
421  {
422  p.direction = pw_direction::SPA_DIRECTION_INPUT;
423  }
424  }
425  }
426 
427  if(p.node_id.empty())
428  return;
429 
430  const auto nid = std::stoul(p.node_id);
431  if(p.physical)
432  {
433  if(p.format.find("audio") != p.format.npos)
434  {
435  if(p.direction == pw_direction::SPA_DIRECTION_OUTPUT)
436  this->current_graph.physical_audio[nid].outputs.push_back(std::move(p));
437  else
438  this->current_graph.physical_audio[nid].inputs.push_back(std::move(p));
439  }
440  else if(p.format.find("midi") != p.format.npos)
441  {
442  if(p.direction == pw_direction::SPA_DIRECTION_OUTPUT)
443  this->current_graph.physical_midi[nid].outputs.push_back(std::move(p));
444  else
445  this->current_graph.physical_midi[nid].inputs.push_back(std::move(p));
446  }
447  else
448  {
449  // TODO, video ?
450  }
451  }
452  else
453  {
454  if(p.format.find("audio") != p.format.npos)
455  {
456  if(p.direction == pw_direction::SPA_DIRECTION_OUTPUT)
457  this->current_graph.software_audio[nid].outputs.push_back(std::move(p));
458  else
459  this->current_graph.software_audio[nid].inputs.push_back(std::move(p));
460  }
461  else if(p.format.find("midi") != p.format.npos)
462  {
463  if(p.direction == pw_direction::SPA_DIRECTION_OUTPUT)
464  this->current_graph.software_midi[nid].outputs.push_back(std::move(p));
465  else
466  this->current_graph.software_midi[nid].inputs.push_back(std::move(p));
467  }
468  else
469  {
470  // TODO, video ?
471  }
472  }
473  }
474 
475  int get_fd() const noexcept
476  {
477  if(!this->lp)
478  return -1;
479 
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);
484  else
485  return -1;
486  }
487 
488  ~pipewire_context()
489  {
490  if(this->registry)
491  pw.proxy_destroy((pw_proxy*)this->registry);
492  for(auto& [id, p, l] : this->port_listener)
493  if(l)
494  pw.proxy_destroy((pw_proxy*)p);
495  if(this->core)
496  pw.core_disconnect(this->core);
497  if(this->context)
498  pw.context_destroy(this->context);
499  if(this->main_loop)
500  pw.main_loop_destroy(this->main_loop);
501 
502  pw.deinit();
503  }
504 };
505 
506 struct audio_setup
507 {
508  std::string name;
509  std::string card_in;
510  std::string card_out;
511 
512  std::vector<std::string> inputs;
513  std::vector<std::string> outputs;
514 
515  int rate{};
516  int buffer_size{};
517 };
518 
519 class pipewire_audio_protocol : public audio_engine
520 {
521 public:
522  struct port
523  {
524  };
525 
526  std::shared_ptr<pipewire_context> loop{};
527  pw_filter* filter{};
528  std::vector<pw_proxy*> links{};
529 
530  explicit pipewire_audio_protocol(
531  std::shared_ptr<pipewire_context> loop, const audio_setup& setup)
532  {
533  auto& pw = libpipewire::instance();
534 
535  static constexpr const struct pw_filter_events filter_events = {
536  .version = PW_VERSION_FILTER_EVENTS,
537  .process = on_process,
538  };
539 
540  this->loop = loop;
541 
542  auto lp = loop->lp;
543  // clang-format off
544  // Create the filter (the main pipewire object which will represent the
545  // software)
546  auto filter_props{
547  pw.properties_new(
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",
561  nullptr)};
562 
563  // clang-format on
564  this->filter = pw.filter_new_simple(
565  lp, setup.name.c_str(), filter_props, &filter_events, this);
566  if(!this->filter)
567  {
568  throw std::runtime_error("PipeWire: could not create filter instance");
569  }
570 
571  // Create the request ports
572  for(std::size_t i = 0; i < setup.inputs.size(); i++)
573  {
574  auto p = (port*)pw.filter_add_port(
575  this->filter, PW_DIRECTION_INPUT, PW_FILTER_PORT_FLAG_MAP_BUFFERS,
576  sizeof(struct port),
577  pw.properties_new(
578  PW_KEY_FORMAT_DSP, "32 bit float mono audio", PW_KEY_PORT_NAME,
579  setup.inputs[i].c_str(), NULL),
580  NULL, 0);
581  input_ports.push_back(p);
582  }
583 
584  for(std::size_t i = 0; i < setup.outputs.size(); i++)
585  {
586  auto p = (port*)pw.filter_add_port(
587  this->filter, PW_DIRECTION_OUTPUT, PW_FILTER_PORT_FLAG_MAP_BUFFERS,
588  sizeof(struct port),
589  pw.properties_new(
590  PW_KEY_FORMAT_DSP, "32 bit float mono audio", PW_KEY_PORT_NAME,
591  setup.outputs[i].c_str(), NULL),
592  NULL, 0);
593  output_ports.push_back(p);
594  }
595 
596  if(pw.filter_connect(this->filter, PW_FILTER_FLAG_RT_PROCESS, nullptr, 0) < 0)
597  {
598  throw std::runtime_error("PipeWire: cannot connect");
599  }
600 
601  // Wait until everything is registered with PipeWire
602  this->loop->synchronize();
603  {
604  int k = 0;
605  auto node_id = filter_node_id();
606  while(node_id == 4294967295)
607  {
608  this->loop->synchronize();
609  node_id = filter_node_id();
610 
611  if(k++; k > 100)
612  return;
613  }
614 
615  // Leave some time to resolve the ports
616  k = 0;
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)
622  {
623  this->loop->synchronize();
624  if(k++; k > 100)
625  return;
626  }
627  }
628 
629  activated = true;
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();
634  }
635 
636  uint32_t filter_node_id() { return this->loop->pw.filter_get_node_id(this->filter); }
637 
638  void autoconnect()
639  {
640  auto node_id = filter_node_id();
641 
642  std::vector<std::pair<std::optional<uint32_t>, std::optional<uint32_t>>>
643  phys_in_to_ossia;
644  std::vector<std::pair<std::optional<uint32_t>, std::optional<uint32_t>>>
645  ossia_to_phys_out;
646 
647  // Link to the first physical soundcard we see
648  for(auto& [node, ports] : loop->current_graph.physical_audio)
649  {
650  auto& [out, in] = ports;
651 
652  // The soundcard outputs are input ports
653  for(auto& port : in)
654  {
655  phys_in_to_ossia.emplace_back(port.id, std::nullopt);
656  }
657 
658  // The soundcard inputs are output ports
659  for(auto& port : out)
660  {
661  ossia_to_phys_out.emplace_back(std::nullopt, port.id);
662  }
663  }
664 
665  // Enumerate our matching local ports
666  for(auto& [node, ports] : loop->current_graph.software_audio)
667  {
668  if(node == node_id)
669  {
670  auto& [in, out] = ports;
671 
672  // Connect our inputs to the soundcard inputs
673  for(std::size_t i = 0; i < in.size(); i++)
674  {
675  if(i >= phys_in_to_ossia.size())
676  break;
677 
678  phys_in_to_ossia[i].second = in[i].id;
679  }
680 
681  // Connect our outputs to the soundcard inputs
682  for(std::size_t i = 0; i < out.size(); i++)
683  {
684  if(i >= ossia_to_phys_out.size())
685  break;
686 
687  ossia_to_phys_out[i].first = out[i].id;
688  }
689  break;
690  }
691  }
692 
693  // Connect as much as we can
694  for(auto [phys, self] : phys_in_to_ossia)
695  {
696  if(phys && self)
697  {
698  if(auto link = this->loop->link_ports(*phys, *self))
699  this->links.push_back(link);
700  }
701  else
702  {
703  break;
704  }
705  }
706 
707  for(auto [self, phys] : ossia_to_phys_out)
708  {
709  if(self && phys)
710  {
711  if(auto link = this->loop->link_ports(*self, *phys))
712  this->links.push_back(link);
713  }
714  else
715  {
716  break;
717  }
718  }
719  }
720 
721  void wait(int ms) override
722  {
723  if(!loop)
724  return;
725 
726  using namespace std::chrono;
727  using clk = high_resolution_clock;
728 
729  auto t0 = clk::now();
730  auto t1 = clk::now();
731  while(duration_cast<milliseconds>(t1 - t0).count() < ms)
732  {
733  pw_loop_iterate(loop->lp, ms);
734  t1 = clk::now();
735  }
736  }
737 
738  bool running() const override
739  {
740  if(!this->loop)
741  return false;
742  return activated;
743  }
744 
745  ~pipewire_audio_protocol()
746  {
747  auto& pw = libpipewire::instance();
748 
749  for(auto link : this->links)
750  pw.proxy_destroy(link);
751 
752  pw.filter_destroy(this->filter);
753  }
754 
755  static void
756  clear_buffers(pipewire_audio_protocol& self, uint32_t nframes, std::size_t outputs)
757  {
758  auto& pw = libpipewire::instance();
759  for(std::size_t i = 0; i < outputs; i++)
760  {
761  auto chan = (float*)pw.filter_get_dsp_buffer(self.output_ports[i], nframes);
762  if(chan)
763  for(std::size_t j = 0; j < nframes; j++)
764  chan[j] = 0.f;
765  }
766 
767  return;
768  }
769 
770  static void on_process(void* userdata, struct spa_io_position* position)
771  {
772  static const thread_local auto _ = [] {
773  ossia::set_thread_name("ossia audio 0");
774  ossia::set_thread_pinned(thread_type::Audio, 0);
775  return 0;
776  }();
777 
778  if(!userdata)
779  return;
780 
781  const auto& pw = libpipewire::instance();
782  auto& self = *(pipewire_audio_protocol*)userdata;
783  const uint32_t nframes = position->clock.duration;
784 
785  self.tick_start();
786 
787  const auto inputs = self.input_ports.size();
788  const auto outputs = self.output_ports.size();
789  if(self.stop_processing)
790  {
791  self.tick_clear();
792  clear_buffers(self, nframes, outputs);
793  return;
794  }
795 
796  auto dummy = (float*)alloca(sizeof(float) * nframes);
797  memset(dummy, 0, sizeof(float) * nframes);
798 
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++)
802  {
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;
806  }
807  for(std::size_t i = 0; i < outputs; i++)
808  {
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;
812  }
813 
814  // Actual execution
815  ossia::audio_tick_state ts{float_input, float_output, (int)inputs,
816  (int)outputs, nframes, position->clock.nsec * 1e-9};
817  self.audio_tick(ts);
818  self.tick_end();
819  }
820 
821  std::vector<port*> input_ports;
822  std::vector<port*> output_ports;
823  bool activated{};
824 };
825 
826 }
827 #endif
828 #endif
Definition: git_info.h:7
spdlog::logger & logger() noexcept
Where the errors will be logged. Default is stderr.
Definition: context.cpp:104