FreeRDP
Loading...
Searching...
No Matches
rdpsnd_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 <winpr/assert.h>
28
29#include <stdio.h>
30#include <stdlib.h>
31#include <string.h>
32#include <time.h>
33
34#include <winpr/cast.h>
35#include <winpr/crt.h>
36#include <winpr/cmdline.h>
37#include <winpr/synch.h>
38
39#include <freerdp/types.h>
40#include <freerdp/channels/log.h>
41
42#include <aaudio/AAudio.h>
43
44#include "rdpsnd_main.h"
45
46/* Buffer cushion (ms) used when the server requests no explicit latency. */
47#define AAUDIO_DEFAULT_LATENCY_MS 100
48
49/* Short write timeout (10ms) to avoid stalling virtual channel playback thread under asyncLock. */
50#define AAUDIO_WRITE_TIMEOUT_NS (10LL * 1000LL * 1000LL)
51
52#define AAUDIO_MAX_RATE 48000
53
54typedef struct
55{
56 rdpsndDevicePlugin device;
57
58 AAudioStream* stream;
59
60 UINT32 rate;
61 UINT32 channels;
62 UINT32 latency;
63
64 UINT32 volume;
65 BOOL opened;
66
67 short* gainbuf;
68 size_t gainbuf_len;
69
71} rdpsndAAudioPlugin;
72
73WINPR_ATTR_NODISCARD
74static BOOL rdpsnd_aaudio_pcm_valid(const AUDIO_FORMAT* format)
75{
76 WINPR_ASSERT(format);
77
78 return (format->wFormatTag == WAVE_FORMAT_PCM) && (format->cbSize == 0) &&
79 (format->wBitsPerSample == 16) && (format->nSamplesPerSec > 0) &&
80 (format->nSamplesPerSec <= AAUDIO_MAX_RATE) &&
81 ((format->nChannels == 1) || (format->nChannels == 2));
82}
83
84static void rdpsnd_aaudio_close_stream(rdpsndAAudioPlugin* aaudio)
85{
86 WINPR_ASSERT(aaudio);
87
88 if (!aaudio->stream)
89 return;
90
91 if (__builtin_available(android 26, *))
92 {
93 aaudio_result_t rc = AAudioStream_requestStop(aaudio->stream);
94 if (rc != AAUDIO_OK)
95 WLog_WARN(TAG, "AAudioStream_requestStop: %s", AAudio_convertResultToText(rc));
96 rc = AAudioStream_close(aaudio->stream);
97 if (rc != AAUDIO_OK)
98 WLog_WARN(TAG, "AAudioStream_close: %s", AAudio_convertResultToText(rc));
99 }
100 aaudio->stream = nullptr;
101}
102
103WINPR_ATTR_NODISCARD
104static BOOL rdpsnd_aaudio_open_stream(rdpsndAAudioPlugin* aaudio)
105{
106 WINPR_ASSERT(aaudio);
107
108 if (aaudio->stream)
109 return TRUE;
110
111 if (__builtin_available(android 26, *))
112 {
113 AAudioStreamBuilder* builder = nullptr;
114
115 aaudio_result_t rc = AAudio_createStreamBuilder(&builder);
116 if (rc != AAUDIO_OK)
117 {
118 WLog_ERR(TAG, "AAudio_createStreamBuilder: %s", AAudio_convertResultToText(rc));
119 return FALSE;
120 }
121
122 AAudioStreamBuilder_setDirection(builder, AAUDIO_DIRECTION_OUTPUT);
123 AAudioStreamBuilder_setSharingMode(builder, AAUDIO_SHARING_MODE_SHARED);
124 AAudioStreamBuilder_setPerformanceMode(builder, AAUDIO_PERFORMANCE_MODE_NONE);
125 AAudioStreamBuilder_setFormat(builder, AAUDIO_FORMAT_PCM_I16);
126 AAudioStreamBuilder_setSampleRate(builder, WINPR_ASSERTING_INT_CAST(int32_t, aaudio->rate));
127 AAudioStreamBuilder_setChannelCount(builder,
128 WINPR_ASSERTING_INT_CAST(int32_t, aaudio->channels));
129#if __ANDROID_API__ >= 28
130 AAudioStreamBuilder_setUsage(builder, AAUDIO_USAGE_MEDIA);
131 AAudioStreamBuilder_setContentType(builder, AAUDIO_CONTENT_TYPE_MUSIC);
132#endif
133
134 const UINT32 targetMs = (aaudio->latency > 0) ? aaudio->latency : AAUDIO_DEFAULT_LATENCY_MS;
135 const UINT64 targetFrames = (UINT64)targetMs * aaudio->rate / 1000;
136 const int32_t targetCapacity = (targetFrames > INT32_MAX)
137 ? INT32_MAX
138 : WINPR_ASSERTING_INT_CAST(int32_t, targetFrames);
139 AAudioStreamBuilder_setBufferCapacityInFrames(builder, targetCapacity);
140
141 rc = AAudioStreamBuilder_openStream(builder, &aaudio->stream);
142 AAudioStreamBuilder_delete(builder);
143
144 if (rc != AAUDIO_OK)
145 {
146 WLog_ERR(TAG, "AAudioStreamBuilder_openStream: %s", AAudio_convertResultToText(rc));
147 aaudio->stream = nullptr;
148 return FALSE;
149 }
150
151 const int32_t actualRate = AAudioStream_getSampleRate(aaudio->stream);
152 const int32_t actualChannels = AAudioStream_getChannelCount(aaudio->stream);
153 const aaudio_format_t actualFormat = AAudioStream_getFormat(aaudio->stream);
154
155 /* The frame size comes from the negotiated format and nothing converts here, so a stream
156 * with a different channel count or sample format would have AAudioStream_write read past
157 * the end of the caller's buffer. */
158 if ((actualChannels < 0) || ((UINT32)actualChannels != aaudio->channels) ||
159 (actualFormat != AAUDIO_FORMAT_PCM_I16))
160 {
161 WLog_ERR(TAG, "AAudio opened as %dch/fmt%d, need %" PRIu32 "ch/I16", actualChannels,
162 (int)actualFormat, aaudio->channels);
163 rdpsnd_aaudio_close_stream(aaudio);
164 return FALSE;
165 }
166
167 if ((actualRate < 0) || ((UINT32)actualRate != aaudio->rate))
168 {
169 WLog_WARN(TAG, "AAudio rate adjusted: requested %" PRIu32 "Hz, got %dHz", aaudio->rate,
170 actualRate);
171 }
172
173 /* fixed buffer cushion */
174 const int32_t burst = AAudioStream_getFramesPerBurst(aaudio->stream);
175 const int32_t capacity = AAudioStream_getBufferCapacityInFrames(aaudio->stream);
176 int32_t target = targetCapacity;
177
178 if ((burst > 0) && (burst <= INT32_MAX / 2) && (target < burst * 2))
179 target = burst * 2;
180 if ((capacity > 0) && (target > capacity))
181 target = capacity;
182
183 const aaudio_result_t size = AAudioStream_setBufferSizeInFrames(aaudio->stream, target);
184 if (size < 0)
185 WLog_WARN(TAG, "AAudioStream_setBufferSizeInFrames(%d): %s", target,
186 AAudio_convertResultToText(size));
187
188 rc = AAudioStream_requestStart(aaudio->stream);
189 if (rc != AAUDIO_OK)
190 {
191 WLog_ERR(TAG, "AAudioStream_requestStart: %s", AAudio_convertResultToText(rc));
192 rdpsnd_aaudio_close_stream(aaudio);
193 return FALSE;
194 }
195
196 return TRUE;
197 }
198
199 WLog_ERR(TAG, "AAudio requires Android 26 or newer");
200 return FALSE;
201}
202
203WINPR_ATTR_NODISCARD
204static BOOL rdpsnd_aaudio_format_supported(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format)
205{
206 WINPR_ASSERT(device);
207 WINPR_ASSERT(format);
208
209 return rdpsnd_aaudio_pcm_valid(format);
210}
211
212WINPR_ATTR_NODISCARD
213static BOOL rdpsnd_aaudio_default_format(rdpsndDevicePlugin* device, const AUDIO_FORMAT* desired,
214 AUDIO_FORMAT* defaultFormat)
215{
216 WINPR_ASSERT(device);
217 WINPR_ASSERT(defaultFormat);
218
219 if (desired)
220 *defaultFormat = *desired;
221
222 defaultFormat->wFormatTag = WAVE_FORMAT_PCM;
223 defaultFormat->wBitsPerSample = 16;
224 defaultFormat->cbSize = 0;
225 defaultFormat->data = nullptr;
226
227 if ((defaultFormat->nChannels != 1) && (defaultFormat->nChannels != 2))
228 defaultFormat->nChannels = 2;
229 if ((defaultFormat->nSamplesPerSec == 0) || (defaultFormat->nSamplesPerSec > AAUDIO_MAX_RATE))
230 defaultFormat->nSamplesPerSec = AAUDIO_MAX_RATE;
231
232 defaultFormat->nBlockAlign = defaultFormat->nChannels * defaultFormat->wBitsPerSample / 8;
233 defaultFormat->nAvgBytesPerSec = defaultFormat->nBlockAlign * defaultFormat->nSamplesPerSec;
234 return TRUE;
235}
236
237WINPR_ATTR_NODISCARD
238static BOOL rdpsnd_aaudio_open(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format,
239 UINT32 latency)
240{
241 rdpsndAAudioPlugin* aaudio = (rdpsndAAudioPlugin*)device;
242 WINPR_ASSERT(aaudio);
243
244 if (format && !rdpsnd_aaudio_pcm_valid(format))
245 {
246 WLog_ERR(TAG, "unsupported format %s", audio_format_get_tag_string(format->wFormatTag));
247 return FALSE;
248 }
249
250 EnterCriticalSection(&aaudio->lock);
251 if (format)
252 {
253 /* A live stream from a previous format must not swallow the new one. */
254 if (aaudio->stream &&
255 ((aaudio->rate != format->nSamplesPerSec) || (aaudio->channels != format->nChannels)))
256 rdpsnd_aaudio_close_stream(aaudio);
257
258 aaudio->rate = format->nSamplesPerSec;
259 aaudio->channels = format->nChannels;
260 }
261
262 aaudio->latency = latency;
263 const BOOL rc = rdpsnd_aaudio_open_stream(aaudio);
264 aaudio->opened = rc;
265 LeaveCriticalSection(&aaudio->lock);
266 return rc;
267}
268
269static void rdpsnd_aaudio_close(rdpsndDevicePlugin* device)
270{
271 rdpsndAAudioPlugin* aaudio = (rdpsndAAudioPlugin*)device;
272 WINPR_ASSERT(aaudio);
273 EnterCriticalSection(&aaudio->lock);
274 aaudio->opened = FALSE;
275 rdpsnd_aaudio_close_stream(aaudio);
276 LeaveCriticalSection(&aaudio->lock);
277}
278
279static void rdpsnd_aaudio_free(rdpsndDevicePlugin* device)
280{
281 rdpsndAAudioPlugin* aaudio = (rdpsndAAudioPlugin*)device;
282 if (!aaudio)
283 return;
284
285 EnterCriticalSection(&aaudio->lock);
286 aaudio->opened = FALSE;
287 rdpsnd_aaudio_close_stream(aaudio);
288 free(aaudio->gainbuf);
289 aaudio->gainbuf = nullptr;
290 aaudio->gainbuf_len = 0;
291 LeaveCriticalSection(&aaudio->lock);
292
293 DeleteCriticalSection(&aaudio->lock);
294 free(aaudio);
295}
296
297WINPR_ATTR_NODISCARD
298static UINT32 rdpsnd_aaudio_get_volume(rdpsndDevicePlugin* device)
299{
300 rdpsndAAudioPlugin* aaudio = (rdpsndAAudioPlugin*)device;
301 WINPR_ASSERT(aaudio);
302 EnterCriticalSection(&aaudio->lock);
303 const UINT32 volume = aaudio->volume;
304 LeaveCriticalSection(&aaudio->lock);
305 return volume;
306}
307
308WINPR_ATTR_NODISCARD
309static BOOL rdpsnd_aaudio_set_volume(rdpsndDevicePlugin* device, UINT32 value)
310{
311 rdpsndAAudioPlugin* aaudio = (rdpsndAAudioPlugin*)device;
312 WINPR_ASSERT(aaudio);
313 EnterCriticalSection(&aaudio->lock);
314 aaudio->volume = value;
315 LeaveCriticalSection(&aaudio->lock);
316 return TRUE;
317}
318
319/* NDK has no per-stream volume, so scale in software (into gainbuf). */
320WINPR_ATTR_NODISCARD
321static const short* rdpsnd_aaudio_apply_volume(rdpsndAAudioPlugin* aaudio, const short* src,
322 size_t samples)
323{
324 WINPR_ASSERT(aaudio);
325 WINPR_ASSERT(src);
326
327 const UINT32 left = aaudio->volume & 0xFFFF;
328 const UINT32 right = (aaudio->volume >> 16) & 0xFFFF;
329
330 if ((left == 0xFFFF) && (right == 0xFFFF))
331 return src;
332
333 if (samples > SIZE_MAX / sizeof(short))
334 return src;
335
336 if (aaudio->gainbuf_len < samples)
337 {
338 short* tmp = (short*)realloc(aaudio->gainbuf, samples * sizeof(short));
339 if (!tmp)
340 return src;
341 aaudio->gainbuf = tmp;
342 aaudio->gainbuf_len = samples;
343 }
344
345 for (size_t i = 0; i < samples; i++)
346 {
347 const UINT32 gain = ((aaudio->channels == 2) && (i & 1)) ? right : left;
348 aaudio->gainbuf[i] = (short)((int64_t)src[i] * gain / 0xFFFF);
349 }
350
351 return aaudio->gainbuf;
352}
353
354WINPR_ATTR_NODISCARD
355static UINT rdpsnd_aaudio_play(rdpsndDevicePlugin* device, const BYTE* data, size_t size)
356{
357 rdpsndAAudioPlugin* aaudio = (rdpsndAAudioPlugin*)device;
358 WINPR_ASSERT(aaudio);
359
360 if (!data || (size < 2))
361 return 0;
362
363 EnterCriticalSection(&aaudio->lock);
364
365 if (!aaudio->opened)
366 {
367 /* Closed by rdpsnd; a wave PDU still in flight must not reopen the device. */
368 LeaveCriticalSection(&aaudio->lock);
369 return 0;
370 }
371
372 if (!aaudio->stream)
373 {
374 if (!rdpsnd_aaudio_open_stream(aaudio))
375 {
376 LeaveCriticalSection(&aaudio->lock);
377 return 0;
378 }
379 }
380
381 UINT latency = 0;
382 if (__builtin_available(android 26, *))
383 {
384 const size_t bytesPerFrame = aaudio->channels * sizeof(INT16);
385 if (bytesPerFrame == 0)
386 {
387 LeaveCriticalSection(&aaudio->lock);
388 return 0;
389 }
390
391 const short* samples =
392 rdpsnd_aaudio_apply_volume(aaudio, (const short*)data, size / sizeof(short));
393 size_t frames = size / bytesPerFrame;
394 const BYTE* pos = (const BYTE*)samples;
395
396 while (frames > 0)
397 {
398 const int32_t chunk =
399 (frames > INT32_MAX) ? INT32_MAX : WINPR_ASSERTING_INT_CAST(int32_t, frames);
400 aaudio_result_t written =
401 AAudioStream_write(aaudio->stream, pos, chunk, AAUDIO_WRITE_TIMEOUT_NS);
402
403 if (written < 0)
404 {
405 /* A timeout is transient. Everything else (route change, audioserver restart,
406 * internal fault) leaves the stream unusable, so drop it and let the next play
407 * call reopen; keeping it would mute audio for the rest of the session. */
408 if (written != AAUDIO_ERROR_TIMEOUT)
409 {
410 WLog_WARN(TAG, "AAudio stream unusable (%s), closing",
411 AAudio_convertResultToText(written));
412 rdpsnd_aaudio_close_stream(aaudio);
413 }
414 else
415 {
416 WLog_ERR(TAG, "AAudioStream_write: %s", AAudio_convertResultToText(written));
417 }
418
419 LeaveCriticalSection(&aaudio->lock);
420 return 0;
421 }
422
423 if ((written == 0) || (written > chunk))
424 break;
425
426 pos += (size_t)written * bytesPerFrame;
427 frames -= (size_t)written;
428 }
429
430 if (aaudio->rate == 0)
431 {
432 LeaveCriticalSection(&aaudio->lock);
433 return 0;
434 }
435
436 /* report accurate presentation latency (A/V sync) */
437 int64_t framePosition = 0;
438 int64_t timeNanoseconds = 0;
439 int64_t queued = 0;
440
441 if (AAudioStream_getTimestamp(aaudio->stream, CLOCK_MONOTONIC, &framePosition,
442 &timeNanoseconds) == AAUDIO_OK)
443 {
444 struct timespec ts = { 0 };
445 if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0)
446 {
447 const int64_t nowNanos = (int64_t)ts.tv_sec * 1000000000LL + ts.tv_nsec;
448 int64_t elapsedNanos = nowNanos - timeNanoseconds;
449 if (elapsedNanos < 0)
450 elapsedNanos = 0;
451 if (elapsedNanos > INT64_MAX / AAUDIO_MAX_RATE)
452 elapsedNanos = INT64_MAX / AAUDIO_MAX_RATE;
453 const int64_t framesPlayed = elapsedNanos * aaudio->rate / 1000000000LL;
454 queued =
455 AAudioStream_getFramesWritten(aaudio->stream) - (framePosition + framesPlayed);
456 }
457 else
458 {
459 const int64_t written = AAudioStream_getFramesWritten(aaudio->stream);
460 const int64_t read = AAudioStream_getFramesRead(aaudio->stream);
461 queued = written - read;
462 }
463 }
464 else
465 {
466 const int64_t written = AAudioStream_getFramesWritten(aaudio->stream);
467 const int64_t read = AAudioStream_getFramesRead(aaudio->stream);
468 queued = written - read;
469 }
470
471 if (queued < 0)
472 queued = 0;
473 const UINT64 latencyMs = (UINT64)queued / aaudio->rate * 1000 +
474 ((UINT64)queued % aaudio->rate) * 1000 / aaudio->rate;
475 latency = (latencyMs > UINT32_MAX) ? UINT32_MAX : (UINT)latencyMs;
476 }
477 LeaveCriticalSection(&aaudio->lock);
478 return latency;
479}
480
481FREERDP_ENTRY_POINT(UINT VCAPITYPE aaudio_freerdp_rdpsnd_client_subsystem_entry(
483{
484 WINPR_ASSERT(pEntryPoints);
485 WINPR_ASSERT(pEntryPoints->pRegisterRdpsndDevice);
486
487 rdpsndAAudioPlugin* aaudio = (rdpsndAAudioPlugin*)calloc(1, sizeof(rdpsndAAudioPlugin));
488 if (!aaudio)
489 return CHANNEL_RC_NO_MEMORY;
490
491 InitializeCriticalSection(&aaudio->lock);
492
493 aaudio->device.Open = rdpsnd_aaudio_open;
494 aaudio->device.FormatSupported = rdpsnd_aaudio_format_supported;
495 aaudio->device.GetVolume = rdpsnd_aaudio_get_volume;
496 aaudio->device.SetVolume = rdpsnd_aaudio_set_volume;
497 aaudio->device.Play = rdpsnd_aaudio_play;
498 aaudio->device.Close = rdpsnd_aaudio_close;
499 aaudio->device.Free = rdpsnd_aaudio_free;
500 aaudio->device.DefaultFormat = rdpsnd_aaudio_default_format;
501
502 aaudio->rate = 48000;
503 aaudio->channels = 2;
504 aaudio->volume = 0xFFFFFFFF;
505
506 pEntryPoints->pRegisterRdpsndDevice(pEntryPoints->rdpsnd, (rdpsndDevicePlugin*)aaudio);
507 return CHANNEL_RC_OK;
508}