FreeRDP
Loading...
Searching...
No Matches
audin_aaudio.c
1
20#include <freerdp/config.h>
21
22#include <stdio.h>
23#include <stdlib.h>
24#include <string.h>
25
26#include <winpr/assert.h>
27#include <winpr/cast.h>
28#include <winpr/crt.h>
29#include <winpr/synch.h>
30#include <winpr/thread.h>
31
32#include <freerdp/freerdp.h>
33#include <freerdp/addin.h>
34#include <freerdp/channels/rdpsnd.h>
35#include <freerdp/client/audin.h>
36
37#include <aaudio/AAudio.h>
38
39#include "audin_main.h"
40
41/* Read blocks at most this long so the capture thread can observe a stop request. */
42#define AAUDIO_READ_TIMEOUT_NS (200LL * 1000LL * 1000LL)
43
44#define AAUDIO_MAX_RATE 48000
45
46typedef struct
47{
48 IAudinDevice iface;
49
50 AAudioStream* stream;
51 HANDLE thread;
52 HANDLE stopEvent;
53
54 /* Guards the stream pointer: the capture thread drops it on a route change while
55 * Close/Free may be stopping it from the channel thread. */
57
58 AUDIO_FORMAT format;
59 UINT32 frames_per_packet;
60
61 AudinReceive receive;
62 void* user_data;
63
64 rdpContext* rdpcontext;
65 wLog* log;
66} AudinAAudioDevice;
67
68WINPR_ATTR_NODISCARD
69static UINT audin_aaudio_close(IAudinDevice* device);
70
71WINPR_ATTR_NODISCARD
72static BOOL audin_aaudio_pcm_valid(const AUDIO_FORMAT* format)
73{
74 WINPR_ASSERT(format);
75
76 return (format->wFormatTag == WAVE_FORMAT_PCM) && (format->cbSize == 0) &&
77 (format->wBitsPerSample == 16) && (format->nSamplesPerSec > 0) &&
78 (format->nSamplesPerSec <= AAUDIO_MAX_RATE) &&
79 ((format->nChannels == 1) || (format->nChannels == 2));
80}
81
82static void audin_aaudio_stop_stream(AudinAAudioDevice* aaudio, AAudioStream* stream)
83{
84 WINPR_ASSERT(aaudio);
85 WINPR_ASSERT(stream);
86
87 const aaudio_result_t rc = AAudioStream_requestStop(stream);
88 if (rc != AAUDIO_OK)
89 WLog_Print(aaudio->log, WLOG_WARN, "AAudioStream_requestStop: %s",
90 AAudio_convertResultToText(rc));
91}
92
93static void audin_aaudio_close_stream(AudinAAudioDevice* aaudio, AAudioStream* stream)
94{
95 WINPR_ASSERT(aaudio);
96 WINPR_ASSERT(stream);
97
98 const aaudio_result_t rc = AAudioStream_close(stream);
99 if (rc != AAUDIO_OK)
100 WLog_Print(aaudio->log, WLOG_WARN, "AAudioStream_close: %s",
101 AAudio_convertResultToText(rc));
102}
103
104WINPR_ATTR_NODISCARD
105static BOOL audin_aaudio_open_stream(AudinAAudioDevice* aaudio)
106{
107 WINPR_ASSERT(aaudio);
108
109 AAudioStreamBuilder* builder = nullptr;
110
111 aaudio_result_t rc = AAudio_createStreamBuilder(&builder);
112 if (rc != AAUDIO_OK)
113 {
114 WLog_Print(aaudio->log, WLOG_ERROR, "AAudio_createStreamBuilder: %s",
115 AAudio_convertResultToText(rc));
116 return FALSE;
117 }
118
119 AAudioStreamBuilder_setDirection(builder, AAUDIO_DIRECTION_INPUT);
120 AAudioStreamBuilder_setSharingMode(builder, AAUDIO_SHARING_MODE_SHARED);
121 AAudioStreamBuilder_setPerformanceMode(builder, AAUDIO_PERFORMANCE_MODE_NONE);
122 AAudioStreamBuilder_setFormat(builder, AAUDIO_FORMAT_PCM_I16);
123 AAudioStreamBuilder_setSampleRate(
124 builder, WINPR_ASSERTING_INT_CAST(int32_t, aaudio->format.nSamplesPerSec));
125 AAudioStreamBuilder_setChannelCount(
126 builder, WINPR_ASSERTING_INT_CAST(int32_t, aaudio->format.nChannels));
127#if __ANDROID_API__ >= 28
128 AAudioStreamBuilder_setInputPreset(builder, AAUDIO_INPUT_PRESET_VOICE_COMMUNICATION);
129#endif
130
131 AAudioStream* stream = nullptr;
132 rc = AAudioStreamBuilder_openStream(builder, &stream);
133 AAudioStreamBuilder_delete(builder);
134
135 if (rc != AAUDIO_OK)
136 {
137 WLog_Print(aaudio->log, WLOG_ERROR, "AAudioStreamBuilder_openStream: %s",
138 AAudio_convertResultToText(rc));
139 return FALSE;
140 }
141
142 /* The frame size is fixed by the negotiated format and there is no conversion here, so a
143 * stream that came back with a different channel count or sample format would have
144 * AAudioStream_read write past the accumulation buffer. */
145 const int32_t actualRate = AAudioStream_getSampleRate(stream);
146 const int32_t actualChannels = AAudioStream_getChannelCount(stream);
147 const aaudio_format_t actualFormat = AAudioStream_getFormat(stream);
148
149 if ((actualChannels < 0) || ((UINT32)actualChannels != aaudio->format.nChannels) ||
150 (actualFormat != AAUDIO_FORMAT_PCM_I16))
151 {
152 WLog_Print(aaudio->log, WLOG_ERROR,
153 "AAudio input opened as %dch/fmt%d, need %" PRIu16 "ch/I16", actualChannels,
154 (int)actualFormat, aaudio->format.nChannels);
155 audin_aaudio_close_stream(aaudio, stream);
156 return FALSE;
157 }
158
159 if ((actualRate < 0) || ((UINT32)actualRate != aaudio->format.nSamplesPerSec))
160 {
161 WLog_Print(aaudio->log, WLOG_WARN,
162 "AAudio input rate adjusted: requested %" PRIu32 "Hz, got %dHz",
163 aaudio->format.nSamplesPerSec, actualRate);
164 }
165
166 rc = AAudioStream_requestStart(stream);
167 if (rc != AAUDIO_OK)
168 {
169 WLog_Print(aaudio->log, WLOG_ERROR, "AAudioStream_requestStart: %s",
170 AAudio_convertResultToText(rc));
171 audin_aaudio_close_stream(aaudio, stream);
172 return FALSE;
173 }
174
175 EnterCriticalSection(&aaudio->lock);
176 aaudio->stream = stream;
177 LeaveCriticalSection(&aaudio->lock);
178 return TRUE;
179}
180
181/* Close and forget the capture stream, if it is still the one the caller saw. */
182static void audin_aaudio_drop_stream(AudinAAudioDevice* aaudio, AAudioStream* stream)
183{
184 WINPR_ASSERT(aaudio);
185
186 EnterCriticalSection(&aaudio->lock);
187 if (aaudio->stream && (!stream || (aaudio->stream == stream)))
188 {
189 audin_aaudio_stop_stream(aaudio, aaudio->stream);
190 audin_aaudio_close_stream(aaudio, aaudio->stream);
191 aaudio->stream = nullptr;
192 }
193 LeaveCriticalSection(&aaudio->lock);
194}
195
196WINPR_ATTR_NODISCARD
197static DWORD WINAPI audin_aaudio_thread(LPVOID arg)
198{
199 AudinAAudioDevice* aaudio = (AudinAAudioDevice*)arg;
200 WINPR_ASSERT(aaudio);
201 WINPR_ASSERT(aaudio->receive);
202 WINPR_ASSERT(aaudio->stopEvent);
203
204 const size_t bytesPerFrame = aaudio->format.nChannels * sizeof(INT16);
205 const size_t framesPerPacket = aaudio->frames_per_packet;
206 UINT error = CHANNEL_RC_OK;
207
208 if ((bytesPerFrame == 0) || (framesPerPacket == 0) || (framesPerPacket > INT32_MAX) ||
209 (framesPerPacket > SIZE_MAX / bytesPerFrame))
210 {
211 audin_aaudio_drop_stream(aaudio, nullptr);
212 return ERROR_INVALID_DATA;
213 }
214
215 const size_t accumulationBytes = framesPerPacket * bytesPerFrame;
216 BYTE* accumulationBuffer = (BYTE*)calloc(framesPerPacket, bytesPerFrame);
217 if (!accumulationBuffer)
218 {
219 audin_aaudio_drop_stream(aaudio, nullptr);
220 return CHANNEL_RC_NO_MEMORY;
221 }
222
223 size_t currentFrames = 0;
224
225 while (WaitForSingleObject(aaudio->stopEvent, 0) != WAIT_OBJECT_0)
226 {
227 EnterCriticalSection(&aaudio->lock);
228 AAudioStream* stream = aaudio->stream;
229 LeaveCriticalSection(&aaudio->lock);
230
231 /* Reconnect loop if disconnected */
232 if (!stream)
233 {
234 if (!audin_aaudio_open_stream(aaudio))
235 {
236 if (WaitForSingleObject(aaudio->stopEvent, 500) == WAIT_OBJECT_0)
237 break;
238 }
239 continue;
240 }
241
242 const int32_t framesToRead =
243 WINPR_ASSERTING_INT_CAST(int32_t, framesPerPacket - currentFrames);
244 BYTE* writePtr = accumulationBuffer + (currentFrames * bytesPerFrame);
245
246 /* Read outside the lock: this thread is the only one closing the stream it reads from,
247 * Close stops it from outside and only closes it after joining. */
248 aaudio_result_t r =
249 AAudioStream_read(stream, writePtr, framesToRead, AAUDIO_READ_TIMEOUT_NS);
250
251 if (r < 0)
252 {
253 if (WaitForSingleObject(aaudio->stopEvent, 0) == WAIT_OBJECT_0)
254 break;
255
256 WLog_Print(aaudio->log, WLOG_ERROR, "AAudioStream_read: %s",
257 AAudio_convertResultToText(r));
258 /* A timeout is transient. Everything else (route change, audioserver restart,
259 * internal fault) leaves the stream unusable, so drop it and reopen on the next
260 * iteration; reading the dead handle again would loop forever. */
261 if (r != AAUDIO_ERROR_TIMEOUT)
262 {
263 audin_aaudio_drop_stream(aaudio, stream);
264 currentFrames = 0;
265 }
266
267 /* Let the route settle; reopening into a half-torn-down route just fails again
268 * and spins on the audio server. */
269 if (WaitForSingleObject(aaudio->stopEvent, 100) == WAIT_OBJECT_0)
270 break;
271 continue;
272 }
273
274 if (r == 0)
275 continue;
276
277 if (r > framesToRead)
278 {
279 WLog_Print(aaudio->log, WLOG_ERROR, "AAudioStream_read returned %d > %d frames", r,
280 framesToRead);
281 audin_aaudio_drop_stream(aaudio, stream);
282 currentFrames = 0;
283 continue;
284 }
285
286 currentFrames += (size_t)r;
287
288 /* Only dispatch full framesPerPacket packets to prevent codec frame corruption */
289 if (currentFrames == framesPerPacket)
290 {
291 error = aaudio->receive(&aaudio->format, accumulationBuffer, accumulationBytes,
292 aaudio->user_data);
293 currentFrames = 0;
294 if (error)
295 {
296 if (aaudio->rdpcontext)
297 setChannelError(aaudio->rdpcontext, error,
298 "audin_aaudio receive reported an error");
299 break;
300 }
301 }
302 }
303
304 audin_aaudio_drop_stream(aaudio, nullptr);
305 free(accumulationBuffer);
306 return error;
307}
308
309WINPR_ATTR_NODISCARD
310static BOOL audin_aaudio_format_supported(IAudinDevice* device, const AUDIO_FORMAT* format)
311{
312 AudinAAudioDevice* aaudio = (AudinAAudioDevice*)device;
313
314 if (!aaudio || !format)
315 return FALSE;
316
317 return audin_aaudio_pcm_valid(format);
318}
319
320WINPR_ATTR_NODISCARD
321static UINT audin_aaudio_set_format(IAudinDevice* device, const AUDIO_FORMAT* format,
322 UINT32 FramesPerPacket)
323{
324 AudinAAudioDevice* aaudio = (AudinAAudioDevice*)device;
325
326 if (!aaudio || !format)
327 return ERROR_INVALID_PARAMETER;
328
329 if (!audin_aaudio_pcm_valid(format))
330 return ERROR_UNSUPPORTED_TYPE;
331
332 const size_t bytesPerFrame = format->nChannels * sizeof(INT16);
333 const UINT32 frames = (FramesPerPacket > 0) ? FramesPerPacket : 1;
334 if ((frames > INT32_MAX) || (frames > SIZE_MAX / bytesPerFrame))
335 {
336 WLog_Print(aaudio->log, WLOG_ERROR, "invalid FramesPerPacket %" PRIu32, FramesPerPacket);
337 return ERROR_INVALID_DATA;
338 }
339
340 aaudio->format = *format;
341 aaudio->frames_per_packet = frames;
342 return CHANNEL_RC_OK;
343}
344
345WINPR_ATTR_NODISCARD
346static UINT audin_aaudio_open(IAudinDevice* device, AudinReceive receive, void* user_data)
347{
348 AudinAAudioDevice* aaudio = (AudinAAudioDevice*)device;
349
350 if (!aaudio || !receive || !user_data)
351 return ERROR_INVALID_PARAMETER;
352
353 /* Reap a capture thread that already exited, otherwise its stale handle blocks the
354 * restart forever. */
355 if (aaudio->thread && (WaitForSingleObject(aaudio->thread, 0) == WAIT_OBJECT_0))
356 {
357 if (!CloseHandle(aaudio->thread))
358 WLog_Print(aaudio->log, WLOG_WARN, "CloseHandle on the exited capture thread failed");
359 aaudio->thread = nullptr;
360 }
361
362 if (aaudio->stream || aaudio->thread)
363 return CHANNEL_RC_OK;
364
365 /* SetFormat must have negotiated a valid PCM format first */
366 if ((aaudio->format.nSamplesPerSec == 0) ||
367 ((aaudio->format.nChannels != 1) && (aaudio->format.nChannels != 2)))
368 {
369 WLog_Print(aaudio->log, WLOG_ERROR, "SetFormat must run with a valid PCM format first");
370 return ERROR_INVALID_PARAMETER;
371 }
372
373 aaudio->receive = receive;
374 aaudio->user_data = user_data;
375
376 if (!audin_aaudio_open_stream(aaudio))
377 goto error_out;
378
379 if (aaudio->stopEvent)
380 {
381 if (!ResetEvent(aaudio->stopEvent))
382 {
383 WLog_Print(aaudio->log, WLOG_ERROR, "ResetEvent failed");
384 goto error_out;
385 }
386 }
387 else
388 {
389 aaudio->stopEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr);
390 if (!aaudio->stopEvent)
391 {
392 WLog_Print(aaudio->log, WLOG_ERROR, "CreateEvent failed");
393 goto error_out;
394 }
395 }
396
397 aaudio->thread = CreateThread(nullptr, 0, audin_aaudio_thread, aaudio, 0, nullptr);
398 if (!aaudio->thread)
399 {
400 WLog_Print(aaudio->log, WLOG_ERROR, "CreateThread failed");
401 goto error_out;
402 }
403
404 return CHANNEL_RC_OK;
405error_out:
406 if (audin_aaudio_close(device) != CHANNEL_RC_OK)
407 WLog_Print(aaudio->log, WLOG_WARN, "audin_aaudio_close failed");
408 return ERROR_INTERNAL_ERROR;
409}
410
411WINPR_ATTR_NODISCARD
412static UINT audin_aaudio_close(IAudinDevice* device)
413{
414 AudinAAudioDevice* aaudio = (AudinAAudioDevice*)device;
415
416 if (!aaudio)
417 return ERROR_INVALID_PARAMETER;
418
419 if (aaudio->stopEvent && !SetEvent(aaudio->stopEvent))
420 WLog_Print(aaudio->log, WLOG_WARN, "SetEvent failed");
421
422 /* Unblock a pending read; requestStop on a stream being read is safe, closing it is not. */
423 EnterCriticalSection(&aaudio->lock);
424 if (aaudio->stream)
425 audin_aaudio_stop_stream(aaudio, aaudio->stream);
426 LeaveCriticalSection(&aaudio->lock);
427
428 if (aaudio->thread)
429 {
430 if (WaitForSingleObject(aaudio->thread, INFINITE) != WAIT_OBJECT_0)
431 WLog_Print(aaudio->log, WLOG_WARN, "WaitForSingleObject failed");
432 if (!CloseHandle(aaudio->thread))
433 WLog_Print(aaudio->log, WLOG_WARN, "CloseHandle failed");
434 aaudio->thread = nullptr;
435 }
436
437 EnterCriticalSection(&aaudio->lock);
438 if (aaudio->stream)
439 {
440 audin_aaudio_close_stream(aaudio, aaudio->stream);
441 aaudio->stream = nullptr;
442 }
443 LeaveCriticalSection(&aaudio->lock);
444
445 if (aaudio->stopEvent)
446 {
447 if (!CloseHandle(aaudio->stopEvent))
448 WLog_Print(aaudio->log, WLOG_WARN, "CloseHandle failed");
449 aaudio->stopEvent = nullptr;
450 }
451
452 aaudio->receive = nullptr;
453 aaudio->user_data = nullptr;
454 return CHANNEL_RC_OK;
455}
456
457WINPR_ATTR_NODISCARD
458static UINT audin_aaudio_free(IAudinDevice* device)
459{
460 AudinAAudioDevice* aaudio = (AudinAAudioDevice*)device;
461
462 if (!aaudio)
463 return ERROR_INVALID_PARAMETER;
464
465 if (audin_aaudio_close(device) != CHANNEL_RC_OK)
466 WLog_Print(aaudio->log, WLOG_WARN, "audin_aaudio_close failed");
467 DeleteCriticalSection(&aaudio->lock);
468 free(aaudio);
469 return CHANNEL_RC_OK;
470}
471
472FREERDP_ENTRY_POINT(UINT VCAPITYPE aaudio_freerdp_audin_client_subsystem_entry(
474{
475 WINPR_ASSERT(pEntryPoints);
476 WINPR_ASSERT(pEntryPoints->pRegisterAudinDevice);
477
478 UINT error = ERROR_INTERNAL_ERROR;
479 AudinAAudioDevice* aaudio = (AudinAAudioDevice*)calloc(1, sizeof(AudinAAudioDevice));
480
481 if (!aaudio)
482 {
483 WLog_ERR(TAG, "calloc failed!");
484 return CHANNEL_RC_NO_MEMORY;
485 }
486
487 InitializeCriticalSection(&aaudio->lock);
488
489 aaudio->log = WLog_Get(TAG);
490 aaudio->iface.Open = audin_aaudio_open;
491 aaudio->iface.FormatSupported = audin_aaudio_format_supported;
492 aaudio->iface.SetFormat = audin_aaudio_set_format;
493 aaudio->iface.Close = audin_aaudio_close;
494 aaudio->iface.Free = audin_aaudio_free;
495 aaudio->rdpcontext = pEntryPoints->rdpcontext;
496
497 if ((error = pEntryPoints->pRegisterAudinDevice(pEntryPoints->plugin, (IAudinDevice*)aaudio)))
498 {
499 WLog_Print(aaudio->log, WLOG_ERROR, "RegisterAudinDevice failed with error %" PRIu32 "!",
500 error);
501 goto error_out;
502 }
503
504 return CHANNEL_RC_OK;
505error_out:
506 DeleteCriticalSection(&aaudio->lock);
507 free(aaudio);
508 return error;
509}