summaryrefslogtreecommitdiffstats
path: root/channels/audin
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--channels/audin/CMakeLists.txt26
-rw-r--r--channels/audin/ChannelOptions.cmake17
-rw-r--r--channels/audin/client/CMakeLists.txt63
-rw-r--r--channels/audin/client/alsa/CMakeLists.txt35
-rw-r--r--channels/audin/client/alsa/audin_alsa.c453
-rw-r--r--channels/audin/client/audin_main.c1109
-rw-r--r--channels/audin/client/audin_main.h33
-rw-r--r--channels/audin/client/ios/CMakeLists.txt39
-rw-r--r--channels/audin/client/ios/audin_ios.m335
-rw-r--r--channels/audin/client/mac/CMakeLists.txt41
-rw-r--r--channels/audin/client/mac/audin_mac.m463
-rw-r--r--channels/audin/client/opensles/CMakeLists.txt36
-rw-r--r--channels/audin/client/opensles/audin_opensl_es.c336
-rw-r--r--channels/audin/client/opensles/opensl_io.c388
-rw-r--r--channels/audin/client/opensles/opensl_io.h65
-rw-r--r--channels/audin/client/oss/CMakeLists.txt36
-rw-r--r--channels/audin/client/oss/audin_oss.c491
-rw-r--r--channels/audin/client/pulse/CMakeLists.txt35
-rw-r--r--channels/audin/client/pulse/audin_pulse.c566
-rw-r--r--channels/audin/client/sndio/CMakeLists.txt36
-rw-r--r--channels/audin/client/sndio/audin_sndio.c352
-rw-r--r--channels/audin/client/winmm/CMakeLists.txt31
-rw-r--r--channels/audin/client/winmm/audin_winmm.c566
-rw-r--r--channels/audin/server/CMakeLists.txt27
-rw-r--r--channels/audin/server/audin.c914
25 files changed, 6493 insertions, 0 deletions
diff --git a/channels/audin/CMakeLists.txt b/channels/audin/CMakeLists.txt
new file mode 100644
index 0000000..d72b102
--- /dev/null
+++ b/channels/audin/CMakeLists.txt
@@ -0,0 +1,26 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+#
+# 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("audin")
+
+if(WITH_CLIENT_CHANNELS)
+ add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME})
+endif()
+
+if(WITH_SERVER_CHANNELS)
+ add_channel_server(${MODULE_PREFIX} ${CHANNEL_NAME})
+endif()
diff --git a/channels/audin/ChannelOptions.cmake b/channels/audin/ChannelOptions.cmake
new file mode 100644
index 0000000..39ca402
--- /dev/null
+++ b/channels/audin/ChannelOptions.cmake
@@ -0,0 +1,17 @@
+
+set(OPTION_DEFAULT OFF)
+set(OPTION_CLIENT_DEFAULT ON)
+set(OPTION_SERVER_DEFAULT ON)
+
+if(ANDROID)
+ set(OPTION_SERVER_DEFAULT OFF)
+endif()
+
+define_channel_options(NAME "audin" TYPE "dynamic"
+ DESCRIPTION "Audio Input Redirection Virtual Channel Extension"
+ SPECIFICATIONS "[MS-RDPEAI]"
+ DEFAULT ${OPTION_DEFAULT})
+
+define_channel_client_options(${OPTION_CLIENT_DEFAULT})
+define_channel_server_options(${OPTION_SERVER_DEFAULT})
+
diff --git a/channels/audin/client/CMakeLists.txt b/channels/audin/client/CMakeLists.txt
new file mode 100644
index 0000000..043e1b5
--- /dev/null
+++ b/channels/audin/client/CMakeLists.txt
@@ -0,0 +1,63 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+#
+# 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("audin")
+
+set(${MODULE_PREFIX}_SRCS
+ audin_main.c
+ audin_main.h
+)
+
+set(${MODULE_PREFIX}_LIBS
+ freerdp winpr
+)
+
+include_directories(..)
+
+add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "DVCPluginEntry")
+
+if(WITH_OSS)
+ add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "oss" "")
+endif()
+
+if(WITH_ALSA)
+ add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "alsa" "")
+endif()
+
+if(WITH_PULSE)
+ add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "pulse" "")
+endif()
+
+if(WITH_OPENSLES)
+ add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "opensles" "")
+endif()
+
+if(WITH_WINMM)
+ add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "winmm" "")
+endif()
+
+if(WITH_MACAUDIO)
+ add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "mac" "")
+endif()
+
+if(WITH_SNDIO)
+ add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "sndio" "")
+endif()
+
+if(WITH_IOSAUDIO)
+ add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "ios" "")
+ endif()
diff --git a/channels/audin/client/alsa/CMakeLists.txt b/channels/audin/client/alsa/CMakeLists.txt
new file mode 100644
index 0000000..1213f74
--- /dev/null
+++ b/channels/audin/client/alsa/CMakeLists.txt
@@ -0,0 +1,35 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+#
+# 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" "alsa" "")
+
+find_package(ALSA REQUIRED)
+
+set(${MODULE_PREFIX}_SRCS
+ audin_alsa.c
+)
+
+set(${MODULE_PREFIX}_LIBS
+ winpr
+ freerdp
+ ${ALSA_LIBRARIES}
+)
+
+include_directories(..)
+include_directories(${ALSA_INCLUDE_DIRS})
+
+add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "")
diff --git a/channels/audin/client/alsa/audin_alsa.c b/channels/audin/client/alsa/audin_alsa.c
new file mode 100644
index 0000000..be3e52a
--- /dev/null
+++ b/channels/audin/client/alsa/audin_alsa.c
@@ -0,0 +1,453 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Audio Input Redirection Virtual Channel - ALSA implementation
+ *
+ * Copyright 2010-2011 Vic Lee
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ *
+ * 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/thread.h>
+#include <winpr/cmdline.h>
+#include <winpr/wlog.h>
+
+#include <alsa/asoundlib.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/addin.h>
+#include <freerdp/channels/rdpsnd.h>
+
+#include "audin_main.h"
+
+typedef struct
+{
+ IAudinDevice iface;
+
+ char* device_name;
+ UINT32 frames_per_packet;
+ AUDIO_FORMAT aformat;
+
+ HANDLE thread;
+ HANDLE stopEvent;
+
+ AudinReceive receive;
+ void* user_data;
+
+ rdpContext* rdpcontext;
+ wLog* log;
+ int bytes_per_frame;
+} AudinALSADevice;
+
+static snd_pcm_format_t audin_alsa_format(UINT32 wFormatTag, UINT32 bitPerChannel)
+{
+ switch (wFormatTag)
+ {
+ case WAVE_FORMAT_PCM:
+ switch (bitPerChannel)
+ {
+ case 16:
+ return SND_PCM_FORMAT_S16_LE;
+
+ case 8:
+ return SND_PCM_FORMAT_S8;
+
+ default:
+ return SND_PCM_FORMAT_UNKNOWN;
+ }
+
+ case WAVE_FORMAT_ALAW:
+ return SND_PCM_FORMAT_A_LAW;
+
+ case WAVE_FORMAT_MULAW:
+ return SND_PCM_FORMAT_MU_LAW;
+
+ default:
+ return SND_PCM_FORMAT_UNKNOWN;
+ }
+}
+
+static BOOL audin_alsa_set_params(AudinALSADevice* alsa, snd_pcm_t* capture_handle)
+{
+ int error = 0;
+ SSIZE_T s = 0;
+ UINT32 channels = alsa->aformat.nChannels;
+ snd_pcm_hw_params_t* hw_params = NULL;
+ snd_pcm_format_t format =
+ audin_alsa_format(alsa->aformat.wFormatTag, alsa->aformat.wBitsPerSample);
+
+ if ((error = snd_pcm_hw_params_malloc(&hw_params)) < 0)
+ {
+ WLog_Print(alsa->log, WLOG_ERROR, "snd_pcm_hw_params_malloc (%s)", snd_strerror(error));
+ return FALSE;
+ }
+
+ snd_pcm_hw_params_any(capture_handle, hw_params);
+ snd_pcm_hw_params_set_access(capture_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED);
+ snd_pcm_hw_params_set_format(capture_handle, hw_params, format);
+ snd_pcm_hw_params_set_rate_near(capture_handle, hw_params, &alsa->aformat.nSamplesPerSec, NULL);
+ snd_pcm_hw_params_set_channels_near(capture_handle, hw_params, &channels);
+ snd_pcm_hw_params(capture_handle, hw_params);
+ snd_pcm_hw_params_free(hw_params);
+ snd_pcm_prepare(capture_handle);
+ if (channels > UINT16_MAX)
+ return FALSE;
+ s = snd_pcm_format_size(format, 1);
+ if ((s < 0) || (s > UINT16_MAX))
+ return FALSE;
+ alsa->aformat.nChannels = (UINT16)channels;
+ alsa->bytes_per_frame = (size_t)s * channels;
+ return TRUE;
+}
+
+static DWORD WINAPI audin_alsa_thread_func(LPVOID arg)
+{
+ int error = 0;
+ BYTE* buffer = NULL;
+ snd_pcm_t* capture_handle = NULL;
+ AudinALSADevice* alsa = (AudinALSADevice*)arg;
+ DWORD status = 0;
+ WLog_Print(alsa->log, WLOG_DEBUG, "in");
+
+ if ((error = snd_pcm_open(&capture_handle, alsa->device_name, SND_PCM_STREAM_CAPTURE, 0)) < 0)
+ {
+ WLog_Print(alsa->log, WLOG_ERROR, "snd_pcm_open (%s)", snd_strerror(error));
+ error = CHANNEL_RC_INITIALIZATION_ERROR;
+ goto out;
+ }
+
+ if (!audin_alsa_set_params(alsa, capture_handle))
+ {
+ WLog_Print(alsa->log, WLOG_ERROR, "audin_alsa_set_params failed");
+ goto out;
+ }
+
+ buffer =
+ (BYTE*)calloc(alsa->frames_per_packet + alsa->aformat.nBlockAlign, alsa->bytes_per_frame);
+
+ if (!buffer)
+ {
+ WLog_Print(alsa->log, WLOG_ERROR, "calloc failed!");
+ error = CHANNEL_RC_NO_MEMORY;
+ goto out;
+ }
+
+ while (1)
+ {
+ size_t frames = alsa->frames_per_packet;
+ status = WaitForSingleObject(alsa->stopEvent, 0);
+
+ if (status == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_Print(alsa->log, WLOG_ERROR, "WaitForSingleObject failed with error %ld!", error);
+ break;
+ }
+
+ if (status == WAIT_OBJECT_0)
+ break;
+
+ error = snd_pcm_readi(capture_handle, buffer, frames);
+
+ if (error == 0)
+ continue;
+
+ if (error == -EPIPE)
+ {
+ snd_pcm_recover(capture_handle, error, 0);
+ continue;
+ }
+ else if (error < 0)
+ {
+ WLog_Print(alsa->log, WLOG_ERROR, "snd_pcm_readi (%s)", snd_strerror(error));
+ break;
+ }
+
+ error = alsa->receive(&alsa->aformat, buffer, (long)error * alsa->bytes_per_frame,
+ alsa->user_data);
+
+ if (error)
+ {
+ WLog_Print(alsa->log, WLOG_ERROR, "audin_alsa_thread_receive failed with error %ld",
+ error);
+ break;
+ }
+ }
+
+ free(buffer);
+
+ if (capture_handle)
+ snd_pcm_close(capture_handle);
+
+out:
+ WLog_Print(alsa->log, WLOG_DEBUG, "out");
+
+ if (error && alsa->rdpcontext)
+ setChannelError(alsa->rdpcontext, error, "audin_alsa_thread_func reported an error");
+
+ ExitThread(error);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_alsa_free(IAudinDevice* device)
+{
+ AudinALSADevice* alsa = (AudinALSADevice*)device;
+
+ if (alsa)
+ free(alsa->device_name);
+
+ free(alsa);
+ return CHANNEL_RC_OK;
+}
+
+static BOOL audin_alsa_format_supported(IAudinDevice* device, const AUDIO_FORMAT* format)
+{
+ if (!device || !format)
+ return FALSE;
+
+ switch (format->wFormatTag)
+ {
+ case WAVE_FORMAT_PCM:
+ if (format->cbSize == 0 && (format->nSamplesPerSec <= 48000) &&
+ (format->wBitsPerSample == 8 || format->wBitsPerSample == 16) &&
+ (format->nChannels == 1 || format->nChannels == 2))
+ {
+ return TRUE;
+ }
+
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ return FALSE;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_alsa_set_format(IAudinDevice* device, const AUDIO_FORMAT* format,
+ UINT32 FramesPerPacket)
+{
+ AudinALSADevice* alsa = (AudinALSADevice*)device;
+
+ if (!alsa || !format)
+ return ERROR_INVALID_PARAMETER;
+
+ alsa->aformat = *format;
+ alsa->frames_per_packet = FramesPerPacket;
+
+ if (audin_alsa_format(format->wFormatTag, format->wBitsPerSample) == SND_PCM_FORMAT_UNKNOWN)
+ return ERROR_INTERNAL_ERROR;
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_alsa_open(IAudinDevice* device, AudinReceive receive, void* user_data)
+{
+ AudinALSADevice* alsa = (AudinALSADevice*)device;
+
+ if (!device || !receive || !user_data)
+ return ERROR_INVALID_PARAMETER;
+
+ alsa->receive = receive;
+ alsa->user_data = user_data;
+
+ if (!(alsa->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ {
+ WLog_Print(alsa->log, WLOG_ERROR, "CreateEvent failed!");
+ goto error_out;
+ }
+
+ if (!(alsa->thread = CreateThread(NULL, 0, audin_alsa_thread_func, alsa, 0, NULL)))
+ {
+ WLog_Print(alsa->log, WLOG_ERROR, "CreateThread failed!");
+ goto error_out;
+ }
+
+ return CHANNEL_RC_OK;
+error_out:
+ CloseHandle(alsa->stopEvent);
+ alsa->stopEvent = NULL;
+ return ERROR_INTERNAL_ERROR;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_alsa_close(IAudinDevice* device)
+{
+ UINT error = CHANNEL_RC_OK;
+ AudinALSADevice* alsa = (AudinALSADevice*)device;
+
+ if (!alsa)
+ return ERROR_INVALID_PARAMETER;
+
+ if (alsa->stopEvent)
+ {
+ SetEvent(alsa->stopEvent);
+
+ if (WaitForSingleObject(alsa->thread, INFINITE) == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_Print(alsa->log, WLOG_ERROR, "WaitForSingleObject failed with error %" PRIu32 "",
+ error);
+ return error;
+ }
+
+ CloseHandle(alsa->stopEvent);
+ alsa->stopEvent = NULL;
+ CloseHandle(alsa->thread);
+ alsa->thread = NULL;
+ }
+
+ alsa->receive = NULL;
+ alsa->user_data = NULL;
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_alsa_parse_addin_args(AudinALSADevice* device, const ADDIN_ARGV* args)
+{
+ int status = 0;
+ DWORD flags = 0;
+ const COMMAND_LINE_ARGUMENT_A* arg = NULL;
+ AudinALSADevice* alsa = (AudinALSADevice*)device;
+ COMMAND_LINE_ARGUMENT_A audin_alsa_args[] = { { "dev", COMMAND_LINE_VALUE_REQUIRED, "<device>",
+ NULL, NULL, -1, NULL, "audio device name" },
+ { NULL, 0, NULL, NULL, NULL, -1, NULL, NULL } };
+ flags =
+ COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD;
+ status = CommandLineParseArgumentsA(args->argc, args->argv, audin_alsa_args, flags, alsa, NULL,
+ NULL);
+
+ if (status < 0)
+ return ERROR_INVALID_PARAMETER;
+
+ arg = audin_alsa_args;
+
+ do
+ {
+ if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT))
+ continue;
+
+ CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "dev")
+ {
+ alsa->device_name = _strdup(arg->Value);
+
+ if (!alsa->device_name)
+ {
+ WLog_Print(alsa->log, WLOG_ERROR, "_strdup failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+ }
+ CommandLineSwitchEnd(arg)
+ } while ((arg = CommandLineFindNextArgumentA(arg)) != NULL);
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+FREERDP_ENTRY_POINT(
+ UINT alsa_freerdp_audin_client_subsystem_entry(PFREERDP_AUDIN_DEVICE_ENTRY_POINTS pEntryPoints))
+{
+ const ADDIN_ARGV* args = NULL;
+ AudinALSADevice* alsa = NULL;
+ UINT error = 0;
+ alsa = (AudinALSADevice*)calloc(1, sizeof(AudinALSADevice));
+
+ if (!alsa)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ alsa->log = WLog_Get(TAG);
+ alsa->iface.Open = audin_alsa_open;
+ alsa->iface.FormatSupported = audin_alsa_format_supported;
+ alsa->iface.SetFormat = audin_alsa_set_format;
+ alsa->iface.Close = audin_alsa_close;
+ alsa->iface.Free = audin_alsa_free;
+ alsa->rdpcontext = pEntryPoints->rdpcontext;
+ args = pEntryPoints->args;
+
+ if ((error = audin_alsa_parse_addin_args(alsa, args)))
+ {
+ WLog_Print(alsa->log, WLOG_ERROR,
+ "audin_alsa_parse_addin_args failed with errorcode %" PRIu32 "!", error);
+ goto error_out;
+ }
+
+ if (!alsa->device_name)
+ {
+ alsa->device_name = _strdup("default");
+
+ if (!alsa->device_name)
+ {
+ WLog_Print(alsa->log, WLOG_ERROR, "_strdup failed!");
+ error = CHANNEL_RC_NO_MEMORY;
+ goto error_out;
+ }
+ }
+
+ alsa->frames_per_packet = 128;
+ alsa->aformat.nChannels = 2;
+ alsa->aformat.wBitsPerSample = 16;
+ alsa->aformat.wFormatTag = WAVE_FORMAT_PCM;
+ alsa->aformat.nSamplesPerSec = 44100;
+
+ if ((error = pEntryPoints->pRegisterAudinDevice(pEntryPoints->plugin, (IAudinDevice*)alsa)))
+ {
+ WLog_Print(alsa->log, WLOG_ERROR, "RegisterAudinDevice failed with error %" PRIu32 "!",
+ error);
+ goto error_out;
+ }
+
+ return CHANNEL_RC_OK;
+error_out:
+ free(alsa->device_name);
+ free(alsa);
+ return error;
+}
diff --git a/channels/audin/client/audin_main.c b/channels/audin/client/audin_main.c
new file mode 100644
index 0000000..1578d26
--- /dev/null
+++ b/channels/audin/client/audin_main.c
@@ -0,0 +1,1109 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Audio Input Redirection Virtual Channel
+ *
+ * Copyright 2010-2011 Vic Lee
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ * Copyright 2015 Armin Novak <armin.novak@thincast.com>
+ *
+ * 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 <errno.h>
+#include <winpr/assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <winpr/crt.h>
+#include <winpr/cmdline.h>
+#include <winpr/wlog.h>
+
+#include <freerdp/addin.h>
+
+#include <winpr/stream.h>
+#include <freerdp/freerdp.h>
+#include <freerdp/codec/dsp.h>
+#include <freerdp/client/channels.h>
+#include <freerdp/channels/audin.h>
+
+#include "audin_main.h"
+
+#define SNDIN_VERSION 0x02
+
+typedef enum
+{
+ MSG_SNDIN_VERSION = 0x01,
+ MSG_SNDIN_FORMATS = 0x02,
+ MSG_SNDIN_OPEN = 0x03,
+ MSG_SNDIN_OPEN_REPLY = 0x04,
+ MSG_SNDIN_DATA_INCOMING = 0x05,
+ MSG_SNDIN_DATA = 0x06,
+ MSG_SNDIN_FORMATCHANGE = 0x07,
+} MSG_SNDIN;
+
+typedef struct
+{
+ IWTSVirtualChannelCallback iface;
+
+ IWTSPlugin* plugin;
+ IWTSVirtualChannelManager* channel_mgr;
+ IWTSVirtualChannel* channel;
+
+ /**
+ * The supported format list sent back to the server, which needs to
+ * be stored as reference when the server sends the format index in
+ * Open PDU and Format Change PDU
+ */
+ AUDIO_FORMAT* formats;
+ UINT32 formats_count;
+} AUDIN_CHANNEL_CALLBACK;
+
+typedef struct
+{
+ IWTSPlugin iface;
+
+ GENERIC_LISTENER_CALLBACK* listener_callback;
+
+ /* Parsed plugin data */
+ AUDIO_FORMAT* fixed_format;
+ char* subsystem;
+ char* device_name;
+
+ /* Device interface */
+ IAudinDevice* device;
+
+ rdpContext* rdpcontext;
+ BOOL attached;
+ wStream* data;
+ AUDIO_FORMAT* format;
+ UINT32 FramesPerPacket;
+
+ FREERDP_DSP_CONTEXT* dsp_context;
+ wLog* log;
+
+ IWTSListener* listener;
+
+ BOOL initialized;
+ UINT32 version;
+} AUDIN_PLUGIN;
+
+static BOOL audin_process_addin_args(AUDIN_PLUGIN* audin, const ADDIN_ARGV* args);
+
+static UINT audin_channel_write_and_free(AUDIN_CHANNEL_CALLBACK* callback, wStream* out,
+ BOOL freeStream)
+{
+ if (!callback || !out)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!callback->channel || !callback->channel->Write)
+ return ERROR_INTERNAL_ERROR;
+
+ Stream_SealLength(out);
+ WINPR_ASSERT(Stream_Length(out) <= ULONG_MAX);
+ const UINT error = callback->channel->Write(callback->channel, (ULONG)Stream_Length(out),
+ Stream_Buffer(out), NULL);
+
+ if (freeStream)
+ Stream_Free(out, TRUE);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_process_version(AUDIN_PLUGIN* audin, AUDIN_CHANNEL_CALLBACK* callback, wStream* s)
+{
+ const UINT32 ClientVersion = SNDIN_VERSION;
+ UINT32 ServerVersion = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, ServerVersion);
+ WLog_Print(audin->log, WLOG_DEBUG, "ServerVersion=%" PRIu32 ", ClientVersion=%" PRIu32,
+ ServerVersion, ClientVersion);
+
+ /* Do not answer server packet, we do not support the channel version. */
+ if (ServerVersion > ClientVersion)
+ {
+ WLog_Print(audin->log, WLOG_WARN,
+ "Incompatible channel version server=%" PRIu32
+ ", client supports version=%" PRIu32,
+ ServerVersion, ClientVersion);
+ return CHANNEL_RC_OK;
+ }
+ audin->version = ServerVersion;
+
+ wStream* out = Stream_New(NULL, 5);
+
+ if (!out)
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "Stream_New failed!");
+ return ERROR_OUTOFMEMORY;
+ }
+
+ Stream_Write_UINT8(out, MSG_SNDIN_VERSION);
+ Stream_Write_UINT32(out, ClientVersion);
+ return audin_channel_write_and_free(callback, out, TRUE);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_send_incoming_data_pdu(AUDIN_CHANNEL_CALLBACK* callback)
+{
+ BYTE out_data[1] = { MSG_SNDIN_DATA_INCOMING };
+
+ if (!callback || !callback->channel || !callback->channel->Write)
+ return ERROR_INTERNAL_ERROR;
+
+ return callback->channel->Write(callback->channel, 1, out_data, NULL);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_process_formats(AUDIN_PLUGIN* audin, AUDIN_CHANNEL_CALLBACK* callback, wStream* s)
+{
+ UINT error = ERROR_INTERNAL_ERROR;
+ UINT32 NumFormats = 0;
+ UINT32 cbSizeFormatsPacket = 0;
+
+ WINPR_ASSERT(audin);
+ WINPR_ASSERT(callback);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, NumFormats);
+ WLog_Print(audin->log, WLOG_DEBUG, "NumFormats %" PRIu32 "", NumFormats);
+
+ if ((NumFormats < 1) || (NumFormats > 1000))
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "bad NumFormats %" PRIu32 "", NumFormats);
+ return ERROR_INVALID_DATA;
+ }
+
+ Stream_Seek_UINT32(s); /* cbSizeFormatsPacket */
+ callback->formats = audio_formats_new(NumFormats);
+
+ if (!callback->formats)
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "calloc failed!");
+ return ERROR_INVALID_DATA;
+ }
+
+ wStream* out = Stream_New(NULL, 9);
+
+ if (!out)
+ {
+ error = CHANNEL_RC_NO_MEMORY;
+ WLog_Print(audin->log, WLOG_ERROR, "Stream_New failed!");
+ goto out;
+ }
+
+ Stream_Seek(out, 9);
+
+ /* SoundFormats (variable) */
+ for (UINT32 i = 0; i < NumFormats; i++)
+ {
+ AUDIO_FORMAT format = { 0 };
+
+ if (!audio_format_read(s, &format))
+ {
+ error = ERROR_INVALID_DATA;
+ goto out;
+ }
+
+ audio_format_print(audin->log, WLOG_DEBUG, &format);
+
+ if (!audio_format_compatible(audin->fixed_format, &format))
+ {
+ audio_format_free(&format);
+ continue;
+ }
+
+ if (freerdp_dsp_supports_format(&format, TRUE) ||
+ audin->device->FormatSupported(audin->device, &format))
+ {
+ /* Store the agreed format in the corresponding index */
+ callback->formats[callback->formats_count++] = format;
+
+ if (!audio_format_write(out, &format))
+ {
+ error = CHANNEL_RC_NO_MEMORY;
+ WLog_Print(audin->log, WLOG_ERROR, "Stream_EnsureRemainingCapacity failed!");
+ goto out;
+ }
+ }
+ else
+ {
+ audio_format_free(&format);
+ }
+ }
+
+ if ((error = audin_send_incoming_data_pdu(callback)))
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "audin_send_incoming_data_pdu failed!");
+ goto out;
+ }
+
+ cbSizeFormatsPacket = (UINT32)Stream_GetPosition(out);
+ Stream_SetPosition(out, 0);
+ Stream_Write_UINT8(out, MSG_SNDIN_FORMATS); /* Header (1 byte) */
+ Stream_Write_UINT32(out, callback->formats_count); /* NumFormats (4 bytes) */
+ Stream_Write_UINT32(out, cbSizeFormatsPacket); /* cbSizeFormatsPacket (4 bytes) */
+ Stream_SetPosition(out, cbSizeFormatsPacket);
+ error = audin_channel_write_and_free(callback, out, FALSE);
+out:
+
+ if (error != CHANNEL_RC_OK)
+ {
+ audio_formats_free(callback->formats, NumFormats);
+ callback->formats = NULL;
+ }
+
+ Stream_Free(out, TRUE);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_send_format_change_pdu(AUDIN_PLUGIN* audin, AUDIN_CHANNEL_CALLBACK* callback,
+ UINT32 NewFormat)
+{
+ WINPR_ASSERT(audin);
+ WINPR_ASSERT(callback);
+
+ wStream* out = Stream_New(NULL, 5);
+
+ if (!out)
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "Stream_New failed!");
+ return CHANNEL_RC_OK;
+ }
+
+ Stream_Write_UINT8(out, MSG_SNDIN_FORMATCHANGE);
+ Stream_Write_UINT32(out, NewFormat);
+ return audin_channel_write_and_free(callback, out, TRUE);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_send_open_reply_pdu(AUDIN_PLUGIN* audin, AUDIN_CHANNEL_CALLBACK* callback,
+ UINT32 Result)
+{
+ WINPR_ASSERT(audin);
+ WINPR_ASSERT(callback);
+
+ wStream* out = Stream_New(NULL, 5);
+
+ if (!out)
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "Stream_New failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_Write_UINT8(out, MSG_SNDIN_OPEN_REPLY);
+ Stream_Write_UINT32(out, Result);
+ return audin_channel_write_and_free(callback, out, TRUE);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_receive_wave_data(const AUDIO_FORMAT* format, const BYTE* data, size_t size,
+ void* user_data)
+{
+ WINPR_ASSERT(format);
+
+ UINT error = ERROR_INTERNAL_ERROR;
+ AUDIN_CHANNEL_CALLBACK* callback = (AUDIN_CHANNEL_CALLBACK*)user_data;
+
+ if (!callback)
+ return CHANNEL_RC_BAD_CHANNEL_HANDLE;
+
+ AUDIN_PLUGIN* audin = (AUDIN_PLUGIN*)callback->plugin;
+
+ if (!audin)
+ return CHANNEL_RC_BAD_CHANNEL_HANDLE;
+
+ if (!audin->attached)
+ return CHANNEL_RC_OK;
+
+ Stream_SetPosition(audin->data, 0);
+
+ if (!Stream_EnsureRemainingCapacity(audin->data, 1))
+ return CHANNEL_RC_NO_MEMORY;
+
+ Stream_Write_UINT8(audin->data, MSG_SNDIN_DATA);
+
+ const BOOL compatible = audio_format_compatible(format, audin->format);
+ if (compatible && audin->device->FormatSupported(audin->device, audin->format))
+ {
+ if (!Stream_EnsureRemainingCapacity(audin->data, size))
+ return CHANNEL_RC_NO_MEMORY;
+
+ Stream_Write(audin->data, data, size);
+ }
+ else
+ {
+ if (!freerdp_dsp_encode(audin->dsp_context, format, data, size, audin->data))
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ /* Did not encode anything, skip this, the codec is not ready for output. */
+ if (Stream_GetPosition(audin->data) <= 1)
+ return CHANNEL_RC_OK;
+
+ audio_format_print(audin->log, WLOG_TRACE, audin->format);
+ WLog_Print(audin->log, WLOG_TRACE, "[%" PRIdz "/%" PRIdz "]", size,
+ Stream_GetPosition(audin->data) - 1);
+
+ if ((error = audin_send_incoming_data_pdu(callback)))
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "audin_send_incoming_data_pdu failed!");
+ return error;
+ }
+
+ return audin_channel_write_and_free(callback, audin->data, FALSE);
+}
+
+static BOOL audin_open_device(AUDIN_PLUGIN* audin, AUDIN_CHANNEL_CALLBACK* callback)
+{
+ UINT error = ERROR_INTERNAL_ERROR;
+ AUDIO_FORMAT format = { 0 };
+
+ if (!audin || !audin->device)
+ return FALSE;
+
+ format = *audin->format;
+ const BOOL supported =
+ IFCALLRESULT(FALSE, audin->device->FormatSupported, audin->device, &format);
+ WLog_Print(audin->log, WLOG_DEBUG, "microphone uses %s codec",
+ audio_format_get_tag_string(format.wFormatTag));
+
+ if (!supported)
+ {
+ /* Default sample rates supported by most backends. */
+ const UINT32 samplerates[] = { format.nSamplesPerSec, 96000, 48000, 44100, 22050 };
+ BOOL test = FALSE;
+
+ format.wFormatTag = WAVE_FORMAT_PCM;
+ format.wBitsPerSample = 16;
+ format.cbSize = 0;
+ for (size_t x = 0; x < ARRAYSIZE(samplerates); x++)
+ {
+ format.nSamplesPerSec = samplerates[x];
+ for (UINT16 y = audin->format->nChannels; y > 0; y--)
+ {
+ format.nChannels = y;
+ format.nBlockAlign = 2 * format.nChannels;
+ test = IFCALLRESULT(FALSE, audin->device->FormatSupported, audin->device, &format);
+ if (test)
+ break;
+ }
+ if (test)
+ break;
+ }
+ if (!test)
+ return FALSE;
+ }
+
+ IFCALLRET(audin->device->SetFormat, error, audin->device, &format, audin->FramesPerPacket);
+
+ if (error != CHANNEL_RC_OK)
+ {
+ WLog_ERR(TAG, "SetFormat failed with errorcode %" PRIu32 "", error);
+ return FALSE;
+ }
+
+ if (!freerdp_dsp_context_reset(audin->dsp_context, audin->format, audin->FramesPerPacket))
+ return FALSE;
+
+ IFCALLRET(audin->device->Open, error, audin->device, audin_receive_wave_data, callback);
+
+ if (error != CHANNEL_RC_OK)
+ {
+ WLog_ERR(TAG, "Open failed with errorcode %" PRIu32 "", error);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_process_open(AUDIN_PLUGIN* audin, AUDIN_CHANNEL_CALLBACK* callback, wStream* s)
+{
+ UINT32 initialFormat = 0;
+ UINT32 FramesPerPacket = 0;
+ UINT error = CHANNEL_RC_OK;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, FramesPerPacket);
+ Stream_Read_UINT32(s, initialFormat);
+ WLog_Print(audin->log, WLOG_DEBUG, "FramesPerPacket=%" PRIu32 " initialFormat=%" PRIu32 "",
+ FramesPerPacket, initialFormat);
+ audin->FramesPerPacket = FramesPerPacket;
+
+ if (initialFormat >= callback->formats_count)
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "invalid format index %" PRIu32 " (total %" PRIu32 ")",
+ initialFormat, callback->formats_count);
+ return ERROR_INVALID_DATA;
+ }
+
+ audin->format = &callback->formats[initialFormat];
+
+ if (!audin_open_device(audin, callback))
+ return ERROR_INTERNAL_ERROR;
+
+ if ((error = audin_send_format_change_pdu(audin, callback, initialFormat)))
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "audin_send_format_change_pdu failed!");
+ return error;
+ }
+
+ if ((error = audin_send_open_reply_pdu(audin, callback, 0)))
+ WLog_Print(audin->log, WLOG_ERROR, "audin_send_open_reply_pdu failed!");
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_process_format_change(AUDIN_PLUGIN* audin, AUDIN_CHANNEL_CALLBACK* callback,
+ wStream* s)
+{
+ UINT32 NewFormat = 0;
+ UINT error = CHANNEL_RC_OK;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, NewFormat);
+ WLog_Print(audin->log, WLOG_DEBUG, "NewFormat=%" PRIu32 "", NewFormat);
+
+ if (NewFormat >= callback->formats_count)
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "invalid format index %" PRIu32 " (total %" PRIu32 ")",
+ NewFormat, callback->formats_count);
+ return ERROR_INVALID_DATA;
+ }
+
+ audin->format = &callback->formats[NewFormat];
+
+ if (audin->device)
+ {
+ IFCALLRET(audin->device->Close, error, audin->device);
+
+ if (error != CHANNEL_RC_OK)
+ {
+ WLog_ERR(TAG, "Close failed with errorcode %" PRIu32 "", error);
+ return error;
+ }
+ }
+
+ if (!audin_open_device(audin, callback))
+ return ERROR_INTERNAL_ERROR;
+
+ if ((error = audin_send_format_change_pdu(audin, callback, NewFormat)))
+ WLog_ERR(TAG, "audin_send_format_change_pdu failed!");
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, wStream* data)
+{
+ UINT error = 0;
+ BYTE MessageId = 0;
+ AUDIN_CHANNEL_CALLBACK* callback = (AUDIN_CHANNEL_CALLBACK*)pChannelCallback;
+
+ if (!callback || !data)
+ return ERROR_INVALID_PARAMETER;
+
+ AUDIN_PLUGIN* audin = (AUDIN_PLUGIN*)callback->plugin;
+
+ if (!audin)
+ return ERROR_INTERNAL_ERROR;
+
+ if (!Stream_CheckAndLogRequiredCapacity(TAG, data, 1))
+ return ERROR_NO_DATA;
+
+ Stream_Read_UINT8(data, MessageId);
+ WLog_Print(audin->log, WLOG_DEBUG, "MessageId=0x%02" PRIx8 "", MessageId);
+
+ switch (MessageId)
+ {
+ case MSG_SNDIN_VERSION:
+ error = audin_process_version(audin, callback, data);
+ break;
+
+ case MSG_SNDIN_FORMATS:
+ error = audin_process_formats(audin, callback, data);
+ break;
+
+ case MSG_SNDIN_OPEN:
+ error = audin_process_open(audin, callback, data);
+ break;
+
+ case MSG_SNDIN_FORMATCHANGE:
+ error = audin_process_format_change(audin, callback, data);
+ break;
+
+ default:
+ WLog_Print(audin->log, WLOG_ERROR, "unknown MessageId=0x%02" PRIx8 "", MessageId);
+ error = ERROR_INVALID_DATA;
+ break;
+ }
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_on_close(IWTSVirtualChannelCallback* pChannelCallback)
+{
+ AUDIN_CHANNEL_CALLBACK* callback = (AUDIN_CHANNEL_CALLBACK*)pChannelCallback;
+ WINPR_ASSERT(callback);
+
+ AUDIN_PLUGIN* audin = (AUDIN_PLUGIN*)callback->plugin;
+ WINPR_ASSERT(audin);
+
+ UINT error = CHANNEL_RC_OK;
+ WLog_Print(audin->log, WLOG_TRACE, "...");
+
+ if (audin->device)
+ {
+ IFCALLRET(audin->device->Close, error, audin->device);
+
+ if (error != CHANNEL_RC_OK)
+ WLog_Print(audin->log, WLOG_ERROR, "Close failed with errorcode %" PRIu32 "", error);
+ }
+
+ audin->format = NULL;
+ audio_formats_free(callback->formats, callback->formats_count);
+ free(callback);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_on_new_channel_connection(IWTSListenerCallback* pListenerCallback,
+ IWTSVirtualChannel* pChannel, BYTE* Data,
+ BOOL* pbAccept, IWTSVirtualChannelCallback** ppCallback)
+{
+ GENERIC_LISTENER_CALLBACK* listener_callback = (GENERIC_LISTENER_CALLBACK*)pListenerCallback;
+
+ if (!listener_callback || !listener_callback->plugin)
+ return ERROR_INTERNAL_ERROR;
+
+ AUDIN_PLUGIN* audin = (AUDIN_PLUGIN*)listener_callback->plugin;
+ WLog_Print(audin->log, WLOG_TRACE, "...");
+ AUDIN_CHANNEL_CALLBACK* callback =
+ (AUDIN_CHANNEL_CALLBACK*)calloc(1, sizeof(AUDIN_CHANNEL_CALLBACK));
+
+ if (!callback)
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "calloc failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ callback->iface.OnDataReceived = audin_on_data_received;
+ callback->iface.OnClose = audin_on_close;
+ callback->plugin = listener_callback->plugin;
+ callback->channel_mgr = listener_callback->channel_mgr;
+ callback->channel = pChannel;
+ *ppCallback = (IWTSVirtualChannelCallback*)callback;
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_plugin_initialize(IWTSPlugin* pPlugin, IWTSVirtualChannelManager* pChannelMgr)
+{
+ AUDIN_PLUGIN* audin = (AUDIN_PLUGIN*)pPlugin;
+
+ if (!audin)
+ return CHANNEL_RC_BAD_CHANNEL_HANDLE;
+
+ if (!pChannelMgr)
+ return ERROR_INVALID_PARAMETER;
+
+ if (audin->initialized)
+ {
+ WLog_ERR(TAG, "[%s] channel initialized twice, aborting", AUDIN_DVC_CHANNEL_NAME);
+ return ERROR_INVALID_DATA;
+ }
+
+ WLog_Print(audin->log, WLOG_TRACE, "...");
+ audin->listener_callback =
+ (GENERIC_LISTENER_CALLBACK*)calloc(1, sizeof(GENERIC_LISTENER_CALLBACK));
+
+ if (!audin->listener_callback)
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "calloc failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ audin->listener_callback->iface.OnNewChannelConnection = audin_on_new_channel_connection;
+ audin->listener_callback->plugin = pPlugin;
+ audin->listener_callback->channel_mgr = pChannelMgr;
+ const UINT rc = pChannelMgr->CreateListener(pChannelMgr, AUDIN_DVC_CHANNEL_NAME, 0,
+ &audin->listener_callback->iface, &audin->listener);
+
+ audin->initialized = rc == CHANNEL_RC_OK;
+ return rc;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_plugin_terminated(IWTSPlugin* pPlugin)
+{
+ AUDIN_PLUGIN* audin = (AUDIN_PLUGIN*)pPlugin;
+ UINT error = CHANNEL_RC_OK;
+
+ if (!audin)
+ return CHANNEL_RC_BAD_CHANNEL_HANDLE;
+
+ WLog_Print(audin->log, WLOG_TRACE, "...");
+
+ if (audin->listener_callback)
+ {
+ IWTSVirtualChannelManager* mgr = audin->listener_callback->channel_mgr;
+ if (mgr)
+ IFCALL(mgr->DestroyListener, mgr, audin->listener);
+ }
+ audio_formats_free(audin->fixed_format, 1);
+
+ if (audin->device)
+ {
+ IFCALLRET(audin->device->Free, error, audin->device);
+
+ if (error != CHANNEL_RC_OK)
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "Free failed with errorcode %" PRIu32 "", error);
+ // dont stop on error
+ }
+
+ audin->device = NULL;
+ }
+
+ freerdp_dsp_context_free(audin->dsp_context);
+ Stream_Free(audin->data, TRUE);
+ free(audin->subsystem);
+ free(audin->device_name);
+ free(audin->listener_callback);
+ free(audin);
+ return CHANNEL_RC_OK;
+}
+
+static UINT audin_plugin_attached(IWTSPlugin* pPlugin)
+{
+ AUDIN_PLUGIN* audin = (AUDIN_PLUGIN*)pPlugin;
+ UINT error = CHANNEL_RC_OK;
+
+ if (!audin)
+ return CHANNEL_RC_BAD_CHANNEL_HANDLE;
+
+ audin->attached = TRUE;
+ return error;
+}
+
+static UINT audin_plugin_detached(IWTSPlugin* pPlugin)
+{
+ AUDIN_PLUGIN* audin = (AUDIN_PLUGIN*)pPlugin;
+ UINT error = CHANNEL_RC_OK;
+
+ if (!audin)
+ return CHANNEL_RC_BAD_CHANNEL_HANDLE;
+
+ audin->attached = FALSE;
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_register_device_plugin(IWTSPlugin* pPlugin, IAudinDevice* device)
+{
+ AUDIN_PLUGIN* audin = (AUDIN_PLUGIN*)pPlugin;
+
+ WINPR_ASSERT(audin);
+
+ if (audin->device)
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "existing device, abort.");
+ return ERROR_ALREADY_EXISTS;
+ }
+
+ WLog_Print(audin->log, WLOG_DEBUG, "device registered.");
+ audin->device = device;
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_load_device_plugin(AUDIN_PLUGIN* audin, const char* name, const ADDIN_ARGV* args)
+{
+ WINPR_ASSERT(audin);
+
+ FREERDP_AUDIN_DEVICE_ENTRY_POINTS entryPoints = { 0 };
+ UINT error = ERROR_INTERNAL_ERROR;
+ const PFREERDP_AUDIN_DEVICE_ENTRY entry =
+ (const PFREERDP_AUDIN_DEVICE_ENTRY)freerdp_load_channel_addin_entry(AUDIN_CHANNEL_NAME,
+ name, NULL, 0);
+
+ if (entry == NULL)
+ {
+ WLog_Print(audin->log, WLOG_ERROR,
+ "freerdp_load_channel_addin_entry did not return any function pointers for %s ",
+ name);
+ return ERROR_INVALID_FUNCTION;
+ }
+
+ entryPoints.plugin = &audin->iface;
+ entryPoints.pRegisterAudinDevice = audin_register_device_plugin;
+ entryPoints.args = args;
+ entryPoints.rdpcontext = audin->rdpcontext;
+
+ if ((error = entry(&entryPoints)))
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "%s entry returned error %" PRIu32 ".", name, error);
+ return error;
+ }
+
+ WLog_Print(audin->log, WLOG_INFO, "Loaded %s backend for audin", name);
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_set_subsystem(AUDIN_PLUGIN* audin, const char* subsystem)
+{
+ WINPR_ASSERT(audin);
+
+ free(audin->subsystem);
+ audin->subsystem = _strdup(subsystem);
+
+ if (!audin->subsystem)
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "_strdup failed!");
+ return ERROR_NOT_ENOUGH_MEMORY;
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_set_device_name(AUDIN_PLUGIN* audin, const char* device_name)
+{
+ WINPR_ASSERT(audin);
+
+ free(audin->device_name);
+ audin->device_name = _strdup(device_name);
+
+ if (!audin->device_name)
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "_strdup failed!");
+ return ERROR_NOT_ENOUGH_MEMORY;
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+BOOL audin_process_addin_args(AUDIN_PLUGIN* audin, const ADDIN_ARGV* args)
+{
+ COMMAND_LINE_ARGUMENT_A audin_args[] = {
+ { "sys", COMMAND_LINE_VALUE_REQUIRED, "<subsystem>", NULL, NULL, -1, NULL, "subsystem" },
+ { "dev", COMMAND_LINE_VALUE_REQUIRED, "<device>", NULL, NULL, -1, NULL, "device" },
+ { "format", COMMAND_LINE_VALUE_REQUIRED, "<format>", NULL, NULL, -1, NULL, "format" },
+ { "rate", COMMAND_LINE_VALUE_REQUIRED, "<rate>", NULL, NULL, -1, NULL, "rate" },
+ { "channel", COMMAND_LINE_VALUE_REQUIRED, "<channel>", NULL, NULL, -1, NULL, "channel" },
+ { NULL, 0, NULL, NULL, NULL, -1, NULL, NULL }
+ };
+
+ if (!args || args->argc == 1)
+ return TRUE;
+
+ const DWORD flags =
+ COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD;
+ const int status =
+ CommandLineParseArgumentsA(args->argc, args->argv, audin_args, flags, audin, NULL, NULL);
+
+ if (status != 0)
+ return FALSE;
+
+ const COMMAND_LINE_ARGUMENT_A* arg = audin_args;
+ errno = 0;
+
+ do
+ {
+ if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT))
+ continue;
+
+ CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "sys")
+ {
+ const UINT error = audin_set_subsystem(audin, arg->Value);
+ if (error != CHANNEL_RC_OK)
+ {
+ WLog_Print(audin->log, WLOG_ERROR,
+ "audin_set_subsystem failed with error %" PRIu32 "!", error);
+ return FALSE;
+ }
+ }
+ CommandLineSwitchCase(arg, "dev")
+ {
+ const UINT error = audin_set_device_name(audin, arg->Value);
+ if (error != CHANNEL_RC_OK)
+ {
+ WLog_Print(audin->log, WLOG_ERROR,
+ "audin_set_device_name failed with error %" PRIu32 "!", error);
+ return FALSE;
+ }
+ }
+ CommandLineSwitchCase(arg, "format")
+ {
+ unsigned long val = strtoul(arg->Value, NULL, 0);
+
+ if ((errno != 0) || (val > UINT16_MAX))
+ return FALSE;
+
+ audin->fixed_format->wFormatTag = (UINT16)val;
+ }
+ CommandLineSwitchCase(arg, "rate")
+ {
+ long val = strtol(arg->Value, NULL, 0);
+
+ if ((errno != 0) || (val < INT32_MIN) || (val > INT32_MAX))
+ return FALSE;
+
+ audin->fixed_format->nSamplesPerSec = val;
+ }
+ CommandLineSwitchCase(arg, "channel")
+ {
+ unsigned long val = strtoul(arg->Value, NULL, 0);
+
+ if ((errno != 0) || (val <= UINT16_MAX))
+ audin->fixed_format->nChannels = (UINT16)val;
+ }
+ CommandLineSwitchDefault(arg)
+ {
+ }
+ CommandLineSwitchEnd(arg)
+ } while ((arg = CommandLineFindNextArgumentA(arg)) != NULL);
+
+ return TRUE;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+FREERDP_ENTRY_POINT(UINT audin_DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints))
+{
+ struct SubsystemEntry
+ {
+ char* subsystem;
+ char* device;
+ };
+ UINT error = CHANNEL_RC_INITIALIZATION_ERROR;
+ struct SubsystemEntry entries[] =
+ {
+#if defined(WITH_PULSE)
+ { "pulse", "" },
+#endif
+#if defined(WITH_OSS)
+ { "oss", "default" },
+#endif
+#if defined(WITH_ALSA)
+ { "alsa", "default" },
+#endif
+#if defined(WITH_OPENSLES)
+ { "opensles", "default" },
+#endif
+#if defined(WITH_WINMM)
+ { "winmm", "default" },
+#endif
+#if defined(WITH_MACAUDIO)
+ { "mac", "default" },
+#endif
+#if defined(WITH_IOSAUDIO)
+ { "ios", "default" },
+#endif
+#if defined(WITH_SNDIO)
+ { "sndio", "default" },
+#endif
+ { NULL, NULL }
+ };
+ struct SubsystemEntry* entry = &entries[0];
+ WINPR_ASSERT(pEntryPoints);
+ WINPR_ASSERT(pEntryPoints->GetPlugin);
+ AUDIN_PLUGIN* audin = (AUDIN_PLUGIN*)pEntryPoints->GetPlugin(pEntryPoints, AUDIN_CHANNEL_NAME);
+
+ if (audin != NULL)
+ return CHANNEL_RC_ALREADY_INITIALIZED;
+
+ audin = (AUDIN_PLUGIN*)calloc(1, sizeof(AUDIN_PLUGIN));
+
+ if (!audin)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ audin->log = WLog_Get(TAG);
+ audin->data = Stream_New(NULL, 4096);
+ audin->fixed_format = audio_format_new();
+
+ if (!audin->fixed_format)
+ goto out;
+
+ if (!audin->data)
+ goto out;
+
+ audin->dsp_context = freerdp_dsp_context_new(TRUE);
+
+ if (!audin->dsp_context)
+ goto out;
+
+ audin->attached = TRUE;
+ audin->iface.Initialize = audin_plugin_initialize;
+ audin->iface.Connected = NULL;
+ audin->iface.Disconnected = NULL;
+ audin->iface.Terminated = audin_plugin_terminated;
+ audin->iface.Attached = audin_plugin_attached;
+ audin->iface.Detached = audin_plugin_detached;
+
+ const ADDIN_ARGV* args = pEntryPoints->GetPluginData(pEntryPoints);
+ audin->rdpcontext = pEntryPoints->GetRdpContext(pEntryPoints);
+
+ if (args)
+ {
+ if (!audin_process_addin_args(audin, args))
+ goto out;
+ }
+
+ if (audin->subsystem)
+ {
+ if ((error = audin_load_device_plugin(audin, audin->subsystem, args)))
+ {
+ WLog_Print(
+ audin->log, WLOG_ERROR,
+ "Unable to load microphone redirection subsystem %s because of error %" PRIu32 "",
+ audin->subsystem, error);
+ goto out;
+ }
+ }
+ else
+ {
+ while (entry && entry->subsystem && !audin->device)
+ {
+ if ((error = audin_set_subsystem(audin, entry->subsystem)))
+ {
+ WLog_Print(audin->log, WLOG_ERROR,
+ "audin_set_subsystem for %s failed with error %" PRIu32 "!",
+ entry->subsystem, error);
+ }
+ else if ((error = audin_set_device_name(audin, entry->device)))
+ {
+ WLog_Print(audin->log, WLOG_ERROR,
+ "audin_set_device_name for %s failed with error %" PRIu32 "!",
+ entry->subsystem, error);
+ }
+ else if ((error = audin_load_device_plugin(audin, audin->subsystem, args)))
+ {
+ WLog_Print(audin->log, WLOG_ERROR,
+ "audin_load_device_plugin %s failed with error %" PRIu32 "!",
+ entry->subsystem, error);
+ }
+
+ entry++;
+ }
+ }
+
+ if (audin->device == NULL)
+ {
+ /* If we have no audin device do not register plugin but still return OK or the client will
+ * just disconnect due to a missing microphone. */
+ WLog_Print(audin->log, WLOG_ERROR, "No microphone device could be found.");
+ error = CHANNEL_RC_OK;
+ goto out;
+ }
+
+ error = pEntryPoints->RegisterPlugin(pEntryPoints, AUDIN_CHANNEL_NAME, &audin->iface);
+ if (error == CHANNEL_RC_OK)
+ return error;
+
+out:
+ audin_plugin_terminated(&audin->iface);
+ return error;
+}
diff --git a/channels/audin/client/audin_main.h b/channels/audin/client/audin_main.h
new file mode 100644
index 0000000..1e6a498
--- /dev/null
+++ b/channels/audin/client/audin_main.h
@@ -0,0 +1,33 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Audio Input Redirection Virtual Channel
+ *
+ * Copyright 2010-2011 Vic Lee
+ *
+ * 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.
+ */
+
+#ifndef FREERDP_CHANNEL_AUDIN_CLIENT_MAIN_H
+#define FREERDP_CHANNEL_AUDIN_CLIENT_MAIN_H
+
+#include <freerdp/config.h>
+
+#include <freerdp/dvc.h>
+#include <freerdp/types.h>
+#include <freerdp/addin.h>
+#include <freerdp/channels/log.h>
+#include <freerdp/client/audin.h>
+
+#define TAG CHANNELS_TAG("audin.client")
+
+#endif /* FREERDP_CHANNEL_AUDIN_CLIENT_MAIN_H */
diff --git a/channels/audin/client/ios/CMakeLists.txt b/channels/audin/client/ios/CMakeLists.txt
new file mode 100644
index 0000000..e6ec3ad
--- /dev/null
+++ b/channels/audin/client/ios/CMakeLists.txt
@@ -0,0 +1,39 @@
+# 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" "ios" "")
+ FIND_LIBRARY(CORE_AUDIO CoreAudio)
+ FIND_LIBRARY(AVFOUNDATION AVFoundation)
+ FIND_LIBRARY(AUDIO_TOOL AudioToolbox)
+
+ set(${MODULE_PREFIX}_SRCS
+ audin_ios.m
+)
+
+set(${MODULE_PREFIX}_LIBS
+ winpr
+ freerdp
+ ${AVFOUNDATION}
+ ${CORE_AUDIO}
+ ${AUDIO_TOOL}
+)
+
+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/ios/audin_ios.m b/channels/audin/client/ios/audin_ios.m
new file mode 100644
index 0000000..ae30aee
--- /dev/null
+++ b/channels/audin/client/ios/audin_ios.m
@@ -0,0 +1,335 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Audio Input Redirection Virtual Channel - iOS 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 <AudioToolbox/AudioToolbox.h>
+#include <AudioToolbox/AudioQueue.h>
+
+#include <freerdp/addin.h>
+#include <freerdp/channels/rdpsnd.h>
+
+#include "audin_main.h"
+
+#define IOS_AUDIO_QUEUE_NUM_BUFFERS 100
+
+typedef struct
+{
+ IAudinDevice iface;
+
+ AUDIO_FORMAT format;
+ UINT32 FramesPerPacket;
+ int dev_unit;
+
+ AudinReceive receive;
+ void *user_data;
+
+ rdpContext *rdpcontext;
+
+ bool isOpen;
+ AudioQueueRef audioQueue;
+ AudioStreamBasicDescription audioFormat;
+ AudioQueueBufferRef audioBuffers[IOS_AUDIO_QUEUE_NUM_BUFFERS];
+} AudinIosDevice;
+
+static AudioFormatID audin_ios_get_format(const AUDIO_FORMAT *format)
+{
+ switch (format->wFormatTag)
+ {
+ case WAVE_FORMAT_PCM:
+ return kAudioFormatLinearPCM;
+
+ default:
+ return 0;
+ }
+}
+
+static AudioFormatFlags audin_ios_get_flags_for_format(const AUDIO_FORMAT *format)
+{
+ switch (format->wFormatTag)
+ {
+ case WAVE_FORMAT_PCM:
+ return kAudioFormatFlagIsSignedInteger;
+
+ default:
+ return 0;
+ }
+}
+
+static BOOL audin_ios_format_supported(IAudinDevice *device, const AUDIO_FORMAT *format)
+{
+ AudinIosDevice *ios = (AudinIosDevice *)device;
+ AudioFormatID req_fmt = 0;
+
+ if (device == NULL || format == NULL)
+ return FALSE;
+
+ req_fmt = audin_ios_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_ios_set_format(IAudinDevice *device, const AUDIO_FORMAT *format,
+ UINT32 FramesPerPacket)
+{
+ AudinIosDevice *ios = (AudinIosDevice *)device;
+
+ if (device == NULL || format == NULL)
+ return ERROR_INVALID_PARAMETER;
+
+ ios->FramesPerPacket = FramesPerPacket;
+ ios->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);
+ ios->audioFormat.mBitsPerChannel = format->wBitsPerSample;
+
+ if (format->wBitsPerSample == 0)
+ ios->audioFormat.mBitsPerChannel = 16;
+
+ ios->audioFormat.mChannelsPerFrame = ios->format.nChannels;
+ ios->audioFormat.mFramesPerPacket = 1;
+
+ ios->audioFormat.mBytesPerFrame =
+ ios->audioFormat.mChannelsPerFrame * (ios->audioFormat.mBitsPerChannel / 8);
+ ios->audioFormat.mBytesPerPacket =
+ ios->audioFormat.mBytesPerFrame * ios->audioFormat.mFramesPerPacket;
+
+ ios->audioFormat.mFormatFlags = audin_ios_get_flags_for_format(format);
+ ios->audioFormat.mFormatID = audin_ios_get_format(format);
+ ios->audioFormat.mReserved = 0;
+ ios->audioFormat.mSampleRate = ios->format.nSamplesPerSec;
+ return CHANNEL_RC_OK;
+}
+
+static void ios_audio_queue_input_cb(void *aqData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer,
+ const AudioTimeStamp *inStartTime, UInt32 inNumPackets,
+ const AudioStreamPacketDescription *inPacketDesc)
+{
+ AudinIosDevice *ios = (AudinIosDevice *)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 = ios->receive(&ios->format, buffer, buffer_size, ios->user_data);
+
+ AudioQueueEnqueueBuffer(inAQ, inBuffer, 0, NULL);
+
+ if (error)
+ {
+ WLog_ERR(TAG, "ios->receive failed with error %" PRIu32 "", error);
+ SetLastError(ERROR_INTERNAL_ERROR);
+ }
+}
+
+static UINT audin_ios_close(IAudinDevice *device)
+{
+ UINT errCode = CHANNEL_RC_OK;
+ char errString[1024];
+ OSStatus devStat;
+ AudinIosDevice *ios = (AudinIosDevice *)device;
+
+ if (device == NULL)
+ return ERROR_INVALID_PARAMETER;
+
+ if (ios->isOpen)
+ {
+ devStat = AudioQueueStop(ios->audioQueue, true);
+
+ if (devStat != 0)
+ {
+ errCode = GetLastError();
+ WLog_ERR(TAG, "AudioQueueStop failed with %s [%" PRIu32 "]",
+ winpr_strerror(errCode, errString, sizeof(errString)), errCode);
+ }
+
+ ios->isOpen = false;
+ }
+
+ if (ios->audioQueue)
+ {
+ devStat = AudioQueueDispose(ios->audioQueue, true);
+
+ if (devStat != 0)
+ {
+ errCode = GetLastError();
+ WLog_ERR(TAG, "AudioQueueDispose failed with %s [%" PRIu32 "]",
+ winpr_strerror(errCode, errString, sizeof(errString)), errCode);
+ }
+
+ ios->audioQueue = NULL;
+ }
+
+ ios->receive = NULL;
+ ios->user_data = NULL;
+ return errCode;
+}
+
+static UINT audin_ios_open(IAudinDevice *device, AudinReceive receive, void *user_data)
+{
+ AudinIosDevice *ios = (AudinIosDevice *)device;
+ DWORD errCode;
+ char errString[1024];
+ OSStatus devStat;
+
+ ios->receive = receive;
+ ios->user_data = user_data;
+ devStat = AudioQueueNewInput(&(ios->audioFormat), ios_audio_queue_input_cb, ios, NULL,
+ kCFRunLoopCommonModes, 0, &(ios->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 < IOS_AUDIO_QUEUE_NUM_BUFFERS; index++)
+ {
+ devStat = AudioQueueAllocateBuffer(ios->audioQueue,
+ ios->FramesPerPacket * 2 * ios->format.nChannels,
+ &ios->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(ios->audioQueue, ios->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(ios->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;
+ }
+
+ ios->isOpen = true;
+ return CHANNEL_RC_OK;
+err_out:
+ audin_ios_close(device);
+ return CHANNEL_RC_INITIALIZATION_ERROR;
+}
+
+static UINT audin_ios_free(IAudinDevice *device)
+{
+ AudinIosDevice *ios = (AudinIosDevice *)device;
+ int error;
+
+ if (device == NULL)
+ return ERROR_INVALID_PARAMETER;
+
+ if ((error = audin_ios_close(device)))
+ {
+ WLog_ERR(TAG, "audin_oss_close failed with error code %d!", error);
+ }
+
+ free(ios);
+ return CHANNEL_RC_OK;
+}
+
+FREERDP_ENTRY_POINT(
+ UINT ios_freerdp_audin_client_subsystem_entry(PFREERDP_AUDIN_DEVICE_ENTRY_POINTS pEntryPoints))
+{
+ DWORD errCode;
+ char errString[1024];
+ const ADDIN_ARGV *args;
+ AudinIosDevice *ios;
+ UINT error;
+ ios = (AudinIosDevice *)calloc(1, sizeof(AudinIosDevice));
+
+ if (!ios)
+ {
+ errCode = GetLastError();
+ WLog_ERR(TAG, "calloc failed with %s [%" PRIu32 "]",
+ winpr_strerror(errCode, errString, sizeof(errString)), errCode);
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ ios->iface.Open = audin_ios_open;
+ ios->iface.FormatSupported = audin_ios_format_supported;
+ ios->iface.SetFormat = audin_ios_set_format;
+ ios->iface.Close = audin_ios_close;
+ ios->iface.Free = audin_ios_free;
+ ios->rdpcontext = pEntryPoints->rdpcontext;
+ ios->dev_unit = -1;
+ args = pEntryPoints->args;
+
+ if ((error = pEntryPoints->pRegisterAudinDevice(pEntryPoints->plugin, (IAudinDevice *)ios)))
+ {
+ WLog_ERR(TAG, "RegisterAudinDevice failed with error %" PRIu32 "!", error);
+ goto error_out;
+ }
+
+ return CHANNEL_RC_OK;
+error_out:
+ free(ios);
+ return error;
+}
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;
+}
diff --git a/channels/audin/client/opensles/CMakeLists.txt b/channels/audin/client/opensles/CMakeLists.txt
new file mode 100644
index 0000000..9b2a287
--- /dev/null
+++ b/channels/audin/client/opensles/CMakeLists.txt
@@ -0,0 +1,36 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2013 Armin Novak <armin.novak@gmail.com>
+#
+# 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" "opensles" "")
+
+find_package(OpenSLES REQUIRED)
+
+set(${MODULE_PREFIX}_SRCS
+ opensl_io.c
+ audin_opensl_es.c
+)
+
+set(${MODULE_PREFIX}_LIBS
+ winpr
+ freerdp
+ ${OpenSLES_LIBRARIES}
+)
+
+include_directories(..)
+include_directories(${OpenSLES_INCLUDE_DIRS})
+
+add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "")
diff --git a/channels/audin/client/opensles/audin_opensl_es.c b/channels/audin/client/opensles/audin_opensl_es.c
new file mode 100644
index 0000000..a59fe05
--- /dev/null
+++ b/channels/audin/client/opensles/audin_opensl_es.c
@@ -0,0 +1,336 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Audio Input Redirection Virtual Channel - OpenSL ES implementation
+ *
+ * Copyright 2013 Armin Novak <armin.novak@gmail.com>
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ *
+ * 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 <winpr/assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <winpr/crt.h>
+#include <winpr/cmdline.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/addin.h>
+#include <freerdp/channels/rdpsnd.h>
+
+#include <SLES/OpenSLES.h>
+#include <freerdp/client/audin.h>
+
+#include "audin_main.h"
+#include "opensl_io.h"
+
+typedef struct
+{
+ IAudinDevice iface;
+
+ char* device_name;
+ OPENSL_STREAM* stream;
+
+ AUDIO_FORMAT format;
+ UINT32 frames_per_packet;
+
+ UINT32 bytes_per_channel;
+
+ AudinReceive receive;
+
+ void* user_data;
+
+ rdpContext* rdpcontext;
+ wLog* log;
+} AudinOpenSLESDevice;
+
+static UINT audin_opensles_close(IAudinDevice* device);
+
+static void audin_receive(void* context, const void* data, size_t size)
+{
+ UINT error;
+ AudinOpenSLESDevice* opensles = (AudinOpenSLESDevice*)context;
+
+ if (!opensles || !data)
+ {
+ WLog_ERR(TAG, "Invalid arguments context=%p, data=%p", opensles, data);
+ return;
+ }
+
+ error = opensles->receive(&opensles->format, data, size, opensles->user_data);
+
+ if (error && opensles->rdpcontext)
+ setChannelError(opensles->rdpcontext, error, "audin_receive reported an error");
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_opensles_free(IAudinDevice* device)
+{
+ AudinOpenSLESDevice* opensles = (AudinOpenSLESDevice*)device;
+
+ if (!opensles)
+ return ERROR_INVALID_PARAMETER;
+
+ WLog_Print(opensles->log, WLOG_DEBUG, "device=%p", (void*)device);
+
+ free(opensles->device_name);
+ free(opensles);
+ return CHANNEL_RC_OK;
+}
+
+static BOOL audin_opensles_format_supported(IAudinDevice* device, const AUDIO_FORMAT* format)
+{
+ AudinOpenSLESDevice* opensles = (AudinOpenSLESDevice*)device;
+
+ if (!opensles || !format)
+ return FALSE;
+
+ WLog_Print(opensles->log, WLOG_DEBUG, "device=%p, format=%p", (void*)opensles, (void*)format);
+ WINPR_ASSERT(format);
+
+ switch (format->wFormatTag)
+ {
+ case WAVE_FORMAT_PCM: /* PCM */
+ if (format->cbSize == 0 && (format->nSamplesPerSec <= 48000) &&
+ (format->wBitsPerSample == 8 || format->wBitsPerSample == 16) &&
+ (format->nChannels >= 1 && format->nChannels <= 2))
+ {
+ return TRUE;
+ }
+
+ break;
+
+ default:
+ WLog_Print(opensles->log, WLOG_DEBUG, "Encoding '%s' [0x%04" PRIX16 "] not supported",
+ audio_format_get_tag_string(format->wFormatTag), format->wFormatTag);
+ break;
+ }
+
+ return FALSE;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_opensles_set_format(IAudinDevice* device, const AUDIO_FORMAT* format,
+ UINT32 FramesPerPacket)
+{
+ AudinOpenSLESDevice* opensles = (AudinOpenSLESDevice*)device;
+
+ if (!opensles || !format)
+ return ERROR_INVALID_PARAMETER;
+
+ WLog_Print(opensles->log, WLOG_DEBUG, "device=%p, format=%p, FramesPerPacket=%" PRIu32 "",
+ (void*)device, (void*)format, FramesPerPacket);
+ WINPR_ASSERT(format);
+
+ opensles->format = *format;
+
+ switch (format->wFormatTag)
+ {
+ case WAVE_FORMAT_PCM:
+ opensles->frames_per_packet = FramesPerPacket;
+
+ switch (format->wBitsPerSample)
+ {
+ case 4:
+ opensles->bytes_per_channel = 1;
+ break;
+
+ case 8:
+ opensles->bytes_per_channel = 1;
+ break;
+
+ case 16:
+ opensles->bytes_per_channel = 2;
+ break;
+
+ default:
+ return ERROR_UNSUPPORTED_TYPE;
+ }
+
+ break;
+
+ default:
+ WLog_Print(opensles->log, WLOG_ERROR,
+ "Encoding '%" PRIu16 "' [%04" PRIX16 "] not supported", format->wFormatTag,
+ format->wFormatTag);
+ return ERROR_UNSUPPORTED_TYPE;
+ }
+
+ WLog_Print(opensles->log, WLOG_DEBUG, "frames_per_packet=%" PRIu32,
+ opensles->frames_per_packet);
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_opensles_open(IAudinDevice* device, AudinReceive receive, void* user_data)
+{
+ AudinOpenSLESDevice* opensles = (AudinOpenSLESDevice*)device;
+
+ if (!opensles || !receive || !user_data)
+ return ERROR_INVALID_PARAMETER;
+
+ WLog_Print(opensles->log, WLOG_DEBUG, "device=%p, receive=%p, user_data=%p", (void*)device,
+ (void*)receive, (void*)user_data);
+
+ if (opensles->stream)
+ goto error_out;
+
+ if (!(opensles->stream = android_OpenRecDevice(
+ opensles, audin_receive, opensles->format.nSamplesPerSec, opensles->format.nChannels,
+ opensles->frames_per_packet, opensles->format.wBitsPerSample)))
+ {
+ WLog_Print(opensles->log, WLOG_ERROR, "android_OpenRecDevice failed!");
+ goto error_out;
+ }
+
+ opensles->receive = receive;
+ opensles->user_data = user_data;
+ return CHANNEL_RC_OK;
+error_out:
+ audin_opensles_close(device);
+ return ERROR_INTERNAL_ERROR;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT audin_opensles_close(IAudinDevice* device)
+{
+ AudinOpenSLESDevice* opensles = (AudinOpenSLESDevice*)device;
+
+ if (!opensles)
+ return ERROR_INVALID_PARAMETER;
+
+ WLog_Print(opensles->log, WLOG_DEBUG, "device=%p", (void*)device);
+ android_CloseRecDevice(opensles->stream);
+ opensles->receive = NULL;
+ opensles->user_data = NULL;
+ opensles->stream = NULL;
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_opensles_parse_addin_args(AudinOpenSLESDevice* device, const ADDIN_ARGV* args)
+{
+ UINT status;
+ DWORD flags;
+ const COMMAND_LINE_ARGUMENT_A* arg;
+ AudinOpenSLESDevice* opensles = (AudinOpenSLESDevice*)device;
+ COMMAND_LINE_ARGUMENT_A audin_opensles_args[] = {
+ { "dev", COMMAND_LINE_VALUE_REQUIRED, "<device>", NULL, NULL, -1, NULL,
+ "audio device name" },
+ { NULL, 0, NULL, NULL, NULL, -1, NULL, NULL }
+ };
+
+ WLog_Print(opensles->log, WLOG_DEBUG, "device=%p, args=%p", (void*)device, (void*)args);
+ flags =
+ COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD;
+ status = CommandLineParseArgumentsA(args->argc, args->argv, audin_opensles_args, flags,
+ opensles, NULL, NULL);
+
+ if (status < 0)
+ return status;
+
+ arg = audin_opensles_args;
+
+ do
+ {
+ if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT))
+ continue;
+
+ CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "dev")
+ {
+ opensles->device_name = _strdup(arg->Value);
+
+ if (!opensles->device_name)
+ {
+ WLog_Print(opensles->log, WLOG_ERROR, "_strdup failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+ }
+ CommandLineSwitchEnd(arg)
+ } while ((arg = CommandLineFindNextArgumentA(arg)) != NULL);
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+FREERDP_ENTRY_POINT(UINT opensles_freerdp_audin_client_subsystem_entry(
+ PFREERDP_AUDIN_DEVICE_ENTRY_POINTS pEntryPoints))
+{
+ const ADDIN_ARGV* args;
+ AudinOpenSLESDevice* opensles;
+ UINT error;
+ opensles = (AudinOpenSLESDevice*)calloc(1, sizeof(AudinOpenSLESDevice));
+
+ if (!opensles)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ opensles->log = WLog_Get(TAG);
+ opensles->iface.Open = audin_opensles_open;
+ opensles->iface.FormatSupported = audin_opensles_format_supported;
+ opensles->iface.SetFormat = audin_opensles_set_format;
+ opensles->iface.Close = audin_opensles_close;
+ opensles->iface.Free = audin_opensles_free;
+ opensles->rdpcontext = pEntryPoints->rdpcontext;
+ args = pEntryPoints->args;
+
+ if ((error = audin_opensles_parse_addin_args(opensles, args)))
+ {
+ WLog_Print(opensles->log, WLOG_ERROR,
+ "audin_opensles_parse_addin_args failed with errorcode %" PRIu32 "!", error);
+ goto error_out;
+ }
+
+ if ((error = pEntryPoints->pRegisterAudinDevice(pEntryPoints->plugin, (IAudinDevice*)opensles)))
+ {
+ WLog_Print(opensles->log, WLOG_ERROR, "RegisterAudinDevice failed with error %" PRIu32 "!",
+ error);
+ goto error_out;
+ }
+
+ return CHANNEL_RC_OK;
+error_out:
+ free(opensles);
+ return error;
+}
diff --git a/channels/audin/client/opensles/opensl_io.c b/channels/audin/client/opensles/opensl_io.c
new file mode 100644
index 0000000..39b3dc8
--- /dev/null
+++ b/channels/audin/client/opensles/opensl_io.c
@@ -0,0 +1,388 @@
+/*
+opensl_io.c:
+Android OpenSL input/output module
+Copyright (c) 2012, Victor Lazzarini
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of the <organization> nor the
+ names of its contributors may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#include <winpr/assert.h>
+
+#include "audin_main.h"
+#include "opensl_io.h"
+#define CONV16BIT 32768
+#define CONVMYFLT (1. / 32768.)
+
+typedef struct
+{
+ size_t size;
+ void* data;
+} queue_element;
+
+struct opensl_stream
+{
+ // engine interfaces
+ SLObjectItf engineObject;
+ SLEngineItf engineEngine;
+
+ // device interfaces
+ SLDeviceVolumeItf deviceVolume;
+
+ // recorder interfaces
+ SLObjectItf recorderObject;
+ SLRecordItf recorderRecord;
+ SLAndroidSimpleBufferQueueItf recorderBufferQueue;
+
+ unsigned int inchannels;
+ unsigned int sr;
+ unsigned int buffersize;
+ unsigned int bits_per_sample;
+
+ queue_element* prep;
+ queue_element* next;
+
+ void* context;
+ opensl_receive_t receive;
+};
+
+static void bqRecorderCallback(SLAndroidSimpleBufferQueueItf bq, void* context);
+
+// creates the OpenSL ES audio engine
+static SLresult openSLCreateEngine(OPENSL_STREAM* p)
+{
+ SLresult result;
+ // create engine
+ result = slCreateEngine(&(p->engineObject), 0, NULL, 0, NULL, NULL);
+
+ if (result != SL_RESULT_SUCCESS)
+ goto engine_end;
+
+ // realize the engine
+ result = (*p->engineObject)->Realize(p->engineObject, SL_BOOLEAN_FALSE);
+
+ if (result != SL_RESULT_SUCCESS)
+ goto engine_end;
+
+ // get the engine interface, which is needed in order to create other objects
+ result = (*p->engineObject)->GetInterface(p->engineObject, SL_IID_ENGINE, &(p->engineEngine));
+
+ if (result != SL_RESULT_SUCCESS)
+ goto engine_end;
+
+ // get the volume interface - important, this is optional!
+ result =
+ (*p->engineObject)->GetInterface(p->engineObject, SL_IID_DEVICEVOLUME, &(p->deviceVolume));
+
+ if (result != SL_RESULT_SUCCESS)
+ {
+ p->deviceVolume = NULL;
+ result = SL_RESULT_SUCCESS;
+ }
+
+engine_end:
+ WINPR_ASSERT(SL_RESULT_SUCCESS == result);
+ return result;
+}
+
+// Open the OpenSL ES device for input
+static SLresult openSLRecOpen(OPENSL_STREAM* p)
+{
+ SLresult result;
+ SLuint32 sr = p->sr;
+ SLuint32 channels = p->inchannels;
+ WINPR_ASSERT(!p->recorderObject);
+
+ if (channels)
+ {
+ switch (sr)
+ {
+ case 8000:
+ sr = SL_SAMPLINGRATE_8;
+ break;
+
+ case 11025:
+ sr = SL_SAMPLINGRATE_11_025;
+ break;
+
+ case 16000:
+ sr = SL_SAMPLINGRATE_16;
+ break;
+
+ case 22050:
+ sr = SL_SAMPLINGRATE_22_05;
+ break;
+
+ case 24000:
+ sr = SL_SAMPLINGRATE_24;
+ break;
+
+ case 32000:
+ sr = SL_SAMPLINGRATE_32;
+ break;
+
+ case 44100:
+ sr = SL_SAMPLINGRATE_44_1;
+ break;
+
+ case 48000:
+ sr = SL_SAMPLINGRATE_48;
+ break;
+
+ case 64000:
+ sr = SL_SAMPLINGRATE_64;
+ break;
+
+ case 88200:
+ sr = SL_SAMPLINGRATE_88_2;
+ break;
+
+ case 96000:
+ sr = SL_SAMPLINGRATE_96;
+ break;
+
+ case 192000:
+ sr = SL_SAMPLINGRATE_192;
+ break;
+
+ default:
+ return -1;
+ }
+
+ // configure audio source
+ SLDataLocator_IODevice loc_dev = { SL_DATALOCATOR_IODEVICE, SL_IODEVICE_AUDIOINPUT,
+ SL_DEFAULTDEVICEID_AUDIOINPUT, NULL };
+ SLDataSource audioSrc = { &loc_dev, NULL };
+ // configure audio sink
+ int speakers;
+
+ if (channels > 1)
+ speakers = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
+ else
+ speakers = SL_SPEAKER_FRONT_CENTER;
+
+ SLDataLocator_AndroidSimpleBufferQueue loc_bq = { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,
+ 2 };
+ SLDataFormat_PCM format_pcm;
+ format_pcm.formatType = SL_DATAFORMAT_PCM;
+ format_pcm.numChannels = channels;
+ format_pcm.samplesPerSec = sr;
+ format_pcm.channelMask = speakers;
+ format_pcm.endianness = SL_BYTEORDER_LITTLEENDIAN;
+
+ if (16 == p->bits_per_sample)
+ {
+ format_pcm.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16;
+ format_pcm.containerSize = 16;
+ }
+ else if (8 == p->bits_per_sample)
+ {
+ format_pcm.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_8;
+ format_pcm.containerSize = 8;
+ }
+ else
+ WINPR_ASSERT(0);
+
+ SLDataSink audioSnk = { &loc_bq, &format_pcm };
+ // create audio recorder
+ // (requires the RECORD_AUDIO permission)
+ const SLInterfaceID id[] = { SL_IID_ANDROIDSIMPLEBUFFERQUEUE };
+ const SLboolean req[] = { SL_BOOLEAN_TRUE };
+ result = (*p->engineEngine)
+ ->CreateAudioRecorder(p->engineEngine, &(p->recorderObject), &audioSrc,
+ &audioSnk, 1, id, req);
+ WINPR_ASSERT(!result);
+
+ if (SL_RESULT_SUCCESS != result)
+ goto end_recopen;
+
+ // realize the audio recorder
+ result = (*p->recorderObject)->Realize(p->recorderObject, SL_BOOLEAN_FALSE);
+ WINPR_ASSERT(!result);
+
+ if (SL_RESULT_SUCCESS != result)
+ goto end_recopen;
+
+ // get the record interface
+ result = (*p->recorderObject)
+ ->GetInterface(p->recorderObject, SL_IID_RECORD, &(p->recorderRecord));
+ WINPR_ASSERT(!result);
+
+ if (SL_RESULT_SUCCESS != result)
+ goto end_recopen;
+
+ // get the buffer queue interface
+ result = (*p->recorderObject)
+ ->GetInterface(p->recorderObject, SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
+ &(p->recorderBufferQueue));
+ WINPR_ASSERT(!result);
+
+ if (SL_RESULT_SUCCESS != result)
+ goto end_recopen;
+
+ // register callback on the buffer queue
+ result = (*p->recorderBufferQueue)
+ ->RegisterCallback(p->recorderBufferQueue, bqRecorderCallback, p);
+ WINPR_ASSERT(!result);
+
+ if (SL_RESULT_SUCCESS != result)
+ goto end_recopen;
+
+ end_recopen:
+ return result;
+ }
+ else
+ return SL_RESULT_SUCCESS;
+}
+
+// close the OpenSL IO and destroy the audio engine
+static void openSLDestroyEngine(OPENSL_STREAM* p)
+{
+ // destroy audio recorder object, and invalidate all associated interfaces
+ if (p->recorderObject != NULL)
+ {
+ (*p->recorderObject)->Destroy(p->recorderObject);
+ p->recorderObject = NULL;
+ p->recorderRecord = NULL;
+ p->recorderBufferQueue = NULL;
+ }
+
+ // destroy engine object, and invalidate all associated interfaces
+ if (p->engineObject != NULL)
+ {
+ (*p->engineObject)->Destroy(p->engineObject);
+ p->engineObject = NULL;
+ p->engineEngine = NULL;
+ }
+}
+
+static queue_element* opensles_queue_element_new(size_t size)
+{
+ queue_element* q = calloc(1, sizeof(queue_element));
+
+ if (!q)
+ goto fail;
+
+ q->size = size;
+ q->data = malloc(size);
+
+ if (!q->data)
+ goto fail;
+
+ return q;
+fail:
+ free(q);
+ return NULL;
+}
+
+static void opensles_queue_element_free(void* obj)
+{
+ queue_element* e = (queue_element*)obj;
+
+ if (e)
+ free(e->data);
+
+ free(e);
+}
+
+// open the android audio device for input
+OPENSL_STREAM* android_OpenRecDevice(void* context, opensl_receive_t receive, int sr,
+ int inchannels, int bufferframes, int bits_per_sample)
+{
+ OPENSL_STREAM* p;
+
+ if (!context || !receive)
+ return NULL;
+
+ p = (OPENSL_STREAM*)calloc(1, sizeof(OPENSL_STREAM));
+
+ if (!p)
+ return NULL;
+
+ p->context = context;
+ p->receive = receive;
+ p->inchannels = inchannels;
+ p->sr = sr;
+ p->buffersize = bufferframes;
+ p->bits_per_sample = bits_per_sample;
+
+ if ((p->bits_per_sample != 8) && (p->bits_per_sample != 16))
+ goto fail;
+
+ if (openSLCreateEngine(p) != SL_RESULT_SUCCESS)
+ goto fail;
+
+ if (openSLRecOpen(p) != SL_RESULT_SUCCESS)
+ goto fail;
+
+ /* Create receive buffers, prepare them and start recording */
+ p->prep = opensles_queue_element_new(p->buffersize * p->bits_per_sample / 8);
+ p->next = opensles_queue_element_new(p->buffersize * p->bits_per_sample / 8);
+
+ if (!p->prep || !p->next)
+ goto fail;
+
+ (*p->recorderBufferQueue)->Enqueue(p->recorderBufferQueue, p->next->data, p->next->size);
+ (*p->recorderBufferQueue)->Enqueue(p->recorderBufferQueue, p->prep->data, p->prep->size);
+ (*p->recorderRecord)->SetRecordState(p->recorderRecord, SL_RECORDSTATE_RECORDING);
+ return p;
+fail:
+ android_CloseRecDevice(p);
+ return NULL;
+}
+
+// close the android audio device
+void android_CloseRecDevice(OPENSL_STREAM* p)
+{
+ if (p == NULL)
+ return;
+
+ opensles_queue_element_free(p->next);
+ opensles_queue_element_free(p->prep);
+ openSLDestroyEngine(p);
+ free(p);
+}
+
+// this callback handler is called every time a buffer finishes recording
+static void bqRecorderCallback(SLAndroidSimpleBufferQueueItf bq, void* context)
+{
+ OPENSL_STREAM* p = (OPENSL_STREAM*)context;
+ queue_element* e;
+
+ if (!p)
+ return;
+
+ e = p->next;
+
+ if (!e)
+ return;
+
+ if (!p->context || !p->receive)
+ WLog_WARN(TAG, "Missing receive callback=%p, context=%p", p->receive, p->context);
+ else
+ p->receive(p->context, e->data, e->size);
+
+ p->next = p->prep;
+ p->prep = e;
+ (*p->recorderBufferQueue)->Enqueue(p->recorderBufferQueue, e->data, e->size);
+}
diff --git a/channels/audin/client/opensles/opensl_io.h b/channels/audin/client/opensles/opensl_io.h
new file mode 100644
index 0000000..e99522c
--- /dev/null
+++ b/channels/audin/client/opensles/opensl_io.h
@@ -0,0 +1,65 @@
+/*
+opensl_io.c:
+Android OpenSL input/output module header
+Copyright (c) 2012, Victor Lazzarini
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of the <organization> nor the
+ names of its contributors may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#ifndef FREERDP_CHANNEL_AUDIN_CLIENT_OPENSL_IO_H
+#define FREERDP_CHANNEL_AUDIN_CLIENT_OPENSL_IO_H
+
+#include <SLES/OpenSLES.h>
+#include <SLES/OpenSLES_Android.h>
+
+#include <freerdp/api.h>
+
+#include <stdlib.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct opensl_stream OPENSL_STREAM;
+
+ typedef void (*opensl_receive_t)(void* context, const void* data, size_t size);
+
+ /*
+ Open the audio device with a given sampling rate (sr), input and output channels and IO buffer
+ size in frames. Returns a handle to the OpenSL stream
+ */
+ FREERDP_LOCAL OPENSL_STREAM* android_OpenRecDevice(void* context, opensl_receive_t receive,
+ int sr, int inchannels, int bufferframes,
+ int bits_per_sample);
+ /*
+ Close the audio device
+ */
+ FREERDP_LOCAL void android_CloseRecDevice(OPENSL_STREAM* p);
+
+#ifdef __cplusplus
+};
+#endif
+
+#endif /* FREERDP_CHANNEL_AUDIN_CLIENT_OPENSL_IO_H */
diff --git a/channels/audin/client/oss/CMakeLists.txt b/channels/audin/client/oss/CMakeLists.txt
new file mode 100644
index 0000000..6b747e4
--- /dev/null
+++ b/channels/audin/client/oss/CMakeLists.txt
@@ -0,0 +1,36 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright (c) 2015 Rozhuk Ivan <rozhuk.im@gmail.com>
+#
+# 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" "oss" "")
+
+find_package(OSS REQUIRED)
+
+set(${MODULE_PREFIX}_SRCS
+ audin_oss.c
+)
+
+set(${MODULE_PREFIX}_LIBS
+ winpr
+ freerdp
+ ${OSS_LIBRARIES}
+)
+
+include_directories(..)
+include_directories(${OSS_INCLUDE_DIRS})
+
+add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "")
+
diff --git a/channels/audin/client/oss/audin_oss.c b/channels/audin/client/oss/audin_oss.c
new file mode 100644
index 0000000..979a800
--- /dev/null
+++ b/channels/audin/client/oss/audin_oss.c
@@ -0,0 +1,491 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Audio Input Redirection Virtual Channel - OSS implementation
+ *
+ * Copyright (c) 2015 Rozhuk Ivan <rozhuk.im@gmail.com>
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ *
+ * 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/cmdline.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libgen.h>
+#include <limits.h>
+#include <unistd.h>
+#if defined(__OpenBSD__)
+#include <soundcard.h>
+#else
+#include <sys/soundcard.h>
+#endif
+#include <sys/ioctl.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/addin.h>
+#include <freerdp/channels/rdpsnd.h>
+
+#include "audin_main.h"
+
+typedef struct
+{
+ IAudinDevice iface;
+
+ HANDLE thread;
+ HANDLE stopEvent;
+
+ AUDIO_FORMAT format;
+ UINT32 FramesPerPacket;
+ int dev_unit;
+
+ AudinReceive receive;
+ void* user_data;
+
+ rdpContext* rdpcontext;
+} AudinOSSDevice;
+
+#define OSS_LOG_ERR(_text, _error) \
+ do \
+ { \
+ if (_error != 0) \
+ { \
+ char buffer[256] = { 0 }; \
+ WLog_ERR(TAG, "%s: %i - %s\n", _text, _error, \
+ winpr_strerror(_error, buffer, sizeof(buffer))); \
+ } \
+ } while (0)
+
+static UINT32 audin_oss_get_format(const AUDIO_FORMAT* format)
+{
+ switch (format->wFormatTag)
+ {
+ case WAVE_FORMAT_PCM:
+ switch (format->wBitsPerSample)
+ {
+ case 8:
+ return AFMT_S8;
+
+ case 16:
+ return AFMT_S16_LE;
+ }
+
+ break;
+
+ case WAVE_FORMAT_ALAW:
+ return AFMT_A_LAW;
+
+ case WAVE_FORMAT_MULAW:
+ return AFMT_MU_LAW;
+ }
+
+ return 0;
+}
+
+static BOOL audin_oss_format_supported(IAudinDevice* device, const AUDIO_FORMAT* format)
+{
+ if (device == NULL || format == NULL)
+ return FALSE;
+
+ switch (format->wFormatTag)
+ {
+ case WAVE_FORMAT_PCM:
+ if (format->cbSize != 0 || format->nSamplesPerSec > 48000 ||
+ (format->wBitsPerSample != 8 && format->wBitsPerSample != 16) ||
+ (format->nChannels != 1 && format->nChannels != 2))
+ return FALSE;
+
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_oss_set_format(IAudinDevice* device, const AUDIO_FORMAT* format,
+ UINT32 FramesPerPacket)
+{
+ AudinOSSDevice* oss = (AudinOSSDevice*)device;
+
+ if (device == NULL || format == NULL)
+ return ERROR_INVALID_PARAMETER;
+
+ oss->FramesPerPacket = FramesPerPacket;
+ oss->format = *format;
+ return CHANNEL_RC_OK;
+}
+
+static DWORD WINAPI audin_oss_thread_func(LPVOID arg)
+{
+ char dev_name[PATH_MAX] = "/dev/dsp";
+ char mixer_name[PATH_MAX] = "/dev/mixer";
+ int pcm_handle = -1;
+ int mixer_handle = 0;
+ BYTE* buffer = NULL;
+ unsigned long tmp = 0;
+ size_t buffer_size = 0;
+ AudinOSSDevice* oss = (AudinOSSDevice*)arg;
+ UINT error = 0;
+ DWORD status = 0;
+
+ if (oss == NULL)
+ {
+ error = ERROR_INVALID_PARAMETER;
+ goto err_out;
+ }
+
+ if (oss->dev_unit != -1)
+ {
+ sprintf_s(dev_name, (PATH_MAX - 1), "/dev/dsp%i", oss->dev_unit);
+ sprintf_s(mixer_name, PATH_MAX - 1, "/dev/mixer%i", oss->dev_unit);
+ }
+
+ WLog_INFO(TAG, "open: %s", dev_name);
+
+ if ((pcm_handle = open(dev_name, O_RDONLY)) < 0)
+ {
+ OSS_LOG_ERR("sound dev open failed", errno);
+ error = ERROR_INTERNAL_ERROR;
+ goto err_out;
+ }
+
+ /* Set rec volume to 100%. */
+ if ((mixer_handle = open(mixer_name, O_RDWR)) < 0)
+ {
+ OSS_LOG_ERR("mixer open failed, not critical", errno);
+ }
+ else
+ {
+ tmp = (100 | (100 << 8));
+
+ if (ioctl(mixer_handle, MIXER_WRITE(SOUND_MIXER_MIC), &tmp) == -1)
+ OSS_LOG_ERR("WRITE_MIXER - SOUND_MIXER_MIC, not critical", errno);
+
+ tmp = (100 | (100 << 8));
+
+ if (ioctl(mixer_handle, MIXER_WRITE(SOUND_MIXER_RECLEV), &tmp) == -1)
+ OSS_LOG_ERR("WRITE_MIXER - SOUND_MIXER_RECLEV, not critical", errno);
+
+ close(mixer_handle);
+ }
+
+#if 0 /* FreeBSD OSS implementation at this moment (2015.03) does not set PCM_CAP_INPUT flag. */
+ tmp = 0;
+
+ if (ioctl(pcm_handle, SNDCTL_DSP_GETCAPS, &tmp) == -1)
+ {
+ OSS_LOG_ERR("SNDCTL_DSP_GETCAPS failed, try ignory", errno);
+ }
+ else if ((tmp & PCM_CAP_INPUT) == 0)
+ {
+ OSS_LOG_ERR("Device does not supports playback", EOPNOTSUPP);
+ goto err_out;
+ }
+
+#endif
+ /* Set format. */
+ tmp = audin_oss_get_format(&oss->format);
+
+ if (ioctl(pcm_handle, SNDCTL_DSP_SETFMT, &tmp) == -1)
+ OSS_LOG_ERR("SNDCTL_DSP_SETFMT failed", errno);
+
+ tmp = oss->format.nChannels;
+
+ if (ioctl(pcm_handle, SNDCTL_DSP_CHANNELS, &tmp) == -1)
+ OSS_LOG_ERR("SNDCTL_DSP_CHANNELS failed", errno);
+
+ tmp = oss->format.nSamplesPerSec;
+
+ if (ioctl(pcm_handle, SNDCTL_DSP_SPEED, &tmp) == -1)
+ OSS_LOG_ERR("SNDCTL_DSP_SPEED failed", errno);
+
+ tmp = oss->format.nBlockAlign;
+
+ if (ioctl(pcm_handle, SNDCTL_DSP_SETFRAGMENT, &tmp) == -1)
+ OSS_LOG_ERR("SNDCTL_DSP_SETFRAGMENT failed", errno);
+
+ buffer_size = (oss->FramesPerPacket * oss->format.nChannels * (oss->format.wBitsPerSample / 8));
+ buffer = (BYTE*)calloc((buffer_size + sizeof(void*)), sizeof(BYTE));
+
+ if (NULL == buffer)
+ {
+ OSS_LOG_ERR("malloc() fail", errno);
+ error = ERROR_NOT_ENOUGH_MEMORY;
+ goto err_out;
+ }
+
+ while (1)
+ {
+ SSIZE_T stmp = -1;
+ status = WaitForSingleObject(oss->stopEvent, 0);
+
+ if (status == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error);
+ goto err_out;
+ }
+
+ if (status == WAIT_OBJECT_0)
+ break;
+
+ stmp = read(pcm_handle, buffer, buffer_size);
+
+ /* Error happen. */
+ if (stmp < 0)
+ {
+ OSS_LOG_ERR("read() error", errno);
+ continue;
+ }
+
+ if ((size_t)stmp < buffer_size) /* Not enouth data. */
+ continue;
+
+ if ((error = oss->receive(&oss->format, buffer, buffer_size, oss->user_data)))
+ {
+ WLog_ERR(TAG, "oss->receive failed with error %" PRIu32 "", error);
+ break;
+ }
+ }
+
+err_out:
+
+ if (error && oss && oss->rdpcontext)
+ setChannelError(oss->rdpcontext, error, "audin_oss_thread_func reported an error");
+
+ if (pcm_handle != -1)
+ {
+ WLog_INFO(TAG, "close: %s", dev_name);
+ close(pcm_handle);
+ }
+
+ free(buffer);
+ ExitThread(error);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_oss_open(IAudinDevice* device, AudinReceive receive, void* user_data)
+{
+ AudinOSSDevice* oss = (AudinOSSDevice*)device;
+ oss->receive = receive;
+ oss->user_data = user_data;
+
+ if (!(oss->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ {
+ WLog_ERR(TAG, "CreateEvent failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ if (!(oss->thread = CreateThread(NULL, 0, audin_oss_thread_func, oss, 0, NULL)))
+ {
+ WLog_ERR(TAG, "CreateThread failed!");
+ CloseHandle(oss->stopEvent);
+ oss->stopEvent = NULL;
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_oss_close(IAudinDevice* device)
+{
+ UINT error = 0;
+ AudinOSSDevice* oss = (AudinOSSDevice*)device;
+
+ if (device == NULL)
+ return ERROR_INVALID_PARAMETER;
+
+ if (oss->stopEvent != NULL)
+ {
+ SetEvent(oss->stopEvent);
+
+ if (WaitForSingleObject(oss->thread, INFINITE) == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error);
+ return error;
+ }
+
+ CloseHandle(oss->stopEvent);
+ oss->stopEvent = NULL;
+ CloseHandle(oss->thread);
+ oss->thread = NULL;
+ }
+
+ oss->receive = NULL;
+ oss->user_data = NULL;
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_oss_free(IAudinDevice* device)
+{
+ AudinOSSDevice* oss = (AudinOSSDevice*)device;
+ UINT error = 0;
+
+ if (device == NULL)
+ return ERROR_INVALID_PARAMETER;
+
+ if ((error = audin_oss_close(device)))
+ {
+ WLog_ERR(TAG, "audin_oss_close failed with error code %" PRIu32 "!", error);
+ }
+
+ free(oss);
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_oss_parse_addin_args(AudinOSSDevice* device, const ADDIN_ARGV* args)
+{
+ int status = 0;
+ char* str_num = NULL;
+ char* eptr = NULL;
+ DWORD flags = 0;
+ const COMMAND_LINE_ARGUMENT_A* arg = NULL;
+ AudinOSSDevice* oss = (AudinOSSDevice*)device;
+ COMMAND_LINE_ARGUMENT_A audin_oss_args[] = { { "dev", COMMAND_LINE_VALUE_REQUIRED, "<device>",
+ NULL, NULL, -1, NULL, "audio device name" },
+ { NULL, 0, NULL, NULL, NULL, -1, NULL, NULL } };
+
+ flags =
+ COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD;
+ status =
+ CommandLineParseArgumentsA(args->argc, args->argv, audin_oss_args, flags, oss, NULL, NULL);
+
+ if (status < 0)
+ return ERROR_INVALID_PARAMETER;
+
+ arg = audin_oss_args;
+ errno = 0;
+
+ do
+ {
+ if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT))
+ continue;
+
+ CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "dev")
+ {
+ str_num = _strdup(arg->Value);
+
+ if (!str_num)
+ {
+ WLog_ERR(TAG, "_strdup failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ {
+ long val = strtol(str_num, &eptr, 10);
+
+ if ((errno != 0) || (val < INT32_MIN) || (val > INT32_MAX))
+ {
+ free(str_num);
+ return CHANNEL_RC_NULL_DATA;
+ }
+
+ oss->dev_unit = (INT32)val;
+ }
+
+ if (oss->dev_unit < 0 || *eptr != '\0')
+ oss->dev_unit = -1;
+
+ free(str_num);
+ }
+ CommandLineSwitchEnd(arg)
+ } while ((arg = CommandLineFindNextArgumentA(arg)) != NULL);
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+FREERDP_ENTRY_POINT(
+ UINT oss_freerdp_audin_client_subsystem_entry(PFREERDP_AUDIN_DEVICE_ENTRY_POINTS pEntryPoints))
+{
+ const ADDIN_ARGV* args = NULL;
+ AudinOSSDevice* oss = NULL;
+ UINT error = 0;
+ oss = (AudinOSSDevice*)calloc(1, sizeof(AudinOSSDevice));
+
+ if (!oss)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ oss->iface.Open = audin_oss_open;
+ oss->iface.FormatSupported = audin_oss_format_supported;
+ oss->iface.SetFormat = audin_oss_set_format;
+ oss->iface.Close = audin_oss_close;
+ oss->iface.Free = audin_oss_free;
+ oss->rdpcontext = pEntryPoints->rdpcontext;
+ oss->dev_unit = -1;
+ args = pEntryPoints->args;
+
+ if ((error = audin_oss_parse_addin_args(oss, args)))
+ {
+ WLog_ERR(TAG, "audin_oss_parse_addin_args failed with errorcode %" PRIu32 "!", error);
+ goto error_out;
+ }
+
+ if ((error = pEntryPoints->pRegisterAudinDevice(pEntryPoints->plugin, (IAudinDevice*)oss)))
+ {
+ WLog_ERR(TAG, "RegisterAudinDevice failed with error %" PRIu32 "!", error);
+ goto error_out;
+ }
+
+ return CHANNEL_RC_OK;
+error_out:
+ free(oss);
+ return error;
+}
diff --git a/channels/audin/client/pulse/CMakeLists.txt b/channels/audin/client/pulse/CMakeLists.txt
new file mode 100644
index 0000000..5bf0fcd
--- /dev/null
+++ b/channels/audin/client/pulse/CMakeLists.txt
@@ -0,0 +1,35 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+#
+# 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" "pulse" "")
+
+find_package(PulseAudio REQUIRED)
+
+set(${MODULE_PREFIX}_SRCS
+ audin_pulse.c)
+
+set(${MODULE_PREFIX}_LIBS
+ winpr
+ freerdp
+ ${PULSEAUDIO_LIBRARY}
+ ${PULSEAUDIO_MAINLOOP_LIBRARY}
+)
+
+include_directories(..)
+include_directories(${PULSEAUDIO_INCLUDE_DIR})
+
+add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "")
diff --git a/channels/audin/client/pulse/audin_pulse.c b/channels/audin/client/pulse/audin_pulse.c
new file mode 100644
index 0000000..0e330ad
--- /dev/null
+++ b/channels/audin/client/pulse/audin_pulse.c
@@ -0,0 +1,566 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Audio Input Redirection Virtual Channel - PulseAudio implementation
+ *
+ * Copyright 2010-2011 Vic Lee
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ *
+ * 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/cmdline.h>
+#include <winpr/wlog.h>
+
+#include <pulse/pulseaudio.h>
+
+#include <freerdp/types.h>
+#include <freerdp/addin.h>
+#include <freerdp/freerdp.h>
+#include <freerdp/codec/audio.h>
+#include <freerdp/client/audin.h>
+
+#include "audin_main.h"
+
+typedef struct
+{
+ IAudinDevice iface;
+
+ char* device_name;
+ UINT32 frames_per_packet;
+ pa_threaded_mainloop* mainloop;
+ pa_context* context;
+ pa_sample_spec sample_spec;
+ pa_stream* stream;
+ AUDIO_FORMAT format;
+
+ size_t bytes_per_frame;
+ size_t buffer_frames;
+
+ AudinReceive receive;
+ void* user_data;
+
+ rdpContext* rdpcontext;
+ wLog* log;
+} AudinPulseDevice;
+
+static const char* pulse_context_state_string(pa_context_state_t state)
+{
+ switch (state)
+ {
+ case PA_CONTEXT_UNCONNECTED:
+ return "PA_CONTEXT_UNCONNECTED";
+ case PA_CONTEXT_CONNECTING:
+ return "PA_CONTEXT_CONNECTING";
+ case PA_CONTEXT_AUTHORIZING:
+ return "PA_CONTEXT_AUTHORIZING";
+ case PA_CONTEXT_SETTING_NAME:
+ return "PA_CONTEXT_SETTING_NAME";
+ case PA_CONTEXT_READY:
+ return "PA_CONTEXT_READY";
+ case PA_CONTEXT_FAILED:
+ return "PA_CONTEXT_FAILED";
+ case PA_CONTEXT_TERMINATED:
+ return "PA_CONTEXT_TERMINATED";
+ default:
+ return "UNKNOWN";
+ }
+}
+
+static const char* pulse_stream_state_string(pa_stream_state_t state)
+{
+ switch (state)
+ {
+ case PA_STREAM_UNCONNECTED:
+ return "PA_STREAM_UNCONNECTED";
+ case PA_STREAM_CREATING:
+ return "PA_STREAM_CREATING";
+ case PA_STREAM_READY:
+ return "PA_STREAM_READY";
+ case PA_STREAM_FAILED:
+ return "PA_STREAM_FAILED";
+ case PA_STREAM_TERMINATED:
+ return "PA_STREAM_TERMINATED";
+ default:
+ return "UNKNOWN";
+ }
+}
+
+static void audin_pulse_context_state_callback(pa_context* context, void* userdata)
+{
+ AudinPulseDevice* pulse = (AudinPulseDevice*)userdata;
+ pa_context_state_t state = pa_context_get_state(context);
+
+ WLog_Print(pulse->log, WLOG_DEBUG, "context state %s", pulse_context_state_string(state));
+ switch (state)
+ {
+ case PA_CONTEXT_READY:
+ pa_threaded_mainloop_signal(pulse->mainloop, 0);
+ break;
+
+ case PA_CONTEXT_FAILED:
+ case PA_CONTEXT_TERMINATED:
+ pa_threaded_mainloop_signal(pulse->mainloop, 0);
+ break;
+
+ default:
+ break;
+ }
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_pulse_connect(IAudinDevice* device)
+{
+ pa_context_state_t state = PA_CONTEXT_FAILED;
+ AudinPulseDevice* pulse = (AudinPulseDevice*)device;
+
+ if (!pulse->context)
+ return ERROR_INVALID_PARAMETER;
+
+ if (pa_context_connect(pulse->context, NULL, 0, NULL))
+ {
+ WLog_Print(pulse->log, WLOG_ERROR, "pa_context_connect failed (%d)",
+ pa_context_errno(pulse->context));
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ pa_threaded_mainloop_lock(pulse->mainloop);
+
+ if (pa_threaded_mainloop_start(pulse->mainloop) < 0)
+ {
+ pa_threaded_mainloop_unlock(pulse->mainloop);
+ WLog_Print(pulse->log, WLOG_ERROR, "pa_threaded_mainloop_start failed (%d)",
+ pa_context_errno(pulse->context));
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ for (;;)
+ {
+ state = pa_context_get_state(pulse->context);
+
+ if (state == PA_CONTEXT_READY)
+ break;
+
+ if (!PA_CONTEXT_IS_GOOD(state))
+ {
+ WLog_Print(pulse->log, WLOG_ERROR, "bad context state (%s: %d)",
+ pulse_context_state_string(state), pa_context_errno(pulse->context));
+ pa_context_disconnect(pulse->context);
+ return ERROR_INVALID_STATE;
+ }
+
+ pa_threaded_mainloop_wait(pulse->mainloop);
+ }
+
+ pa_threaded_mainloop_unlock(pulse->mainloop);
+ WLog_Print(pulse->log, WLOG_DEBUG, "connected");
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_pulse_free(IAudinDevice* device)
+{
+ AudinPulseDevice* pulse = (AudinPulseDevice*)device;
+
+ if (!pulse)
+ return ERROR_INVALID_PARAMETER;
+
+ if (pulse->mainloop)
+ {
+ pa_threaded_mainloop_stop(pulse->mainloop);
+ }
+
+ if (pulse->context)
+ {
+ pa_context_disconnect(pulse->context);
+ pa_context_unref(pulse->context);
+ pulse->context = NULL;
+ }
+
+ if (pulse->mainloop)
+ {
+ pa_threaded_mainloop_free(pulse->mainloop);
+ pulse->mainloop = NULL;
+ }
+
+ free(pulse);
+ return CHANNEL_RC_OK;
+}
+
+static BOOL audin_pulse_format_supported(IAudinDevice* device, const AUDIO_FORMAT* format)
+{
+ AudinPulseDevice* pulse = (AudinPulseDevice*)device;
+
+ if (!pulse || !format)
+ return FALSE;
+
+ if (!pulse->context)
+ return 0;
+
+ switch (format->wFormatTag)
+ {
+ case WAVE_FORMAT_PCM:
+ if (format->cbSize == 0 && (format->nSamplesPerSec <= PA_RATE_MAX) &&
+ (format->wBitsPerSample == 8 || format->wBitsPerSample == 16) &&
+ (format->nChannels >= 1 && format->nChannels <= PA_CHANNELS_MAX))
+ {
+ return TRUE;
+ }
+
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ return FALSE;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_pulse_set_format(IAudinDevice* device, const AUDIO_FORMAT* format,
+ UINT32 FramesPerPacket)
+{
+ pa_sample_spec sample_spec = { 0 };
+ AudinPulseDevice* pulse = (AudinPulseDevice*)device;
+
+ if (!pulse || !format)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!pulse->context)
+ return ERROR_INVALID_PARAMETER;
+
+ if (FramesPerPacket > 0)
+ pulse->frames_per_packet = FramesPerPacket;
+
+ sample_spec.rate = format->nSamplesPerSec;
+ sample_spec.channels = format->nChannels;
+
+ switch (format->wFormatTag)
+ {
+ case WAVE_FORMAT_PCM: /* PCM */
+ switch (format->wBitsPerSample)
+ {
+ case 8:
+ sample_spec.format = PA_SAMPLE_U8;
+ break;
+
+ case 16:
+ sample_spec.format = PA_SAMPLE_S16LE;
+ break;
+
+ default:
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ break;
+
+ case WAVE_FORMAT_ALAW: /* A-LAW */
+ sample_spec.format = PA_SAMPLE_ALAW;
+ break;
+
+ case WAVE_FORMAT_MULAW: /* U-LAW */
+ sample_spec.format = PA_SAMPLE_ULAW;
+ break;
+
+ default:
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ pulse->sample_spec = sample_spec;
+ pulse->format = *format;
+ return CHANNEL_RC_OK;
+}
+
+static void audin_pulse_stream_state_callback(pa_stream* stream, void* userdata)
+{
+ AudinPulseDevice* pulse = (AudinPulseDevice*)userdata;
+ pa_stream_state_t state = pa_stream_get_state(stream);
+
+ WLog_Print(pulse->log, WLOG_DEBUG, "stream state %s", pulse_stream_state_string(state));
+ switch (state)
+ {
+ case PA_STREAM_READY:
+ pa_threaded_mainloop_signal(pulse->mainloop, 0);
+ break;
+
+ case PA_STREAM_FAILED:
+ case PA_STREAM_TERMINATED:
+ pa_threaded_mainloop_signal(pulse->mainloop, 0);
+ break;
+
+ case PA_STREAM_UNCONNECTED:
+ case PA_STREAM_CREATING:
+ default:
+ break;
+ }
+}
+
+static void audin_pulse_stream_request_callback(pa_stream* stream, size_t length, void* userdata)
+{
+ const void* data = NULL;
+ AudinPulseDevice* pulse = (AudinPulseDevice*)userdata;
+ UINT error = CHANNEL_RC_OK;
+ pa_stream_peek(stream, &data, &length);
+ error =
+ IFCALLRESULT(CHANNEL_RC_OK, pulse->receive, &pulse->format, data, length, pulse->user_data);
+ pa_stream_drop(stream);
+
+ if (error && pulse->rdpcontext)
+ setChannelError(pulse->rdpcontext, error, "audin_pulse_thread_func reported an error");
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_pulse_close(IAudinDevice* device)
+{
+ AudinPulseDevice* pulse = (AudinPulseDevice*)device;
+
+ if (!pulse)
+ return ERROR_INVALID_PARAMETER;
+
+ if (pulse->stream)
+ {
+ pa_threaded_mainloop_lock(pulse->mainloop);
+ pa_stream_disconnect(pulse->stream);
+ pa_stream_unref(pulse->stream);
+ pulse->stream = NULL;
+ pa_threaded_mainloop_unlock(pulse->mainloop);
+ }
+
+ pulse->receive = NULL;
+ pulse->user_data = NULL;
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_pulse_open(IAudinDevice* device, AudinReceive receive, void* user_data)
+{
+ pa_stream_state_t state = PA_STREAM_FAILED;
+ pa_buffer_attr buffer_attr = { 0 };
+ AudinPulseDevice* pulse = (AudinPulseDevice*)device;
+
+ if (!pulse || !receive || !user_data)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!pulse->context)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!pulse->sample_spec.rate || pulse->stream)
+ return ERROR_INVALID_PARAMETER;
+
+ pulse->receive = receive;
+ pulse->user_data = user_data;
+ pa_threaded_mainloop_lock(pulse->mainloop);
+ pulse->stream = pa_stream_new(pulse->context, "freerdp_audin", &pulse->sample_spec, NULL);
+
+ if (!pulse->stream)
+ {
+ pa_threaded_mainloop_unlock(pulse->mainloop);
+ WLog_Print(pulse->log, WLOG_DEBUG, "pa_stream_new failed (%d)",
+ pa_context_errno(pulse->context));
+ return pa_context_errno(pulse->context);
+ }
+
+ pulse->bytes_per_frame = pa_frame_size(&pulse->sample_spec);
+ pa_stream_set_state_callback(pulse->stream, audin_pulse_stream_state_callback, pulse);
+ pa_stream_set_read_callback(pulse->stream, audin_pulse_stream_request_callback, pulse);
+ buffer_attr.maxlength = (UINT32)-1;
+ buffer_attr.tlength = (UINT32)-1;
+ buffer_attr.prebuf = (UINT32)-1;
+ buffer_attr.minreq = (UINT32)-1;
+ /* 500ms latency */
+ buffer_attr.fragsize = pulse->bytes_per_frame * pulse->frames_per_packet;
+
+ if (buffer_attr.fragsize % pulse->format.nBlockAlign)
+ buffer_attr.fragsize +=
+ pulse->format.nBlockAlign - buffer_attr.fragsize % pulse->format.nBlockAlign;
+
+ if (pa_stream_connect_record(pulse->stream, pulse->device_name, &buffer_attr,
+ PA_STREAM_ADJUST_LATENCY) < 0)
+ {
+ pa_threaded_mainloop_unlock(pulse->mainloop);
+ WLog_Print(pulse->log, WLOG_ERROR, "pa_stream_connect_playback failed (%d)",
+ pa_context_errno(pulse->context));
+ return pa_context_errno(pulse->context);
+ }
+
+ for (;;)
+ {
+ state = pa_stream_get_state(pulse->stream);
+
+ if (state == PA_STREAM_READY)
+ break;
+
+ if (!PA_STREAM_IS_GOOD(state))
+ {
+ audin_pulse_close(device);
+ WLog_Print(pulse->log, WLOG_ERROR, "bad stream state (%s: %d)",
+ pulse_stream_state_string(state), pa_context_errno(pulse->context));
+ pa_threaded_mainloop_unlock(pulse->mainloop);
+ return pa_context_errno(pulse->context);
+ }
+
+ pa_threaded_mainloop_wait(pulse->mainloop);
+ }
+
+ pa_threaded_mainloop_unlock(pulse->mainloop);
+ pulse->buffer_frames = 0;
+ WLog_Print(pulse->log, WLOG_DEBUG, "connected");
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_pulse_parse_addin_args(AudinPulseDevice* device, const ADDIN_ARGV* args)
+{
+ int status = 0;
+ DWORD flags = 0;
+ const COMMAND_LINE_ARGUMENT_A* arg = NULL;
+ AudinPulseDevice* pulse = (AudinPulseDevice*)device;
+ COMMAND_LINE_ARGUMENT_A audin_pulse_args[] = { { "dev", COMMAND_LINE_VALUE_REQUIRED, "<device>",
+ NULL, NULL, -1, NULL, "audio device name" },
+ { NULL, 0, NULL, NULL, NULL, -1, NULL, NULL } };
+
+ flags =
+ COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD;
+ status = CommandLineParseArgumentsA(args->argc, args->argv, audin_pulse_args, flags, pulse,
+ NULL, NULL);
+
+ if (status < 0)
+ return ERROR_INVALID_PARAMETER;
+
+ arg = audin_pulse_args;
+
+ do
+ {
+ if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT))
+ continue;
+
+ CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "dev")
+ {
+ pulse->device_name = _strdup(arg->Value);
+
+ if (!pulse->device_name)
+ {
+ WLog_Print(pulse->log, WLOG_ERROR, "_strdup failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+ }
+ CommandLineSwitchEnd(arg)
+ } while ((arg = CommandLineFindNextArgumentA(arg)) != NULL);
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+FREERDP_ENTRY_POINT(UINT pulse_freerdp_audin_client_subsystem_entry(
+ PFREERDP_AUDIN_DEVICE_ENTRY_POINTS pEntryPoints))
+{
+ const ADDIN_ARGV* args = NULL;
+ AudinPulseDevice* pulse = NULL;
+ UINT error = 0;
+ pulse = (AudinPulseDevice*)calloc(1, sizeof(AudinPulseDevice));
+
+ if (!pulse)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ pulse->log = WLog_Get(TAG);
+ pulse->iface.Open = audin_pulse_open;
+ pulse->iface.FormatSupported = audin_pulse_format_supported;
+ pulse->iface.SetFormat = audin_pulse_set_format;
+ pulse->iface.Close = audin_pulse_close;
+ pulse->iface.Free = audin_pulse_free;
+ pulse->rdpcontext = pEntryPoints->rdpcontext;
+ args = pEntryPoints->args;
+
+ if ((error = audin_pulse_parse_addin_args(pulse, args)))
+ {
+ WLog_Print(pulse->log, WLOG_ERROR,
+ "audin_pulse_parse_addin_args failed with error %" PRIu32 "!", error);
+ goto error_out;
+ }
+
+ pulse->mainloop = pa_threaded_mainloop_new();
+
+ if (!pulse->mainloop)
+ {
+ WLog_Print(pulse->log, WLOG_ERROR, "pa_threaded_mainloop_new failed");
+ error = CHANNEL_RC_NO_MEMORY;
+ goto error_out;
+ }
+
+ pulse->context = pa_context_new(pa_threaded_mainloop_get_api(pulse->mainloop), "freerdp");
+
+ if (!pulse->context)
+ {
+ WLog_Print(pulse->log, WLOG_ERROR, "pa_context_new failed");
+ error = CHANNEL_RC_NO_MEMORY;
+ goto error_out;
+ }
+
+ pa_context_set_state_callback(pulse->context, audin_pulse_context_state_callback, pulse);
+
+ if ((error = audin_pulse_connect(&pulse->iface)))
+ {
+ WLog_Print(pulse->log, WLOG_ERROR, "audin_pulse_connect failed");
+ goto error_out;
+ }
+
+ if ((error = pEntryPoints->pRegisterAudinDevice(pEntryPoints->plugin, &pulse->iface)))
+ {
+ WLog_Print(pulse->log, WLOG_ERROR, "RegisterAudinDevice failed with error %" PRIu32 "!",
+ error);
+ goto error_out;
+ }
+
+ return CHANNEL_RC_OK;
+error_out:
+ audin_pulse_free(&pulse->iface);
+ return error;
+}
diff --git a/channels/audin/client/sndio/CMakeLists.txt b/channels/audin/client/sndio/CMakeLists.txt
new file mode 100644
index 0000000..ef68228
--- /dev/null
+++ b/channels/audin/client/sndio/CMakeLists.txt
@@ -0,0 +1,36 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright (c) 2015 Rozhuk Ivan <rozhuk.im@gmail.com>
+# Copyright (c) 2020 Ingo Feinerer <feinerer@logic.at>
+#
+# 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" "sndio" "")
+
+find_package(SNDIO REQUIRED)
+
+set(${MODULE_PREFIX}_SRCS
+ audin_sndio.c)
+
+set(${MODULE_PREFIX}_LIBS
+ winpr
+ freerdp
+ ${SNDIO_LIBRARIES}
+)
+
+include_directories(..)
+include_directories(${SNDIO_INCLUDE_DIRS})
+
+add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "")
+
diff --git a/channels/audin/client/sndio/audin_sndio.c b/channels/audin/client/sndio/audin_sndio.c
new file mode 100644
index 0000000..92dac13
--- /dev/null
+++ b/channels/audin/client/sndio/audin_sndio.c
@@ -0,0 +1,352 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Audio Input Redirection Virtual Channel - sndio implementation
+ *
+ * Copyright (c) 2015 Rozhuk Ivan <rozhuk.im@gmail.com>
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ * Copyright 2020 Ingo Feinerer <feinerer@logic.at>
+ *
+ * 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 <sndio.h>
+
+#include <winpr/cmdline.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/channels/rdpsnd.h>
+
+#include "audin_main.h"
+
+typedef struct
+{
+ IAudinDevice device;
+
+ HANDLE thread;
+ HANDLE stopEvent;
+
+ AUDIO_FORMAT format;
+ UINT32 FramesPerPacket;
+
+ AudinReceive receive;
+ void* user_data;
+
+ rdpContext* rdpcontext;
+} AudinSndioDevice;
+
+static BOOL audin_sndio_format_supported(IAudinDevice* device, const AUDIO_FORMAT* format)
+{
+ if (device == NULL || format == NULL)
+ return FALSE;
+
+ return (format->wFormatTag == WAVE_FORMAT_PCM);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_sndio_set_format(IAudinDevice* device, AUDIO_FORMAT* format,
+ UINT32 FramesPerPacket)
+{
+ AudinSndioDevice* sndio = (AudinSndioDevice*)device;
+
+ if (device == NULL || format == NULL)
+ return ERROR_INVALID_PARAMETER;
+
+ if (format->wFormatTag != WAVE_FORMAT_PCM)
+ return ERROR_INTERNAL_ERROR;
+
+ sndio->format = *format;
+ sndio->FramesPerPacket = FramesPerPacket;
+
+ return CHANNEL_RC_OK;
+}
+
+static void* audin_sndio_thread_func(void* arg)
+{
+ struct sio_hdl* hdl;
+ struct sio_par par;
+ BYTE* buffer = NULL;
+ size_t n, nbytes;
+ AudinSndioDevice* sndio = (AudinSndioDevice*)arg;
+ UINT error = 0;
+ DWORD status;
+
+ if (arg == NULL)
+ {
+ error = ERROR_INVALID_PARAMETER;
+ goto err_out;
+ }
+
+ hdl = sio_open(SIO_DEVANY, SIO_REC, 0);
+ if (hdl == NULL)
+ {
+ WLog_ERR(TAG, "could not open audio device");
+ error = ERROR_INTERNAL_ERROR;
+ goto err_out;
+ }
+
+ sio_initpar(&par);
+ par.bits = sndio->format.wBitsPerSample;
+ par.rchan = sndio->format.nChannels;
+ par.rate = sndio->format.nSamplesPerSec;
+ if (!sio_setpar(hdl, &par))
+ {
+ WLog_ERR(TAG, "could not set audio parameters");
+ error = ERROR_INTERNAL_ERROR;
+ goto err_out;
+ }
+ if (!sio_getpar(hdl, &par))
+ {
+ WLog_ERR(TAG, "could not get audio parameters");
+ error = ERROR_INTERNAL_ERROR;
+ goto err_out;
+ }
+
+ if (!sio_start(hdl))
+ {
+ WLog_ERR(TAG, "could not start audio device");
+ error = ERROR_INTERNAL_ERROR;
+ goto err_out;
+ }
+
+ nbytes =
+ (sndio->FramesPerPacket * sndio->format.nChannels * (sndio->format.wBitsPerSample / 8));
+ buffer = (BYTE*)calloc((nbytes + sizeof(void*)), sizeof(BYTE));
+
+ if (buffer == NULL)
+ {
+ error = ERROR_NOT_ENOUGH_MEMORY;
+ goto err_out;
+ }
+
+ while (1)
+ {
+ status = WaitForSingleObject(sndio->stopEvent, 0);
+
+ if (status == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error);
+ goto err_out;
+ }
+
+ if (status == WAIT_OBJECT_0)
+ break;
+
+ n = sio_read(hdl, buffer, nbytes);
+
+ if (n == 0)
+ {
+ WLog_ERR(TAG, "could not read");
+ continue;
+ }
+
+ if (n < nbytes)
+ continue;
+
+ if ((error = sndio->receive(&sndio->format, buffer, nbytes, sndio->user_data)))
+ {
+ WLog_ERR(TAG, "sndio->receive failed with error %" PRIu32 "", error);
+ break;
+ }
+ }
+
+err_out:
+ if (error && sndio->rdpcontext)
+ setChannelError(sndio->rdpcontext, error, "audin_sndio_thread_func reported an error");
+
+ if (hdl != NULL)
+ {
+ WLog_INFO(TAG, "sio_close");
+ sio_stop(hdl);
+ sio_close(hdl);
+ }
+
+ free(buffer);
+ ExitThread(0);
+ return NULL;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_sndio_open(IAudinDevice* device, AudinReceive receive, void* user_data)
+{
+ AudinSndioDevice* sndio = (AudinSndioDevice*)device;
+ sndio->receive = receive;
+ sndio->user_data = user_data;
+
+ if (!(sndio->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ {
+ WLog_ERR(TAG, "CreateEvent failed");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ if (!(sndio->thread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)audin_sndio_thread_func,
+ sndio, 0, NULL)))
+ {
+ WLog_ERR(TAG, "CreateThread failed");
+ CloseHandle(sndio->stopEvent);
+ sndio->stopEvent = NULL;
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_sndio_close(IAudinDevice* device)
+{
+ UINT error;
+ AudinSndioDevice* sndio = (AudinSndioDevice*)device;
+
+ if (device == NULL)
+ return ERROR_INVALID_PARAMETER;
+
+ if (sndio->stopEvent != NULL)
+ {
+ SetEvent(sndio->stopEvent);
+
+ if (WaitForSingleObject(sndio->thread, INFINITE) == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error);
+ return error;
+ }
+
+ CloseHandle(sndio->stopEvent);
+ sndio->stopEvent = NULL;
+ CloseHandle(sndio->thread);
+ sndio->thread = NULL;
+ }
+
+ sndio->receive = NULL;
+ sndio->user_data = NULL;
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_sndio_free(IAudinDevice* device)
+{
+ AudinSndioDevice* sndio = (AudinSndioDevice*)device;
+ int error;
+
+ if (device == NULL)
+ return ERROR_INVALID_PARAMETER;
+
+ if ((error = audin_sndio_close(device)))
+ {
+ WLog_ERR(TAG, "audin_sndio_close failed with error code %d", error);
+ }
+
+ free(sndio);
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_sndio_parse_addin_args(AudinSndioDevice* device, ADDIN_ARGV* args)
+{
+ int status;
+ DWORD flags;
+ COMMAND_LINE_ARGUMENT_A* arg;
+ AudinSndioDevice* sndio = (AudinSndioDevice*)device;
+ COMMAND_LINE_ARGUMENT_A audin_sndio_args[] = { { NULL, 0, NULL, NULL, NULL, -1, NULL, NULL } };
+ flags =
+ COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD;
+ status = CommandLineParseArgumentsA(args->argc, (const char**)args->argv, audin_sndio_args,
+ flags, sndio, NULL, NULL);
+
+ if (status < 0)
+ return ERROR_INVALID_PARAMETER;
+
+ arg = audin_sndio_args;
+
+ do
+ {
+ if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT))
+ continue;
+
+ CommandLineSwitchStart(arg) CommandLineSwitchEnd(arg)
+ } while ((arg = CommandLineFindNextArgumentA(arg)) != NULL);
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+FREERDP_ENTRY_POINT(UINT sndio_freerdp_audin_client_subsystem_entry(
+ PFREERDP_AUDIN_DEVICE_ENTRY_POINTS pEntryPoints))
+{
+ ADDIN_ARGV* args;
+ AudinSndioDevice* sndio;
+ UINT ret = CHANNEL_RC_OK;
+ sndio = (AudinSndioDevice*)calloc(1, sizeof(AudinSndioDevice));
+
+ if (sndio == NULL)
+ return CHANNEL_RC_NO_MEMORY;
+
+ sndio->device.Open = audin_sndio_open;
+ sndio->device.FormatSupported = audin_sndio_format_supported;
+ sndio->device.SetFormat = audin_sndio_set_format;
+ sndio->device.Close = audin_sndio_close;
+ sndio->device.Free = audin_sndio_free;
+ sndio->rdpcontext = pEntryPoints->rdpcontext;
+ args = pEntryPoints->args;
+
+ if (args->argc > 1)
+ {
+ ret = audin_sndio_parse_addin_args(sndio, args);
+
+ if (ret != CHANNEL_RC_OK)
+ {
+ WLog_ERR(TAG, "error parsing arguments");
+ goto error;
+ }
+ }
+
+ if ((ret = pEntryPoints->pRegisterAudinDevice(pEntryPoints->plugin, (IAudinDevice*)sndio)))
+ {
+ WLog_ERR(TAG, "RegisterAudinDevice failed with error %" PRIu32 "", ret);
+ goto error;
+ }
+
+ return ret;
+error:
+ audin_sndio_free(&sndio->device);
+ return ret;
+}
diff --git a/channels/audin/client/winmm/CMakeLists.txt b/channels/audin/client/winmm/CMakeLists.txt
new file mode 100644
index 0000000..8657942
--- /dev/null
+++ b/channels/audin/client/winmm/CMakeLists.txt
@@ -0,0 +1,31 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+#
+# 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" "winmm" "")
+
+set(${MODULE_PREFIX}_SRCS
+ audin_winmm.c)
+
+set(${MODULE_PREFIX}_LIBS
+ winpr
+ freerdp
+ winmm.lib
+)
+
+include_directories(..)
+
+add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "")
diff --git a/channels/audin/client/winmm/audin_winmm.c b/channels/audin/client/winmm/audin_winmm.c
new file mode 100644
index 0000000..b4172a5
--- /dev/null
+++ b/channels/audin/client/winmm/audin_winmm.c
@@ -0,0 +1,566 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Audio Input Redirection Virtual Channel - WinMM implementation
+ *
+ * Copyright 2013 Zhang Zhaolong <zhangzl2013@126.com>
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ *
+ * 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 <windows.h>
+#include <mmsystem.h>
+
+#include <winpr/crt.h>
+#include <winpr/cmdline.h>
+#include <freerdp/freerdp.h>
+#include <freerdp/addin.h>
+#include <freerdp/client/audin.h>
+
+#include "audin_main.h"
+
+/* fix missing definitions in mingw */
+#ifndef WAVE_MAPPED_DEFAULT_COMMUNICATION_DEVICE
+#define WAVE_MAPPED_DEFAULT_COMMUNICATION_DEVICE 0x0010
+#endif
+
+typedef struct
+{
+ IAudinDevice iface;
+
+ char* device_name;
+ AudinReceive receive;
+ void* user_data;
+ HANDLE thread;
+ HANDLE stopEvent;
+ HWAVEIN hWaveIn;
+ PWAVEFORMATEX* ppwfx;
+ PWAVEFORMATEX pwfx_cur;
+ UINT32 ppwfx_size;
+ UINT32 cFormats;
+ UINT32 frames_per_packet;
+ rdpContext* rdpcontext;
+ wLog* log;
+} AudinWinmmDevice;
+
+static void CALLBACK waveInProc(HWAVEIN hWaveIn, UINT uMsg, DWORD_PTR dwInstance,
+ DWORD_PTR dwParam1, DWORD_PTR dwParam2)
+{
+ AudinWinmmDevice* winmm = (AudinWinmmDevice*)dwInstance;
+ PWAVEHDR pWaveHdr;
+ UINT error = CHANNEL_RC_OK;
+ MMRESULT mmResult;
+
+ switch (uMsg)
+ {
+ case WIM_CLOSE:
+ break;
+
+ case WIM_DATA:
+ pWaveHdr = (WAVEHDR*)dwParam1;
+
+ if (WHDR_DONE == (WHDR_DONE & pWaveHdr->dwFlags))
+ {
+ if (pWaveHdr->dwBytesRecorded &&
+ !(WaitForSingleObject(winmm->stopEvent, 0) == WAIT_OBJECT_0))
+ {
+ AUDIO_FORMAT format;
+ format.cbSize = winmm->pwfx_cur->cbSize;
+ format.nBlockAlign = winmm->pwfx_cur->nBlockAlign;
+ format.nAvgBytesPerSec = winmm->pwfx_cur->nAvgBytesPerSec;
+ format.nChannels = winmm->pwfx_cur->nChannels;
+ format.nSamplesPerSec = winmm->pwfx_cur->nSamplesPerSec;
+ format.wBitsPerSample = winmm->pwfx_cur->wBitsPerSample;
+ format.wFormatTag = winmm->pwfx_cur->wFormatTag;
+
+ if ((error = winmm->receive(&format, pWaveHdr->lpData,
+ pWaveHdr->dwBytesRecorded, winmm->user_data)))
+ break;
+
+ mmResult = waveInAddBuffer(hWaveIn, pWaveHdr, sizeof(WAVEHDR));
+
+ if (mmResult != MMSYSERR_NOERROR)
+ error = ERROR_INTERNAL_ERROR;
+ }
+ }
+
+ break;
+
+ case WIM_OPEN:
+ break;
+
+ default:
+ break;
+ }
+
+ if (error && winmm->rdpcontext)
+ setChannelError(winmm->rdpcontext, error, "waveInProc reported an error");
+}
+
+static BOOL log_mmresult(AudinWinmmDevice* winmm, const char* what, MMRESULT result)
+{
+ if (result != MMSYSERR_NOERROR)
+ {
+ CHAR buffer[8192] = { 0 };
+ CHAR msg[8192] = { 0 };
+ CHAR cmsg[8192] = { 0 };
+ waveInGetErrorTextA(result, buffer, sizeof(buffer));
+
+ _snprintf(msg, sizeof(msg) - 1, "%s failed. %" PRIu32 " [%s]", what, result, buffer);
+ _snprintf(cmsg, sizeof(cmsg) - 1, "audin_winmm_thread_func reported an error '%s'", msg);
+ WLog_Print(winmm->log, WLOG_DEBUG, "%s", msg);
+ if (winmm->rdpcontext)
+ setChannelError(winmm->rdpcontext, ERROR_INTERNAL_ERROR, cmsg);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static BOOL test_format_supported(const PWAVEFORMATEX pwfx)
+{
+ MMRESULT rc;
+ WAVEINCAPSA caps = { 0 };
+
+ rc = waveInGetDevCapsA(WAVE_MAPPER, &caps, sizeof(caps));
+ if (rc != MMSYSERR_NOERROR)
+ return FALSE;
+
+ switch (pwfx->nChannels)
+ {
+ case 1:
+ if ((caps.dwFormats &
+ (WAVE_FORMAT_1M08 | WAVE_FORMAT_2M08 | WAVE_FORMAT_4M08 | WAVE_FORMAT_96M08 |
+ WAVE_FORMAT_1M16 | WAVE_FORMAT_2M16 | WAVE_FORMAT_4M16 | WAVE_FORMAT_96M16)) == 0)
+ return FALSE;
+ break;
+ case 2:
+ if ((caps.dwFormats &
+ (WAVE_FORMAT_1S08 | WAVE_FORMAT_2S08 | WAVE_FORMAT_4S08 | WAVE_FORMAT_96S08 |
+ WAVE_FORMAT_1S16 | WAVE_FORMAT_2S16 | WAVE_FORMAT_4S16 | WAVE_FORMAT_96S16)) == 0)
+ return FALSE;
+ break;
+ default:
+ return FALSE;
+ }
+
+ rc = waveInOpen(NULL, WAVE_MAPPER, pwfx, 0, 0,
+ WAVE_FORMAT_QUERY | WAVE_MAPPED_DEFAULT_COMMUNICATION_DEVICE);
+ return (rc == MMSYSERR_NOERROR);
+}
+
+static DWORD WINAPI audin_winmm_thread_func(LPVOID arg)
+{
+ AudinWinmmDevice* winmm = (AudinWinmmDevice*)arg;
+ char* buffer = NULL;
+ int size = 0;
+ WAVEHDR waveHdr[4] = { 0 };
+ DWORD status = 0;
+ MMRESULT rc = 0;
+
+ if (!winmm->hWaveIn)
+ {
+ MMRESULT rc;
+ rc = waveInOpen(&winmm->hWaveIn, WAVE_MAPPER, winmm->pwfx_cur, (DWORD_PTR)waveInProc,
+ (DWORD_PTR)winmm,
+ CALLBACK_FUNCTION | WAVE_MAPPED_DEFAULT_COMMUNICATION_DEVICE);
+ if (!log_mmresult(winmm, "waveInOpen", rc))
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ size =
+ (winmm->pwfx_cur->wBitsPerSample * winmm->pwfx_cur->nChannels * winmm->frames_per_packet +
+ 7) /
+ 8;
+
+ for (int i = 0; i < 4; i++)
+ {
+ buffer = (char*)malloc(size);
+
+ if (!buffer)
+ return CHANNEL_RC_NO_MEMORY;
+
+ waveHdr[i].dwBufferLength = size;
+ waveHdr[i].dwFlags = 0;
+ waveHdr[i].lpData = buffer;
+ rc = waveInPrepareHeader(winmm->hWaveIn, &waveHdr[i], sizeof(waveHdr[i]));
+
+ if (!log_mmresult(winmm, "waveInPrepareHeader", rc))
+ {
+ }
+
+ rc = waveInAddBuffer(winmm->hWaveIn, &waveHdr[i], sizeof(waveHdr[i]));
+
+ if (!log_mmresult(winmm, "waveInAddBuffer", rc))
+ {
+ }
+ }
+
+ rc = waveInStart(winmm->hWaveIn);
+
+ if (!log_mmresult(winmm, "waveInStart", rc))
+ {
+ }
+
+ status = WaitForSingleObject(winmm->stopEvent, INFINITE);
+
+ if (status == WAIT_FAILED)
+ {
+ WLog_Print(winmm->log, WLOG_DEBUG, "WaitForSingleObject failed.");
+
+ if (winmm->rdpcontext)
+ setChannelError(winmm->rdpcontext, ERROR_INTERNAL_ERROR,
+ "audin_winmm_thread_func reported an error");
+ }
+
+ rc = waveInReset(winmm->hWaveIn);
+
+ if (!log_mmresult(winmm, "waveInReset", rc))
+ {
+ }
+
+ for (int i = 0; i < 4; i++)
+ {
+ rc = waveInUnprepareHeader(winmm->hWaveIn, &waveHdr[i], sizeof(waveHdr[i]));
+
+ if (!log_mmresult(winmm, "waveInUnprepareHeader", rc))
+ {
+ }
+
+ free(waveHdr[i].lpData);
+ }
+
+ rc = waveInClose(winmm->hWaveIn);
+
+ if (!log_mmresult(winmm, "waveInClose", rc))
+ {
+ }
+
+ winmm->hWaveIn = NULL;
+ return 0;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_winmm_free(IAudinDevice* device)
+{
+ AudinWinmmDevice* winmm = (AudinWinmmDevice*)device;
+
+ if (!winmm)
+ return ERROR_INVALID_PARAMETER;
+
+ for (UINT32 i = 0; i < winmm->cFormats; i++)
+ {
+ free(winmm->ppwfx[i]);
+ }
+
+ free(winmm->ppwfx);
+ free(winmm->device_name);
+ free(winmm);
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_winmm_close(IAudinDevice* device)
+{
+ DWORD status;
+ UINT error = CHANNEL_RC_OK;
+ AudinWinmmDevice* winmm = (AudinWinmmDevice*)device;
+
+ if (!winmm)
+ return ERROR_INVALID_PARAMETER;
+
+ SetEvent(winmm->stopEvent);
+ status = WaitForSingleObject(winmm->thread, INFINITE);
+
+ if (status == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_Print(winmm->log, WLOG_ERROR, "WaitForSingleObject failed with error %" PRIu32 "!",
+ error);
+ return error;
+ }
+
+ CloseHandle(winmm->thread);
+ CloseHandle(winmm->stopEvent);
+ winmm->thread = NULL;
+ winmm->stopEvent = NULL;
+ winmm->receive = NULL;
+ winmm->user_data = NULL;
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_winmm_set_format(IAudinDevice* device, const AUDIO_FORMAT* format,
+ UINT32 FramesPerPacket)
+{
+ AudinWinmmDevice* winmm = (AudinWinmmDevice*)device;
+
+ if (!winmm || !format)
+ return ERROR_INVALID_PARAMETER;
+
+ winmm->frames_per_packet = FramesPerPacket;
+
+ for (UINT32 i = 0; i < winmm->cFormats; i++)
+ {
+ const PWAVEFORMATEX ppwfx = winmm->ppwfx[i];
+ if ((ppwfx->wFormatTag == format->wFormatTag) && (ppwfx->nChannels == format->nChannels) &&
+ (ppwfx->wBitsPerSample == format->wBitsPerSample) &&
+ (ppwfx->nSamplesPerSec == format->nSamplesPerSec))
+ {
+ /* BUG: Many devices report to support stereo recording but fail here.
+ * Ensure we always use mono. */
+ if (ppwfx->nChannels > 1)
+ {
+ ppwfx->nChannels = 1;
+ }
+
+ if (ppwfx->nBlockAlign != 2)
+ {
+ ppwfx->nBlockAlign = 2;
+ ppwfx->nAvgBytesPerSec = ppwfx->nSamplesPerSec * ppwfx->nBlockAlign;
+ }
+
+ if (!test_format_supported(ppwfx))
+ return ERROR_INVALID_PARAMETER;
+ winmm->pwfx_cur = ppwfx;
+ return CHANNEL_RC_OK;
+ }
+ }
+
+ return ERROR_INVALID_PARAMETER;
+}
+
+static BOOL audin_winmm_format_supported(IAudinDevice* device, const AUDIO_FORMAT* format)
+{
+ AudinWinmmDevice* winmm = (AudinWinmmDevice*)device;
+ PWAVEFORMATEX pwfx;
+ BYTE* data;
+
+ if (!winmm || !format)
+ return FALSE;
+
+ if (format->wFormatTag != WAVE_FORMAT_PCM)
+ return FALSE;
+
+ if (format->nChannels != 1)
+ return FALSE;
+
+ pwfx = (PWAVEFORMATEX)malloc(sizeof(WAVEFORMATEX) + format->cbSize);
+
+ if (!pwfx)
+ return FALSE;
+
+ pwfx->cbSize = format->cbSize;
+ pwfx->wFormatTag = format->wFormatTag;
+ pwfx->nChannels = format->nChannels;
+ pwfx->nSamplesPerSec = format->nSamplesPerSec;
+ pwfx->nBlockAlign = format->nBlockAlign;
+ pwfx->wBitsPerSample = format->wBitsPerSample;
+ data = (BYTE*)pwfx + sizeof(WAVEFORMATEX);
+ memcpy(data, format->data, format->cbSize);
+
+ pwfx->nAvgBytesPerSec = pwfx->nSamplesPerSec * pwfx->nBlockAlign;
+
+ if (!test_format_supported(pwfx))
+ goto fail;
+
+ if (winmm->cFormats >= winmm->ppwfx_size)
+ {
+ PWAVEFORMATEX* tmp_ppwfx;
+ tmp_ppwfx = realloc(winmm->ppwfx, sizeof(PWAVEFORMATEX) * winmm->ppwfx_size * 2);
+
+ if (!tmp_ppwfx)
+ goto fail;
+
+ winmm->ppwfx_size *= 2;
+ winmm->ppwfx = tmp_ppwfx;
+ }
+
+ winmm->ppwfx[winmm->cFormats++] = pwfx;
+ return TRUE;
+
+fail:
+ free(pwfx);
+ return FALSE;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_winmm_open(IAudinDevice* device, AudinReceive receive, void* user_data)
+{
+ AudinWinmmDevice* winmm = (AudinWinmmDevice*)device;
+
+ if (!winmm || !receive || !user_data)
+ return ERROR_INVALID_PARAMETER;
+
+ winmm->receive = receive;
+ winmm->user_data = user_data;
+
+ if (!(winmm->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ {
+ WLog_Print(winmm->log, WLOG_ERROR, "CreateEvent failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ if (!(winmm->thread = CreateThread(NULL, 0, audin_winmm_thread_func, winmm, 0, NULL)))
+ {
+ WLog_Print(winmm->log, WLOG_ERROR, "CreateThread failed!");
+ CloseHandle(winmm->stopEvent);
+ winmm->stopEvent = NULL;
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_winmm_parse_addin_args(AudinWinmmDevice* device, const ADDIN_ARGV* args)
+{
+ int status;
+ DWORD flags;
+ const COMMAND_LINE_ARGUMENT_A* arg;
+ AudinWinmmDevice* winmm = (AudinWinmmDevice*)device;
+ COMMAND_LINE_ARGUMENT_A audin_winmm_args[] = { { "dev", COMMAND_LINE_VALUE_REQUIRED, "<device>",
+ NULL, NULL, -1, NULL, "audio device name" },
+ { NULL, 0, NULL, NULL, NULL, -1, NULL, NULL } };
+
+ flags =
+ COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD;
+ status = CommandLineParseArgumentsA(args->argc, args->argv, audin_winmm_args, flags, winmm,
+ NULL, NULL);
+ arg = audin_winmm_args;
+
+ do
+ {
+ if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT))
+ continue;
+
+ CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "dev")
+ {
+ winmm->device_name = _strdup(arg->Value);
+
+ if (!winmm->device_name)
+ {
+ WLog_Print(winmm->log, WLOG_ERROR, "_strdup failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+ }
+ CommandLineSwitchEnd(arg)
+ } while ((arg = CommandLineFindNextArgumentA(arg)) != NULL);
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+FREERDP_ENTRY_POINT(UINT winmm_freerdp_audin_client_subsystem_entry(
+ PFREERDP_AUDIN_DEVICE_ENTRY_POINTS pEntryPoints))
+{
+ const ADDIN_ARGV* args;
+ AudinWinmmDevice* winmm;
+ UINT error;
+
+ if (waveInGetNumDevs() == 0)
+ {
+ WLog_Print(WLog_Get(TAG), WLOG_ERROR, "No microphone available!");
+ return ERROR_DEVICE_NOT_AVAILABLE;
+ }
+
+ winmm = (AudinWinmmDevice*)calloc(1, sizeof(AudinWinmmDevice));
+
+ if (!winmm)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ winmm->log = WLog_Get(TAG);
+ winmm->iface.Open = audin_winmm_open;
+ winmm->iface.FormatSupported = audin_winmm_format_supported;
+ winmm->iface.SetFormat = audin_winmm_set_format;
+ winmm->iface.Close = audin_winmm_close;
+ winmm->iface.Free = audin_winmm_free;
+ winmm->rdpcontext = pEntryPoints->rdpcontext;
+ args = pEntryPoints->args;
+
+ if ((error = audin_winmm_parse_addin_args(winmm, args)))
+ {
+ WLog_Print(winmm->log, WLOG_ERROR,
+ "audin_winmm_parse_addin_args failed with error %" PRIu32 "!", error);
+ goto error_out;
+ }
+
+ if (!winmm->device_name)
+ {
+ winmm->device_name = _strdup("default");
+
+ if (!winmm->device_name)
+ {
+ WLog_Print(winmm->log, WLOG_ERROR, "_strdup failed!");
+ error = CHANNEL_RC_NO_MEMORY;
+ goto error_out;
+ }
+ }
+
+ winmm->ppwfx_size = 10;
+ winmm->ppwfx = calloc(winmm->ppwfx_size, sizeof(PWAVEFORMATEX));
+
+ if (!winmm->ppwfx)
+ {
+ WLog_Print(winmm->log, WLOG_ERROR, "malloc failed!");
+ error = CHANNEL_RC_NO_MEMORY;
+ goto error_out;
+ }
+
+ if ((error = pEntryPoints->pRegisterAudinDevice(pEntryPoints->plugin, &winmm->iface)))
+ {
+ WLog_Print(winmm->log, WLOG_ERROR, "RegisterAudinDevice failed with error %" PRIu32 "!",
+ error);
+ goto error_out;
+ }
+
+ return CHANNEL_RC_OK;
+error_out:
+ free(winmm->ppwfx);
+ free(winmm->device_name);
+ free(winmm);
+ return error;
+}
diff --git a/channels/audin/server/CMakeLists.txt b/channels/audin/server/CMakeLists.txt
new file mode 100644
index 0000000..454d2a6
--- /dev/null
+++ b/channels/audin/server/CMakeLists.txt
@@ -0,0 +1,27 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+#
+# 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_server("audin")
+
+set(${MODULE_PREFIX}_SRCS
+ audin.c
+)
+
+set(${MODULE_PREFIX}_LIBS
+ freerdp
+)
+add_channel_server_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "DVCPluginEntry")
diff --git a/channels/audin/server/audin.c b/channels/audin/server/audin.c
new file mode 100644
index 0000000..d67937a
--- /dev/null
+++ b/channels/audin/server/audin.c
@@ -0,0 +1,914 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Server Audio Input Virtual Channel
+ *
+ * Copyright 2012 Vic Lee
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ * Copyright 2022 Pascal Nowack <Pascal.Nowack@gmx.de>
+ *
+ * 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 <winpr/crt.h>
+#include <winpr/assert.h>
+#include <winpr/synch.h>
+#include <winpr/thread.h>
+#include <winpr/stream.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/server/server-common.h>
+#include <freerdp/server/audin.h>
+#include <freerdp/channels/log.h>
+
+#define AUDIN_TAG CHANNELS_TAG("audin.server")
+
+#define SNDIN_HEADER_SIZE 1
+
+typedef enum
+{
+ MSG_SNDIN_VERSION = 0x01,
+ MSG_SNDIN_FORMATS = 0x02,
+ MSG_SNDIN_OPEN = 0x03,
+ MSG_SNDIN_OPEN_REPLY = 0x04,
+ MSG_SNDIN_DATA_INCOMING = 0x05,
+ MSG_SNDIN_DATA = 0x06,
+ MSG_SNDIN_FORMATCHANGE = 0x07,
+} MSG_SNDIN;
+
+typedef struct
+{
+ audin_server_context context;
+
+ HANDLE stopEvent;
+
+ HANDLE thread;
+ void* audin_channel;
+
+ DWORD SessionId;
+
+ AUDIO_FORMAT* audin_server_formats;
+ UINT32 audin_n_server_formats;
+ AUDIO_FORMAT* audin_negotiated_format;
+ UINT32 audin_client_format_idx;
+ wLog* log;
+} audin_server;
+
+static UINT audin_server_recv_version(audin_server_context* context, wStream* s,
+ const SNDIN_PDU* header)
+{
+ audin_server* audin = (audin_server*)context;
+ SNDIN_VERSION pdu = { 0 };
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(header);
+
+ pdu.Header = *header;
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(audin->log, s, 4))
+ return ERROR_NO_DATA;
+
+ Stream_Read_UINT32(s, pdu.Version);
+
+ IFCALLRET(context->ReceiveVersion, error, context, &pdu);
+ if (error)
+ WLog_Print(audin->log, WLOG_ERROR, "context->ReceiveVersion failed with error %" PRIu32 "",
+ error);
+
+ return error;
+}
+
+static UINT audin_server_recv_formats(audin_server_context* context, wStream* s,
+ const SNDIN_PDU* header)
+{
+ audin_server* audin = (audin_server*)context;
+ SNDIN_FORMATS pdu = { 0 };
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(header);
+
+ pdu.Header = *header;
+
+ /* Implementations MUST, at a minimum, support WAVE_FORMAT_PCM (0x0001) */
+ if (!Stream_CheckAndLogRequiredLengthWLog(audin->log, s, 4 + 4 + 18))
+ return ERROR_NO_DATA;
+
+ Stream_Read_UINT32(s, pdu.NumFormats);
+ Stream_Read_UINT32(s, pdu.cbSizeFormatsPacket);
+
+ if (pdu.NumFormats == 0)
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "Sound Formats PDU contains no formats");
+ return ERROR_INVALID_DATA;
+ }
+
+ pdu.SoundFormats = audio_formats_new(pdu.NumFormats);
+ if (!pdu.SoundFormats)
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "Failed to allocate %u SoundFormats", pdu.NumFormats);
+ return ERROR_NOT_ENOUGH_MEMORY;
+ }
+
+ for (UINT32 i = 0; i < pdu.NumFormats; ++i)
+ {
+ AUDIO_FORMAT* format = &pdu.SoundFormats[i];
+
+ if (!audio_format_read(s, format))
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "Failed to read audio format");
+ audio_formats_free(pdu.SoundFormats, i + i);
+ return ERROR_INVALID_DATA;
+ }
+
+ audio_format_print(audin->log, WLOG_DEBUG, format);
+ }
+
+ if (pdu.cbSizeFormatsPacket != Stream_GetPosition(s))
+ {
+ WLog_Print(audin->log, WLOG_WARN,
+ "cbSizeFormatsPacket is invalid! Expected: %u Got: %zu. Fixing size",
+ pdu.cbSizeFormatsPacket, Stream_GetPosition(s));
+ const size_t pos = Stream_GetPosition(s);
+ if (pos > UINT32_MAX)
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "Stream too long, %" PRIuz " exceeds UINT32_MAX",
+ pos);
+ error = ERROR_INVALID_PARAMETER;
+ goto fail;
+ }
+ pdu.cbSizeFormatsPacket = (UINT32)pos;
+ }
+
+ pdu.ExtraDataSize = Stream_GetRemainingLength(s);
+
+ IFCALLRET(context->ReceiveFormats, error, context, &pdu);
+ if (error)
+ WLog_Print(audin->log, WLOG_ERROR, "context->ReceiveFormats failed with error %" PRIu32 "",
+ error);
+
+fail:
+ audio_formats_free(pdu.SoundFormats, pdu.NumFormats);
+
+ return error;
+}
+
+static UINT audin_server_recv_open_reply(audin_server_context* context, wStream* s,
+ const SNDIN_PDU* header)
+{
+ audin_server* audin = (audin_server*)context;
+ SNDIN_OPEN_REPLY pdu = { 0 };
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(header);
+
+ pdu.Header = *header;
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(audin->log, s, 4))
+ return ERROR_NO_DATA;
+
+ Stream_Read_UINT32(s, pdu.Result);
+
+ IFCALLRET(context->OpenReply, error, context, &pdu);
+ if (error)
+ WLog_Print(audin->log, WLOG_ERROR, "context->OpenReply failed with error %" PRIu32 "",
+ error);
+
+ return error;
+}
+
+static UINT audin_server_recv_data_incoming(audin_server_context* context, wStream* s,
+ const SNDIN_PDU* header)
+{
+ audin_server* audin = (audin_server*)context;
+ SNDIN_DATA_INCOMING pdu = { 0 };
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(header);
+
+ pdu.Header = *header;
+
+ IFCALLRET(context->IncomingData, error, context, &pdu);
+ if (error)
+ WLog_Print(audin->log, WLOG_ERROR, "context->IncomingData failed with error %" PRIu32 "",
+ error);
+
+ return error;
+}
+
+static UINT audin_server_recv_data(audin_server_context* context, wStream* s,
+ const SNDIN_PDU* header)
+{
+ audin_server* audin = (audin_server*)context;
+ SNDIN_DATA pdu = { 0 };
+ wStream dataBuffer = { 0 };
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(header);
+
+ pdu.Header = *header;
+
+ pdu.Data = Stream_StaticInit(&dataBuffer, Stream_Pointer(s), Stream_GetRemainingLength(s));
+
+ IFCALLRET(context->Data, error, context, &pdu);
+ if (error)
+ WLog_Print(audin->log, WLOG_ERROR, "context->Data failed with error %" PRIu32 "", error);
+
+ return error;
+}
+
+static UINT audin_server_recv_format_change(audin_server_context* context, wStream* s,
+ const SNDIN_PDU* header)
+{
+ audin_server* audin = (audin_server*)context;
+ SNDIN_FORMATCHANGE pdu = { 0 };
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(header);
+
+ pdu.Header = *header;
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(audin->log, s, 4))
+ return ERROR_NO_DATA;
+
+ Stream_Read_UINT32(s, pdu.NewFormat);
+
+ IFCALLRET(context->ReceiveFormatChange, error, context, &pdu);
+ if (error)
+ WLog_Print(audin->log, WLOG_ERROR,
+ "context->ReceiveFormatChange failed with error %" PRIu32 "", error);
+
+ return error;
+}
+
+static DWORD WINAPI audin_server_thread_func(LPVOID arg)
+{
+ wStream* s = NULL;
+ void* buffer = NULL;
+ DWORD nCount = 0;
+ HANDLE events[8] = { 0 };
+ BOOL ready = FALSE;
+ HANDLE ChannelEvent = NULL;
+ DWORD BytesReturned = 0;
+ audin_server* audin = (audin_server*)arg;
+ UINT error = CHANNEL_RC_OK;
+ DWORD status = ERROR_INTERNAL_ERROR;
+
+ WINPR_ASSERT(audin);
+
+ if (WTSVirtualChannelQuery(audin->audin_channel, WTSVirtualEventHandle, &buffer,
+ &BytesReturned) == TRUE)
+ {
+ if (BytesReturned == sizeof(HANDLE))
+ CopyMemory(&ChannelEvent, buffer, sizeof(HANDLE));
+
+ WTSFreeMemory(buffer);
+ }
+ else
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "WTSVirtualChannelQuery failed");
+ error = ERROR_INTERNAL_ERROR;
+ goto out;
+ }
+
+ nCount = 0;
+ events[nCount++] = audin->stopEvent;
+ events[nCount++] = ChannelEvent;
+
+ /* Wait for the client to confirm that the Audio Input dynamic channel is ready */
+
+ while (1)
+ {
+ if ((status = WaitForMultipleObjects(nCount, events, FALSE, 100)) == WAIT_OBJECT_0)
+ goto out;
+
+ if (status == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_Print(audin->log, WLOG_ERROR,
+ "WaitForMultipleObjects failed with error %" PRIu32 "", error);
+ goto out;
+ }
+ if (status == WAIT_OBJECT_0)
+ goto out;
+
+ if (WTSVirtualChannelQuery(audin->audin_channel, WTSVirtualChannelReady, &buffer,
+ &BytesReturned) == FALSE)
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "WTSVirtualChannelQuery failed");
+ error = ERROR_INTERNAL_ERROR;
+ goto out;
+ }
+
+ ready = *((BOOL*)buffer);
+ WTSFreeMemory(buffer);
+
+ if (ready)
+ break;
+ }
+
+ s = Stream_New(NULL, 4096);
+
+ if (!s)
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "Stream_New failed!");
+ error = CHANNEL_RC_NO_MEMORY;
+ goto out;
+ }
+
+ if (ready)
+ {
+ SNDIN_VERSION version = { 0 };
+
+ version.Version = audin->context.serverVersion;
+
+ if ((error = audin->context.SendVersion(&audin->context, &version)))
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "SendVersion failed with error %" PRIu32 "!", error);
+ goto out_capacity;
+ }
+ }
+
+ while (ready)
+ {
+ SNDIN_PDU header = { 0 };
+
+ if ((status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE)) == WAIT_OBJECT_0)
+ break;
+
+ if (status == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_Print(audin->log, WLOG_ERROR,
+ "WaitForMultipleObjects failed with error %" PRIu32 "", error);
+ break;
+ }
+ if (status == WAIT_OBJECT_0)
+ break;
+
+ Stream_SetPosition(s, 0);
+
+ if (!WTSVirtualChannelRead(audin->audin_channel, 0, NULL, 0, &BytesReturned))
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "WTSVirtualChannelRead failed!");
+ error = ERROR_INTERNAL_ERROR;
+ break;
+ }
+
+ if (BytesReturned < 1)
+ continue;
+
+ if (!Stream_EnsureRemainingCapacity(s, BytesReturned))
+ break;
+
+ WINPR_ASSERT(Stream_Capacity(s) <= ULONG_MAX);
+ if (WTSVirtualChannelRead(audin->audin_channel, 0, (PCHAR)Stream_Buffer(s),
+ (ULONG)Stream_Capacity(s), &BytesReturned) == FALSE)
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "WTSVirtualChannelRead failed!");
+ error = ERROR_INTERNAL_ERROR;
+ break;
+ }
+
+ Stream_SetLength(s, BytesReturned);
+ if (!Stream_CheckAndLogRequiredLengthWLog(audin->log, s, SNDIN_HEADER_SIZE))
+ {
+ error = ERROR_INTERNAL_ERROR;
+ break;
+ }
+
+ Stream_Read_UINT8(s, header.MessageId);
+
+ switch (header.MessageId)
+ {
+ case MSG_SNDIN_VERSION:
+ error = audin_server_recv_version(&audin->context, s, &header);
+ break;
+ case MSG_SNDIN_FORMATS:
+ error = audin_server_recv_formats(&audin->context, s, &header);
+ break;
+ case MSG_SNDIN_OPEN_REPLY:
+ error = audin_server_recv_open_reply(&audin->context, s, &header);
+ break;
+ case MSG_SNDIN_DATA_INCOMING:
+ error = audin_server_recv_data_incoming(&audin->context, s, &header);
+ break;
+ case MSG_SNDIN_DATA:
+ error = audin_server_recv_data(&audin->context, s, &header);
+ break;
+ case MSG_SNDIN_FORMATCHANGE:
+ error = audin_server_recv_format_change(&audin->context, s, &header);
+ break;
+ default:
+ WLog_Print(audin->log, WLOG_ERROR,
+ "audin_server_thread_func: unknown or invalid MessageId %" PRIu8 "",
+ header.MessageId);
+ error = ERROR_INVALID_DATA;
+ break;
+ }
+ if (error)
+ break;
+ }
+
+out_capacity:
+ Stream_Free(s, TRUE);
+out:
+ WTSVirtualChannelClose(audin->audin_channel);
+ audin->audin_channel = NULL;
+
+ if (error && audin->context.rdpcontext)
+ setChannelError(audin->context.rdpcontext, error,
+ "audin_server_thread_func reported an error");
+
+ ExitThread(error);
+ return error;
+}
+
+static BOOL audin_server_open(audin_server_context* context)
+{
+ audin_server* audin = (audin_server*)context;
+
+ WINPR_ASSERT(audin);
+ if (!audin->thread)
+ {
+ PULONG pSessionId = NULL;
+ DWORD BytesReturned = 0;
+ audin->SessionId = WTS_CURRENT_SESSION;
+ UINT32 channelId = 0;
+ BOOL status = TRUE;
+
+ if (WTSQuerySessionInformationA(context->vcm, WTS_CURRENT_SESSION, WTSSessionId,
+ (LPSTR*)&pSessionId, &BytesReturned))
+ {
+ audin->SessionId = (DWORD)*pSessionId;
+ WTSFreeMemory(pSessionId);
+ }
+
+ audin->audin_channel = WTSVirtualChannelOpenEx(audin->SessionId, AUDIN_DVC_CHANNEL_NAME,
+ WTS_CHANNEL_OPTION_DYNAMIC);
+
+ if (!audin->audin_channel)
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "WTSVirtualChannelOpenEx failed!");
+ return FALSE;
+ }
+
+ channelId = WTSChannelGetIdByHandle(audin->audin_channel);
+
+ IFCALLRET(context->ChannelIdAssigned, status, context, channelId);
+ if (!status)
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "context->ChannelIdAssigned failed!");
+ return FALSE;
+ }
+
+ if (!(audin->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "CreateEvent failed!");
+ return FALSE;
+ }
+
+ if (!(audin->thread =
+ CreateThread(NULL, 0, audin_server_thread_func, (void*)audin, 0, NULL)))
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "CreateThread failed!");
+ CloseHandle(audin->stopEvent);
+ audin->stopEvent = NULL;
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+
+ WLog_Print(audin->log, WLOG_ERROR, "thread already running!");
+ return FALSE;
+}
+
+static BOOL audin_server_is_open(audin_server_context* context)
+{
+ audin_server* audin = (audin_server*)context;
+
+ WINPR_ASSERT(audin);
+ return audin->thread != NULL;
+}
+
+static BOOL audin_server_close(audin_server_context* context)
+{
+ audin_server* audin = (audin_server*)context;
+ WINPR_ASSERT(audin);
+
+ if (audin->thread)
+ {
+ SetEvent(audin->stopEvent);
+
+ if (WaitForSingleObject(audin->thread, INFINITE) == WAIT_FAILED)
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "WaitForSingleObject failed with error %" PRIu32 "",
+ GetLastError());
+ return FALSE;
+ }
+
+ CloseHandle(audin->thread);
+ CloseHandle(audin->stopEvent);
+ audin->thread = NULL;
+ audin->stopEvent = NULL;
+ }
+
+ if (audin->audin_channel)
+ {
+ WTSVirtualChannelClose(audin->audin_channel);
+ audin->audin_channel = NULL;
+ }
+
+ audin->audin_negotiated_format = NULL;
+
+ return TRUE;
+}
+
+static wStream* audin_server_packet_new(wLog* log, size_t size, BYTE MessageId)
+{
+ WINPR_ASSERT(log);
+
+ /* Allocate what we need plus header bytes */
+ wStream* s = Stream_New(NULL, size + SNDIN_HEADER_SIZE);
+ if (!s)
+ {
+ WLog_Print(log, WLOG_ERROR, "Stream_New failed!");
+ return NULL;
+ }
+
+ Stream_Write_UINT8(s, MessageId);
+
+ return s;
+}
+
+static UINT audin_server_packet_send(audin_server_context* context, wStream* s)
+{
+ audin_server* audin = (audin_server*)context;
+ UINT error = CHANNEL_RC_OK;
+ ULONG written = 0;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(s);
+
+ const size_t pos = Stream_GetPosition(s);
+ if (pos > UINT32_MAX)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!WTSVirtualChannelWrite(audin->audin_channel, (PCHAR)Stream_Buffer(s), (UINT32)pos,
+ &written))
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "WTSVirtualChannelWrite failed!");
+ error = ERROR_INTERNAL_ERROR;
+ goto out;
+ }
+
+ if (written < Stream_GetPosition(s))
+ {
+ WLog_Print(audin->log, WLOG_WARN, "Unexpected bytes written: %" PRIu32 "/%" PRIuz "",
+ written, Stream_GetPosition(s));
+ }
+
+out:
+ Stream_Free(s, TRUE);
+ return error;
+}
+
+static UINT audin_server_send_version(audin_server_context* context, const SNDIN_VERSION* version)
+{
+ audin_server* audin = (audin_server*)context;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(version);
+
+ wStream* s = audin_server_packet_new(audin->log, 4, MSG_SNDIN_VERSION);
+ if (!s)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ Stream_Write_UINT32(s, version->Version);
+
+ return audin_server_packet_send(context, s);
+}
+
+static UINT audin_server_send_formats(audin_server_context* context, const SNDIN_FORMATS* formats)
+{
+ audin_server* audin = (audin_server*)context;
+
+ WINPR_ASSERT(audin);
+ WINPR_ASSERT(formats);
+
+ wStream* s = audin_server_packet_new(audin->log, 4 + 4 + 18, MSG_SNDIN_FORMATS);
+ if (!s)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ Stream_Write_UINT32(s, formats->NumFormats);
+ Stream_Write_UINT32(s, formats->cbSizeFormatsPacket);
+
+ for (UINT32 i = 0; i < formats->NumFormats; ++i)
+ {
+ AUDIO_FORMAT* format = &formats->SoundFormats[i];
+
+ if (!audio_format_write(s, format))
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "Failed to write audio format");
+ Stream_Free(s, TRUE);
+ return CHANNEL_RC_NO_MEMORY;
+ }
+ }
+
+ return audin_server_packet_send(context, s);
+}
+
+static UINT audin_server_send_open(audin_server_context* context, const SNDIN_OPEN* open)
+{
+ audin_server* audin = (audin_server*)context;
+ WINPR_ASSERT(audin);
+ WINPR_ASSERT(open);
+
+ wStream* s = audin_server_packet_new(audin->log, 4 + 4 + 18 + 22, MSG_SNDIN_OPEN);
+ if (!s)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ Stream_Write_UINT32(s, open->FramesPerPacket);
+ Stream_Write_UINT32(s, open->initialFormat);
+
+ Stream_Write_UINT16(s, open->captureFormat.wFormatTag);
+ Stream_Write_UINT16(s, open->captureFormat.nChannels);
+ Stream_Write_UINT32(s, open->captureFormat.nSamplesPerSec);
+ Stream_Write_UINT32(s, open->captureFormat.nAvgBytesPerSec);
+ Stream_Write_UINT16(s, open->captureFormat.nBlockAlign);
+ Stream_Write_UINT16(s, open->captureFormat.wBitsPerSample);
+
+ if (open->ExtraFormatData)
+ {
+ Stream_Write_UINT16(s, 22); /* cbSize */
+
+ Stream_Write_UINT16(s, open->ExtraFormatData->Samples.wReserved);
+ Stream_Write_UINT32(s, open->ExtraFormatData->dwChannelMask);
+
+ Stream_Write_UINT32(s, open->ExtraFormatData->SubFormat.Data1);
+ Stream_Write_UINT16(s, open->ExtraFormatData->SubFormat.Data2);
+ Stream_Write_UINT16(s, open->ExtraFormatData->SubFormat.Data3);
+ Stream_Write_UINT8(s, open->ExtraFormatData->SubFormat.Data4[0]);
+ Stream_Write_UINT8(s, open->ExtraFormatData->SubFormat.Data4[1]);
+ Stream_Write_UINT8(s, open->ExtraFormatData->SubFormat.Data4[2]);
+ Stream_Write_UINT8(s, open->ExtraFormatData->SubFormat.Data4[3]);
+ Stream_Write_UINT8(s, open->ExtraFormatData->SubFormat.Data4[4]);
+ Stream_Write_UINT8(s, open->ExtraFormatData->SubFormat.Data4[5]);
+ Stream_Write_UINT8(s, open->ExtraFormatData->SubFormat.Data4[6]);
+ Stream_Write_UINT8(s, open->ExtraFormatData->SubFormat.Data4[7]);
+ }
+ else
+ {
+ WINPR_ASSERT(open->captureFormat.wFormatTag != WAVE_FORMAT_EXTENSIBLE);
+
+ Stream_Write_UINT16(s, 0); /* cbSize */
+ }
+
+ return audin_server_packet_send(context, s);
+}
+
+static UINT audin_server_send_format_change(audin_server_context* context,
+ const SNDIN_FORMATCHANGE* format_change)
+{
+ audin_server* audin = (audin_server*)context;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(format_change);
+
+ wStream* s = audin_server_packet_new(audin->log, 4, MSG_SNDIN_FORMATCHANGE);
+ if (!s)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ Stream_Write_UINT32(s, format_change->NewFormat);
+
+ return audin_server_packet_send(context, s);
+}
+
+static UINT audin_server_receive_version_default(audin_server_context* audin_ctx,
+ const SNDIN_VERSION* version)
+{
+ audin_server* audin = (audin_server*)audin_ctx;
+ SNDIN_FORMATS formats = { 0 };
+
+ WINPR_ASSERT(audin);
+ WINPR_ASSERT(version);
+
+ if (version->Version == 0)
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "Received invalid AUDIO_INPUT version from client");
+ return ERROR_INVALID_DATA;
+ }
+
+ WLog_Print(audin->log, WLOG_DEBUG, "AUDIO_INPUT version of client: %u", version->Version);
+
+ formats.NumFormats = audin->audin_n_server_formats;
+ formats.SoundFormats = audin->audin_server_formats;
+
+ return audin->context.SendFormats(&audin->context, &formats);
+}
+
+static UINT send_open(audin_server* audin)
+{
+ SNDIN_OPEN open = { 0 };
+
+ WINPR_ASSERT(audin);
+
+ open.FramesPerPacket = 441;
+ open.initialFormat = audin->audin_client_format_idx;
+ open.captureFormat.wFormatTag = WAVE_FORMAT_PCM;
+ open.captureFormat.nChannels = 2;
+ open.captureFormat.nSamplesPerSec = 44100;
+ open.captureFormat.nAvgBytesPerSec = 44100 * 2 * 2;
+ open.captureFormat.nBlockAlign = 4;
+ open.captureFormat.wBitsPerSample = 16;
+
+ WINPR_ASSERT(audin->context.SendOpen);
+ return audin->context.SendOpen(&audin->context, &open);
+}
+
+static UINT audin_server_receive_formats_default(audin_server_context* context,
+ const SNDIN_FORMATS* formats)
+{
+ audin_server* audin = (audin_server*)context;
+ WINPR_ASSERT(audin);
+ WINPR_ASSERT(formats);
+
+ if (audin->audin_negotiated_format)
+ {
+ WLog_Print(audin->log, WLOG_ERROR,
+ "Received client formats, but negotiation was already done");
+ return ERROR_INVALID_DATA;
+ }
+
+ for (UINT32 i = 0; i < audin->audin_n_server_formats; ++i)
+ {
+ for (UINT32 j = 0; j < formats->NumFormats; ++j)
+ {
+ if (audio_format_compatible(&audin->audin_server_formats[i], &formats->SoundFormats[j]))
+ {
+ audin->audin_negotiated_format = &audin->audin_server_formats[i];
+ audin->audin_client_format_idx = i;
+ return send_open(audin);
+ }
+ }
+ }
+
+ WLog_Print(audin->log, WLOG_ERROR, "Could not agree on a audio format with the server");
+
+ return ERROR_INVALID_DATA;
+}
+
+static UINT audin_server_receive_format_change_default(audin_server_context* context,
+ const SNDIN_FORMATCHANGE* format_change)
+{
+ audin_server* audin = (audin_server*)context;
+
+ WINPR_ASSERT(audin);
+ WINPR_ASSERT(format_change);
+
+ if (format_change->NewFormat != audin->audin_client_format_idx)
+ {
+ WLog_Print(audin->log, WLOG_ERROR,
+ "NewFormat in FormatChange differs from requested format");
+ return ERROR_INVALID_DATA;
+ }
+
+ WLog_Print(audin->log, WLOG_DEBUG, "Received Format Change PDU: %u", format_change->NewFormat);
+
+ return CHANNEL_RC_OK;
+}
+
+static UINT audin_server_incoming_data_default(audin_server_context* context,
+ const SNDIN_DATA_INCOMING* data_incoming)
+{
+ audin_server* audin = (audin_server*)context;
+ WINPR_ASSERT(audin);
+ WINPR_ASSERT(data_incoming);
+
+ /* TODO: Implement bandwidth measure of clients uplink */
+ WLog_Print(audin->log, WLOG_DEBUG, "Received Incoming Data PDU");
+ return CHANNEL_RC_OK;
+}
+
+static UINT audin_server_open_reply_default(audin_server_context* context,
+ const SNDIN_OPEN_REPLY* open_reply)
+{
+ audin_server* audin = (audin_server*)context;
+ WINPR_ASSERT(audin);
+ WINPR_ASSERT(open_reply);
+
+ /* TODO: Implement failure handling */
+ WLog_Print(audin->log, WLOG_DEBUG, "Open Reply PDU: Result: %i", open_reply->Result);
+ return CHANNEL_RC_OK;
+}
+
+audin_server_context* audin_server_context_new(HANDLE vcm)
+{
+ audin_server* audin = (audin_server*)calloc(1, sizeof(audin_server));
+
+ if (!audin)
+ {
+ WLog_ERR(AUDIN_TAG, "calloc failed!");
+ return NULL;
+ }
+ audin->log = WLog_Get(AUDIN_TAG);
+ audin->context.vcm = vcm;
+ audin->context.Open = audin_server_open;
+ audin->context.IsOpen = audin_server_is_open;
+ audin->context.Close = audin_server_close;
+
+ audin->context.SendVersion = audin_server_send_version;
+ audin->context.SendFormats = audin_server_send_formats;
+ audin->context.SendOpen = audin_server_send_open;
+ audin->context.SendFormatChange = audin_server_send_format_change;
+
+ /* Default values */
+ audin->context.serverVersion = SNDIN_VERSION_Version_2;
+ audin->context.ReceiveVersion = audin_server_receive_version_default;
+ audin->context.ReceiveFormats = audin_server_receive_formats_default;
+ audin->context.ReceiveFormatChange = audin_server_receive_format_change_default;
+ audin->context.IncomingData = audin_server_incoming_data_default;
+ audin->context.OpenReply = audin_server_open_reply_default;
+
+ return &audin->context;
+}
+
+void audin_server_context_free(audin_server_context* context)
+{
+ audin_server* audin = (audin_server*)context;
+
+ if (!audin)
+ return;
+
+ audin_server_close(context);
+ audio_formats_free(audin->audin_server_formats, audin->audin_n_server_formats);
+ audin->audin_server_formats = NULL;
+ free(audin);
+}
+
+BOOL audin_server_set_formats(audin_server_context* context, SSIZE_T count,
+ const AUDIO_FORMAT* formats)
+{
+ audin_server* audin = (audin_server*)context;
+ WINPR_ASSERT(audin);
+
+ audio_formats_free(audin->audin_server_formats, audin->audin_n_server_formats);
+ audin->audin_n_server_formats = 0;
+ audin->audin_server_formats = NULL;
+ audin->audin_negotiated_format = NULL;
+
+ if (count < 0)
+ {
+ const size_t audin_n_server_formats =
+ server_audin_get_formats(&audin->audin_server_formats);
+ WINPR_ASSERT(audin_n_server_formats <= UINT32_MAX);
+
+ audin->audin_n_server_formats = (UINT32)audin_n_server_formats;
+ }
+ else
+ {
+ AUDIO_FORMAT* audin_server_formats = audio_formats_new(count);
+ if (!audin_server_formats)
+ return count == 0;
+
+ for (SSIZE_T x = 0; x < count; x++)
+ {
+ if (!audio_format_copy(&formats[x], &audin_server_formats[x]))
+ {
+ audio_formats_free(audin_server_formats, count);
+ return FALSE;
+ }
+ }
+
+ WINPR_ASSERT(count <= UINT32_MAX);
+ audin->audin_server_formats = audin_server_formats;
+ audin->audin_n_server_formats = (UINT32)count;
+ }
+ return audin->audin_n_server_formats > 0;
+}
+
+const AUDIO_FORMAT* audin_server_get_negotiated_format(const audin_server_context* context)
+{
+ const audin_server* audin = (const audin_server*)context;
+ WINPR_ASSERT(audin);
+
+ return audin->audin_negotiated_format;
+}