MayaFlux 1.0.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
WasapiBackend.hpp
Go to the documentation of this file.
1#pragma once
2
3#include "AudioBackend.hpp"
4
5#ifdef WASAPI_BACKEND
6
7
8#include <windows.h>
9#include <audioclient.h>
10#include <initguid.h>
11#include <mmdeviceapi.h>
12#include <propsys.h>
13
14#ifdef ERROR
15#undef ERROR
16#endif
17
18namespace MayaFlux::Core {
19
20/**
21 * @class WasapiBackend
22 * @brief Native WASAPI implementation of the audio backend interface.
23 *
24 * Header-only Windows SDK COM interfaces; no external DLL dependencies beyond
25 * those already present in every Windows installation. Owns the
26 * IMMDeviceEnumerator lifetime for the process. Shared mode only.
27 */
28class MAYAFLUX_API WasapiBackend : public IAudioBackend {
29public:
30 WasapiBackend();
31 ~WasapiBackend() override;
32
33 std::unique_ptr<AudioDevice> create_device_manager() override;
34
35 std::unique_ptr<AudioStream> create_stream(
36 unsigned int output_device_id,
37 unsigned int input_device_id,
38 GlobalStreamInfo& stream_info,
39 void* user_data) override;
40
41 [[nodiscard]] std::string get_version_string() const override;
42
43 void cleanup() override;
44
45 [[nodiscard]] IMMDeviceEnumerator* enumerator() const { return m_enumerator; }
46
47private:
48 IMMDeviceEnumerator* m_enumerator = nullptr;
49};
50
51/**
52 * @class WasapiDevice
53 * @brief WASAPI implementation of the audio device interface.
54 *
55 * Enumerates active render and capture endpoints via IMMDeviceCollection.
56 * Device IDs are indices into the enumerated collections, offset by 1 so
57 * that 0 maps to the system default endpoint. This matches the convention
58 * used by RtAudioDevice and is consumed by WasapiStream::open().
59 */
60class MAYAFLUX_API WasapiDevice : public AudioDevice {
61public:
62 explicit WasapiDevice(IMMDeviceEnumerator* enumerator);
63 ~WasapiDevice() override;
64
65 [[nodiscard]] std::vector<DeviceInfo> get_output_devices() const override;
66 [[nodiscard]] std::vector<DeviceInfo> get_input_devices() const override;
67 [[nodiscard]] unsigned int get_default_output_device() const override;
68 [[nodiscard]] unsigned int get_default_input_device() const override;
69
70 /**
71 * @brief Resolves an IMMDevice* from an engine device ID.
72 * @param id Engine device ID (0 = default).
73 * @param flow EDataFlow::eRender or EDataFlow::eCapture.
74 * @return Activated IMMDevice pointer; caller must Release().
75 */
76 [[nodiscard]] IMMDevice* resolve_device(unsigned int id, EDataFlow flow) const;
77
78private:
79 struct EndpointEntry {
80 DeviceInfo info;
81 std::wstring endpoint_id;
82 };
83
84 void enumerate(IMMDeviceEnumerator* enumerator, EDataFlow flow,
85 std::vector<EndpointEntry>& out, unsigned int& default_idx);
86
87 IMMDeviceEnumerator* m_enumerator = nullptr;
88
89 std::vector<EndpointEntry> m_outputs;
90 std::vector<EndpointEntry> m_inputs;
91
92 unsigned int m_default_output = 0;
93 unsigned int m_default_input = 0;
94};
95
96/**
97 * @class WasapiStream
98 * @brief WASAPI shared-mode audio stream.
99 *
100 * Opens an IAudioClient in shared mode, negotiates WAVEFORMATEXTENSIBLE for
101 * IEEE_FLOAT with the engine's requested sample rate and channel count, then
102 * drives the render/capture loop on a dedicated high-priority thread. The
103 * thread blocks on an event handle supplied to WASAPI (SetEventHandle path),
104 * waking exactly once per buffer period with no busy-wait.
105 *
106 * The internal loop converts between WASAPI's native float32 interleaved
107 * layout and the engine's float64 interleaved layout in the hot path.
108 * Format negotiation falls back to the mix format reported by WASAPI if the
109 * requested sample rate is not supported in shared mode.
110 *
111 * Input capture is implemented when stream_info.input.enabled is true and
112 * input_device_id resolves to a valid capture endpoint.
113 */
114class MAYAFLUX_API WasapiStream : public AudioStream {
115public:
116 WasapiStream(
117 IMMDevice* output_device,
118 IMMDevice* input_device,
119 GlobalStreamInfo& stream_info,
120 void* user_data);
121
122 ~WasapiStream() override;
123
124 void open() override;
125 void start() override;
126 void stop() override;
127 void pause() override;
128 void resume() override;
129 void close() override;
130
131 [[nodiscard]] bool is_running() const override;
132 [[nodiscard]] bool is_open() const override;
133
134 void set_process_callback(
135 std::function<int(void*, void*, unsigned int)> cb) override;
136
137private:
138 static DWORD WINAPI render_thread_proc(LPVOID param);
139 void render_loop();
140
141 /**
142 * @brief Negotiates a WAVEFORMATEXTENSIBLE for shared-mode float32.
143 * @param client IAudioClient to negotiate against.
144 * @param channels Requested channel count.
145 * @param sample_rate Requested sample rate.
146 * @param[out] out_fmt Populated on success; caller frees with CoTaskMemFree.
147 * @return true if negotiation succeeded.
148 */
149 bool negotiate_format(
150 IAudioClient* client,
151 uint32_t channels,
152 uint32_t sample_rate,
153 WAVEFORMATEX** out_fmt);
154
155 IMMDevice* m_output_device = nullptr;
156 IMMDevice* m_input_device = nullptr;
157
158 IAudioClient* m_render_client = nullptr;
159 IAudioRenderClient* m_render_sink = nullptr;
160 IAudioClient* m_capture_client = nullptr;
161 IAudioCaptureClient* m_capture_src = nullptr;
162
163 HANDLE m_render_event = nullptr;
164 HANDLE m_capture_event = nullptr;
165 HANDLE m_stop_event = nullptr;
166
167 HANDLE m_render_thread = nullptr;
168
169 uint32_t m_render_buffer_frames = 0;
170 uint32_t m_capture_buffer_frames = 0;
171
172 uint32_t m_negotiated_output_rate = 0;
173 uint32_t m_negotiated_input_rate = 0;
174 uint32_t m_negotiated_output_channels = 0;
175 uint32_t m_negotiated_input_channels = 0;
176
177 std::vector<float> m_render_staging;
178 std::vector<double> m_output_staging;
179 std::vector<float> m_capture_staging;
180 std::vector<double> m_input_staging;
181 std::atomic<bool> m_input_ready { false };
182
183 std::vector<double> m_render_ring;
184 uint32_t m_ring_write_pos { 0 };
185 uint32_t m_ring_read_pos { 0 };
186 uint32_t m_ring_frames { 0 };
187 std::vector<double> m_capture_ring;
188 uint32_t m_cap_ring_write_pos { 0 };
189 uint32_t m_cap_ring_read_pos { 0 };
190 uint32_t m_cap_ring_frames { 0 };
191
192 GlobalStreamInfo& m_stream_info;
193 void* m_user_data = nullptr;
194
195 std::function<int(void*, void*, unsigned int)> m_process_callback;
196
197 std::atomic<bool> m_is_open { false };
198 std::atomic<bool> m_is_running { false };
199 std::atomic<bool> m_is_paused { false };
200};
201
202} // namespace MayaFlux::Core
203
204#endif // WASAPI_BACKEND
std::vector< float > * out
uint32_t channels
void close(std::span< const float > mask, std::span< float > tmp, std::span< float > dst, uint32_t w, uint32_t h, uint32_t radius)
Morphological closing writing into dst.
void open(std::span< const float > mask, std::span< float > tmp, std::span< float > dst, uint32_t w, uint32_t h, uint32_t radius)
Morphological opening writing into dst.
void stop()
Stop all Portal::Graphics operations.
Definition Graphics.cpp:69