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 const UINT64 bytes = 2ull * mac->FramesPerPacket * mac->format.nChannels;
277 if (bytes > UINT32_MAX)
278 goto err_out;
279
280 devStat = AudioQueueAllocateBuffer(mac->audioQueue, WINPR_ASSERTING_INT_CAST(UInt32, bytes),
281 &mac->audioBuffers[index]);
282
283 if (devStat != 0)
284 {
285 errCode = GetLastError();
286 WLog_ERR(TAG, "AudioQueueAllocateBuffer failed with %s [%" PRIu32 "]",
287 winpr_strerror(errCode, errString, sizeof(errString)), errCode);
288 goto err_out;
289 }
290
291 devStat = AudioQueueEnqueueBuffer(mac->audioQueue, mac->audioBuffers[index], 0, nullptr);
292
293 if (devStat != 0)
294 {
295 errCode = GetLastError();
296 WLog_ERR(TAG, "AudioQueueEnqueueBuffer failed with %s [%" PRIu32 "]",
297 winpr_strerror(errCode, errString, sizeof(errString)), errCode);
298 goto err_out;
299 }
300 }
301
302 devStat = AudioQueueStart(mac->audioQueue, nullptr);
303
304 if (devStat != 0)
305 {
306 errCode = GetLastError();
307 WLog_ERR(TAG, "AudioQueueStart failed with %s [%" PRIu32 "]",
308 winpr_strerror(errCode, errString, sizeof(errString)), errCode);
309 goto err_out;
310 }
311
312 mac->isOpen = true;
313 return CHANNEL_RC_OK;
314err_out:
315 (void)audin_mac_close(device);
316 return CHANNEL_RC_INITIALIZATION_ERROR;
317}
318
319static UINT audin_mac_free(IAudinDevice *device)
320{
321 AudinMacDevice *mac = (AudinMacDevice *)device;
322 int error;
323
324 if (device == nullptr)
325 return ERROR_INVALID_PARAMETER;
326
327 (void)audin_mac_close(device);
328
329 free(mac);
330 return CHANNEL_RC_OK;
331}
332
333static UINT audin_mac_parse_addin_args(AudinMacDevice *device, const ADDIN_ARGV *args)
334{
335 DWORD errCode;
336 char errString[1024];
337 int status;
338 char *str_num, *eptr;
339 DWORD flags;
340 const COMMAND_LINE_ARGUMENT_A *arg;
341 COMMAND_LINE_ARGUMENT_A audin_mac_args[] = {
342 { "dev", COMMAND_LINE_VALUE_REQUIRED, "<device>", nullptr, nullptr, -1, nullptr,
343 "audio device name" },
344 { nullptr, 0, nullptr, nullptr, nullptr, -1, nullptr, nullptr }
345 };
346
347 AudinMacDevice *mac = (AudinMacDevice *)device;
348
349 if (args->argc == 1)
350 return CHANNEL_RC_OK;
351
352 flags =
353 COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD;
354 status = CommandLineParseArgumentsA(args->argc, args->argv, audin_mac_args, flags, mac, nullptr,
355 nullptr);
356
357 if (status < 0)
358 return ERROR_INVALID_PARAMETER;
359
360 arg = audin_mac_args;
361
362 do
363 {
364 if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT))
365 continue;
366
367 CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "dev")
368 {
369 str_num = _strdup(arg->Value);
370
371 if (!str_num)
372 {
373 errCode = GetLastError();
374 WLog_ERR(TAG, "_strdup failed with %s [%d]",
375 winpr_strerror(errCode, errString, sizeof(errString)), errCode);
376 return CHANNEL_RC_NO_MEMORY;
377 }
378
379 mac->dev_unit = strtol(str_num, &eptr, 10);
380
381 if (mac->dev_unit < 0 || *eptr != '\0')
382 mac->dev_unit = -1;
383
384 free(str_num);
385 }
386 CommandLineSwitchEnd(arg)
387 } while ((arg = CommandLineFindNextArgumentA(arg)) != nullptr);
388
389 return CHANNEL_RC_OK;
390}
391
392FREERDP_ENTRY_POINT(UINT VCAPITYPE mac_freerdp_audin_client_subsystem_entry(
394{
395 DWORD errCode;
396 char errString[1024];
397 const ADDIN_ARGV *args;
398 AudinMacDevice *mac;
399 UINT error;
400 mac = (AudinMacDevice *)calloc(1, sizeof(AudinMacDevice));
401
402 if (!mac)
403 {
404 errCode = GetLastError();
405 WLog_ERR(TAG, "calloc failed with %s [%" PRIu32 "]",
406 winpr_strerror(errCode, errString, sizeof(errString)), errCode);
407 return CHANNEL_RC_NO_MEMORY;
408 }
409
410 mac->iface.Open = audin_mac_open;
411 mac->iface.FormatSupported = audin_mac_format_supported;
412 mac->iface.SetFormat = audin_mac_set_format;
413 mac->iface.Close = audin_mac_close;
414 mac->iface.Free = audin_mac_free;
415 mac->rdpcontext = pEntryPoints->rdpcontext;
416 mac->dev_unit = -1;
417 args = pEntryPoints->args;
418
419 if ((error = audin_mac_parse_addin_args(mac, args)))
420 {
421 WLog_ERR(TAG, "audin_mac_parse_addin_args failed with %" PRIu32 "!", error);
422 goto error_out;
423 }
424
425 if ((error = pEntryPoints->pRegisterAudinDevice(pEntryPoints->plugin, (IAudinDevice *)mac)))
426 {
427 WLog_ERR(TAG, "RegisterAudinDevice failed with error %" PRIu32 "!", error);
428 goto error_out;
429 }
430
431#if defined(MAC_OS_X_VERSION_10_14)
432 if (@available(macOS 10.14, *))
433 {
434 @autoreleasepool
435 {
436 AVAuthorizationStatus status =
437 [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio];
438 switch (status)
439 {
440 case AVAuthorizationStatusAuthorized:
441 mac->isAuthorized = TRUE;
442 break;
443 case AVAuthorizationStatusNotDetermined:
444 [AVCaptureDevice
445 requestAccessForMediaType:AVMediaTypeAudio
446 completionHandler:^(BOOL granted) {
447 if (granted == YES)
448 {
449 mac->isAuthorized = TRUE;
450 }
451 else
452 WLog_WARN(TAG, "Microphone access denied by user");
453 }];
454 break;
455 case AVAuthorizationStatusRestricted:
456 WLog_WARN(TAG, "Microphone access restricted by policy");
457 break;
458 case AVAuthorizationStatusDenied:
459 WLog_WARN(TAG, "Microphone access denied by policy");
460 break;
461 default:
462 break;
463 }
464 }
465 }
466#endif
467
468 return CHANNEL_RC_OK;
469error_out:
470 free(mac);
471 return error;
472}