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