FreeRDP
audin_mac.m
1 
21 #include <freerdp/config.h>
22 
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 
27 #include <winpr/crt.h>
28 #include <winpr/synch.h>
29 #include <winpr/string.h>
30 #include <winpr/thread.h>
31 #include <winpr/debug.h>
32 #include <winpr/cmdline.h>
33 
34 #import <AVFoundation/AVFoundation.h>
35 
36 #define __COREFOUNDATION_CFPLUGINCOM__ 1
37 #define IUNKNOWN_C_GUTS \
38  void *_reserved; \
39  void *QueryInterface; \
40  void *AddRef; \
41  void *Release
42 
43 #include <CoreAudio/CoreAudioTypes.h>
44 #include <CoreAudio/CoreAudio.h>
45 #include <AudioToolbox/AudioToolbox.h>
46 #include <AudioToolbox/AudioQueue.h>
47 
48 #include <freerdp/addin.h>
49 #include <freerdp/channels/rdpsnd.h>
50 
51 #include "audin_main.h"
52 
53 #define MAC_AUDIO_QUEUE_NUM_BUFFERS 100
54 
55 /* Fix for #4462: Provide type alias if not declared (Mac OS < 10.10)
56  * https://developer.apple.com/documentation/coreaudio/audioformatid
57  */
58 #ifndef AudioFormatID
59 typedef UInt32 AudioFormatID;
60 #endif
61 
62 #ifndef AudioFormatFlags
63 typedef UInt32 AudioFormatFlags;
64 #endif
65 
66 typedef struct
67 {
68  IAudinDevice iface;
69 
70  AUDIO_FORMAT format;
71  UINT32 FramesPerPacket;
72  int dev_unit;
73 
74  AudinReceive receive;
75  void *user_data;
76 
77  rdpContext *rdpcontext;
78 
79  bool isAuthorized;
80  bool isOpen;
81  AudioQueueRef audioQueue;
82  AudioStreamBasicDescription audioFormat;
83  AudioQueueBufferRef audioBuffers[MAC_AUDIO_QUEUE_NUM_BUFFERS];
84 } AudinMacDevice;
85 
86 static AudioFormatID audin_mac_get_format(const AUDIO_FORMAT *format)
87 {
88  switch (format->wFormatTag)
89  {
90  case WAVE_FORMAT_PCM:
91  return kAudioFormatLinearPCM;
92 
93  default:
94  return 0;
95  }
96 }
97 
98 static AudioFormatFlags audin_mac_get_flags_for_format(const AUDIO_FORMAT *format)
99 {
100  switch (format->wFormatTag)
101  {
102  case WAVE_FORMAT_PCM:
103  return kAudioFormatFlagIsSignedInteger;
104 
105  default:
106  return 0;
107  }
108 }
109 
110 static BOOL audin_mac_format_supported(IAudinDevice *device, const AUDIO_FORMAT *format)
111 {
112  AudinMacDevice *mac = (AudinMacDevice *)device;
113  AudioFormatID req_fmt = 0;
114 
115  if (!mac->isAuthorized)
116  return FALSE;
117 
118  if (device == NULL || format == NULL)
119  return FALSE;
120 
121  if (format->nChannels != 2)
122  return FALSE;
123 
124  req_fmt = audin_mac_get_format(format);
125 
126  if (req_fmt == 0)
127  return FALSE;
128 
129  return TRUE;
130 }
131 
137 static UINT audin_mac_set_format(IAudinDevice *device, const AUDIO_FORMAT *format,
138  UINT32 FramesPerPacket)
139 {
140  AudinMacDevice *mac = (AudinMacDevice *)device;
141 
142  if (!mac->isAuthorized)
143  return ERROR_INTERNAL_ERROR;
144 
145  if (device == NULL || format == NULL)
146  return ERROR_INVALID_PARAMETER;
147 
148  mac->FramesPerPacket = FramesPerPacket;
149  mac->format = *format;
150  WLog_INFO(TAG, "Audio Format %s [channels=%d, samples=%d, bits=%d]",
151  audio_format_get_tag_string(format->wFormatTag), format->nChannels,
152  format->nSamplesPerSec, format->wBitsPerSample);
153  mac->audioFormat.mBitsPerChannel = format->wBitsPerSample;
154 
155  if (format->wBitsPerSample == 0)
156  mac->audioFormat.mBitsPerChannel = 16;
157 
158  mac->audioFormat.mChannelsPerFrame = mac->format.nChannels;
159  mac->audioFormat.mFramesPerPacket = 1;
160 
161  mac->audioFormat.mBytesPerFrame =
162  mac->audioFormat.mChannelsPerFrame * (mac->audioFormat.mBitsPerChannel / 8);
163  mac->audioFormat.mBytesPerPacket =
164  mac->audioFormat.mBytesPerFrame * mac->audioFormat.mFramesPerPacket;
165 
166  mac->audioFormat.mFormatFlags = audin_mac_get_flags_for_format(format);
167  mac->audioFormat.mFormatID = audin_mac_get_format(format);
168  mac->audioFormat.mReserved = 0;
169  mac->audioFormat.mSampleRate = mac->format.nSamplesPerSec;
170  return CHANNEL_RC_OK;
171 }
172 
173 static void mac_audio_queue_input_cb(void *aqData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer,
174  const AudioTimeStamp *inStartTime, UInt32 inNumPackets,
175  const AudioStreamPacketDescription *inPacketDesc)
176 {
177  AudinMacDevice *mac = (AudinMacDevice *)aqData;
178  UINT error = CHANNEL_RC_OK;
179  const BYTE *buffer = inBuffer->mAudioData;
180  int buffer_size = inBuffer->mAudioDataByteSize;
181  (void)inAQ;
182  (void)inStartTime;
183  (void)inNumPackets;
184  (void)inPacketDesc;
185 
186  if (buffer_size > 0)
187  error = mac->receive(&mac->format, buffer, buffer_size, mac->user_data);
188 
189  AudioQueueEnqueueBuffer(inAQ, inBuffer, 0, NULL);
190 
191  if (error)
192  {
193  WLog_ERR(TAG, "mac->receive failed with error %" PRIu32 "", error);
194  SetLastError(ERROR_INTERNAL_ERROR);
195  }
196 }
197 
198 static UINT audin_mac_close(IAudinDevice *device)
199 {
200  UINT errCode = CHANNEL_RC_OK;
201  char errString[1024];
202  OSStatus devStat;
203  AudinMacDevice *mac = (AudinMacDevice *)device;
204 
205  if (!mac->isAuthorized)
206  return ERROR_INTERNAL_ERROR;
207 
208  if (device == NULL)
209  return ERROR_INVALID_PARAMETER;
210 
211  if (mac->isOpen)
212  {
213  devStat = AudioQueueStop(mac->audioQueue, true);
214 
215  if (devStat != 0)
216  {
217  errCode = GetLastError();
218  WLog_ERR(TAG, "AudioQueueStop failed with %s [%" PRIu32 "]",
219  winpr_strerror(errCode, errString, sizeof(errString)), errCode);
220  }
221 
222  mac->isOpen = false;
223  }
224 
225  if (mac->audioQueue)
226  {
227  devStat = AudioQueueDispose(mac->audioQueue, true);
228 
229  if (devStat != 0)
230  {
231  errCode = GetLastError();
232  WLog_ERR(TAG, "AudioQueueDispose failed with %s [%" PRIu32 "]",
233  winpr_strerror(errCode, errString, sizeof(errString)), errCode);
234  }
235 
236  mac->audioQueue = NULL;
237  }
238 
239  mac->receive = NULL;
240  mac->user_data = NULL;
241  return errCode;
242 }
243 
244 static UINT audin_mac_open(IAudinDevice *device, AudinReceive receive, void *user_data)
245 {
246  AudinMacDevice *mac = (AudinMacDevice *)device;
247  DWORD errCode;
248  char errString[1024];
249  OSStatus devStat;
250 
251  if (!mac->isAuthorized)
252  return ERROR_INTERNAL_ERROR;
253 
254  mac->receive = receive;
255  mac->user_data = user_data;
256  devStat = AudioQueueNewInput(&(mac->audioFormat), mac_audio_queue_input_cb, mac, NULL,
257  kCFRunLoopCommonModes, 0, &(mac->audioQueue));
258 
259  if (devStat != 0)
260  {
261  errCode = GetLastError();
262  WLog_ERR(TAG, "AudioQueueNewInput failed with %s [%" PRIu32 "]",
263  winpr_strerror(errCode, errString, sizeof(errString)), errCode);
264  goto err_out;
265  }
266 
267  for (size_t index = 0; index < MAC_AUDIO_QUEUE_NUM_BUFFERS; index++)
268  {
269  devStat = AudioQueueAllocateBuffer(mac->audioQueue,
270  mac->FramesPerPacket * 2 * mac->format.nChannels,
271  &mac->audioBuffers[index]);
272 
273  if (devStat != 0)
274  {
275  errCode = GetLastError();
276  WLog_ERR(TAG, "AudioQueueAllocateBuffer failed with %s [%" PRIu32 "]",
277  winpr_strerror(errCode, errString, sizeof(errString)), errCode);
278  goto err_out;
279  }
280 
281  devStat = AudioQueueEnqueueBuffer(mac->audioQueue, mac->audioBuffers[index], 0, NULL);
282 
283  if (devStat != 0)
284  {
285  errCode = GetLastError();
286  WLog_ERR(TAG, "AudioQueueEnqueueBuffer failed with %s [%" PRIu32 "]",
287  winpr_strerror(errCode, errString, sizeof(errString)), errCode);
288  goto err_out;
289  }
290  }
291 
292  devStat = AudioQueueStart(mac->audioQueue, NULL);
293 
294  if (devStat != 0)
295  {
296  errCode = GetLastError();
297  WLog_ERR(TAG, "AudioQueueStart failed with %s [%" PRIu32 "]",
298  winpr_strerror(errCode, errString, sizeof(errString)), errCode);
299  goto err_out;
300  }
301 
302  mac->isOpen = true;
303  return CHANNEL_RC_OK;
304 err_out:
305  audin_mac_close(device);
306  return CHANNEL_RC_INITIALIZATION_ERROR;
307 }
308 
309 static UINT audin_mac_free(IAudinDevice *device)
310 {
311  AudinMacDevice *mac = (AudinMacDevice *)device;
312  int error;
313 
314  if (device == NULL)
315  return ERROR_INVALID_PARAMETER;
316 
317  if ((error = audin_mac_close(device)))
318  {
319  WLog_ERR(TAG, "audin_oss_close failed with error code %d!", error);
320  }
321 
322  free(mac);
323  return CHANNEL_RC_OK;
324 }
325 
326 static UINT audin_mac_parse_addin_args(AudinMacDevice *device, const ADDIN_ARGV *args)
327 {
328  DWORD errCode;
329  char errString[1024];
330  int status;
331  char *str_num, *eptr;
332  DWORD flags;
333  const COMMAND_LINE_ARGUMENT_A *arg;
334  COMMAND_LINE_ARGUMENT_A audin_mac_args[] = { { "dev", COMMAND_LINE_VALUE_REQUIRED, "<device>",
335  NULL, NULL, -1, NULL, "audio device name" },
336  { NULL, 0, NULL, NULL, NULL, -1, NULL, NULL } };
337 
338  AudinMacDevice *mac = (AudinMacDevice *)device;
339 
340  if (args->argc == 1)
341  return CHANNEL_RC_OK;
342 
343  flags =
344  COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD;
345  status =
346  CommandLineParseArgumentsA(args->argc, args->argv, audin_mac_args, flags, mac, NULL, NULL);
347 
348  if (status < 0)
349  return ERROR_INVALID_PARAMETER;
350 
351  arg = audin_mac_args;
352 
353  do
354  {
355  if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT))
356  continue;
357 
358  CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "dev")
359  {
360  str_num = _strdup(arg->Value);
361 
362  if (!str_num)
363  {
364  errCode = GetLastError();
365  WLog_ERR(TAG, "_strdup failed with %s [%d]",
366  winpr_strerror(errCode, errString, sizeof(errString)), errCode);
367  return CHANNEL_RC_NO_MEMORY;
368  }
369 
370  mac->dev_unit = strtol(str_num, &eptr, 10);
371 
372  if (mac->dev_unit < 0 || *eptr != '\0')
373  mac->dev_unit = -1;
374 
375  free(str_num);
376  }
377  CommandLineSwitchEnd(arg)
378  } while ((arg = CommandLineFindNextArgumentA(arg)) != NULL);
379 
380  return CHANNEL_RC_OK;
381 }
382 
383 FREERDP_ENTRY_POINT(UINT VCAPITYPE mac_freerdp_audin_client_subsystem_entry(
385 {
386  DWORD errCode;
387  char errString[1024];
388  const ADDIN_ARGV *args;
389  AudinMacDevice *mac;
390  UINT error;
391  mac = (AudinMacDevice *)calloc(1, sizeof(AudinMacDevice));
392 
393  if (!mac)
394  {
395  errCode = GetLastError();
396  WLog_ERR(TAG, "calloc failed with %s [%" PRIu32 "]",
397  winpr_strerror(errCode, errString, sizeof(errString)), errCode);
398  return CHANNEL_RC_NO_MEMORY;
399  }
400 
401  mac->iface.Open = audin_mac_open;
402  mac->iface.FormatSupported = audin_mac_format_supported;
403  mac->iface.SetFormat = audin_mac_set_format;
404  mac->iface.Close = audin_mac_close;
405  mac->iface.Free = audin_mac_free;
406  mac->rdpcontext = pEntryPoints->rdpcontext;
407  mac->dev_unit = -1;
408  args = pEntryPoints->args;
409 
410  if ((error = audin_mac_parse_addin_args(mac, args)))
411  {
412  WLog_ERR(TAG, "audin_mac_parse_addin_args failed with %" PRIu32 "!", error);
413  goto error_out;
414  }
415 
416  if ((error = pEntryPoints->pRegisterAudinDevice(pEntryPoints->plugin, (IAudinDevice *)mac)))
417  {
418  WLog_ERR(TAG, "RegisterAudinDevice failed with error %" PRIu32 "!", error);
419  goto error_out;
420  }
421 
422 #if defined(MAC_OS_X_VERSION_10_14)
423  if (@available(macOS 10.14, *))
424  {
425  @autoreleasepool
426  {
427  AVAuthorizationStatus status =
428  [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio];
429  switch (status)
430  {
431  case AVAuthorizationStatusAuthorized:
432  mac->isAuthorized = TRUE;
433  break;
434  case AVAuthorizationStatusNotDetermined:
435  [AVCaptureDevice
436  requestAccessForMediaType:AVMediaTypeAudio
437  completionHandler:^(BOOL granted) {
438  if (granted == YES)
439  {
440  mac->isAuthorized = TRUE;
441  }
442  else
443  WLog_WARN(TAG, "Microphone access denied by user");
444  }];
445  break;
446  case AVAuthorizationStatusRestricted:
447  WLog_WARN(TAG, "Microphone access restricted by policy");
448  break;
449  case AVAuthorizationStatusDenied:
450  WLog_WARN(TAG, "Microphone access denied by policy");
451  break;
452  default:
453  break;
454  }
455  }
456  }
457 #endif
458 
459  return CHANNEL_RC_OK;
460 error_out:
461  free(mac);
462  return error;
463 }
Definition: client/audin.h:59