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