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