MayaFlux 0.4.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
WasapiBackend.cpp
Go to the documentation of this file.
1#include "WasapiBackend.hpp"
3
4#ifdef WASAPI_BACKEND
5
6#include <avrt.h>
7
8#pragma comment(lib, "avrt.lib")
9#pragma comment(lib, "ole32.lib")
10
11namespace {
14}
15
16namespace MayaFlux::Core {
17
18// ============================================================================
19// Helpers
20// ============================================================================
21
22namespace {
23
24 void check_hr(HRESULT hr, const char* msg, std::source_location loc = std::source_location::current())
25 {
26 if (FAILED(hr)) {
27 error(C, X, loc, "{} (HRESULT 0x{:08X})", msg, static_cast<unsigned>(hr));
28 }
29 }
30
31 std::string wstring_to_utf8(const std::wstring& ws)
32 {
33 if (ws.empty())
34 return {};
35 int sz = WideCharToMultiByte(CP_UTF8, 0, ws.data(), static_cast<int>(ws.size()),
36 nullptr, 0, nullptr, nullptr);
37 std::string out(sz, '\0');
38 WideCharToMultiByte(CP_UTF8, 0, ws.data(), static_cast<int>(ws.size()),
39 out.data(), sz, nullptr, nullptr);
40 return out;
41 }
42
43 std::wstring get_device_friendly_name(IMMDevice* dev)
44 {
45 IPropertyStore* props = nullptr;
46 if (FAILED(dev->OpenPropertyStore(STGM_READ, &props)))
47 return L"Unknown";
48
49 /** PKEY_Device_FriendlyName = {a45c254e-df1c-4efd-8020-67d146a850e0}, 14
50 * Hardcoded to avoid functiondiscoverykeys_devpkey.h include-order issues.
51 **/
52 static const PROPERTYKEY k_friendly_name = {
53 { 0xa45c254e, 0xdf1c, 0x4efd, { 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0 } }, 14
54 };
55
56 PROPVARIANT pv;
57 PropVariantInit(&pv);
58 std::wstring name = L"Unknown";
59
60 if (SUCCEEDED(props->GetValue(k_friendly_name, &pv))
61 && pv.vt == VT_LPWSTR && pv.pwszVal) {
62 name = pv.pwszVal;
63 }
64 PropVariantClear(&pv);
65 props->Release();
66 return name;
67 }
68
69} // namespace
70
71// ============================================================================
72// WasapiBackend
73// ============================================================================
74
75WasapiBackend::WasapiBackend()
76{
77 check_hr(CoInitializeEx(nullptr, COINIT_MULTITHREADED),
78 "WasapiBackend: CoInitializeEx failed");
79
80 check_hr(CoCreateInstance(
81 __uuidof(MMDeviceEnumerator), nullptr, CLSCTX_ALL,
82 __uuidof(IMMDeviceEnumerator),
83 reinterpret_cast<void**>(&m_enumerator)),
84 "WasapiBackend: CoCreateInstance(MMDeviceEnumerator) failed");
85
86 MF_INFO(C, X, "WasapiBackend initialised (Windows WASAPI shared mode)");
87}
88
89WasapiBackend::~WasapiBackend()
90{
91 cleanup();
92}
93
94std::unique_ptr<AudioDevice> WasapiBackend::create_device_manager()
95{
96 return std::make_unique<WasapiDevice>(m_enumerator);
97}
98
99std::unique_ptr<AudioStream> WasapiBackend::create_stream(
100 unsigned int output_device_id,
101 unsigned int input_device_id,
102 GlobalStreamInfo& stream_info,
103 void* user_data)
104{
105 auto dev_mgr = WasapiDevice(m_enumerator);
106
107 IMMDevice* out_dev = dev_mgr.resolve_device(output_device_id, eRender);
108 if (!out_dev)
109 error(C, X, std::source_location::current(),
110 "WasapiBackend: failed to resolve output device id {}", output_device_id);
111
112 IMMDevice* in_dev = nullptr;
113 if (stream_info.input.enabled && stream_info.input.channels > 0)
114 in_dev = dev_mgr.resolve_device(input_device_id, eCapture);
115
116 return std::make_unique<WasapiStream>(out_dev, in_dev, stream_info, user_data);
117}
118
119std::string WasapiBackend::get_version_string() const
120{
121 return "WASAPI (Windows native shared mode)";
122}
123
124void WasapiBackend::cleanup()
125{
126 if (m_enumerator) {
127 m_enumerator->Release();
128 m_enumerator = nullptr;
129 }
130 CoUninitialize();
131}
132
133// ============================================================================
134// WasapiDevice
135// ============================================================================
136
137WasapiDevice::WasapiDevice(IMMDeviceEnumerator* enumerator)
138 : m_enumerator(enumerator)
139{
140 enumerate(enumerator, eRender, m_outputs, m_default_output);
141 enumerate(enumerator, eCapture, m_inputs, m_default_input);
142}
143
144WasapiDevice::~WasapiDevice() = default;
145
146void WasapiDevice::enumerate(
147 IMMDeviceEnumerator* enumerator,
148 EDataFlow flow,
149 std::vector<EndpointEntry>& out,
150 unsigned int& default_idx)
151{
152 default_idx = 0;
153
154 IMMDevice* def_dev = nullptr;
155 std::wstring def_id;
156 if (SUCCEEDED(enumerator->GetDefaultAudioEndpoint(flow, eConsole, &def_dev))) {
157 LPWSTR id = nullptr;
158 if (SUCCEEDED(def_dev->GetId(&id))) {
159 def_id = id;
160 CoTaskMemFree(id);
161 }
162 def_dev->Release();
163 }
164
165 IMMDeviceCollection* col = nullptr;
166 if (FAILED(enumerator->EnumAudioEndpoints(flow, DEVICE_STATE_ACTIVE, &col)))
167 return;
168
169 UINT count = 0;
170 col->GetCount(&count);
171
172 for (UINT i = 0; i < count; ++i) {
173 IMMDevice* dev = nullptr;
174 if (FAILED(col->Item(i, &dev)))
175 continue;
176
177 LPWSTR ep_id = nullptr;
178 std::wstring ep_id_str;
179 if (SUCCEEDED(dev->GetId(&ep_id))) {
180 ep_id_str = ep_id;
181 CoTaskMemFree(ep_id);
182 }
183
184 IAudioClient* client = nullptr;
185 uint32_t preferred_rate = 48000;
186 uint32_t channels = (flow == eRender) ? 2u : 1u;
187
188 if (SUCCEEDED(dev->Activate(__uuidof(IAudioClient), CLSCTX_ALL, nullptr,
189 reinterpret_cast<void**>(&client)))) {
190 WAVEFORMATEX* mix_fmt = nullptr;
191 if (SUCCEEDED(client->GetMixFormat(&mix_fmt)) && mix_fmt) {
192 preferred_rate = mix_fmt->nSamplesPerSec;
193 channels = mix_fmt->nChannels;
194 CoTaskMemFree(mix_fmt);
195 }
196 client->Release();
197 }
198
199 DeviceInfo info;
200 info.name = wstring_to_utf8(get_device_friendly_name(dev));
201 info.preferred_sample_rate = preferred_rate;
202 info.output_channels = (flow == eRender) ? channels : 0;
203 info.input_channels = (flow == eCapture) ? channels : 0;
204 info.is_default_output = (flow == eRender && ep_id_str == def_id);
205 info.is_default_input = (flow == eCapture && ep_id_str == def_id);
206
207 if (info.is_default_output || info.is_default_input)
208 default_idx = static_cast<unsigned int>(out.size() + 1);
209
210 out.push_back({ info, ep_id_str });
211 dev->Release();
212 }
213
214 col->Release();
215}
216
217std::vector<DeviceInfo> WasapiDevice::get_output_devices() const
218{
219 std::vector<DeviceInfo> result;
220 result.reserve(m_outputs.size());
221 for (const auto& e : m_outputs)
222 result.push_back(e.info);
223 return result;
224}
225
226std::vector<DeviceInfo> WasapiDevice::get_input_devices() const
227{
228 std::vector<DeviceInfo> result;
229 result.reserve(m_inputs.size());
230 for (const auto& e : m_inputs)
231 result.push_back(e.info);
232 return result;
233}
234
235unsigned int WasapiDevice::get_default_output_device() const { return m_default_output; }
236unsigned int WasapiDevice::get_default_input_device() const { return m_default_input; }
237
238IMMDevice* WasapiDevice::resolve_device(unsigned int id, EDataFlow flow) const
239{
240 const auto& list = (flow == eRender) ? m_outputs : m_inputs;
241
242 if (id == 0) {
243 IMMDevice* dev = nullptr;
244 if (SUCCEEDED(m_enumerator->GetDefaultAudioEndpoint(flow, eConsole, &dev)))
245 return dev;
246 return nullptr;
247 }
248
249 const unsigned int idx = id - 1;
250 if (idx >= list.size())
251 return nullptr;
252
253 IMMDevice* dev = nullptr;
254 m_enumerator->GetDevice(list[idx].endpoint_id.c_str(), &dev);
255 return dev;
256}
257
258// ============================================================================
259// WasapiStream
260// ============================================================================
261
262WasapiStream::WasapiStream(
263 IMMDevice* output_device,
264 IMMDevice* input_device,
265 GlobalStreamInfo& stream_info,
266 void* user_data)
267 : m_output_device(output_device)
268 , m_input_device(input_device)
269 , m_stream_info(stream_info)
270 , m_user_data(user_data)
271{
272}
273
274WasapiStream::~WasapiStream()
275{
276 if (is_running() || is_open())
277 close();
278}
279
280bool WasapiStream::negotiate_format(
281 IAudioClient* client,
282 uint32_t channels,
283 uint32_t sample_rate,
284 WAVEFORMATEX** out_fmt)
285{
286 auto* wfx = static_cast<WAVEFORMATEXTENSIBLE*>(
287 CoTaskMemAlloc(sizeof(WAVEFORMATEXTENSIBLE)));
288 if (!wfx)
289 return false;
290
291 std::memset(wfx, 0, sizeof(WAVEFORMATEXTENSIBLE));
292 wfx->Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
293 wfx->Format.nChannels = static_cast<WORD>(channels);
294 wfx->Format.nSamplesPerSec = sample_rate;
295 wfx->Format.wBitsPerSample = 32;
296 wfx->Format.nBlockAlign = static_cast<WORD>(channels * sizeof(float));
297 wfx->Format.nAvgBytesPerSec = wfx->Format.nBlockAlign * sample_rate;
298 wfx->Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
299 wfx->Samples.wValidBitsPerSample = 32;
300 wfx->dwChannelMask = (channels == 2)
301 ? (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT)
302 : KSAUDIO_SPEAKER_DIRECTOUT;
303 wfx->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
304
305 WAVEFORMATEX* closest = nullptr;
306 HRESULT hr = client->IsFormatSupported(
307 AUDCLNT_SHAREMODE_SHARED,
308 reinterpret_cast<WAVEFORMATEX*>(wfx),
309 &closest);
310
311 if (hr == S_OK) {
312 *out_fmt = reinterpret_cast<WAVEFORMATEX*>(wfx);
313 if (closest)
314 CoTaskMemFree(closest);
315 return true;
316 }
317
318 CoTaskMemFree(wfx);
319
320 if (hr == S_FALSE && closest) {
321 MF_INFO(C, X,
322 "WASAPI shared mode: negotiated mix format ({} Hz, {} ch); "
323 "engine converts float64<->float32 on the render path.",
324 closest->nSamplesPerSec, closest->nChannels);
325 *out_fmt = closest;
326 return true;
327 }
328
329 if (closest)
330 CoTaskMemFree(closest);
331
332 WAVEFORMATEX* mix = nullptr;
333 if (SUCCEEDED(client->GetMixFormat(&mix))) {
334 MF_INFO(C, X,
335 "WASAPI shared mode: using device mix format ({} Hz, {} ch).",
336 mix->nSamplesPerSec, mix->nChannels);
337 *out_fmt = mix;
338 return true;
339 }
340
341 return false;
342}
343
344void WasapiStream::open()
345{
346 if (m_is_open.load())
347 return;
348
349 m_stop_event = CreateEventW(nullptr, TRUE, FALSE, nullptr);
350 if (!m_stop_event)
351 error(C, X, std::source_location::current(), "WasapiStream: CreateEvent (stop) failed");
352
353 {
354 check_hr(m_output_device->Activate(
355 __uuidof(IAudioClient), CLSCTX_ALL, nullptr,
356 reinterpret_cast<void**>(&m_render_client)),
357 "WasapiStream: Activate render IAudioClient failed");
358
359 WAVEFORMATEX* fmt = nullptr;
360 if (!negotiate_format(m_render_client,
361 m_stream_info.output.channels,
362 m_stream_info.sample_rate, &fmt))
363
364 error(C, X, std::source_location::current(),
365 "WasapiStream: render format negotiation failed");
366
367 m_negotiated_output_rate = fmt->nSamplesPerSec;
368 m_negotiated_output_channels = fmt->nChannels;
369
370 if (m_negotiated_output_rate != m_stream_info.sample_rate) {
371 MF_WARN(C, X, "WASAPI render: negotiated {} Hz (requested {})",
372 m_negotiated_output_rate, m_stream_info.sample_rate);
373 m_stream_info.sample_rate = m_negotiated_output_rate;
374 }
375
376 if (m_negotiated_output_channels != m_stream_info.output.channels) {
377 MF_WARN(C, X, "WASAPI render: negotiated {} channels (requested {})",
378 m_negotiated_output_channels, m_stream_info.output.channels);
379 m_stream_info.output.channels = m_negotiated_output_channels;
380 }
381
382 const REFERENCE_TIME period = static_cast<REFERENCE_TIME>(
383 10000.0 * 1000.0 * m_stream_info.buffer_size / m_stream_info.sample_rate);
384
385 check_hr(m_render_client->Initialize(
386 AUDCLNT_SHAREMODE_SHARED,
387 AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
388 period, 0, fmt, nullptr),
389 "WasapiStream: IAudioClient::Initialize (render) failed");
390
391 m_render_event = CreateEventW(nullptr, FALSE, FALSE, nullptr);
392 if (!m_render_event)
393 error(C, X, std::source_location::current(),
394 "WasapiStream: CreateEvent (render) failed");
395
396 check_hr(m_render_client->SetEventHandle(m_render_event),
397 "WasapiStream: SetEventHandle (render) failed");
398
399 check_hr(m_render_client->GetBufferSize(&m_render_buffer_frames),
400 "WasapiStream: GetBufferSize (render) failed");
401
402 if (m_render_buffer_frames != m_stream_info.buffer_size)
403 MF_INFO(C, X, "WASAPI shared mode buffer: {} frames (requested {})",
404 m_render_buffer_frames, m_stream_info.buffer_size);
405
406 check_hr(m_render_client->GetService(
407 __uuidof(IAudioRenderClient),
408 reinterpret_cast<void**>(&m_render_sink)),
409 "WasapiStream: GetService(IAudioRenderClient) failed");
410
411 CoTaskMemFree(fmt);
412
413 m_ring_frames = std::max(m_stream_info.buffer_size, m_render_buffer_frames) * 4;
414 m_ring_write_pos = 0;
415 m_ring_read_pos = 0;
416 m_render_ring.assign(
417 static_cast<size_t>(m_ring_frames) * m_stream_info.output.channels, 0.0);
418
419 m_output_staging.resize(
420 static_cast<size_t>(m_stream_info.buffer_size) * m_stream_info.output.channels);
421 m_render_staging.resize(m_output_staging.size());
422 }
423
424 if (m_input_device) {
425 check_hr(m_input_device->Activate(
426 __uuidof(IAudioClient), CLSCTX_ALL, nullptr,
427 reinterpret_cast<void**>(&m_capture_client)),
428 "WasapiStream: Activate capture IAudioClient failed");
429
430 WAVEFORMATEX* fmt = nullptr;
431 if (!negotiate_format(m_capture_client,
432 m_stream_info.input.channels,
433 m_stream_info.sample_rate, &fmt)) {
434 MF_WARN(C, X, "WasapiStream: capture format negotiation failed; input disabled");
435 m_capture_client->Release();
436 m_capture_client = nullptr;
437 } else {
438 m_negotiated_input_rate = fmt->nSamplesPerSec;
439 m_negotiated_input_channels = fmt->nChannels;
440
441 const REFERENCE_TIME period = static_cast<REFERENCE_TIME>(
442 10000.0 * 1000.0 * m_stream_info.buffer_size / m_stream_info.sample_rate);
443
444 if (FAILED(m_capture_client->Initialize(
445 AUDCLNT_SHAREMODE_SHARED,
446 AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
447 period, 0, fmt, nullptr))) {
448 MF_WARN(C, X, "WasapiStream: IAudioClient::Initialize (capture) failed; input disabled");
449 m_capture_client->Release();
450 m_capture_client = nullptr;
451 } else {
452 m_capture_event = CreateEventW(nullptr, FALSE, FALSE, nullptr);
453 m_capture_client->SetEventHandle(m_capture_event);
454
455 UINT32 cap_frames = 0;
456 m_capture_client->GetBufferSize(&cap_frames);
457 m_capture_buffer_frames = cap_frames;
458
459 m_cap_ring_frames = std::max(m_stream_info.buffer_size, m_capture_buffer_frames) * 4;
460 m_cap_ring_write_pos = 0;
461 m_cap_ring_read_pos = 0;
462 m_capture_ring.assign(
463 static_cast<size_t>(m_cap_ring_frames) * m_stream_info.input.channels, 0.0);
464
465 m_capture_client->GetService(
466 __uuidof(IAudioCaptureClient),
467 reinterpret_cast<void**>(&m_capture_src));
468
469 m_capture_staging.resize(
470 static_cast<size_t>(m_capture_buffer_frames) * m_stream_info.input.channels);
471 m_input_staging.resize(m_capture_staging.size());
472 }
473 CoTaskMemFree(fmt);
474 }
475 }
476
477 m_is_open.store(true, std::memory_order_release);
478 MF_INFO(C, X, "WasapiStream opened ({} Hz, {} out-ch, engine buffer {} frames, hw buffer {} frames)",
479 m_stream_info.sample_rate, m_stream_info.output.channels,
480 m_stream_info.buffer_size, m_render_buffer_frames);
481}
482
483void WasapiStream::start()
484{
485 if (!m_is_open.load())
486 error(C, X, std::source_location::current(),
487 "WasapiStream::start() called before open()");
488 if (m_is_running.load())
489 return;
490
491 ResetEvent(m_stop_event);
492
493 if (m_capture_client)
494 m_capture_client->Start();
495 m_render_client->Start();
496
497 m_render_thread = CreateThread(
498 nullptr, 0, render_thread_proc, this, 0, nullptr);
499 if (!m_render_thread)
500 error(C, X, std::source_location::current(),
501 "WasapiStream: CreateThread failed");
502
503 m_is_running.store(true, std::memory_order_release);
504}
505
506void WasapiStream::stop()
507{
508 if (!m_is_running.load())
509 return;
510
511 SetEvent(m_stop_event);
512
513 if (m_render_thread) {
514 WaitForSingleObject(m_render_thread, 2000);
515 CloseHandle(m_render_thread);
516 m_render_thread = nullptr;
517 }
518
519 m_render_client->Stop();
520 if (m_capture_client)
521 m_capture_client->Stop();
522
523 m_is_running.store(false, std::memory_order_release);
524}
525
526void WasapiStream::pause()
527{
528 if (!m_is_running.load() || m_is_paused.load())
529 return;
530
531 SetEvent(m_stop_event);
532 if (m_render_thread) {
533 WaitForSingleObject(m_render_thread, 2000);
534 CloseHandle(m_render_thread);
535 m_render_thread = nullptr;
536 }
537
538 m_render_client->Stop();
539 if (m_capture_client)
540 m_capture_client->Stop();
541
542 m_is_paused.store(true, std::memory_order_release);
543 m_is_running.store(false, std::memory_order_release);
544}
545
546void WasapiStream::resume()
547{
548 if (!m_is_paused.load())
549 return;
550
551 ResetEvent(m_stop_event);
552
553 if (m_capture_client)
554 m_capture_client->Start();
555 m_render_client->Start();
556
557 m_render_thread = CreateThread(
558 nullptr, 0, render_thread_proc, this, 0, nullptr);
559 if (!m_render_thread)
560 error(C, X, std::source_location::current(),
561 "WasapiStream: CreateThread failed on resume");
562
563 m_is_paused.store(false, std::memory_order_release);
564 m_is_running.store(true, std::memory_order_release);
565}
566
567void WasapiStream::close()
568{
569 if (!m_is_open.load())
570 return;
571
572 if (m_is_running.load() || m_is_paused.load())
573 stop();
574
575 if (m_render_sink) {
576 m_render_sink->Release();
577 m_render_sink = nullptr;
578 }
579 if (m_render_client) {
580 m_render_client->Release();
581 m_render_client = nullptr;
582 }
583 if (m_capture_src) {
584 m_capture_src->Release();
585 m_capture_src = nullptr;
586 }
587 if (m_capture_client) {
588 m_capture_client->Release();
589 m_capture_client = nullptr;
590 }
591
592 if (m_output_device) {
593 m_output_device->Release();
594 m_output_device = nullptr;
595 }
596 if (m_input_device) {
597 m_input_device->Release();
598 m_input_device = nullptr;
599 }
600
601 if (m_render_event) {
602 CloseHandle(m_render_event);
603 m_render_event = nullptr;
604 }
605 if (m_capture_event) {
606 CloseHandle(m_capture_event);
607 m_capture_event = nullptr;
608 }
609 if (m_stop_event) {
610 CloseHandle(m_stop_event);
611 m_stop_event = nullptr;
612 }
613
614 m_cap_ring_write_pos = 0;
615 m_cap_ring_read_pos = 0;
616 m_ring_write_pos = 0;
617 m_ring_read_pos = 0;
618
619 m_is_open.store(false, std::memory_order_release);
620}
621
622bool WasapiStream::is_running() const { return m_is_running.load(std::memory_order_acquire); }
623bool WasapiStream::is_open() const { return m_is_open.load(std::memory_order_acquire); }
624
625void WasapiStream::set_process_callback(
626 std::function<int(void*, void*, unsigned int)> cb)
627{
628 m_process_callback = std::move(cb);
629}
630
631DWORD WINAPI WasapiStream::render_thread_proc(LPVOID param)
632{
633 auto* self = static_cast<WasapiStream*>(param);
634
635 DWORD task_idx = 0;
636 HANDLE task = AvSetMmThreadCharacteristicsW(L"Pro Audio", &task_idx);
637
638 self->render_loop();
639
640 if (task)
641 AvRevertMmThreadCharacteristics(task);
642
643 return 0;
644}
645
646void WasapiStream::render_loop()
647{
648 const uint32_t out_ch = m_negotiated_output_channels;
649 const uint32_t in_ch_wire = m_negotiated_input_channels;
650 const uint32_t in_ch_engine = m_stream_info.input.channels;
651 const uint32_t hw_frames = m_render_buffer_frames;
652 const uint32_t eng_frames = m_stream_info.buffer_size;
653 const uint32_t ring_frames = m_ring_frames;
654 const uint32_t ring_ch = m_stream_info.output.channels;
655 const uint32_t cap_ring_frames = m_cap_ring_frames;
656
657 HANDLE wait_handles[2] = { m_render_event, m_stop_event };
658
659 while (true) {
660 DWORD wait = WaitForMultipleObjects(2, wait_handles, FALSE, 2000);
661 if (wait == WAIT_OBJECT_0 + 1 || wait == WAIT_FAILED || wait == WAIT_TIMEOUT)
662 break;
663
664 if (m_capture_src && in_ch_wire > 0) {
665 BYTE* data = nullptr;
666 UINT32 cap_frames = 0;
667 DWORD flags = 0;
668
669 if (SUCCEEDED(m_capture_src->GetBuffer(&data, &cap_frames, &flags, nullptr, nullptr))) {
670 if (data && !(flags & AUDCLNT_BUFFERFLAGS_SILENT)) {
671 const auto* src = reinterpret_cast<const float*>(data);
672 const uint32_t copy_ch = std::min(in_ch_wire, in_ch_engine);
673 for (uint32_t f = 0; f < cap_frames; ++f) {
674 const uint32_t dst = m_cap_ring_write_pos * in_ch_engine;
675 for (uint32_t c = 0; c < copy_ch; ++c)
676 m_capture_ring[dst + c] = static_cast<double>(src[f * in_ch_wire + c]);
677 for (uint32_t c = copy_ch; c < in_ch_engine; ++c)
678 m_capture_ring[dst + c] = 0.0;
679 m_cap_ring_write_pos = (m_cap_ring_write_pos + 1) % cap_ring_frames;
680 }
681 }
682 m_capture_src->ReleaseBuffer(cap_frames);
683 }
684 }
685
686 auto cap_ring_used = [&]() -> uint32_t {
687 return (m_cap_ring_write_pos >= m_cap_ring_read_pos)
688 ? m_cap_ring_write_pos - m_cap_ring_read_pos
689 : cap_ring_frames - m_cap_ring_read_pos + m_cap_ring_write_pos;
690 };
691
692 auto ring_used = [&]() -> uint32_t {
693 return (m_ring_write_pos >= m_ring_read_pos)
694 ? m_ring_write_pos - m_ring_read_pos
695 : ring_frames - m_ring_read_pos + m_ring_write_pos;
696 };
697
698 while (ring_used() < hw_frames && m_process_callback) {
699 const size_t n = static_cast<size_t>(eng_frames) * ring_ch;
700 if (n > m_output_staging.size())
701 m_output_staging.resize(n);
702
703 std::fill(m_output_staging.begin(), m_output_staging.begin() + n, 0.0);
704
705 double* in_ptr = nullptr;
706 if (cap_ring_used() >= eng_frames) {
707 const size_t in_n = static_cast<size_t>(eng_frames) * in_ch_engine;
708 if (in_n > m_input_staging.size())
709 m_input_staging.resize(in_n);
710
711 for (uint32_t f = 0; f < eng_frames; ++f) {
712 const uint32_t src = m_cap_ring_read_pos * in_ch_engine;
713 const uint32_t dst = f * in_ch_engine;
714 for (uint32_t c = 0; c < in_ch_engine; ++c)
715 m_input_staging[dst + c] = m_capture_ring[src + c];
716 m_cap_ring_read_pos = (m_cap_ring_read_pos + 1) % cap_ring_frames;
717 }
718 in_ptr = m_input_staging.data();
719 }
720
721 m_process_callback(m_output_staging.data(), in_ptr, eng_frames);
722
723 for (uint32_t i = 0; i < eng_frames; ++i) {
724 const uint32_t dst = m_ring_write_pos * ring_ch;
725 const uint32_t src = i * ring_ch;
726 for (uint32_t c = 0; c < ring_ch; ++c)
727 m_render_ring[dst + c] = m_output_staging[src + c];
728 m_ring_write_pos = (m_ring_write_pos + 1) % ring_frames;
729 }
730 }
731
732 UINT32 padding = 0;
733 if (FAILED(m_render_client->GetCurrentPadding(&padding)))
734 continue;
735
736 const UINT32 available = hw_frames - padding;
737 if (available == 0)
738 continue;
739
740 const UINT32 to_write = std::min(available, ring_used());
741 if (to_write == 0)
742 continue;
743
744 BYTE* buf = nullptr;
745 if (FAILED(m_render_sink->GetBuffer(to_write, &buf)) || !buf)
746 continue;
747
748 auto* dst = reinterpret_cast<float*>(buf);
749 for (uint32_t i = 0; i < to_write; ++i) {
750 const uint32_t src = m_ring_read_pos * ring_ch;
751 for (uint32_t c = 0; c < out_ch && c < ring_ch; ++c)
752 dst[i * out_ch + c] = static_cast<float>(m_render_ring[src + c]);
753 m_ring_read_pos = (m_ring_read_pos + 1) % ring_frames;
754 }
755
756 m_render_sink->ReleaseBuffer(to_write, 0);
757 }
758}
759
760} // namespace MayaFlux::Core
761
762#endif // WASAPI_BACKEND
#define MF_INFO(comp, ctx,...)
#define MF_WARN(comp, ctx,...)
size_t count
@ AudioBackend
Audio processing backend (Pipewire, wasapi, coreaudio)
void error(Component component, Context context, std::source_location location, std::string_view message)
Log an error message and optionally throw an exception.
@ Core
Core engine, backend, subsystems.
void stop()
Stop all Portal::Graphics operations.
Definition Graphics.cpp:69
std::vector< double > mix(const std::vector< std::vector< double > > &streams)
Mix multiple data streams with equal weighting.
Definition Yantra.cpp:945