summaryrefslogtreecommitdiffstats
path: root/channels/audin/server/audin.c
diff options
context:
space:
mode:
Diffstat (limited to 'channels/audin/server/audin.c')
-rw-r--r--channels/audin/server/audin.c914
1 files changed, 914 insertions, 0 deletions
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;
+}