FreeRDP
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 
40 typedef 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 
55 static 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 
69 static 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 
126 static 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 
137 static 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 
212 static 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 
234 static void rdpsnd_mac_free(rdpsndDevicePlugin *device)
235 {
236  rdpsndMacPlugin *mac = (rdpsndMacPlugin *)device;
237  device->Close(device);
238  free(mac);
239 }
240 
241 static 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 
260 static 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 
282 static 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 
311 static 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 
385 FREERDP_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 }
Definition: client/rdpsnd.h:76