21#include <freerdp/config.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>
34#import <AVFoundation/AVFoundation.h>
36#define __COREFOUNDATION_CFPLUGINCOM__ 1
37#define IUNKNOWN_C_GUTS \
39 void *QueryInterface; \
43#include <CoreAudio/CoreAudioTypes.h>
44#include <CoreAudio/CoreAudio.h>
45#include <AudioToolbox/AudioToolbox.h>
46#include <AudioToolbox/AudioQueue.h>
48#include <freerdp/addin.h>
49#include <freerdp/channels/rdpsnd.h>
51#include "audin_main.h"
53#define MAC_AUDIO_QUEUE_NUM_BUFFERS 100
59typedef UInt32 AudioFormatID;
62#ifndef AudioFormatFlags
63typedef UInt32 AudioFormatFlags;
71 UINT32 FramesPerPacket;
77 rdpContext *rdpcontext;
81 AudioQueueRef audioQueue;
82 AudioStreamBasicDescription audioFormat;
83 AudioQueueBufferRef audioBuffers[MAC_AUDIO_QUEUE_NUM_BUFFERS];
86static AudioFormatID audin_mac_get_format(
const AUDIO_FORMAT *format)
88 switch (format->wFormatTag)
91 return kAudioFormatLinearPCM;
98static AudioFormatFlags audin_mac_get_flags_for_format(
const AUDIO_FORMAT *format)
100 switch (format->wFormatTag)
102 case WAVE_FORMAT_PCM:
103 return kAudioFormatFlagIsSignedInteger;
110static BOOL audin_mac_format_supported(IAudinDevice *device,
const AUDIO_FORMAT *format)
112 AudinMacDevice *mac = (AudinMacDevice *)device;
113 AudioFormatID req_fmt = 0;
115 if (!mac->isAuthorized)
118 if (device ==
nullptr || format ==
nullptr)
121 if (format->nChannels != 2)
124 req_fmt = audin_mac_get_format(format);
137static UINT audin_mac_set_format(IAudinDevice *device,
const AUDIO_FORMAT *format,
138 UINT32 FramesPerPacket)
140 AudinMacDevice *mac = (AudinMacDevice *)device;
142 if (!mac->isAuthorized)
143 return ERROR_INTERNAL_ERROR;
145 if (device ==
nullptr || format ==
nullptr)
146 return ERROR_INVALID_PARAMETER;
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;
155 if (format->wBitsPerSample == 0)
156 mac->audioFormat.mBitsPerChannel = 16;
158 mac->audioFormat.mChannelsPerFrame = mac->format.nChannels;
159 mac->audioFormat.mFramesPerPacket = 1;
161 mac->audioFormat.mBytesPerFrame =
162 mac->audioFormat.mChannelsPerFrame * (mac->audioFormat.mBitsPerChannel / 8);
163 mac->audioFormat.mBytesPerPacket =
164 mac->audioFormat.mBytesPerFrame * mac->audioFormat.mFramesPerPacket;
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;
173static void mac_audio_queue_input_cb(
void *aqData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer,
174 const AudioTimeStamp *inStartTime, UInt32 inNumPackets,
175 const AudioStreamPacketDescription *inPacketDesc)
177 AudinMacDevice *mac = (AudinMacDevice *)aqData;
178 UINT error = CHANNEL_RC_OK;
179 const BYTE *buffer = inBuffer->mAudioData;
180 int buffer_size = inBuffer->mAudioDataByteSize;
187 error = mac->receive(&mac->format, buffer, buffer_size, mac->user_data);
189 AudioQueueEnqueueBuffer(inAQ, inBuffer, 0,
nullptr);
193 WLog_ERR(TAG,
"mac->receive failed with error %" PRIu32
"", error);
194 SetLastError(ERROR_INTERNAL_ERROR);
198static UINT audin_mac_close(IAudinDevice *device)
200 UINT errCode = CHANNEL_RC_OK;
201 AudinMacDevice *mac = (AudinMacDevice *)device;
204 if (!mac->isAuthorized)
206 WLog_ERR(TAG,
"not authorized");
207 return ERROR_INTERNAL_ERROR;
210 if (device ==
nullptr)
212 WLog_ERR(TAG,
"device == nullptr");
213 return ERROR_INVALID_PARAMETER;
218 const OSStatus devStat = AudioQueueStop(mac->audioQueue,
true);
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);
233 const OSStatus devStat = AudioQueueDispose(mac->audioQueue,
true);
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);
243 mac->audioQueue =
nullptr;
246 mac->receive =
nullptr;
247 mac->user_data =
nullptr;
251static UINT audin_mac_open(IAudinDevice *device, AudinReceive receive,
void *user_data)
253 AudinMacDevice *mac = (AudinMacDevice *)device;
255 char errString[1024];
258 if (!mac->isAuthorized)
259 return ERROR_INTERNAL_ERROR;
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));
268 errCode = GetLastError();
269 WLog_ERR(TAG,
"AudioQueueNewInput failed with %s [%" PRIu32
"]",
270 winpr_strerror(errCode, errString,
sizeof(errString)), errCode);
274 for (
size_t index = 0; index < MAC_AUDIO_QUEUE_NUM_BUFFERS; index++)
276 const UINT64 bytes = 2ull * mac->FramesPerPacket * mac->format.nChannels;
277 if (bytes > UINT32_MAX)
280 devStat = AudioQueueAllocateBuffer(mac->audioQueue, WINPR_ASSERTING_INT_CAST(UInt32, bytes),
281 &mac->audioBuffers[index]);
285 errCode = GetLastError();
286 WLog_ERR(TAG,
"AudioQueueAllocateBuffer failed with %s [%" PRIu32
"]",
287 winpr_strerror(errCode, errString,
sizeof(errString)), errCode);
291 devStat = AudioQueueEnqueueBuffer(mac->audioQueue, mac->audioBuffers[index], 0,
nullptr);
295 errCode = GetLastError();
296 WLog_ERR(TAG,
"AudioQueueEnqueueBuffer failed with %s [%" PRIu32
"]",
297 winpr_strerror(errCode, errString,
sizeof(errString)), errCode);
302 devStat = AudioQueueStart(mac->audioQueue,
nullptr);
306 errCode = GetLastError();
307 WLog_ERR(TAG,
"AudioQueueStart failed with %s [%" PRIu32
"]",
308 winpr_strerror(errCode, errString,
sizeof(errString)), errCode);
313 return CHANNEL_RC_OK;
315 (void)audin_mac_close(device);
316 return CHANNEL_RC_INITIALIZATION_ERROR;
319static UINT audin_mac_free(IAudinDevice *device)
321 AudinMacDevice *mac = (AudinMacDevice *)device;
324 if (device ==
nullptr)
325 return ERROR_INVALID_PARAMETER;
327 (void)audin_mac_close(device);
330 return CHANNEL_RC_OK;
333static UINT audin_mac_parse_addin_args(AudinMacDevice *device,
const ADDIN_ARGV *args)
336 char errString[1024];
338 char *str_num, *eptr;
342 {
"dev", COMMAND_LINE_VALUE_REQUIRED,
"<device>",
nullptr,
nullptr, -1, nullptr,
343 "audio device name" },
344 {
nullptr, 0,
nullptr,
nullptr,
nullptr, -1, nullptr,
nullptr }
347 AudinMacDevice *mac = (AudinMacDevice *)device;
350 return CHANNEL_RC_OK;
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,
358 return ERROR_INVALID_PARAMETER;
360 arg = audin_mac_args;
364 if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT))
367 CommandLineSwitchStart(arg) CommandLineSwitchCase(arg,
"dev")
369 str_num = _strdup(arg->Value);
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;
379 mac->dev_unit = strtol(str_num, &eptr, 10);
381 if (mac->dev_unit < 0 || *eptr !=
'\0')
386 CommandLineSwitchEnd(arg)
387 }
while ((arg = CommandLineFindNextArgumentA(arg)) !=
nullptr);
389 return CHANNEL_RC_OK;
392FREERDP_ENTRY_POINT(UINT VCAPITYPE mac_freerdp_audin_client_subsystem_entry(
396 char errString[1024];
400 mac = (AudinMacDevice *)calloc(1,
sizeof(AudinMacDevice));
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;
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;
417 args = pEntryPoints->args;
419 if ((error = audin_mac_parse_addin_args(mac, args)))
421 WLog_ERR(TAG,
"audin_mac_parse_addin_args failed with %" PRIu32
"!", error);
425 if ((error = pEntryPoints->pRegisterAudinDevice(pEntryPoints->plugin, (IAudinDevice *)mac)))
427 WLog_ERR(TAG,
"RegisterAudinDevice failed with error %" PRIu32
"!", error);
431#if defined(MAC_OS_X_VERSION_10_14)
432 if (@available(macOS 10.14, *))
436 AVAuthorizationStatus status =
437 [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio];
440 case AVAuthorizationStatusAuthorized:
441 mac->isAuthorized = TRUE;
443 case AVAuthorizationStatusNotDetermined:
445 requestAccessForMediaType:AVMediaTypeAudio
446 completionHandler:^(BOOL granted) {
449 mac->isAuthorized = TRUE;
452 WLog_WARN(TAG, "Microphone access denied by user");
455 case AVAuthorizationStatusRestricted:
456 WLog_WARN(TAG,
"Microphone access restricted by policy");
458 case AVAuthorizationStatusDenied:
459 WLog_WARN(TAG,
"Microphone access denied by policy");
468 return CHANNEL_RC_OK;