FreeRDP
Loading...
Searching...
No Matches
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 == nullptr || format == nullptr)
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 == nullptr || format == nullptr)
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, nullptr);
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 AudinMacDevice *mac = (AudinMacDevice *)device;
202
203 WINPR_ASSERT(mac);
204 if (!mac->isAuthorized)
205 {
206 WLog_ERR(TAG, "not authorized");
207 return ERROR_INTERNAL_ERROR;
208 }
209
210 if (device == nullptr)
211 {
212 WLog_ERR(TAG, "device == nullptr");
213 return ERROR_INVALID_PARAMETER;
214 }
215
216 if (mac->isOpen)
217 {
218 const OSStatus devStat = AudioQueueStop(mac->audioQueue, true);
219
220 if (devStat != 0)
221 {
222 errCode = GetLastError();
223 char errString[1024] = WINPR_C_ARRAY_INIT;
224 WLog_ERR(TAG, "AudioQueueStop failed with %s [%" PRIu32 "]",
225 winpr_strerror(errCode, errString, sizeof(errString)), errCode);
226 }
227
228 mac->isOpen = false;
229 }
230
231 if (mac->audioQueue)
232 {
233 const OSStatus devStat = AudioQueueDispose(mac->audioQueue, true);
234
235 if (devStat != 0)
236 {
237 errCode = GetLastError();
238 char errString[1024] = WINPR_C_ARRAY_INIT;
239 WLog_ERR(TAG, "AudioQueueDispose failed with %s [%" PRIu32 "]",
240 winpr_strerror(errCode, errString, sizeof(errString)), errCode);
241 }
242
243 mac->audioQueue = nullptr;
244 }
245
246 mac->receive = nullptr;
247 mac->user_data = nullptr;
248 return errCode;
249}
250
251static UINT audin_mac_open(IAudinDevice *device, AudinReceive receive, void *user_data)
252{
253 AudinMacDevice *mac = (AudinMacDevice *)device;
254 DWORD errCode;
255 char errString[1024];
256 OSStatus devStat;
257
258 if (!mac->isAuthorized)
259 return ERROR_INTERNAL_ERROR;
260
261 mac->receive = receive;
262 mac->user_data = user_data;
263 devStat = AudioQueueNewInput(&(mac->audioFormat), mac_audio_queue_input_cb, mac, nullptr,
264 kCFRunLoopCommonModes, 0, &(mac->audioQueue));
265
266 if (devStat != 0)
267 {
268 errCode = GetLastError();
269 WLog_ERR(TAG, "AudioQueueNewInput failed with %s [%" PRIu32 "]",
270 winpr_strerror(errCode, errString, sizeof(errString)), errCode);
271 goto err_out;
272 }
273
274 for (size_t index = 0; index < MAC_AUDIO_QUEUE_NUM_BUFFERS; index++)
275 {
276 devStat = AudioQueueAllocateBuffer(mac->audioQueue,
277 mac->FramesPerPacket * 2 * mac->format.nChannels,
278 &mac->audioBuffers[index]);
279
280 if (devStat != 0)
281 {
282 errCode = GetLastError();
283 WLog_ERR(TAG, "AudioQueueAllocateBuffer failed with %s [%" PRIu32 "]",
284 winpr_strerror(errCode, errString, sizeof(errString)), errCode);
285 goto err_out;
286 }
287
288 devStat = AudioQueueEnqueueBuffer(mac->audioQueue, mac->audioBuffers[index], 0, nullptr);
289
290 if (devStat != 0)
291 {
292 errCode = GetLastError();
293 WLog_ERR(TAG, "AudioQueueEnqueueBuffer failed with %s [%" PRIu32 "]",
294 winpr_strerror(errCode, errString, sizeof(errString)), errCode);
295 goto err_out;
296 }
297 }
298
299 devStat = AudioQueueStart(mac->audioQueue, nullptr);
300
301 if (devStat != 0)
302 {
303 errCode = GetLastError();
304 WLog_ERR(TAG, "AudioQueueStart failed with %s [%" PRIu32 "]",
305 winpr_strerror(errCode, errString, sizeof(errString)), errCode);
306 goto err_out;
307 }
308
309 mac->isOpen = true;
310 return CHANNEL_RC_OK;
311err_out:
312 (void)audin_mac_close(device);
313 return CHANNEL_RC_INITIALIZATION_ERROR;
314}
315
316static UINT audin_mac_free(IAudinDevice *device)
317{
318 AudinMacDevice *mac = (AudinMacDevice *)device;
319 int error;
320
321 if (device == nullptr)
322 return ERROR_INVALID_PARAMETER;
323
324 (void)audin_mac_close(device);
325
326 free(mac);
327 return CHANNEL_RC_OK;
328}
329
330static UINT audin_mac_parse_addin_args(AudinMacDevice *device, const ADDIN_ARGV *args)
331{
332 DWORD errCode;
333 char errString[1024];
334 int status;
335 char *str_num, *eptr;
336 DWORD flags;
337 const COMMAND_LINE_ARGUMENT_A *arg;
338 COMMAND_LINE_ARGUMENT_A audin_mac_args[] = {
339 { "dev", COMMAND_LINE_VALUE_REQUIRED, "<device>", nullptr, nullptr, -1, nullptr,
340 "audio device name" },
341 { nullptr, 0, nullptr, nullptr, nullptr, -1, nullptr, nullptr }
342 };
343
344 AudinMacDevice *mac = (AudinMacDevice *)device;
345
346 if (args->argc == 1)
347 return CHANNEL_RC_OK;
348
349 flags =
350 COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD;
351 status = CommandLineParseArgumentsA(args->argc, args->argv, audin_mac_args, flags, mac, nullptr,
352 nullptr);
353
354 if (status < 0)
355 return ERROR_INVALID_PARAMETER;
356
357 arg = audin_mac_args;
358
359 do
360 {
361 if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT))
362 continue;
363
364 CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "dev")
365 {
366 str_num = _strdup(arg->Value);
367
368 if (!str_num)
369 {
370 errCode = GetLastError();
371 WLog_ERR(TAG, "_strdup failed with %s [%d]",
372 winpr_strerror(errCode, errString, sizeof(errString)), errCode);
373 return CHANNEL_RC_NO_MEMORY;
374 }
375
376 mac->dev_unit = strtol(str_num, &eptr, 10);
377
378 if (mac->dev_unit < 0 || *eptr != '\0')
379 mac->dev_unit = -1;
380
381 free(str_num);
382 }
383 CommandLineSwitchEnd(arg)
384 } while ((arg = CommandLineFindNextArgumentA(arg)) != nullptr);
385
386 return CHANNEL_RC_OK;
387}
388
389FREERDP_ENTRY_POINT(UINT VCAPITYPE mac_freerdp_audin_client_subsystem_entry(
391{
392 DWORD errCode;
393 char errString[1024];
394 const ADDIN_ARGV *args;
395 AudinMacDevice *mac;
396 UINT error;
397 mac = (AudinMacDevice *)calloc(1, sizeof(AudinMacDevice));
398
399 if (!mac)
400 {
401 errCode = GetLastError();
402 WLog_ERR(TAG, "calloc failed with %s [%" PRIu32 "]",
403 winpr_strerror(errCode, errString, sizeof(errString)), errCode);
404 return CHANNEL_RC_NO_MEMORY;
405 }
406
407 mac->iface.Open = audin_mac_open;
408 mac->iface.FormatSupported = audin_mac_format_supported;
409 mac->iface.SetFormat = audin_mac_set_format;
410 mac->iface.Close = audin_mac_close;
411 mac->iface.Free = audin_mac_free;
412 mac->rdpcontext = pEntryPoints->rdpcontext;
413 mac->dev_unit = -1;
414 args = pEntryPoints->args;
415
416 if ((error = audin_mac_parse_addin_args(mac, args)))
417 {
418 WLog_ERR(TAG, "audin_mac_parse_addin_args failed with %" PRIu32 "!", error);
419 goto error_out;
420 }
421
422 if ((error = pEntryPoints->pRegisterAudinDevice(pEntryPoints->plugin, (IAudinDevice *)mac)))
423 {
424 WLog_ERR(TAG, "RegisterAudinDevice failed with error %" PRIu32 "!", error);
425 goto error_out;
426 }
427
428#if defined(MAC_OS_X_VERSION_10_14)
429 if (@available(macOS 10.14, *))
430 {
431 @autoreleasepool
432 {
433 AVAuthorizationStatus status =
434 [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio];
435 switch (status)
436 {
437 case AVAuthorizationStatusAuthorized:
438 mac->isAuthorized = TRUE;
439 break;
440 case AVAuthorizationStatusNotDetermined:
441 [AVCaptureDevice
442 requestAccessForMediaType:AVMediaTypeAudio
443 completionHandler:^(BOOL granted) {
444 if (granted == YES)
445 {
446 mac->isAuthorized = TRUE;
447 }
448 else
449 WLog_WARN(TAG, "Microphone access denied by user");
450 }];
451 break;
452 case AVAuthorizationStatusRestricted:
453 WLog_WARN(TAG, "Microphone access restricted by policy");
454 break;
455 case AVAuthorizationStatusDenied:
456 WLog_WARN(TAG, "Microphone access denied by policy");
457 break;
458 default:
459 break;
460 }
461 }
462 }
463#endif
464
465 return CHANNEL_RC_OK;
466error_out:
467 free(mac);
468 return error;
469}