FreeRDP
Loading...
Searching...
No Matches
win_audio.c
1
19#include <windows.h>
20#include <objbase.h>
21
22#include <winpr/crt.h>
23#include <winpr/synch.h>
24#include <winpr/thread.h>
25#include <winpr/stream.h>
26#include <winpr/sysinfo.h>
27
28#include <freerdp/log.h>
29#include <freerdp/codec/audio.h>
30#include <freerdp/codec/dsp.h>
31
32#include "win_shadow.h"
33#include "win_audio.h"
34#include "win_wasapi.h"
35
36#define TAG SERVER_TAG("shadow.win")
37
38/* The loopback stream is polled: win_wasapi_loopback_read() takes what the capture has and
39 * does not wait for more, so this wait (on the stop event) paces the loop. 10 ms is the period
40 * of the shared mode audio engine, the rate at which new samples arrive; with 0 the loop would
41 * spin a CPU core. (An event driven loopback stream is not signalled on every Windows version.) */
42#define WIN_AUDIO_POLL_MS 10
43
44/* What the clients get: 48 kHz, 2 channels, 16 bits. The build has no
45 * resampler (SOXR or FFmpeg) unless asked for one, so every format offered
46 * has the rate and channels of the captured samples, and 16 bits a sample:
47 * rdpsnd sizes its buffers from the bits and channels of the first one. Opus
48 * first, so that a client that has it takes it (96 kbit/s instead of
49 * 1.5 Mbit/s for PCM). Its block alignment is 1: rdpsnd pads every wave to a
50 * multiple of it with zeros, and an Opus packet with zeros after it no longer
51 * decodes. */
52static const AUDIO_FORMAT win_audio_offered_formats[] = {
53 { WAVE_FORMAT_OPUS, WIN_WASAPI_CHANNELS, WIN_WASAPI_RATE, 12000, 1, 16, 0, nullptr },
54 { WAVE_FORMAT_PCM, WIN_WASAPI_CHANNELS, WIN_WASAPI_RATE, WIN_WASAPI_RATE * 4, 4, 16, 0,
55 nullptr },
56};
57
58/* The format of the samples in each message: it outlives every client,
59 * since rdpsnd keeps the pointer as its source format */
60static AUDIO_FORMAT win_audio_source_format = {
61 WAVE_FORMAT_PCM, WIN_WASAPI_CHANNELS, WIN_WASAPI_RATE, WIN_WASAPI_RATE * 4, 4, 16, 0, nullptr
62};
63
64static void win_audio_msg_free(UINT32 id, SHADOW_MSG_OUT* msg)
65{
66 WINPR_UNUSED(id);
67
69 if (samples)
70 free(samples->buf);
71 free(samples);
72}
73
74/* Sends what s holds to every client, as one message */
75static void win_audio_post(winShadowSubsystem* subsystem, wStream* s)
76{
77 WINPR_ASSERT(subsystem);
78
79 const size_t size = Stream_GetPosition(s);
80 Stream_ResetPosition(s);
81 if (size == 0)
82 return;
83
84 rdpShadowServer* server = subsystem->base.server;
85 if (!server || !server->clients || (ArrayList_Count(server->clients) == 0))
86 return;
87
90 if (!msg)
91 return;
92
93 msg->buf = malloc(size);
94 if (!msg->buf)
95 {
96 free(msg);
97 return;
98 }
99
100 memcpy(msg->buf, Stream_Buffer(s), size);
101 msg->common.Free = win_audio_msg_free;
102 msg->audio_format = &win_audio_source_format;
103 msg->nFrames = size / WIN_WASAPI_FRAME_BYTES;
104 msg->wTimestamp = (UINT16)(GetTickCount64() & 0xFFFF);
105 /* the message is freed with its last reference, also when no client took it */
106 (void)shadow_client_boardcast_msg(server, nullptr, SHADOW_MSG_OUT_AUDIO_OUT_SAMPLES_ID,
107 &msg->common, nullptr);
108}
109
110static DWORD WINAPI win_audio_thread(LPVOID arg)
111{
112 winShadowSubsystem* subsystem = (winShadowSubsystem*)arg;
113 WINPR_ASSERT(subsystem);
114
115 const HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
116 if (FAILED(hr))
117 {
118 WLog_ERR(TAG, "audio: CoInitializeEx failed (0x%08lX): no sound", hr);
119 return 1;
120 }
121
122 wStream* s = Stream_New(nullptr, 1ull * WIN_WASAPI_RATE * WIN_WASAPI_FRAME_BYTES / 10);
123 BOOL log = TRUE;
124
125 while (s && (WaitForSingleObject(subsystem->audioStopEvent, 0) == WAIT_TIMEOUT))
126 {
127 winWasapiLoopback* loopback = win_wasapi_loopback_open(log);
128 if (!loopback)
129 {
130 /* tried again every 2 s, quietly: the audio service may start later
131 * than we do, a sound card may be plugged in */
132 log = FALSE;
133 (void)WaitForSingleObject(subsystem->audioStopEvent, 2000);
134 continue;
135 }
136
137 /* until stopped, or the endpoint went away: then the default one again */
138 log = TRUE;
139 while (WaitForSingleObject(subsystem->audioStopEvent, WIN_AUDIO_POLL_MS) == WAIT_TIMEOUT)
140 {
141 const BOOL rc = win_wasapi_loopback_read(loopback, s);
142 win_audio_post(subsystem, s);
143 if (!rc)
144 break;
145 }
146 win_wasapi_loopback_close(loopback);
147 }
148
149 Stream_Free(s, TRUE);
150 CoUninitialize();
151 return 0;
152}
153
154BOOL win_shadow_audio_init(winShadowSubsystem* subsystem)
155{
156 WINPR_ASSERT(subsystem);
157
158 AUDIO_FORMAT* formats = audio_formats_new(ARRAYSIZE(win_audio_offered_formats));
159 if (!formats)
160 return FALSE;
161
162 size_t count = 0;
163 for (size_t x = 0; x < ARRAYSIZE(win_audio_offered_formats); x++)
164 {
165 const AUDIO_FORMAT* format = &win_audio_offered_formats[x];
166 if (freerdp_dsp_supports_format(format, TRUE))
167 formats[count++] = *format;
168 }
169
170 subsystem->base.rdpsndFormats = formats;
171 subsystem->base.nRdpsndFormats = count;
172 return TRUE;
173}
174
175void win_shadow_audio_uninit(winShadowSubsystem* subsystem)
176{
177 WINPR_ASSERT(subsystem);
178
179 win_shadow_audio_stop(subsystem);
180 audio_formats_free(subsystem->base.rdpsndFormats, subsystem->base.nRdpsndFormats);
181 subsystem->base.rdpsndFormats = nullptr;
182 subsystem->base.nRdpsndFormats = 0;
183}
184
185BOOL win_shadow_audio_start(winShadowSubsystem* subsystem)
186{
187 WINPR_ASSERT(subsystem);
188
189 if (subsystem->audioThread)
190 return TRUE;
191
192 /* without its formats rdpsnd would expect 44.1 kHz from us */
193 if (!subsystem->base.rdpsndFormats)
194 return FALSE;
195
196 subsystem->audioStopEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr);
197 if (!subsystem->audioStopEvent)
198 return FALSE;
199
200 subsystem->audioThread = CreateThread(nullptr, 0, win_audio_thread, subsystem, 0, nullptr);
201 if (!subsystem->audioThread)
202 {
203 WLog_ERR(TAG, "audio: failed to create the capture thread");
204 (void)CloseHandle(subsystem->audioStopEvent);
205 subsystem->audioStopEvent = nullptr;
206 return FALSE;
207 }
208
209 return TRUE;
210}
211
212void win_shadow_audio_stop(winShadowSubsystem* subsystem)
213{
214 WINPR_ASSERT(subsystem);
215
216 if (!subsystem->audioThread)
217 return;
218
219 (void)SetEvent(subsystem->audioStopEvent);
220 (void)WaitForSingleObject(subsystem->audioThread, INFINITE);
221 (void)CloseHandle(subsystem->audioThread);
222 (void)CloseHandle(subsystem->audioStopEvent);
223 subsystem->audioThread = nullptr;
224 subsystem->audioStopEvent = nullptr;
225}