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 devStat = AudioQueueAllocateBuffer(mac->audioQueue,
277 mac->FramesPerPacket * 2 * mac->format.nChannels,
278 &mac->audioBuffers[index]);
282 errCode = GetLastError();
283 WLog_ERR(TAG,
"AudioQueueAllocateBuffer failed with %s [%" PRIu32
"]",
284 winpr_strerror(errCode, errString,
sizeof(errString)), errCode);
288 devStat = AudioQueueEnqueueBuffer(mac->audioQueue, mac->audioBuffers[index], 0,
nullptr);
292 errCode = GetLastError();
293 WLog_ERR(TAG,
"AudioQueueEnqueueBuffer failed with %s [%" PRIu32
"]",
294 winpr_strerror(errCode, errString,
sizeof(errString)), errCode);
299 devStat = AudioQueueStart(mac->audioQueue,
nullptr);
303 errCode = GetLastError();
304 WLog_ERR(TAG,
"AudioQueueStart failed with %s [%" PRIu32
"]",
305 winpr_strerror(errCode, errString,
sizeof(errString)), errCode);
310 return CHANNEL_RC_OK;
312 (void)audin_mac_close(device);
313 return CHANNEL_RC_INITIALIZATION_ERROR;
316static UINT audin_mac_free(IAudinDevice *device)
318 AudinMacDevice *mac = (AudinMacDevice *)device;
321 if (device ==
nullptr)
322 return ERROR_INVALID_PARAMETER;
324 (void)audin_mac_close(device);
327 return CHANNEL_RC_OK;
330static UINT audin_mac_parse_addin_args(AudinMacDevice *device,
const ADDIN_ARGV *args)
333 char errString[1024];
335 char *str_num, *eptr;
339 {
"dev", COMMAND_LINE_VALUE_REQUIRED,
"<device>",
nullptr,
nullptr, -1, nullptr,
340 "audio device name" },
341 {
nullptr, 0,
nullptr,
nullptr,
nullptr, -1, nullptr,
nullptr }
344 AudinMacDevice *mac = (AudinMacDevice *)device;
347 return CHANNEL_RC_OK;
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,
355 return ERROR_INVALID_PARAMETER;
357 arg = audin_mac_args;
361 if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT))
364 CommandLineSwitchStart(arg) CommandLineSwitchCase(arg,
"dev")
366 str_num = _strdup(arg->Value);
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;
376 mac->dev_unit = strtol(str_num, &eptr, 10);
378 if (mac->dev_unit < 0 || *eptr !=
'\0')
383 CommandLineSwitchEnd(arg)
384 }
while ((arg = CommandLineFindNextArgumentA(arg)) !=
nullptr);
386 return CHANNEL_RC_OK;
389FREERDP_ENTRY_POINT(UINT VCAPITYPE mac_freerdp_audin_client_subsystem_entry(
393 char errString[1024];
397 mac = (AudinMacDevice *)calloc(1,
sizeof(AudinMacDevice));
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;
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;
414 args = pEntryPoints->args;
416 if ((error = audin_mac_parse_addin_args(mac, args)))
418 WLog_ERR(TAG,
"audin_mac_parse_addin_args failed with %" PRIu32
"!", error);
422 if ((error = pEntryPoints->pRegisterAudinDevice(pEntryPoints->plugin, (IAudinDevice *)mac)))
424 WLog_ERR(TAG,
"RegisterAudinDevice failed with error %" PRIu32
"!", error);
428#if defined(MAC_OS_X_VERSION_10_14)
429 if (@available(macOS 10.14, *))
433 AVAuthorizationStatus status =
434 [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio];
437 case AVAuthorizationStatusAuthorized:
438 mac->isAuthorized = TRUE;
440 case AVAuthorizationStatusNotDetermined:
442 requestAccessForMediaType:AVMediaTypeAudio
443 completionHandler:^(BOOL granted) {
446 mac->isAuthorized = TRUE;
449 WLog_WARN(TAG, "Microphone access denied by user");
452 case AVAuthorizationStatusRestricted:
453 WLog_WARN(TAG,
"Microphone access restricted by policy");
455 case AVAuthorizationStatusDenied:
456 WLog_WARN(TAG,
"Microphone access denied by policy");
465 return CHANNEL_RC_OK;