FreeRDP
Loading...
Searching...
No Matches
rdpsnd_mac.m
1
24#include <freerdp/config.h>
25
26#include <stdio.h>
27#include <stdlib.h>
28#include <string.h>
29
30#include <winpr/crt.h>
31#include <winpr/sysinfo.h>
32
33#include <freerdp/types.h>
34
35#include <AVFoundation/AVAudioBuffer.h>
36#include <AVFoundation/AVFoundation.h>
37
38#include "rdpsnd_main.h"
39
40typedef struct
41{
42 rdpsndDevicePlugin device;
43
44 BOOL isOpen;
45 BOOL isPlaying;
46
47 UINT32 latency;
48 AUDIO_FORMAT format;
49
50 AVAudioEngine *engine;
51 AVAudioPlayerNode *player;
52 UINT64 diff;
53} rdpsndMacPlugin;
54
55static BOOL rdpsnd_mac_set_format(rdpsndDevicePlugin *device, const AUDIO_FORMAT *format,
56 UINT32 latency)
57{
58 rdpsndMacPlugin *mac = (rdpsndMacPlugin *)device;
59 if (!mac || !format)
60 return FALSE;
61
62 mac->latency = latency;
63 mac->format = *format;
64
65 audio_format_print(WLog_Get(TAG), WLOG_DEBUG, format);
66 return TRUE;
67}
68
69static char *FormatError(OSStatus st)
70{
71 switch (st)
72 {
73 case kAudioFileUnspecifiedError:
74 return "kAudioFileUnspecifiedError";
75
76 case kAudioFileUnsupportedFileTypeError:
77 return "kAudioFileUnsupportedFileTypeError";
78
79 case kAudioFileUnsupportedDataFormatError:
80 return "kAudioFileUnsupportedDataFormatError";
81
82 case kAudioFileUnsupportedPropertyError:
83 return "kAudioFileUnsupportedPropertyError";
84
85 case kAudioFileBadPropertySizeError:
86 return "kAudioFileBadPropertySizeError";
87
88 case kAudioFilePermissionsError:
89 return "kAudioFilePermissionsError";
90
91 case kAudioFileNotOptimizedError:
92 return "kAudioFileNotOptimizedError";
93
94 case kAudioFileInvalidChunkError:
95 return "kAudioFileInvalidChunkError";
96
97 case kAudioFileDoesNotAllow64BitDataSizeError:
98 return "kAudioFileDoesNotAllow64BitDataSizeError";
99
100 case kAudioFileInvalidPacketOffsetError:
101 return "kAudioFileInvalidPacketOffsetError";
102
103 case kAudioFileInvalidFileError:
104 return "kAudioFileInvalidFileError";
105
106 case kAudioFileOperationNotSupportedError:
107 return "kAudioFileOperationNotSupportedError";
108
109 case kAudioFileNotOpenError:
110 return "kAudioFileNotOpenError";
111
112 case kAudioFileEndOfFileError:
113 return "kAudioFileEndOfFileError";
114
115 case kAudioFilePositionError:
116 return "kAudioFilePositionError";
117
118 case kAudioFileFileNotFoundError:
119 return "kAudioFileFileNotFoundError";
120
121 default:
122 return "unknown error";
123 }
124}
125
126static void rdpsnd_mac_release(rdpsndMacPlugin *mac)
127{
128 if (mac->player)
129 [mac->player release];
130 mac->player = nullptr;
131
132 if (mac->engine)
133 [mac->engine release];
134 mac->engine = nullptr;
135}
136
137static BOOL rdpsnd_mac_connect_player(rdpsndMacPlugin *mac)
138{
139 /* AVAudioPlayerNode does not resample scheduled buffers, so the connection
140 * must use the sample rate of the stream instead of the default format. */
141 AVAudioFormat *format = [[AVAudioFormat alloc] initWithCommonFormat:AVAudioPCMFormatFloat32
142 sampleRate:mac->format.nSamplesPerSec
143 channels:mac->format.nChannels
144 interleaved:NO];
145 if (!format)
146 {
147 WLog_ERR(TAG, "AVAudioFormat::init() failed");
148 return FALSE;
149 }
150
151 [mac->engine connect:mac->player to:mac->engine.mainMixerNode format:format];
152 [format release];
153 return TRUE;
154}
155
156static BOOL rdpsnd_mac_open(rdpsndDevicePlugin *device, const AUDIO_FORMAT *format, UINT32 latency)
157{
158 @autoreleasepool
159 {
160 AudioDeviceID outputDeviceID;
161 UInt32 propertySize;
162 OSStatus err;
163 NSError *error;
164 rdpsndMacPlugin *mac = (rdpsndMacPlugin *)device;
165 AudioObjectPropertyAddress propertyAddress = {
166 kAudioHardwarePropertyDefaultOutputDevice,
167 kAudioObjectPropertyScopeGlobal,
168#if defined(MAC_OS_VERSION_12_0)
169 kAudioObjectPropertyElementMain
170#else
171 kAudioObjectPropertyElementMaster
172#endif
173 };
174
175 if (mac->isOpen)
176 return TRUE;
177
178 if (!rdpsnd_mac_set_format(device, format, latency))
179 return FALSE;
180
181 propertySize = sizeof(outputDeviceID);
182 err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, nullptr,
183 &propertySize, &outputDeviceID);
184 if (err)
185 {
186 WLog_ERR(TAG, "AudioHardwareGetProperty: %s", FormatError(err));
187 return FALSE;
188 }
189
190 mac->engine = [[AVAudioEngine alloc] init];
191 if (!mac->engine)
192 return FALSE;
193
194 err = AudioUnitSetProperty(mac->engine.outputNode.audioUnit,
195 kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global,
196 0, &outputDeviceID, sizeof(outputDeviceID));
197 if (err)
198 {
199 rdpsnd_mac_release(mac);
200 WLog_ERR(TAG, "AudioUnitSetProperty: %s", FormatError(err));
201 return FALSE;
202 }
203
204 mac->player = [[AVAudioPlayerNode alloc] init];
205 if (!mac->player)
206 {
207 rdpsnd_mac_release(mac);
208 WLog_ERR(TAG, "AVAudioPlayerNode::init() failed");
209 return FALSE;
210 }
211
212 [mac->engine attachNode:mac->player];
213
214 if (!rdpsnd_mac_connect_player(mac))
215 {
216 rdpsnd_mac_release(mac);
217 return FALSE;
218 }
219
220 [mac->engine prepare];
221
222 if (![mac->engine startAndReturnError:&error])
223 {
224 device->Close(device);
225 WLog_ERR(TAG, "Failed to start audio player %s",
226 [error.localizedDescription UTF8String]);
227 return FALSE;
228 }
229
230 mac->isOpen = TRUE;
231 return TRUE;
232 }
233}
234
235static void rdpsnd_mac_close(rdpsndDevicePlugin *device)
236{
237 @autoreleasepool
238 {
239 rdpsndMacPlugin *mac = (rdpsndMacPlugin *)device;
240
241 if (mac->isPlaying)
242 {
243 [mac->player stop];
244 mac->isPlaying = FALSE;
245 }
246
247 if (mac->isOpen)
248 {
249 [mac->engine stop];
250 mac->isOpen = FALSE;
251 }
252
253 rdpsnd_mac_release(mac);
254 }
255}
256
257static void rdpsnd_mac_free(rdpsndDevicePlugin *device)
258{
259 rdpsndMacPlugin *mac = (rdpsndMacPlugin *)device;
260 device->Close(device);
261 free(mac);
262}
263
264static BOOL rdpsnd_mac_format_supported(rdpsndDevicePlugin *device, const AUDIO_FORMAT *format)
265{
266 WINPR_UNUSED(device);
267
268 switch (format->wFormatTag)
269 {
270 case WAVE_FORMAT_PCM:
271 if (format->wBitsPerSample != 16)
272 return FALSE;
273
274 if (format->nChannels != 2)
275 return FALSE;
276 return TRUE;
277
278 default:
279 return FALSE;
280 }
281}
282
283static BOOL rdpsnd_mac_set_volume(rdpsndDevicePlugin *device, UINT32 value)
284{
285 @autoreleasepool
286 {
287 Float32 fVolume;
288 UINT16 volumeLeft;
289 UINT16 volumeRight;
290 rdpsndMacPlugin *mac = (rdpsndMacPlugin *)device;
291
292 if (!mac->player)
293 return FALSE;
294
295 volumeLeft = (value & 0xFFFF);
296 volumeRight = ((value >> 16) & 0xFFFF);
297 fVolume = ((float)volumeLeft) / 65535.0f;
298
299 mac->player.volume = fVolume;
300
301 return TRUE;
302 }
303}
304
305static void rdpsnd_mac_start(rdpsndDevicePlugin *device)
306{
307 @autoreleasepool
308 {
309 rdpsndMacPlugin *mac = (rdpsndMacPlugin *)device;
310
311 if (!mac->engine.isRunning)
312 {
313 NSError *error;
314 if (!rdpsnd_mac_connect_player(mac))
315 {
316 device->Close(device);
317 return;
318 }
319 [mac->engine prepare];
320 if (![mac->engine startAndReturnError:&error])
321 {
322 device->Close(device);
323 WLog_ERR(TAG, "Failed to start audio player %s",
324 [error.localizedDescription UTF8String]);
325 return;
326 }
327 mac->isPlaying = FALSE; /* force [player play] below */
328 }
329
330 if (!mac->isPlaying)
331 {
332 [mac->player play];
333
334 mac->isPlaying = TRUE;
335 mac->diff = 100; /* Initial latency, corrected after first sample is played. */
336 }
337 }
338}
339
340static UINT rdpsnd_mac_play(rdpsndDevicePlugin *device, const BYTE *data, size_t size)
341{
342 @autoreleasepool
343 {
344 rdpsndMacPlugin *mac = (rdpsndMacPlugin *)device;
345 AVAudioPCMBuffer *buffer;
346 AVAudioFormat *format;
347 float *const *db;
348 size_t step;
349 AVAudioFrameCount count;
350 UINT64 start = GetTickCount64();
351
352 if (!mac->isOpen)
353 return 0;
354
355 step = 2 * mac->format.nChannels;
356
357 count = size / step;
358 format = [[AVAudioFormat alloc] initWithCommonFormat:AVAudioPCMFormatFloat32
359 sampleRate:mac->format.nSamplesPerSec
360 channels:mac->format.nChannels
361 interleaved:NO];
362
363 if (!format)
364 {
365 WLog_WARN(TAG, "AVAudioFormat::init() failed");
366 return 0;
367 }
368
369 buffer = [[AVAudioPCMBuffer alloc] initWithPCMFormat:format frameCapacity:count];
370 [format release];
371
372 if (!buffer)
373 {
374 WLog_WARN(TAG, "AVAudioPCMBuffer::init() failed");
375 return 0;
376 }
377
378 buffer.frameLength = buffer.frameCapacity;
379 db = buffer.floatChannelData;
380
381 for (size_t pos = 0; pos < count; pos++)
382 {
383 const BYTE *d = &data[pos * step];
384 for (size_t x = 0; x < mac->format.nChannels; x++)
385 {
386 const float val = (int16_t)((uint16_t)d[0] | ((uint16_t)d[1] << 8)) / 32768.0f;
387 db[x][pos] = val;
388 d += sizeof(int16_t);
389 }
390 }
391
392 rdpsnd_mac_start(device);
393
394 [mac->player scheduleBuffer:buffer
395 completionHandler:^{
396 UINT64 stop = GetTickCount64();
397 if (start > stop)
398 mac->diff = 0;
399 else
400 mac->diff = stop - start;
401 }];
402
403 [buffer release];
404
405 return mac->diff > UINT_MAX ? UINT_MAX : mac->diff;
406 }
407}
408
414FREERDP_ENTRY_POINT(UINT VCAPITYPE mac_freerdp_rdpsnd_client_subsystem_entry(
416{
417 rdpsndMacPlugin *mac;
418 mac = (rdpsndMacPlugin *)calloc(1, sizeof(rdpsndMacPlugin));
419
420 if (!mac)
421 return CHANNEL_RC_NO_MEMORY;
422
423 mac->device.Open = rdpsnd_mac_open;
424 mac->device.FormatSupported = rdpsnd_mac_format_supported;
425 mac->device.SetVolume = rdpsnd_mac_set_volume;
426 mac->device.Play = rdpsnd_mac_play;
427 mac->device.Close = rdpsnd_mac_close;
428 mac->device.Free = rdpsnd_mac_free;
429 pEntryPoints->pRegisterRdpsndDevice(pEntryPoints->rdpsnd, (rdpsndDevicePlugin *)mac);
430 return CHANNEL_RC_OK;
431}