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 = NULL;
131
132 if (mac->engine)
133 [mac->engine release];
134 mac->engine = NULL;
135}
136
137static BOOL rdpsnd_mac_open(rdpsndDevicePlugin *device, const AUDIO_FORMAT *format, UINT32 latency)
138{
139 @autoreleasepool
140 {
141 AudioDeviceID outputDeviceID;
142 UInt32 propertySize;
143 OSStatus err;
144 NSError *error;
145 rdpsndMacPlugin *mac = (rdpsndMacPlugin *)device;
146 AudioObjectPropertyAddress propertyAddress = {
147 kAudioHardwarePropertyDefaultOutputDevice,
148 kAudioObjectPropertyScopeGlobal,
149#if defined(MAC_OS_VERSION_12_0)
150 kAudioObjectPropertyElementMain
151#else
152 kAudioObjectPropertyElementMaster
153#endif
154 };
155
156 if (mac->isOpen)
157 return TRUE;
158
159 if (!rdpsnd_mac_set_format(device, format, latency))
160 return FALSE;
161
162 propertySize = sizeof(outputDeviceID);
163 err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL,
164 &propertySize, &outputDeviceID);
165 if (err)
166 {
167 WLog_ERR(TAG, "AudioHardwareGetProperty: %s", FormatError(err));
168 return FALSE;
169 }
170
171 mac->engine = [[AVAudioEngine alloc] init];
172 if (!mac->engine)
173 return FALSE;
174
175 err = AudioUnitSetProperty(mac->engine.outputNode.audioUnit,
176 kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global,
177 0, &outputDeviceID, sizeof(outputDeviceID));
178 if (err)
179 {
180 rdpsnd_mac_release(mac);
181 WLog_ERR(TAG, "AudioUnitSetProperty: %s", FormatError(err));
182 return FALSE;
183 }
184
185 mac->player = [[AVAudioPlayerNode alloc] init];
186 if (!mac->player)
187 {
188 rdpsnd_mac_release(mac);
189 WLog_ERR(TAG, "AVAudioPlayerNode::init() failed");
190 return FALSE;
191 }
192
193 [mac->engine attachNode:mac->player];
194
195 [mac->engine connect:mac->player to:mac->engine.mainMixerNode format:nil];
196
197 [mac->engine prepare];
198
199 if (![mac->engine startAndReturnError:&error])
200 {
201 device->Close(device);
202 WLog_ERR(TAG, "Failed to start audio player %s",
203 [error.localizedDescription UTF8String]);
204 return FALSE;
205 }
206
207 mac->isOpen = TRUE;
208 return TRUE;
209 }
210}
211
212static void rdpsnd_mac_close(rdpsndDevicePlugin *device)
213{
214 @autoreleasepool
215 {
216 rdpsndMacPlugin *mac = (rdpsndMacPlugin *)device;
217
218 if (mac->isPlaying)
219 {
220 [mac->player stop];
221 mac->isPlaying = FALSE;
222 }
223
224 if (mac->isOpen)
225 {
226 [mac->engine stop];
227 mac->isOpen = FALSE;
228 }
229
230 rdpsnd_mac_release(mac);
231 }
232}
233
234static void rdpsnd_mac_free(rdpsndDevicePlugin *device)
235{
236 rdpsndMacPlugin *mac = (rdpsndMacPlugin *)device;
237 device->Close(device);
238 free(mac);
239}
240
241static BOOL rdpsnd_mac_format_supported(rdpsndDevicePlugin *device, const AUDIO_FORMAT *format)
242{
243 WINPR_UNUSED(device);
244
245 switch (format->wFormatTag)
246 {
247 case WAVE_FORMAT_PCM:
248 if (format->wBitsPerSample != 16)
249 return FALSE;
250
251 if (format->nChannels != 2)
252 return FALSE;
253 return TRUE;
254
255 default:
256 return FALSE;
257 }
258}
259
260static BOOL rdpsnd_mac_set_volume(rdpsndDevicePlugin *device, UINT32 value)
261{
262 @autoreleasepool
263 {
264 Float32 fVolume;
265 UINT16 volumeLeft;
266 UINT16 volumeRight;
267 rdpsndMacPlugin *mac = (rdpsndMacPlugin *)device;
268
269 if (!mac->player)
270 return FALSE;
271
272 volumeLeft = (value & 0xFFFF);
273 volumeRight = ((value >> 16) & 0xFFFF);
274 fVolume = ((float)volumeLeft) / 65535.0f;
275
276 mac->player.volume = fVolume;
277
278 return TRUE;
279 }
280}
281
282static void rdpsnd_mac_start(rdpsndDevicePlugin *device)
283{
284 @autoreleasepool
285 {
286 rdpsndMacPlugin *mac = (rdpsndMacPlugin *)device;
287
288 if (!mac->isPlaying)
289 {
290 if (!mac->engine.isRunning)
291 {
292 NSError *error;
293
294 if (![mac->engine startAndReturnError:&error])
295 {
296 device->Close(device);
297 WLog_ERR(TAG, "Failed to start audio player %s",
298 [error.localizedDescription UTF8String]);
299 return;
300 }
301 }
302
303 [mac->player play];
304
305 mac->isPlaying = TRUE;
306 mac->diff = 100; /* Initial latency, corrected after first sample is played. */
307 }
308 }
309}
310
311static UINT rdpsnd_mac_play(rdpsndDevicePlugin *device, const BYTE *data, size_t size)
312{
313 @autoreleasepool
314 {
315 rdpsndMacPlugin *mac = (rdpsndMacPlugin *)device;
316 AVAudioPCMBuffer *buffer;
317 AVAudioFormat *format;
318 float *const *db;
319 size_t step;
320 AVAudioFrameCount count;
321 UINT64 start = GetTickCount64();
322
323 if (!mac->isOpen)
324 return 0;
325
326 step = 2 * mac->format.nChannels;
327
328 count = size / step;
329 format = [[AVAudioFormat alloc] initWithCommonFormat:AVAudioPCMFormatFloat32
330 sampleRate:mac->format.nSamplesPerSec
331 channels:mac->format.nChannels
332 interleaved:NO];
333
334 if (!format)
335 {
336 WLog_WARN(TAG, "AVAudioFormat::init() failed");
337 return 0;
338 }
339
340 buffer = [[AVAudioPCMBuffer alloc] initWithPCMFormat:format frameCapacity:count];
341 [format release];
342
343 if (!buffer)
344 {
345 WLog_WARN(TAG, "AVAudioPCMBuffer::init() failed");
346 return 0;
347 }
348
349 buffer.frameLength = buffer.frameCapacity;
350 db = buffer.floatChannelData;
351
352 for (size_t pos = 0; pos < count; pos++)
353 {
354 const BYTE *d = &data[pos * step];
355 for (size_t x = 0; x < mac->format.nChannels; x++)
356 {
357 const float val = (int16_t)((uint16_t)d[0] | ((uint16_t)d[1] << 8)) / 32768.0f;
358 db[x][pos] = val;
359 d += sizeof(int16_t);
360 }
361 }
362
363 rdpsnd_mac_start(device);
364
365 [mac->player scheduleBuffer:buffer
366 completionHandler:^{
367 UINT64 stop = GetTickCount64();
368 if (start > stop)
369 mac->diff = 0;
370 else
371 mac->diff = stop - start;
372 }];
373
374 [buffer release];
375
376 return mac->diff > UINT_MAX ? UINT_MAX : mac->diff;
377 }
378}
379
385FREERDP_ENTRY_POINT(UINT VCAPITYPE mac_freerdp_rdpsnd_client_subsystem_entry(
387{
388 rdpsndMacPlugin *mac;
389 mac = (rdpsndMacPlugin *)calloc(1, sizeof(rdpsndMacPlugin));
390
391 if (!mac)
392 return CHANNEL_RC_NO_MEMORY;
393
394 mac->device.Open = rdpsnd_mac_open;
395 mac->device.FormatSupported = rdpsnd_mac_format_supported;
396 mac->device.SetVolume = rdpsnd_mac_set_volume;
397 mac->device.Play = rdpsnd_mac_play;
398 mac->device.Close = rdpsnd_mac_close;
399 mac->device.Free = rdpsnd_mac_free;
400 pEntryPoints->pRegisterRdpsndDevice(pEntryPoints->rdpsnd, (rdpsndDevicePlugin *)mac);
401 return CHANNEL_RC_OK;
402}