summaryrefslogtreecommitdiffstats
path: root/channels/audin/client/mac
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 01:24:41 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 01:24:41 +0000
commita9bcc81f821d7c66f623779fa5147e728eb3c388 (patch)
tree98676963bcdd537ae5908a067a8eb110b93486a6 /channels/audin/client/mac
parentInitial commit. (diff)
downloadfreerdp3-a9bcc81f821d7c66f623779fa5147e728eb3c388.tar.xz
freerdp3-a9bcc81f821d7c66f623779fa5147e728eb3c388.zip
Adding upstream version 3.3.0+dfsg1.upstream/3.3.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'channels/audin/client/mac')
-rw-r--r--channels/audin/client/mac/CMakeLists.txt41
-rw-r--r--channels/audin/client/mac/audin_mac.m463
2 files changed, 504 insertions, 0 deletions
diff --git a/channels/audin/client/mac/CMakeLists.txt b/channels/audin/client/mac/CMakeLists.txt
new file mode 100644
index 0000000..6b3e792
--- /dev/null
+++ b/channels/audin/client/mac/CMakeLists.txt
@@ -0,0 +1,41 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright (c) 2015 Armin Novak <armin.novak@thincast.com>
+# Copyright (c) 2015 Thincast Technologies GmbH
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel_client_subsystem("audin" "mac" "")
+FIND_LIBRARY(CORE_AUDIO CoreAudio)
+FIND_LIBRARY(AVFOUNDATION AVFoundation)
+FIND_LIBRARY(AUDIO_TOOL AudioToolbox)
+FIND_LIBRARY(APP_SERVICES ApplicationServices)
+
+set(${MODULE_PREFIX}_SRCS
+ audin_mac.m
+)
+
+set(${MODULE_PREFIX}_LIBS
+ winpr
+ freerdp
+ ${AVFOUNDATION}
+ ${CORE_AUDIO}
+ ${AUDIO_TOOL}
+ ${APP_SERVICES}
+)
+
+include_directories(..)
+include_directories(${MAC_INCLUDE_DIRS})
+
+add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "")
diff --git a/channels/audin/client/mac/audin_mac.m b/channels/audin/client/mac/audin_mac.m
new file mode 100644
index 0000000..19749a9
--- /dev/null
+++ b/channels/audin/client/mac/audin_mac.m
@@ -0,0 +1,463 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Audio Input Redirection Virtual Channel - Mac OS X implementation
+ *
+ * Copyright (c) 2015 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2015 Thincast Technologies GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/string.h>
+#include <winpr/thread.h>
+#include <winpr/debug.h>
+#include <winpr/cmdline.h>
+
+#import <AVFoundation/AVFoundation.h>
+
+#define __COREFOUNDATION_CFPLUGINCOM__ 1
+#define IUNKNOWN_C_GUTS \
+ void *_reserved; \
+ void *QueryInterface; \
+ void *AddRef; \
+ void *Release
+
+#include <CoreAudio/CoreAudioTypes.h>
+#include <CoreAudio/CoreAudio.h>
+#include <AudioToolbox/AudioToolbox.h>
+#include <AudioToolbox/AudioQueue.h>
+
+#include <freerdp/addin.h>
+#include <freerdp/channels/rdpsnd.h>
+
+#include "audin_main.h"
+
+#define MAC_AUDIO_QUEUE_NUM_BUFFERS 100
+
+/* Fix for #4462: Provide type alias if not declared (Mac OS < 10.10)
+ * https://developer.apple.com/documentation/coreaudio/audioformatid
+ */
+#ifndef AudioFormatID
+typedef UInt32 AudioFormatID;
+#endif
+
+#ifndef AudioFormatFlags
+typedef UInt32 AudioFormatFlags;
+#endif
+
+typedef struct
+{
+ IAudinDevice iface;
+
+ AUDIO_FORMAT format;
+ UINT32 FramesPerPacket;
+ int dev_unit;
+
+ AudinReceive receive;
+ void *user_data;
+
+ rdpContext *rdpcontext;
+
+ bool isAuthorized;
+ bool isOpen;
+ AudioQueueRef audioQueue;
+ AudioStreamBasicDescription audioFormat;
+ AudioQueueBufferRef audioBuffers[MAC_AUDIO_QUEUE_NUM_BUFFERS];
+} AudinMacDevice;
+
+static AudioFormatID audin_mac_get_format(const AUDIO_FORMAT *format)
+{
+ switch (format->wFormatTag)
+ {
+ case WAVE_FORMAT_PCM:
+ return kAudioFormatLinearPCM;
+
+ default:
+ return 0;
+ }
+}
+
+static AudioFormatFlags audin_mac_get_flags_for_format(const AUDIO_FORMAT *format)
+{
+ switch (format->wFormatTag)
+ {
+ case WAVE_FORMAT_PCM:
+ return kAudioFormatFlagIsSignedInteger;
+
+ default:
+ return 0;
+ }
+}
+
+static BOOL audin_mac_format_supported(IAudinDevice *device, const AUDIO_FORMAT *format)
+{
+ AudinMacDevice *mac = (AudinMacDevice *)device;
+ AudioFormatID req_fmt = 0;
+
+ if (!mac->isAuthorized)
+ return FALSE;
+
+ if (device == NULL || format == NULL)
+ return FALSE;
+
+ if (format->nChannels != 2)
+ return FALSE;
+
+ req_fmt = audin_mac_get_format(format);
+
+ if (req_fmt == 0)
+ return FALSE;
+
+ return TRUE;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_mac_set_format(IAudinDevice *device, const AUDIO_FORMAT *format,
+ UINT32 FramesPerPacket)
+{
+ AudinMacDevice *mac = (AudinMacDevice *)device;
+
+ if (!mac->isAuthorized)
+ return ERROR_INTERNAL_ERROR;
+
+ if (device == NULL || format == NULL)
+ return ERROR_INVALID_PARAMETER;
+
+ mac->FramesPerPacket = FramesPerPacket;
+ mac->format = *format;
+ WLog_INFO(TAG, "Audio Format %s [channels=%d, samples=%d, bits=%d]",
+ audio_format_get_tag_string(format->wFormatTag), format->nChannels,
+ format->nSamplesPerSec, format->wBitsPerSample);
+ mac->audioFormat.mBitsPerChannel = format->wBitsPerSample;
+
+ if (format->wBitsPerSample == 0)
+ mac->audioFormat.mBitsPerChannel = 16;
+
+ mac->audioFormat.mChannelsPerFrame = mac->format.nChannels;
+ mac->audioFormat.mFramesPerPacket = 1;
+
+ mac->audioFormat.mBytesPerFrame =
+ mac->audioFormat.mChannelsPerFrame * (mac->audioFormat.mBitsPerChannel / 8);
+ mac->audioFormat.mBytesPerPacket =
+ mac->audioFormat.mBytesPerFrame * mac->audioFormat.mFramesPerPacket;
+
+ mac->audioFormat.mFormatFlags = audin_mac_get_flags_for_format(format);
+ mac->audioFormat.mFormatID = audin_mac_get_format(format);
+ mac->audioFormat.mReserved = 0;
+ mac->audioFormat.mSampleRate = mac->format.nSamplesPerSec;
+ return CHANNEL_RC_OK;
+}
+
+static void mac_audio_queue_input_cb(void *aqData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer,
+ const AudioTimeStamp *inStartTime, UInt32 inNumPackets,
+ const AudioStreamPacketDescription *inPacketDesc)
+{
+ AudinMacDevice *mac = (AudinMacDevice *)aqData;
+ UINT error = CHANNEL_RC_OK;
+ const BYTE *buffer = inBuffer->mAudioData;
+ int buffer_size = inBuffer->mAudioDataByteSize;
+ (void)inAQ;
+ (void)inStartTime;
+ (void)inNumPackets;
+ (void)inPacketDesc;
+
+ if (buffer_size > 0)
+ error = mac->receive(&mac->format, buffer, buffer_size, mac->user_data);
+
+ AudioQueueEnqueueBuffer(inAQ, inBuffer, 0, NULL);
+
+ if (error)
+ {
+ WLog_ERR(TAG, "mac->receive failed with error %" PRIu32 "", error);
+ SetLastError(ERROR_INTERNAL_ERROR);
+ }
+}
+
+static UINT audin_mac_close(IAudinDevice *device)
+{
+ UINT errCode = CHANNEL_RC_OK;
+ char errString[1024];
+ OSStatus devStat;
+ AudinMacDevice *mac = (AudinMacDevice *)device;
+
+ if (!mac->isAuthorized)
+ return ERROR_INTERNAL_ERROR;
+
+ if (device == NULL)
+ return ERROR_INVALID_PARAMETER;
+
+ if (mac->isOpen)
+ {
+ devStat = AudioQueueStop(mac->audioQueue, true);
+
+ if (devStat != 0)
+ {
+ errCode = GetLastError();
+ WLog_ERR(TAG, "AudioQueueStop failed with %s [%" PRIu32 "]",
+ winpr_strerror(errCode, errString, sizeof(errString)), errCode);
+ }
+
+ mac->isOpen = false;
+ }
+
+ if (mac->audioQueue)
+ {
+ devStat = AudioQueueDispose(mac->audioQueue, true);
+
+ if (devStat != 0)
+ {
+ errCode = GetLastError();
+ WLog_ERR(TAG, "AudioQueueDispose failed with %s [%" PRIu32 "]",
+ winpr_strerror(errCode, errString, sizeof(errString)), errCode);
+ }
+
+ mac->audioQueue = NULL;
+ }
+
+ mac->receive = NULL;
+ mac->user_data = NULL;
+ return errCode;
+}
+
+static UINT audin_mac_open(IAudinDevice *device, AudinReceive receive, void *user_data)
+{
+ AudinMacDevice *mac = (AudinMacDevice *)device;
+ DWORD errCode;
+ char errString[1024];
+ OSStatus devStat;
+
+ if (!mac->isAuthorized)
+ return ERROR_INTERNAL_ERROR;
+
+ mac->receive = receive;
+ mac->user_data = user_data;
+ devStat = AudioQueueNewInput(&(mac->audioFormat), mac_audio_queue_input_cb, mac, NULL,
+ kCFRunLoopCommonModes, 0, &(mac->audioQueue));
+
+ if (devStat != 0)
+ {
+ errCode = GetLastError();
+ WLog_ERR(TAG, "AudioQueueNewInput failed with %s [%" PRIu32 "]",
+ winpr_strerror(errCode, errString, sizeof(errString)), errCode);
+ goto err_out;
+ }
+
+ for (size_t index = 0; index < MAC_AUDIO_QUEUE_NUM_BUFFERS; index++)
+ {
+ devStat = AudioQueueAllocateBuffer(mac->audioQueue,
+ mac->FramesPerPacket * 2 * mac->format.nChannels,
+ &mac->audioBuffers[index]);
+
+ if (devStat != 0)
+ {
+ errCode = GetLastError();
+ WLog_ERR(TAG, "AudioQueueAllocateBuffer failed with %s [%" PRIu32 "]",
+ winpr_strerror(errCode, errString, sizeof(errString)), errCode);
+ goto err_out;
+ }
+
+ devStat = AudioQueueEnqueueBuffer(mac->audioQueue, mac->audioBuffers[index], 0, NULL);
+
+ if (devStat != 0)
+ {
+ errCode = GetLastError();
+ WLog_ERR(TAG, "AudioQueueEnqueueBuffer failed with %s [%" PRIu32 "]",
+ winpr_strerror(errCode, errString, sizeof(errString)), errCode);
+ goto err_out;
+ }
+ }
+
+ devStat = AudioQueueStart(mac->audioQueue, NULL);
+
+ if (devStat != 0)
+ {
+ errCode = GetLastError();
+ WLog_ERR(TAG, "AudioQueueStart failed with %s [%" PRIu32 "]",
+ winpr_strerror(errCode, errString, sizeof(errString)), errCode);
+ goto err_out;
+ }
+
+ mac->isOpen = true;
+ return CHANNEL_RC_OK;
+err_out:
+ audin_mac_close(device);
+ return CHANNEL_RC_INITIALIZATION_ERROR;
+}
+
+static UINT audin_mac_free(IAudinDevice *device)
+{
+ AudinMacDevice *mac = (AudinMacDevice *)device;
+ int error;
+
+ if (device == NULL)
+ return ERROR_INVALID_PARAMETER;
+
+ if ((error = audin_mac_close(device)))
+ {
+ WLog_ERR(TAG, "audin_oss_close failed with error code %d!", error);
+ }
+
+ free(mac);
+ return CHANNEL_RC_OK;
+}
+
+static UINT audin_mac_parse_addin_args(AudinMacDevice *device, const ADDIN_ARGV *args)
+{
+ DWORD errCode;
+ char errString[1024];
+ int status;
+ char *str_num, *eptr;
+ DWORD flags;
+ const COMMAND_LINE_ARGUMENT_A *arg;
+ COMMAND_LINE_ARGUMENT_A audin_mac_args[] = { { "dev", COMMAND_LINE_VALUE_REQUIRED, "<device>",
+ NULL, NULL, -1, NULL, "audio device name" },
+ { NULL, 0, NULL, NULL, NULL, -1, NULL, NULL } };
+
+ AudinMacDevice *mac = (AudinMacDevice *)device;
+
+ if (args->argc == 1)
+ return CHANNEL_RC_OK;
+
+ flags =
+ COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD;
+ status =
+ CommandLineParseArgumentsA(args->argc, args->argv, audin_mac_args, flags, mac, NULL, NULL);
+
+ if (status < 0)
+ return ERROR_INVALID_PARAMETER;
+
+ arg = audin_mac_args;
+
+ do
+ {
+ if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT))
+ continue;
+
+ CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "dev")
+ {
+ str_num = _strdup(arg->Value);
+
+ if (!str_num)
+ {
+ errCode = GetLastError();
+ WLog_ERR(TAG, "_strdup failed with %s [%d]",
+ winpr_strerror(errCode, errString, sizeof(errString)), errCode);
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ mac->dev_unit = strtol(str_num, &eptr, 10);
+
+ if (mac->dev_unit < 0 || *eptr != '\0')
+ mac->dev_unit = -1;
+
+ free(str_num);
+ }
+ CommandLineSwitchEnd(arg)
+ } while ((arg = CommandLineFindNextArgumentA(arg)) != NULL);
+
+ return CHANNEL_RC_OK;
+}
+
+FREERDP_ENTRY_POINT(
+ UINT mac_freerdp_audin_client_subsystem_entry(PFREERDP_AUDIN_DEVICE_ENTRY_POINTS pEntryPoints))
+{
+ DWORD errCode;
+ char errString[1024];
+ const ADDIN_ARGV *args;
+ AudinMacDevice *mac;
+ UINT error;
+ mac = (AudinMacDevice *)calloc(1, sizeof(AudinMacDevice));
+
+ if (!mac)
+ {
+ errCode = GetLastError();
+ WLog_ERR(TAG, "calloc failed with %s [%" PRIu32 "]",
+ winpr_strerror(errCode, errString, sizeof(errString)), errCode);
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ mac->iface.Open = audin_mac_open;
+ mac->iface.FormatSupported = audin_mac_format_supported;
+ mac->iface.SetFormat = audin_mac_set_format;
+ mac->iface.Close = audin_mac_close;
+ mac->iface.Free = audin_mac_free;
+ mac->rdpcontext = pEntryPoints->rdpcontext;
+ mac->dev_unit = -1;
+ args = pEntryPoints->args;
+
+ if ((error = audin_mac_parse_addin_args(mac, args)))
+ {
+ WLog_ERR(TAG, "audin_mac_parse_addin_args failed with %" PRIu32 "!", error);
+ goto error_out;
+ }
+
+ if ((error = pEntryPoints->pRegisterAudinDevice(pEntryPoints->plugin, (IAudinDevice *)mac)))
+ {
+ WLog_ERR(TAG, "RegisterAudinDevice failed with error %" PRIu32 "!", error);
+ goto error_out;
+ }
+
+#if defined(MAC_OS_X_VERSION_10_14)
+ if (@available(macOS 10.14, *))
+ {
+ @autoreleasepool
+ {
+ AVAuthorizationStatus status =
+ [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio];
+ switch (status)
+ {
+ case AVAuthorizationStatusAuthorized:
+ mac->isAuthorized = TRUE;
+ break;
+ case AVAuthorizationStatusNotDetermined:
+ [AVCaptureDevice
+ requestAccessForMediaType:AVMediaTypeAudio
+ completionHandler:^(BOOL granted) {
+ if (granted == YES)
+ {
+ mac->isAuthorized = TRUE;
+ }
+ else
+ WLog_WARN(TAG, "Microphone access denied by user");
+ }];
+ break;
+ case AVAuthorizationStatusRestricted:
+ WLog_WARN(TAG, "Microphone access restricted by policy");
+ break;
+ case AVAuthorizationStatusDenied:
+ WLog_WARN(TAG, "Microphone access denied by policy");
+ break;
+ default:
+ break;
+ }
+ }
+ }
+#endif
+
+ return CHANNEL_RC_OK;
+error_out:
+ free(mac);
+ return error;
+}