FreeRDP
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Modules Pages
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
59typedef UInt32 AudioFormatID;
60#endif
61
62#ifndef AudioFormatFlags
63typedef UInt32 AudioFormatFlags;
64#endif
65
66typedef 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
86static 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
98static 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
110static 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
137static 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
173static 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
198static 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
244static 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;
304err_out:
305 audin_mac_close(device);
306 return CHANNEL_RC_INITIALIZATION_ERROR;
307}
308
309static 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
326static 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
383FREERDP_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;
460error_out:
461 free(mac);
462 return error;
463}