1/** 2 * FreeRDP: A Remote Desktop Protocol Implementation 3 * Audio Input Redirection Virtual Channel - Mac OS X implementation 4 * 5 * Copyright (c) 2015 Armin Novak <armin.novak@thincast.com> 6 * Copyright 2015 Thincast Technologies GmbH 7 * 8 * Licensed under the Apache License, Version 2.0 (the "License"); 9 * you may not use this file except in compliance with the License. 10 * You may obtain a copy of the License at 11 * 12 * http://www.apache.org/licenses/LICENSE-2.0 13 * 14 * Unless required by applicable law or agreed to in writing, software 15 * distributed under the License is distributed on an "AS IS" BASIS, 16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 * See the License for the specific language governing permissions and 18 * limitations under the License. 19 */ 20 21#ifdef HAVE_CONFIG_H 22#include "config.h" 23#endif 24 25#include <stdio.h> 26#include <stdlib.h> 27#include <string.h> 28 29#include <winpr/crt.h> 30#include <winpr/synch.h> 31#include <winpr/string.h> 32#include <winpr/thread.h> 33#include <winpr/debug.h> 34#include <winpr/cmdline.h> 35 36#import <AVFoundation/AVFoundation.h> 37 38#define __COREFOUNDATION_CFPLUGINCOM__ 1 39#define IUNKNOWN_C_GUTS \ 40 void *_reserved; \ 41 void *QueryInterface; \ 42 void *AddRef; \ 43 void *Release 44 45#include <CoreAudio/CoreAudioTypes.h> 46#include <CoreAudio/CoreAudio.h> 47#include <AudioToolbox/AudioToolbox.h> 48#include <AudioToolbox/AudioQueue.h> 49 50#include <freerdp/addin.h> 51#include <freerdp/channels/rdpsnd.h> 52 53#include "audin_main.h" 54 55#define MAC_AUDIO_QUEUE_NUM_BUFFERS 100 56 57/* Fix for #4462: Provide type alias if not declared (Mac OS < 10.10) 58 * https://developer.apple.com/documentation/coreaudio/audioformatid 59 */ 60#ifndef AudioFormatID 61typedef UInt32 AudioFormatID; 62#endif 63 64#ifndef AudioFormatFlags 65typedef UInt32 AudioFormatFlags; 66#endif 67 68typedef struct _AudinMacDevice 69{ 70 IAudinDevice iface; 71 72 AUDIO_FORMAT format; 73 UINT32 FramesPerPacket; 74 int dev_unit; 75 76 AudinReceive receive; 77 void *user_data; 78 79 rdpContext *rdpcontext; 80 81 bool isAuthorized; 82 bool isOpen; 83 AudioQueueRef audioQueue; 84 AudioStreamBasicDescription audioFormat; 85 AudioQueueBufferRef audioBuffers[MAC_AUDIO_QUEUE_NUM_BUFFERS]; 86} AudinMacDevice; 87 88static AudioFormatID audin_mac_get_format(const AUDIO_FORMAT *format) 89{ 90 switch (format->wFormatTag) 91 { 92 case WAVE_FORMAT_PCM: 93 return kAudioFormatLinearPCM; 94 95 default: 96 return 0; 97 } 98} 99 100static AudioFormatFlags audin_mac_get_flags_for_format(const AUDIO_FORMAT *format) 101{ 102 switch (format->wFormatTag) 103 { 104 case WAVE_FORMAT_PCM: 105 return kAudioFormatFlagIsSignedInteger; 106 107 default: 108 return 0; 109 } 110} 111 112static BOOL audin_mac_format_supported(IAudinDevice *device, const AUDIO_FORMAT *format) 113{ 114 AudinMacDevice *mac = (AudinMacDevice *)device; 115 AudioFormatID req_fmt = 0; 116 117 if (!mac->isAuthorized) 118 return FALSE; 119 120 if (device == NULL || format == NULL) 121 return FALSE; 122 123 req_fmt = audin_mac_get_format(format); 124 125 if (req_fmt == 0) 126 return FALSE; 127 128 return TRUE; 129} 130 131/** 132 * Function description 133 * 134 * @return 0 on success, otherwise a Win32 error code 135 */ 136static UINT audin_mac_set_format(IAudinDevice *device, const AUDIO_FORMAT *format, 137 UINT32 FramesPerPacket) 138{ 139 AudinMacDevice *mac = (AudinMacDevice *)device; 140 141 if (!mac->isAuthorized) 142 return ERROR_INTERNAL_ERROR; 143 144 if (device == NULL || format == NULL) 145 return ERROR_INVALID_PARAMETER; 146 147 mac->FramesPerPacket = FramesPerPacket; 148 mac->format = *format; 149 WLog_INFO(TAG, "Audio Format %s [channels=%d, samples=%d, bits=%d]", 150 audio_format_get_tag_string(format->wFormatTag), format->nChannels, 151 format->nSamplesPerSec, format->wBitsPerSample); 152 mac->audioFormat.mBitsPerChannel = format->wBitsPerSample; 153 154 if (format->wBitsPerSample == 0) 155 mac->audioFormat.mBitsPerChannel = 16; 156 157 mac->audioFormat.mBytesPerFrame = 0; 158 mac->audioFormat.mBytesPerPacket = 0; 159 mac->audioFormat.mChannelsPerFrame = mac->format.nChannels; 160 mac->audioFormat.mFormatFlags = audin_mac_get_flags_for_format(format); 161 mac->audioFormat.mFormatID = audin_mac_get_format(format); 162 mac->audioFormat.mFramesPerPacket = 1; 163 mac->audioFormat.mReserved = 0; 164 mac->audioFormat.mSampleRate = mac->format.nSamplesPerSec; 165 return CHANNEL_RC_OK; 166} 167 168static void mac_audio_queue_input_cb(void *aqData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer, 169 const AudioTimeStamp *inStartTime, UInt32 inNumPackets, 170 const AudioStreamPacketDescription *inPacketDesc) 171{ 172 AudinMacDevice *mac = (AudinMacDevice *)aqData; 173 UINT error = CHANNEL_RC_OK; 174 const BYTE *buffer = inBuffer->mAudioData; 175 int buffer_size = inBuffer->mAudioDataByteSize; 176 (void)inAQ; 177 (void)inStartTime; 178 (void)inNumPackets; 179 (void)inPacketDesc; 180 181 if (buffer_size > 0) 182 error = mac->receive(&mac->format, buffer, buffer_size, mac->user_data); 183 184 AudioQueueEnqueueBuffer(inAQ, inBuffer, 0, NULL); 185 186 if (error) 187 { 188 WLog_ERR(TAG, "mac->receive failed with error %" PRIu32 "", error); 189 SetLastError(ERROR_INTERNAL_ERROR); 190 } 191} 192 193static UINT audin_mac_close(IAudinDevice *device) 194{ 195 UINT errCode = CHANNEL_RC_OK; 196 char errString[1024]; 197 OSStatus devStat; 198 AudinMacDevice *mac = (AudinMacDevice *)device; 199 200 if (!mac->isAuthorized) 201 return ERROR_INTERNAL_ERROR; 202 203 if (device == NULL) 204 return ERROR_INVALID_PARAMETER; 205 206 if (mac->isOpen) 207 { 208 devStat = AudioQueueStop(mac->audioQueue, true); 209 210 if (devStat != 0) 211 { 212 errCode = GetLastError(); 213 WLog_ERR(TAG, "AudioQueueStop failed with %s [%" PRIu32 "]", 214 winpr_strerror(errCode, errString, sizeof(errString)), errCode); 215 } 216 217 mac->isOpen = false; 218 } 219 220 if (mac->audioQueue) 221 { 222 devStat = AudioQueueDispose(mac->audioQueue, true); 223 224 if (devStat != 0) 225 { 226 errCode = GetLastError(); 227 WLog_ERR(TAG, "AudioQueueDispose failed with %s [%" PRIu32 "]", 228 winpr_strerror(errCode, errString, sizeof(errString)), errCode); 229 } 230 231 mac->audioQueue = NULL; 232 } 233 234 mac->receive = NULL; 235 mac->user_data = NULL; 236 return errCode; 237} 238 239static UINT audin_mac_open(IAudinDevice *device, AudinReceive receive, void *user_data) 240{ 241 AudinMacDevice *mac = (AudinMacDevice *)device; 242 DWORD errCode; 243 char errString[1024]; 244 OSStatus devStat; 245 size_t index; 246 247 if (!mac->isAuthorized) 248 return ERROR_INTERNAL_ERROR; 249 250 mac->receive = receive; 251 mac->user_data = user_data; 252 devStat = AudioQueueNewInput(&(mac->audioFormat), mac_audio_queue_input_cb, mac, NULL, 253 kCFRunLoopCommonModes, 0, &(mac->audioQueue)); 254 255 if (devStat != 0) 256 { 257 errCode = GetLastError(); 258 WLog_ERR(TAG, "AudioQueueNewInput failed with %s [%" PRIu32 "]", 259 winpr_strerror(errCode, errString, sizeof(errString)), errCode); 260 goto err_out; 261 } 262 263 for (index = 0; index < MAC_AUDIO_QUEUE_NUM_BUFFERS; index++) 264 { 265 devStat = AudioQueueAllocateBuffer(mac->audioQueue, 266 mac->FramesPerPacket * 2 * mac->format.nChannels, 267 &mac->audioBuffers[index]); 268 269 if (devStat != 0) 270 { 271 errCode = GetLastError(); 272 WLog_ERR(TAG, "AudioQueueAllocateBuffer failed with %s [%" PRIu32 "]", 273 winpr_strerror(errCode, errString, sizeof(errString)), errCode); 274 goto err_out; 275 } 276 277 devStat = AudioQueueEnqueueBuffer(mac->audioQueue, mac->audioBuffers[index], 0, NULL); 278 279 if (devStat != 0) 280 { 281 errCode = GetLastError(); 282 WLog_ERR(TAG, "AudioQueueEnqueueBuffer failed with %s [%" PRIu32 "]", 283 winpr_strerror(errCode, errString, sizeof(errString)), errCode); 284 goto err_out; 285 } 286 } 287 288 devStat = AudioQueueStart(mac->audioQueue, NULL); 289 290 if (devStat != 0) 291 { 292 errCode = GetLastError(); 293 WLog_ERR(TAG, "AudioQueueStart failed with %s [%" PRIu32 "]", 294 winpr_strerror(errCode, errString, sizeof(errString)), errCode); 295 goto err_out; 296 } 297 298 mac->isOpen = true; 299 return CHANNEL_RC_OK; 300err_out: 301 audin_mac_close(device); 302 return CHANNEL_RC_INITIALIZATION_ERROR; 303} 304 305static UINT audin_mac_free(IAudinDevice *device) 306{ 307 AudinMacDevice *mac = (AudinMacDevice *)device; 308 int error; 309 310 if (device == NULL) 311 return ERROR_INVALID_PARAMETER; 312 313 if ((error = audin_mac_close(device))) 314 { 315 WLog_ERR(TAG, "audin_oss_close failed with error code %d!", error); 316 } 317 318 free(mac); 319 return CHANNEL_RC_OK; 320} 321 322static UINT audin_mac_parse_addin_args(AudinMacDevice *device, ADDIN_ARGV *args) 323{ 324 DWORD errCode; 325 char errString[1024]; 326 int status; 327 char *str_num, *eptr; 328 DWORD flags; 329 COMMAND_LINE_ARGUMENT_A *arg; 330 COMMAND_LINE_ARGUMENT_A audin_mac_args[] = { { "dev", COMMAND_LINE_VALUE_REQUIRED, "<device>", 331 NULL, NULL, -1, NULL, "audio device name" }, 332 { NULL, 0, NULL, NULL, NULL, -1, NULL, NULL } }; 333 334 AudinMacDevice *mac = (AudinMacDevice *)device; 335 336 if (args->argc == 1) 337 return CHANNEL_RC_OK; 338 339 flags = 340 COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD; 341 status = 342 CommandLineParseArgumentsA(args->argc, args->argv, audin_mac_args, flags, mac, NULL, NULL); 343 344 if (status < 0) 345 return ERROR_INVALID_PARAMETER; 346 347 arg = audin_mac_args; 348 349 do 350 { 351 if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT)) 352 continue; 353 354 CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "dev") 355 { 356 str_num = _strdup(arg->Value); 357 358 if (!str_num) 359 { 360 errCode = GetLastError(); 361 WLog_ERR(TAG, "_strdup failed with %s [%d]", 362 winpr_strerror(errCode, errString, sizeof(errString)), errCode); 363 return CHANNEL_RC_NO_MEMORY; 364 } 365 366 mac->dev_unit = strtol(str_num, &eptr, 10); 367 368 if (mac->dev_unit < 0 || *eptr != '\0') 369 mac->dev_unit = -1; 370 371 free(str_num); 372 } 373 CommandLineSwitchEnd(arg) 374 } while ((arg = CommandLineFindNextArgumentA(arg)) != NULL); 375 376 return CHANNEL_RC_OK; 377} 378 379#ifdef BUILTIN_CHANNELS 380#define freerdp_audin_client_subsystem_entry mac_freerdp_audin_client_subsystem_entry 381#else 382#define freerdp_audin_client_subsystem_entry FREERDP_API freerdp_audin_client_subsystem_entry 383#endif 384 385UINT freerdp_audin_client_subsystem_entry(PFREERDP_AUDIN_DEVICE_ENTRY_POINTS pEntryPoints) 386{ 387 DWORD errCode; 388 char errString[1024]; 389 ADDIN_ARGV *args; 390 AudinMacDevice *mac; 391 UINT error; 392 mac = (AudinMacDevice *)calloc(1, sizeof(AudinMacDevice)); 393 394 if (!mac) 395 { 396 errCode = GetLastError(); 397 WLog_ERR(TAG, "calloc failed with %s [%" PRIu32 "]", 398 winpr_strerror(errCode, errString, sizeof(errString)), errCode); 399 return CHANNEL_RC_NO_MEMORY; 400 } 401 402 mac->iface.Open = audin_mac_open; 403 mac->iface.FormatSupported = audin_mac_format_supported; 404 mac->iface.SetFormat = audin_mac_set_format; 405 mac->iface.Close = audin_mac_close; 406 mac->iface.Free = audin_mac_free; 407 mac->rdpcontext = pEntryPoints->rdpcontext; 408 mac->dev_unit = -1; 409 args = pEntryPoints->args; 410 411 if ((error = audin_mac_parse_addin_args(mac, args))) 412 { 413 WLog_ERR(TAG, "audin_mac_parse_addin_args failed with %" PRIu32 "!", error); 414 goto error_out; 415 } 416 417 if ((error = pEntryPoints->pRegisterAudinDevice(pEntryPoints->plugin, (IAudinDevice *)mac))) 418 { 419 WLog_ERR(TAG, "RegisterAudinDevice failed with error %" PRIu32 "!", error); 420 goto error_out; 421 } 422 423#if defined(MAC_OS_X_VERSION_10_14) 424 if (@available(macOS 10.14, *)) 425 { 426 AVAuthorizationStatus status = 427 [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio]; 428 switch (status) 429 { 430 case AVAuthorizationStatusAuthorized: 431 mac->isAuthorized = TRUE; 432 break; 433 case AVAuthorizationStatusNotDetermined: 434 [AVCaptureDevice requestAccessForMediaType:AVMediaTypeAudio 435 completionHandler:^(BOOL granted) { 436 if (granted == YES) 437 { 438 mac->isAuthorized = TRUE; 439 } 440 else 441 WLog_WARN(TAG, "Microphone access denied by user"); 442 }]; 443 break; 444 case AVAuthorizationStatusRestricted: 445 WLog_WARN(TAG, "Microphone access restricted by policy"); 446 break; 447 case AVAuthorizationStatusDenied: 448 WLog_WARN(TAG, "Microphone access denied by policy"); 449 break; 450 default: 451 break; 452 } 453 } 454#endif 455 456 return CHANNEL_RC_OK; 457error_out: 458 free(mac); 459 return error; 460} 461