channels/audin: Rework API to be closer to documentation

The current server side channel handling of AUDIO_INPUT is currently
very constrained:

- Server implementations cannot measure the clients uplink, since the
  Incoming Data PDU is currently unhandled and FreeRDPs DSP handling
  delays the callback call of ReceiveSamples
- Servers currently cannot prefer a different protocol version
- Servers currently cannot change the used format

To solve these issues without running into the risk that some
simplifications constraint certain API usage, rework the current channel
handling to be very close to the documentation.
This means, that all documented API calls can be made by server
implementations and all documented PDUs, that the server side is
expected to receive are just parsed inside FreeRDP and then forwarded to
the API implementation.
This commit is contained in:
Pascal Nowack 2022-07-20 13:40:40 +02:00 committed by akallabeth
parent a659290bd9
commit c5278c874f
13 changed files with 1144 additions and 684 deletions

View File

@ -44,7 +44,7 @@
#define SNDIN_VERSION 0x02
enum MSG_SNDIN_CMD
typedef enum
{
MSG_SNDIN_VERSION = 0x01,
MSG_SNDIN_FORMATS = 0x02,
@ -52,8 +52,8 @@ enum MSG_SNDIN_CMD
MSG_SNDIN_OPEN_REPLY = 0x04,
MSG_SNDIN_DATA_INCOMING = 0x05,
MSG_SNDIN_DATA = 0x06,
MSG_SNDIN_FORMATCHANGE = 0x07
};
MSG_SNDIN_FORMATCHANGE = 0x07,
} MSG_SNDIN;
typedef struct
{

View File

@ -5,6 +5,7 @@
* 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.
@ -21,10 +22,6 @@
#include <freerdp/config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winpr/crt.h>
#include <winpr/assert.h>
#include <winpr/synch.h>
@ -32,337 +29,205 @@
#include <winpr/stream.h>
#include <freerdp/freerdp.h>
#include <freerdp/codec/dsp.h>
#include <freerdp/codec/audio.h>
#include <freerdp/channels/wtsvc.h>
#include <freerdp/channels/audin.h>
#include <freerdp/server/audin.h>
#include <freerdp/channels/log.h>
#define TAG CHANNELS_TAG("audin.server")
#define MSG_SNDIN_VERSION 0x01
#define MSG_SNDIN_FORMATS 0x02
#define MSG_SNDIN_OPEN 0x03
#define MSG_SNDIN_OPEN_REPLY 0x04
#define MSG_SNDIN_DATA_INCOMING 0x05
#define MSG_SNDIN_DATA 0x06
#define MSG_SNDIN_FORMATCHANGE 0x07
#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;
BOOL opened;
HANDLE stopEvent;
HANDLE thread;
void* audin_channel;
DWORD SessionId;
FREERDP_DSP_CONTEXT* dsp_context;
} audin_server;
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT audin_server_select_format(audin_server_context* context, size_t client_format_index)
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(audin);
WINPR_ASSERT(context);
WINPR_ASSERT(header);
if (client_format_index >= context->num_client_formats)
{
WLog_ERR(TAG, "error in protocol: client_format_index >= context->num_client_formats!");
return ERROR_INVALID_DATA;
}
pdu.Header = *header;
context->selected_client_format = (SSIZE_T)client_format_index;
if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
return ERROR_NO_DATA;
if (!freerdp_dsp_context_reset(audin->dsp_context,
&audin->context.client_formats[client_format_index], 0u))
{
WLog_ERR(TAG, "Failed to reset dsp context format!");
return ERROR_INTERNAL_ERROR;
}
Stream_Read_UINT32(s, pdu.Version);
if (audin->opened)
{
/* TODO: send MSG_SNDIN_FORMATCHANGE */
}
IFCALLRET(context->ReceiveVersion, error, context, &pdu);
if (error)
WLog_ERR(TAG, "context->ReceiveVersion failed with error %" PRIu32 "", error);
return CHANNEL_RC_OK;
return error;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT audin_server_send_version(audin_server* audin, wStream* s)
static UINT audin_server_recv_formats(audin_server_context* context, wStream* s,
const SNDIN_PDU* header)
{
ULONG written;
WINPR_ASSERT(audin);
SNDIN_FORMATS pdu = { 0 };
UINT error = CHANNEL_RC_OK;
Stream_Write_UINT8(s, MSG_SNDIN_VERSION);
Stream_Write_UINT32(s, 1); /* Version (4 bytes) */
WINPR_ASSERT(context);
WINPR_ASSERT(header);
WINPR_ASSERT(Stream_GetPosition(s) <= ULONG_MAX);
if (!WTSVirtualChannelWrite(audin->audin_channel, (PCHAR)Stream_Buffer(s),
(ULONG)Stream_GetPosition(s), &written))
pdu.Header = *header;
/* Implementations MUST, at a minimum, support WAVE_FORMAT_PCM (0x0001) */
if (!Stream_CheckAndLogRequiredLength(TAG, 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_ERR(TAG, "WTSVirtualChannelWrite failed!");
return ERROR_INTERNAL_ERROR;
}
return CHANNEL_RC_OK;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT audin_server_recv_version(audin_server* audin, wStream* s, UINT32 length)
{
UINT32 Version;
WINPR_ASSERT(audin);
if (length < 4)
{
WLog_ERR(TAG, "error parsing version info: expected at least 4 bytes, got %" PRIu32 "",
length);
WLog_ERR(TAG, "Sound Formats PDU contains no formats");
return ERROR_INVALID_DATA;
}
Stream_Read_UINT32(s, Version);
if (Version < 1)
pdu.SoundFormats = audio_formats_new(pdu.NumFormats);
if (!pdu.SoundFormats)
{
WLog_ERR(TAG, "expected Version > 0 but got %" PRIu32 "", Version);
return ERROR_INVALID_DATA;
}
return CHANNEL_RC_OK;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT audin_server_send_formats(audin_server* audin, wStream* s)
{
size_t i;
ULONG written;
WINPR_ASSERT(audin);
Stream_SetPosition(s, 0);
Stream_Write_UINT8(s, MSG_SNDIN_FORMATS);
WINPR_ASSERT(audin->context.num_server_formats <= UINT32_MAX);
Stream_Write_UINT32(s, audin->context.num_server_formats); /* NumFormats (4 bytes) */
Stream_Write_UINT32(s, 0); /* cbSizeFormatsPacket (4 bytes), client-to-server only */
for (i = 0; i < audin->context.num_server_formats; i++)
{
AUDIO_FORMAT format = audin->context.server_formats[i];
if (!audio_format_write(s, &format))
{
WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!");
return CHANNEL_RC_NO_MEMORY;
}
}
WINPR_ASSERT(Stream_GetPosition(s) <= ULONG_MAX);
return WTSVirtualChannelWrite(audin->audin_channel, (PCHAR)Stream_Buffer(s),
(ULONG)Stream_GetPosition(s), &written)
? CHANNEL_RC_OK
: ERROR_INTERNAL_ERROR;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT audin_server_recv_formats(audin_server* audin, wStream* s, UINT32 length)
{
size_t i;
UINT success = CHANNEL_RC_OK;
WINPR_ASSERT(audin);
if (length < 8)
{
WLog_ERR(TAG, "error parsing rec formats: expected at least 8 bytes, got %" PRIu32 "",
length);
return ERROR_INVALID_DATA;
}
Stream_Read_UINT32(s, audin->context.num_client_formats); /* NumFormats (4 bytes) */
Stream_Seek_UINT32(s); /* cbSizeFormatsPacket (4 bytes) */
length -= 8;
if (audin->context.num_client_formats <= 0)
{
WLog_ERR(TAG, "num_client_formats expected > 0 but got %" PRIuz,
audin->context.num_client_formats);
return ERROR_INVALID_DATA;
}
audin->context.client_formats = audio_formats_new(audin->context.num_client_formats);
if (!audin->context.client_formats)
WLog_ERR(TAG, "Failed to allocate %u SoundFormats", pdu.NumFormats);
return ERROR_NOT_ENOUGH_MEMORY;
}
for (i = 0; i < audin->context.num_client_formats; i++)
for (UINT32 i = 0; i < pdu.NumFormats; ++i)
{
AUDIO_FORMAT* format = &audin->context.client_formats[i];
AUDIO_FORMAT* format = &pdu.SoundFormats[i];
if (!audio_format_read(s, format))
{
audio_formats_free(audin->context.client_formats, i);
audin->context.client_formats = NULL;
WLog_ERR(TAG, "expected length at least 18, but got %" PRIu32 "", length);
WLog_ERR(TAG, "Failed to read audio format");
audio_formats_free(pdu.SoundFormats, i + i);
return ERROR_INVALID_DATA;
}
audio_format_print(WLog_Get(TAG), WLOG_DEBUG, format);
}
IFCALLRET(audin->context.Opening, success, &audin->context);
if (pdu.cbSizeFormatsPacket != Stream_GetPosition(s))
{
WLog_WARN(TAG, "cbSizeFormatsPacket is invalid! Expected: %u Got: %zu. Fixing size",
pdu.cbSizeFormatsPacket, Stream_GetPosition(s));
pdu.cbSizeFormatsPacket = Stream_GetPosition(s);
}
if (success)
WLog_ERR(TAG, "context.Opening failed with error %" PRIu32 "", success);
pdu.ExtraDataSize = Stream_GetRemainingLength(s);
return success;
IFCALLRET(context->ReceiveFormats, error, context, &pdu);
if (error)
WLog_ERR(TAG, "context->ReceiveFormats failed with error %" PRIu32 "", error);
audio_formats_free(pdu.SoundFormats, pdu.NumFormats);
return error;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT audin_server_send_open(audin_server* audin, wStream* s)
static UINT audin_server_recv_open_reply(audin_server_context* context, wStream* s,
const SNDIN_PDU* header)
{
ULONG written;
SNDIN_OPEN_REPLY pdu = { 0 };
UINT error = CHANNEL_RC_OK;
WINPR_ASSERT(audin);
if (audin->context.selected_client_format < 0)
{
WLog_ERR(TAG, "audin->context.selected_client_format = %" PRIdz,
audin->context.selected_client_format);
return ERROR_INVALID_DATA;
}
WINPR_ASSERT(context);
WINPR_ASSERT(header);
audin->opened = TRUE;
Stream_SetPosition(s, 0);
Stream_Write_UINT8(s, MSG_SNDIN_OPEN);
Stream_Write_UINT32(s, audin->context.frames_per_packet); /* FramesPerPacket (4 bytes) */
WINPR_ASSERT(audin->context.selected_client_format >= 0);
WINPR_ASSERT(audin->context.selected_client_format <= UINT32_MAX);
Stream_Write_UINT32(
s, (UINT32)audin->context.selected_client_format); /* initialFormat (4 bytes) */
/*
* [MS-RDPEAI] 3.2.5.1.6
* The second format specify the format that SHOULD be used to capture data from
* the actual audio input device.
*/
Stream_Write_UINT16(s, 1); /* wFormatTag = PCM */
Stream_Write_UINT16(s, 2); /* nChannels */
Stream_Write_UINT32(s, 44100); /* nSamplesPerSec */
Stream_Write_UINT32(s, 44100 * 2 * 2); /* nAvgBytesPerSec */
Stream_Write_UINT16(s, 4); /* nBlockAlign */
Stream_Write_UINT16(s, 16); /* wBitsPerSample */
Stream_Write_UINT16(s, 0); /* cbSize */
WINPR_ASSERT(Stream_GetPosition(s) <= ULONG_MAX);
return WTSVirtualChannelWrite(audin->audin_channel, (PCHAR)Stream_Buffer(s),
(ULONG)Stream_GetPosition(s), &written)
? CHANNEL_RC_OK
: ERROR_INTERNAL_ERROR;
pdu.Header = *header;
if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
return ERROR_NO_DATA;
Stream_Read_UINT32(s, pdu.Result);
IFCALLRET(context->OpenReply, error, context, &pdu);
if (error)
WLog_ERR(TAG, "context->OpenReply failed with error %" PRIu32 "", error);
return error;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT audin_server_recv_open_reply(audin_server* audin, wStream* s, UINT32 length)
static UINT audin_server_recv_data_incoming(audin_server_context* context, wStream* s,
const SNDIN_PDU* header)
{
UINT32 Result;
UINT success = CHANNEL_RC_OK;
SNDIN_DATA_INCOMING pdu = { 0 };
UINT error = CHANNEL_RC_OK;
WINPR_ASSERT(audin);
if (length < 4)
{
WLog_ERR(TAG, "error parsing version info: expected at least 4 bytes, got %" PRIu32 "",
length);
return ERROR_INVALID_DATA;
}
WINPR_ASSERT(context);
WINPR_ASSERT(header);
Stream_Read_UINT32(s, Result);
IFCALLRET(audin->context.OpenResult, success, &audin->context, Result);
pdu.Header = *header;
if (success)
WLog_ERR(TAG, "context.OpenResult failed with error %" PRIu32 "", success);
IFCALLRET(context->IncomingData, error, context, &pdu);
if (error)
WLog_ERR(TAG, "context->IncomingData failed with error %" PRIu32 "", error);
return success;
return error;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT audin_server_recv_data(audin_server* audin, wStream* s, UINT32 length)
static UINT audin_server_recv_data(audin_server_context* context, wStream* s,
const SNDIN_PDU* header)
{
AUDIO_FORMAT* format;
size_t sbytes_per_sample;
size_t sbytes_per_frame;
size_t frames;
wStream* out;
UINT success = ERROR_INTERNAL_ERROR;
SNDIN_DATA pdu = { 0 };
wStream dataBuffer = { 0 };
UINT error = CHANNEL_RC_OK;
WINPR_ASSERT(audin);
if (audin->context.selected_client_format < 0)
{
WLog_ERR(TAG, "audin->context.selected_client_format = %" PRIdz,
audin->context.selected_client_format);
return ERROR_INVALID_DATA;
}
WINPR_ASSERT(context);
WINPR_ASSERT(header);
out = Stream_New(NULL, 4096);
pdu.Header = *header;
if (!out)
return ERROR_OUTOFMEMORY;
pdu.Data = Stream_StaticInit(&dataBuffer, Stream_Pointer(s), Stream_GetRemainingLength(s));
format = &audin->context.client_formats[audin->context.selected_client_format];
IFCALLRET(context->Data, error, context, &pdu);
if (error)
WLog_ERR(TAG, "context->Data failed with error %" PRIu32 "", error);
if (freerdp_dsp_decode(audin->dsp_context, format, Stream_Pointer(s), length, out))
{
AUDIO_FORMAT dformat = *format;
dformat.wFormatTag = WAVE_FORMAT_PCM;
dformat.wBitsPerSample = 16;
Stream_SealLength(out);
Stream_SetPosition(out, 0);
sbytes_per_sample = format->wBitsPerSample / 8UL;
sbytes_per_frame = format->nChannels * sbytes_per_sample;
frames = Stream_Length(out) / sbytes_per_frame;
IFCALLRET(audin->context.ReceiveSamples, success, &audin->context, &dformat, out, frames);
return error;
}
if (success)
WLog_ERR(TAG, "context.ReceiveSamples failed with error %" PRIu32 "", success);
}
else
WLog_ERR(TAG, "freerdp_dsp_decode failed!");
static UINT audin_server_recv_format_change(audin_server_context* context, wStream* s,
const SNDIN_PDU* header)
{
SNDIN_FORMATCHANGE pdu = { 0 };
UINT error = CHANNEL_RC_OK;
Stream_Free(out, TRUE);
return success;
WINPR_ASSERT(context);
WINPR_ASSERT(header);
pdu.Header = *header;
if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
return ERROR_NO_DATA;
Stream_Read_UINT32(s, pdu.NewFormat);
IFCALLRET(context->ReceiveFormatChange, error, context, &pdu);
if (error)
WLog_ERR(TAG, "context->ReceiveFormatChange failed with error %" PRIu32 "", error);
return error;
}
static DWORD WINAPI audin_server_thread_func(LPVOID arg)
@ -370,7 +235,6 @@ static DWORD WINAPI audin_server_thread_func(LPVOID arg)
wStream* s;
void* buffer;
DWORD nCount;
BYTE MessageId;
HANDLE events[8];
BOOL ready = FALSE;
HANDLE ChannelEvent;
@ -379,7 +243,6 @@ static DWORD WINAPI audin_server_thread_func(LPVOID arg)
UINT error = CHANNEL_RC_OK;
DWORD status;
buffer = NULL;
BytesReturned = 0;
ChannelEvent = NULL;
WINPR_ASSERT(audin);
@ -416,6 +279,8 @@ static DWORD WINAPI audin_server_thread_func(LPVOID arg)
WLog_ERR(TAG, "WaitForMultipleObjects failed with error %" PRIu32 "", error);
goto out;
}
if (status == WAIT_OBJECT_0)
goto out;
if (WTSVirtualChannelQuery(audin->audin_channel, WTSVirtualChannelReady, &buffer,
&BytesReturned) == FALSE)
@ -443,15 +308,21 @@ static DWORD WINAPI audin_server_thread_func(LPVOID arg)
if (ready)
{
if ((error = audin_server_send_version(audin, s)))
SNDIN_VERSION version = { 0 };
version.Version = audin->context.serverVersion;
if ((error = audin->context.SendVersion(&audin->context, &version)))
{
WLog_ERR(TAG, "audin_server_send_version failed with error %" PRIu32 "!", error);
WLog_ERR(TAG, "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;
@ -459,8 +330,10 @@ static DWORD WINAPI audin_server_thread_func(LPVOID arg)
{
error = GetLastError();
WLog_ERR(TAG, "WaitForMultipleObjects failed with error %" PRIu32 "", error);
goto out;
break;
}
if (status == WAIT_OBJECT_0)
break;
Stream_SetPosition(s, 0);
@ -486,73 +359,43 @@ static DWORD WINAPI audin_server_thread_func(LPVOID arg)
break;
}
Stream_Read_UINT8(s, MessageId);
BytesReturned--;
Stream_SetLength(s, BytesReturned);
if (!Stream_CheckAndLogRequiredLength(TAG, s, SNDIN_HEADER_SIZE))
{
error = ERROR_INTERNAL_ERROR;
break;
}
switch (MessageId)
Stream_Read_UINT8(s, header.MessageId);
switch (header.MessageId)
{
case MSG_SNDIN_VERSION:
if ((error = audin_server_recv_version(audin, s, BytesReturned)))
{
WLog_ERR(TAG, "audin_server_recv_version failed with error %" PRIu32 "!",
error);
goto out_capacity;
}
if ((error = audin_server_send_formats(audin, s)))
{
WLog_ERR(TAG, "audin_server_send_formats failed with error %" PRIu32 "!",
error);
goto out_capacity;
}
error = audin_server_recv_version(&audin->context, s, &header);
break;
case MSG_SNDIN_FORMATS:
if ((error = audin_server_recv_formats(audin, s, BytesReturned)))
{
WLog_ERR(TAG, "audin_server_recv_formats failed with error %" PRIu32 "!",
error);
goto out_capacity;
}
if ((error = audin_server_send_open(audin, s)))
{
WLog_ERR(TAG, "audin_server_send_open failed with error %" PRIu32 "!", error);
goto out_capacity;
}
error = audin_server_recv_formats(&audin->context, s, &header);
break;
case MSG_SNDIN_OPEN_REPLY:
if ((error = audin_server_recv_open_reply(audin, s, BytesReturned)))
{
WLog_ERR(TAG, "audin_server_recv_open_reply failed with error %" PRIu32 "!",
error);
goto out_capacity;
}
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:
if ((error = audin_server_recv_data(audin, s, BytesReturned)))
{
WLog_ERR(TAG, "audin_server_recv_data failed with error %" PRIu32 "!", error);
goto out_capacity;
}
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_ERR(TAG, "audin_server_thread_func: unknown MessageId %" PRIu8 "", MessageId);
WLog_ERR(TAG, "audin_server_thread_func: unknown or invalid MessageId %" PRIu8 "",
header.MessageId);
error = ERROR_INVALID_DATA;
break;
}
if (error)
break;
}
out_capacity:
@ -664,10 +507,166 @@ static BOOL audin_server_close(audin_server_context* context)
audin->audin_channel = NULL;
}
audin->context.selected_client_format = -1;
return TRUE;
}
static wStream* audin_server_packet_new(size_t size, BYTE MessageId)
{
wStream* s;
/* Allocate what we need plus header bytes */
s = Stream_New(NULL, size + SNDIN_HEADER_SIZE);
if (!s)
{
WLog_ERR(TAG, "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;
WINPR_ASSERT(context);
WINPR_ASSERT(s);
if (!WTSVirtualChannelWrite(audin->audin_channel, (PCHAR)Stream_Buffer(s),
Stream_GetPosition(s), &written))
{
WLog_ERR(TAG, "WTSVirtualChannelWrite failed!");
error = ERROR_INTERNAL_ERROR;
goto out;
}
if (written < Stream_GetPosition(s))
{
WLog_WARN(TAG, "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)
{
wStream* s;
WINPR_ASSERT(context);
WINPR_ASSERT(version);
s = audin_server_packet_new(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)
{
wStream* s;
WINPR_ASSERT(context);
WINPR_ASSERT(formats);
s = audin_server_packet_new(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_ERR(TAG, "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)
{
wStream* s;
WINPR_ASSERT(context);
WINPR_ASSERT(open);
s = audin_server_packet_new(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)
{
wStream* s;
WINPR_ASSERT(context);
WINPR_ASSERT(format_change);
s = audin_server_packet_new(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);
}
audin_server_context* audin_server_context_new(HANDLE vcm)
{
audin_server* audin;
@ -680,20 +679,17 @@ audin_server_context* audin_server_context_new(HANDLE vcm)
}
audin->context.vcm = vcm;
audin->context.selected_client_format = -1;
audin->context.frames_per_packet = 4096;
audin->context.SelectFormat = audin_server_select_format;
audin->context.Open = audin_server_open;
audin->context.IsOpen = audin_server_is_open;
audin->context.Close = audin_server_close;
audin->dsp_context = freerdp_dsp_context_new(FALSE);
if (!audin->dsp_context)
{
WLog_ERR(TAG, "freerdp_dsp_context_new failed!");
free(audin);
return NULL;
}
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;
return (audin_server_context*)audin;
}
@ -706,8 +702,5 @@ void audin_server_context_free(audin_server_context* context)
return;
audin_server_close(context);
freerdp_dsp_context_free(audin->dsp_context);
audio_formats_free(audin->context.client_formats, audin->context.num_client_formats);
audio_formats_free(audin->context.server_formats, audin->context.num_server_formats);
free(audin);
}

View File

@ -3,6 +3,7 @@
* Audio Input Redirection Virtual Channel
*
* Copyright 2010-2011 Vic Lee
* Copyright 2023 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.
@ -21,9 +22,102 @@
#define FREERDP_CHANNEL_AUDIN_H
#include <freerdp/api.h>
#include <freerdp/codec/audio.h>
#include <freerdp/dvc.h>
#include <freerdp/types.h>
#define AUDIN_DVC_CHANNEL_NAME "AUDIO_INPUT"
typedef struct
{
BYTE MessageId;
} SNDIN_PDU;
typedef enum
{
SNDIN_VERSION_Version_1 = 0x00000001,
SNDIN_VERSION_Version_2 = 0x00000002,
} SNDIN_VERSION_Version;
typedef struct
{
SNDIN_PDU Header;
SNDIN_VERSION_Version Version;
} SNDIN_VERSION;
typedef struct
{
SNDIN_PDU Header;
UINT32 NumFormats;
UINT32 cbSizeFormatsPacket;
AUDIO_FORMAT* SoundFormats;
size_t ExtraDataSize;
} SNDIN_FORMATS;
typedef enum
{
SPEAKER_FRONT_LEFT = 0x00000001,
SPEAKER_FRONT_RIGHT = 0x00000002,
SPEAKER_FRONT_CENTER = 0x00000004,
SPEAKER_LOW_FREQUENCY = 0x00000008,
SPEAKER_BACK_LEFT = 0x00000010,
SPEAKER_BACK_RIGHT = 0x00000020,
SPEAKER_FRONT_LEFT_OF_CENTER = 0x00000040,
SPEAKER_FRONT_RIGHT_OF_CENTER = 0x00000080,
SPEAKER_BACK_CENTER = 0x00000100,
SPEAKER_SIDE_LEFT = 0x00000200,
SPEAKER_SIDE_RIGHT = 0x00000400,
SPEAKER_TOP_CENTER = 0x00000800,
SPEAKER_TOP_FRONT_LEFT = 0x00001000,
SPEAKER_TOP_FRONT_CENTER = 0x00002000,
SPEAKER_TOP_FRONT_RIGHT = 0x00004000,
SPEAKER_TOP_BACK_LEFT = 0x00008000,
SPEAKER_TOP_BACK_CENTER = 0x00010000,
SPEAKER_TOP_BACK_RIGHT = 0x00020000,
} AUDIN_SPEAKER;
typedef struct
{
union
{
UINT16 wValidBitsPerSample;
UINT16 wSamplesPerBlock;
UINT16 wReserved;
} Samples;
AUDIN_SPEAKER dwChannelMask;
GUID SubFormat;
} WAVEFORMAT_EXTENSIBLE;
typedef struct
{
SNDIN_PDU Header;
UINT32 FramesPerPacket;
UINT32 initialFormat;
AUDIO_FORMAT captureFormat;
WAVEFORMAT_EXTENSIBLE* ExtraFormatData;
} SNDIN_OPEN;
typedef struct
{
SNDIN_PDU Header;
UINT32 Result;
} SNDIN_OPEN_REPLY;
typedef struct
{
SNDIN_PDU Header;
} SNDIN_DATA_INCOMING;
typedef struct
{
SNDIN_PDU Header;
wStream* Data;
} SNDIN_DATA;
typedef struct
{
SNDIN_PDU Header;
UINT32 NewFormat;
} SNDIN_FORMATCHANGE;
#endif /* FREERDP_CHANNEL_AUDIN_H */

View File

@ -30,8 +30,8 @@ extern "C"
{
#endif
struct AUDIO_FORMAT
{
struct AUDIO_FORMAT
{
UINT16 wFormatTag;
UINT16 nChannels;
UINT32 nSamplesPerSec;
@ -40,8 +40,8 @@ struct AUDIO_FORMAT
UINT16 wBitsPerSample;
UINT16 cbSize;
BYTE* data;
};
typedef struct AUDIO_FORMAT AUDIO_FORMAT;
};
typedef struct AUDIO_FORMAT AUDIO_FORMAT;
#define SNDC_CLOSE 1
#define SNDC_WAVE 2
@ -65,7 +65,7 @@ typedef struct AUDIO_FORMAT AUDIO_FORMAT;
#define MEDIUM_QUALITY 0x0001
#define HIGH_QUALITY 0x0002
/*
/*
* Format Tags:
* http://tools.ietf.org/html/rfc2361
*/
@ -192,7 +192,9 @@ typedef struct AUDIO_FORMAT AUDIO_FORMAT;
#endif /* !WAVE_FORMAT_LUCENT_G723 */
#define WAVE_FORMAT_AAC_MS 0xA106
/**
#define WAVE_FORMAT_EXTENSIBLE 0xFFFE
/**
* Audio Format Functions
*/

View File

@ -5,6 +5,7 @@
* Copyright 2012 Vic Lee
* Copyright 2015 Thincast Technologies GmbH
* Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
* Copyright 2023 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.
@ -23,9 +24,9 @@
#define FREERDP_CHANNEL_AUDIN_SERVER_H
#include <freerdp/config.h>
#include <freerdp/codec/audio.h>
#include <freerdp/channels/audin.h>
#include <freerdp/channels/wtsvc.h>
#include <freerdp/channels/rdpsnd.h>
#if !defined(CHANNEL_AUDIN_SERVER)
#error "This header must not be included if CHANNEL_AUDIN_SERVER is not defined"
@ -38,84 +39,122 @@ extern "C"
typedef struct s_audin_server_context audin_server_context;
typedef BOOL (*psAudinServerChannelOpen)(audin_server_context* context);
typedef BOOL (*psAudinServerChannelIsOpen)(audin_server_context* context);
typedef BOOL (*psAudinServerChannelClose)(audin_server_context* context);
typedef BOOL (*psAudinServerChannelIdAssigned)(audin_server_context* context, UINT32 channelId);
typedef UINT (*psAudinServerSelectFormat)(audin_server_context* context,
size_t client_format_index);
typedef BOOL (*psAudinServerOpen)(audin_server_context* context);
typedef BOOL (*psAudinServerIsOpen)(audin_server_context* context);
typedef BOOL (*psAudinServerClose)(audin_server_context* context);
typedef UINT (*psAudinServerOpening)(audin_server_context* context);
typedef UINT (*psAudinServerOpenResult)(audin_server_context* context, UINT32 result);
typedef UINT (*psAudinServerReceiveSamples)(audin_server_context* context,
const AUDIO_FORMAT* format, wStream* buf,
size_t nframes);
typedef UINT (*psAudinServerVersion)(audin_server_context* context,
const SNDIN_VERSION* version);
typedef UINT (*psAudinServerFormats)(audin_server_context* context,
const SNDIN_FORMATS* formats);
typedef UINT (*psAudinServerOpen)(audin_server_context* context, const SNDIN_OPEN* open);
typedef UINT (*psAudinServerOpenReply)(audin_server_context* context,
const SNDIN_OPEN_REPLY* open_reply);
typedef UINT (*psAudinServerIncomingData)(audin_server_context* context,
const SNDIN_DATA_INCOMING* data_incoming);
typedef UINT (*psAudinServerData)(audin_server_context* context, const SNDIN_DATA* data);
typedef UINT (*psAudinServerFormatChange)(audin_server_context* context,
const SNDIN_FORMATCHANGE* format_change);
struct s_audin_server_context
{
HANDLE vcm;
/* Server self-defined pointer. */
void* data;
void* userdata;
/* Server supported formats. Set by server. */
AUDIO_FORMAT* server_formats;
size_t num_server_formats;
/* Server destination PCM audio format. Set by server. */
AUDIO_FORMAT* dst_format;
/* Server preferred frames per packet. */
int frames_per_packet;
/* Client supported formats. */
AUDIO_FORMAT* client_formats;
size_t num_client_formats;
SSIZE_T selected_client_format;
/**
* Server version to send to the client, when the DVC was successfully
* opened.
**/
SNDIN_VERSION_Version serverVersion;
/*** APIs called by the server. ***/
/**
* Choose the audio format to be received. The index argument is an index into
* the client_formats array and must be smaller than num_client_formats.
*/
psAudinServerSelectFormat SelectFormat;
/**
* Open the audio input stream.
*/
psAudinServerOpen Open;
psAudinServerIsOpen IsOpen;
/**
* Close the audio stream.
* Open the audio input channel.
*/
psAudinServerClose Close;
psAudinServerChannelOpen Open;
/**
* Check, whether the audio input channel thread was created
*/
psAudinServerChannelIsOpen IsOpen;
/**
* Close the audio input channel.
*/
psAudinServerChannelClose Close;
/**
* For the following server to client PDUs,
* the message header does not have to be set.
*/
/**
* Send a Version PDU.
*/
psAudinServerVersion SendVersion;
/**
* Send a Sound Formats PDU.
*/
psAudinServerFormats SendFormats;
/**
* Send an Open PDU.
*
* In case of ExtraFormatData is not NULL, the SubFormat is always
* KSDATAFORMAT_SUBTYPE_PCM, i.e. it is not required to be explicitly set by
* the API user.
*/
psAudinServerOpen SendOpen;
/**
* Send a Format Change PDU.
*/
psAudinServerFormatChange SendFormatChange;
/*** Callbacks registered by the server. ***/
/**
* It's ready to open the audio input stream. The server should examine client
* formats and call SelectFormat to choose the desired one in this callback.
*/
psAudinServerOpening Opening;
/**
* Client replied HRESULT of the open operation.
*/
psAudinServerOpenResult OpenResult;
/**
* Receive audio samples. Actual bytes in the buffer is:
* nframes * dst_format.nBitsPerSample * dst_format.nChannels / 8
* Note that this callback is called from a different thread context so the
* server must be careful of thread synchronization.
*/
psAudinServerReceiveSamples ReceiveSamples;
rdpContext* rdpcontext;
/**
* Callback, when the channel got its id assigned.
*/
psAudinServerChannelIdAssigned ChannelIdAssigned;
/*
* Callback for the Version PDU.
*/
psAudinServerVersion ReceiveVersion;
/*
* Callback for the Sound Formats PDU.
*/
psAudinServerFormats ReceiveFormats;
/*
* Callback for the Open Reply PDU.
*/
psAudinServerOpenReply OpenReply;
/*
* Callback for the Incoming Data PDU.
*/
psAudinServerIncomingData IncomingData;
/*
* Callback for the Data PDU.
*/
psAudinServerData Data;
/*
* Callback for the Format Change PDU.
*/
psAudinServerFormatChange ReceiveFormatChange;
rdpContext* rdpcontext;
};
FREERDP_API audin_server_context* audin_server_context_new(HANDLE vcm);

View File

@ -99,7 +99,7 @@ extern "C"
typedef BOOL (*pfnShadowChannelAudinServerReceiveSamples)(rdpShadowSubsystem* subsystem,
rdpShadowClient* client,
const AUDIO_FORMAT* format,
wStream* buf, size_t nframes);
wStream* data);
struct rdp_shadow_client
{
@ -129,6 +129,10 @@ extern "C"
RdpsndServerContext* rdpsnd;
#if defined(CHANNEL_AUDIN_SERVER)
audin_server_context* audin;
AUDIO_FORMAT* audin_server_formats;
size_t audin_n_server_formats;
AUDIO_FORMAT* audin_negotiated_format;
UINT32 audin_client_format_idx;
#endif
RdpgfxServerContext* rdpgfx;
@ -137,8 +141,8 @@ extern "C"
UINT32 resizeHeight;
};
struct rdp_shadow_server
{
struct rdp_shadow_server
{
void* ext;
HANDLE thread;
HANDLE StopEvent;
@ -171,10 +175,10 @@ struct rdp_shadow_server
char* PrivateKeyFile;
CRITICAL_SECTION lock;
freerdp_listener* listener;
};
};
struct rdp_shadow_surface
{
struct rdp_shadow_surface
{
rdpShadowServer* server;
UINT16 x;
@ -187,10 +191,10 @@ struct rdp_shadow_surface
CRITICAL_SECTION lock;
REGION16 invalidRegion;
};
};
struct S_RDP_SHADOW_ENTRY_POINTS
{
struct S_RDP_SHADOW_ENTRY_POINTS
{
pfnShadowSubsystemNew New;
pfnShadowSubsystemFree Free;
@ -201,10 +205,10 @@ struct S_RDP_SHADOW_ENTRY_POINTS
pfnShadowSubsystemStop Stop;
pfnShadowEnumMonitors EnumMonitors;
};
};
struct rdp_shadow_subsystem
{
struct rdp_shadow_subsystem
{
RDP_SHADOW_ENTRY_POINTS ep;
HANDLE event;
UINT32 numMonitors;
@ -240,35 +244,35 @@ struct rdp_shadow_subsystem
pfnShadowClientCapabilities ClientCapabilities;
rdpShadowServer* server;
};
};
/* Definition of message between subsystem and clients */
#define SHADOW_MSG_IN_REFRESH_REQUEST_ID 1001
typedef struct S_SHADOW_MSG_OUT SHADOW_MSG_OUT;
typedef void (*MSG_OUT_FREE_FN)(UINT32 id,
typedef struct S_SHADOW_MSG_OUT SHADOW_MSG_OUT;
typedef void (*MSG_OUT_FREE_FN)(UINT32 id,
SHADOW_MSG_OUT* msg); /* function to free SHADOW_MSG_OUT */
struct S_SHADOW_MSG_OUT
{
struct S_SHADOW_MSG_OUT
{
int refCount;
MSG_OUT_FREE_FN Free;
};
};
#define SHADOW_MSG_OUT_POINTER_POSITION_UPDATE_ID 2001
#define SHADOW_MSG_OUT_POINTER_ALPHA_UPDATE_ID 2002
#define SHADOW_MSG_OUT_AUDIO_OUT_SAMPLES_ID 2003
#define SHADOW_MSG_OUT_AUDIO_OUT_VOLUME_ID 2004
typedef struct
{
typedef struct
{
SHADOW_MSG_OUT common;
UINT32 xPos;
UINT32 yPos;
} SHADOW_MSG_OUT_POINTER_POSITION_UPDATE;
} SHADOW_MSG_OUT_POINTER_POSITION_UPDATE;
typedef struct
{
typedef struct
{
SHADOW_MSG_OUT common;
UINT32 xHot;
UINT32 yHot;
@ -278,23 +282,23 @@ typedef struct
UINT32 lengthXorMask;
BYTE* xorMaskData;
BYTE* andMaskData;
} SHADOW_MSG_OUT_POINTER_ALPHA_UPDATE;
} SHADOW_MSG_OUT_POINTER_ALPHA_UPDATE;
typedef struct
{
typedef struct
{
SHADOW_MSG_OUT common;
AUDIO_FORMAT* audio_format;
void* buf;
size_t nFrames;
UINT16 wTimestamp;
} SHADOW_MSG_OUT_AUDIO_OUT_SAMPLES;
} SHADOW_MSG_OUT_AUDIO_OUT_SAMPLES;
typedef struct
{
typedef struct
{
SHADOW_MSG_OUT common;
UINT16 left;
UINT16 right;
} SHADOW_MSG_OUT_AUDIO_OUT_VOLUME;
} SHADOW_MSG_OUT_AUDIO_OUT_VOLUME;
FREERDP_API void shadow_subsystem_set_entry_builtin(const char* name);
FREERDP_API void shadow_subsystem_set_entry(pfnShadowSubsystemEntry pEntry);

View File

@ -5,6 +5,7 @@
* Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
* Copyright 2015 Thincast Technologies GmbH
* Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
* Copyright 2023 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.
@ -29,47 +30,170 @@
#include <freerdp/log.h>
#define TAG SERVER_TAG("mac")
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT mf_peer_audin_opening(audin_server_context* context)
static UINT mf_peer_audin_receive_version(audin_server_context* audin, const SNDIN_VERSION* version)
{
context->SelectFormat(context, 0);
mfPeerContext* context;
SNDIN_FORMATS formats = { 0 };
WINPR_ASSERT(audin);
WINPR_ASSERT(version);
context = audin->userdata;
WINPR_ASSERT(context);
if (version->Version == 0)
{
WLog_ERR(TAG, "Received invalid AUDIO_INPUT version from client");
return ERROR_INVALID_DATA;
}
WLog_DBG(TAG, "AUDIO_INPUT version of client: %u", version->Version);
formats.NumFormats = context->audin_n_server_formats;
formats.SoundFormats = context->audin_server_formats;
return audin->SendFormats(audin, &formats);
}
static UINT send_open(audin_server_context* audin)
{
mfPeerContext* context;
SNDIN_OPEN open = { 0 };
WINPR_ASSERT(audin);
context = audin->userdata;
WINPR_ASSERT(context);
open.FramesPerPacket = 441;
open.initialFormat = context->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;
return audin->SendOpen(audin, &open);
}
static UINT mf_peer_audin_receive_formats(audin_server_context* audin, const SNDIN_FORMATS* formats)
{
mfPeerContext* context;
WINPR_ASSERT(audin);
WINPR_ASSERT(formats);
context = audin->userdata;
WINPR_ASSERT(context);
if (context->audin_negotiated_format)
{
WLog_ERR(TAG, "Received client formats, but negotiation was already done");
return ERROR_INVALID_DATA;
}
for (size_t i = 0; i < context->audin_n_server_formats; ++i)
{
for (UINT32 j = 0; j < formats->NumFormats; ++j)
{
if (audio_format_compatible(&context->audin_server_formats[i],
&formats->SoundFormats[j]))
{
context->audin_negotiated_format = &context->audin_server_formats[i];
context->audin_client_format_idx = i;
return send_open(audin);
}
}
}
WLog_ERR(TAG, "Could not agree on a audio format with the server");
return ERROR_INVALID_DATA;
}
static UINT mf_peer_audin_open_reply(audin_server_context* audin,
const SNDIN_OPEN_REPLY* open_reply)
{
WINPR_ASSERT(audin);
WINPR_ASSERT(open_reply);
/* TODO: Implement failure handling */
WLog_DBG(TAG, "Open Reply PDU: Result: %i", open_reply->Result);
return CHANNEL_RC_OK;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT mf_peer_audin_open_result(audin_server_context* context, UINT32 result)
static UINT mf_peer_audin_incoming_data(audin_server_context* audin,
const SNDIN_DATA_INCOMING* data_incoming)
{
WINPR_ASSERT(audin);
WINPR_ASSERT(data_incoming);
/* TODO: Implement bandwidth measure of clients uplink */
WLog_DBG(TAG, "Received Incoming Data PDU");
return CHANNEL_RC_OK;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT mf_peer_audin_receive_samples(audin_server_context* context, const AUDIO_FORMAT* format,
wStream* buf, size_t nframes)
static UINT mf_peer_audin_data(audin_server_context* audin, const SNDIN_DATA* data)
{
/* TODO: Implement */
WINPR_ASSERT(audin);
WINPR_ASSERT(data);
WLog_WARN(TAG, "not implemented");
WLog_DBG(TAG, "receive %" PRIdz " bytes.", Stream_Length(data->Data));
return CHANNEL_RC_OK;
}
static UINT mf_peer_audin_receive_format_change(audin_server_context* audin,
const SNDIN_FORMATCHANGE* format_change)
{
mfPeerContext* context;
WINPR_ASSERT(audin);
WINPR_ASSERT(format_change);
context = audin->userdata;
WINPR_ASSERT(context);
if (format_change->NewFormat != context->audin_client_format_idx)
{
WLog_ERR(TAG, "NewFormat in FormatChange differs from requested format");
return ERROR_INVALID_DATA;
}
WLog_DBG(TAG, "Received Format Change PDU: %u", format_change->NewFormat);
return CHANNEL_RC_OK;
}
void mf_peer_audin_init(mfPeerContext* context)
{
WINPR_ASSERT(context);
context->audin = audin_server_context_new(context->vcm);
context->audin->rdpcontext = &context->_p;
context->audin->data = context;
context->audin->num_server_formats = server_audin_get_formats(&context->audin->server_formats);
if (context->audin->num_server_formats > 0)
context->audin->dst_format = &context->audin->server_formats[0];
context->audin->Opening = mf_peer_audin_opening;
context->audin->OpenResult = mf_peer_audin_open_result;
context->audin->ReceiveSamples = mf_peer_audin_receive_samples;
context->audin->userdata = context;
context->audin->ReceiveVersion = mf_peer_audin_receive_version;
context->audin->ReceiveFormats = mf_peer_audin_receive_formats;
context->audin->OpenReply = mf_peer_audin_open_reply;
context->audin->IncomingData = mf_peer_audin_incoming_data;
context->audin->Data = mf_peer_audin_data;
context->audin->ReceiveFormatChange = mf_peer_audin_receive_format_change;
context->audin_n_server_formats = server_audin_get_formats(&context->audin_server_formats);
}
void mf_peer_audin_uninit(mfPeerContext* context)
{
WINPR_ASSERT(context);
if (context->audin)
{
audio_formats_free(context->audin_server_formats, context->audin_n_server_formats);
context->audin_server_formats = NULL;
audin_server_context_free(context->audin);
context->audin = NULL;
}
}

View File

@ -29,4 +29,6 @@
void mf_peer_audin_init(mfPeerContext* context);
void mf_peer_audin_uninit(mfPeerContext* context);
#endif /* FREERDP_SERVER_MAC_AUDIN_H */

View File

@ -30,19 +30,19 @@
#include <winpr/crt.h>
//#ifdef WITH_SERVER_CHANNELS
// #ifdef WITH_SERVER_CHANNELS
#include <freerdp/channels/wtsvc.h>
//#endif
// #endif
//#ifdef CHANNEL_RDPSND_SERVER
// #ifdef CHANNEL_RDPSND_SERVER
#include <freerdp/server/rdpsnd.h>
//#include "mf_rdpsnd.h"
//#endif
// #include "mf_rdpsnd.h"
// #endif
//#ifdef CHANNEL_AUDIN_SERVER
// #ifdef CHANNEL_AUDIN_SERVER
#include <freerdp/server/audin.h>
//#include "mf_audin.h"
//#endif
// #include "mf_audin.h"
// #endif
typedef struct mf_info mfInfo;
typedef struct mf_peer_context mfPeerContext;
@ -59,16 +59,20 @@ struct mf_peer_context
RFX_CONTEXT* rfx_context;
NSC_CONTEXT* nsc_context;
//#ifdef WITH_SERVER_CHANNELS
// #ifdef WITH_SERVER_CHANNELS
HANDLE vcm;
//#endif
//#ifdef CHANNEL_AUDIN_SERVER
// #endif
// #ifdef CHANNEL_AUDIN_SERVER
audin_server_context* audin;
//#endif
AUDIO_FORMAT* audin_server_formats;
size_t audin_n_server_formats;
AUDIO_FORMAT* audin_negotiated_format;
UINT32 audin_client_format_idx;
// #endif
//#ifdef CHANNEL_RDPSND_SERVER
// #ifdef CHANNEL_RDPSND_SERVER
RdpsndServerContext* rdpsnd;
//#endif
// #endif
};
struct mf_info

View File

@ -215,8 +215,7 @@ static void mf_peer_context_free(freerdp_peer* client, rdpContext* context)
// nsc_context_free(peer->nsc_context);
#ifdef CHANNEL_AUDIN_SERVER
if (peer->audin)
audin_server_context_free(peer->audin);
mf_peer_audin_uninit(peer);
#endif
#ifdef CHANNEL_RDPSND_SERVER

View File

@ -5,6 +5,7 @@
* Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
* Copyright 2015 Thincast Technologies GmbH
* Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
* Copyright 2023 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.
@ -32,51 +33,140 @@
#define TAG SERVER_TAG("sample")
#if defined(CHANNEL_AUDIN_SERVER)
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT sf_peer_audin_opening(audin_server_context* context)
static UINT sf_peer_audin_receive_version(audin_server_context* audin, const SNDIN_VERSION* version)
{
testPeerContext* context;
SNDIN_FORMATS formats = { 0 };
WINPR_ASSERT(audin);
WINPR_ASSERT(version);
context = audin->userdata;
WINPR_ASSERT(context);
WLog_DBG(TAG, "AUDIN opening.");
/* Simply choose the first format supported by the client. */
context->SelectFormat(context, 0);
if (version->Version == 0)
{
WLog_ERR(TAG, "Received invalid AUDIO_INPUT version from client");
return ERROR_INVALID_DATA;
}
WLog_DBG(TAG, "AUDIO_INPUT version of client: %u", version->Version);
formats.NumFormats = context->audin_n_server_formats;
formats.SoundFormats = context->audin_server_formats;
return audin->SendFormats(audin, &formats);
}
static UINT send_open(audin_server_context* audin)
{
testPeerContext* context;
SNDIN_OPEN open = { 0 };
WINPR_ASSERT(audin);
context = audin->userdata;
WINPR_ASSERT(context);
open.FramesPerPacket = 441;
open.initialFormat = context->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;
return audin->SendOpen(audin, &open);
}
static UINT sf_peer_audin_receive_formats(audin_server_context* audin, const SNDIN_FORMATS* formats)
{
testPeerContext* context;
WINPR_ASSERT(audin);
WINPR_ASSERT(formats);
context = audin->userdata;
WINPR_ASSERT(context);
if (context->audin_negotiated_format)
{
WLog_ERR(TAG, "Received client formats, but negotiation was already done");
return ERROR_INVALID_DATA;
}
for (size_t i = 0; i < context->audin_n_server_formats; ++i)
{
for (UINT32 j = 0; j < formats->NumFormats; ++j)
{
if (audio_format_compatible(&context->audin_server_formats[i],
&formats->SoundFormats[j]))
{
context->audin_negotiated_format = &context->audin_server_formats[i];
context->audin_client_format_idx = i;
return send_open(audin);
}
}
}
WLog_ERR(TAG, "Could not agree on a audio format with the server");
return ERROR_INVALID_DATA;
}
static UINT sf_peer_audin_open_reply(audin_server_context* audin,
const SNDIN_OPEN_REPLY* open_reply)
{
WINPR_ASSERT(audin);
WINPR_ASSERT(open_reply);
/* TODO: Implement failure handling */
WLog_DBG(TAG, "Open Reply PDU: Result: %i", open_reply->Result);
return CHANNEL_RC_OK;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT sf_peer_audin_open_result(audin_server_context* context, UINT32 result)
static UINT sf_peer_audin_incoming_data(audin_server_context* audin,
const SNDIN_DATA_INCOMING* data_incoming)
{
/* TODO: Implement */
WINPR_ASSERT(context);
WINPR_ASSERT(audin);
WINPR_ASSERT(data_incoming);
WLog_WARN(TAG, "not implemented");
WLog_DBG(TAG, "AUDIN open result %" PRIu32 ".", result);
/* TODO: Implement bandwidth measure of clients uplink */
WLog_DBG(TAG, "Received Incoming Data PDU");
return CHANNEL_RC_OK;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT sf_peer_audin_receive_samples(audin_server_context* context, const AUDIO_FORMAT* format,
wStream* buf, size_t nframes)
static UINT sf_peer_audin_data(audin_server_context* audin, const SNDIN_DATA* data)
{
/* TODO: Implement */
WINPR_ASSERT(context);
WINPR_ASSERT(format);
WINPR_ASSERT(buf);
WINPR_ASSERT(audin);
WINPR_ASSERT(data);
WLog_WARN(TAG, "not implemented");
WLog_DBG(TAG, "receive %" PRIdz " frames.", nframes);
WLog_DBG(TAG, "receive %" PRIdz " bytes.", Stream_Length(data->Data));
return CHANNEL_RC_OK;
}
static UINT sf_peer_audin_receive_format_change(audin_server_context* audin,
const SNDIN_FORMATCHANGE* format_change)
{
testPeerContext* context;
WINPR_ASSERT(audin);
WINPR_ASSERT(format_change);
context = audin->userdata;
WINPR_ASSERT(context);
if (format_change->NewFormat != context->audin_client_format_idx)
{
WLog_ERR(TAG, "NewFormat in FormatChange differs from requested format");
return ERROR_INVALID_DATA;
}
WLog_DBG(TAG, "Received Format Change PDU: %u", format_change->NewFormat);
return CHANNEL_RC_OK;
}
#endif
@ -89,15 +179,16 @@ void sf_peer_audin_init(testPeerContext* context)
WINPR_ASSERT(context->audin);
context->audin->rdpcontext = &context->_p;
context->audin->data = context;
context->audin->num_server_formats = server_audin_get_formats(&context->audin->server_formats);
context->audin->userdata = context;
if (context->audin->num_server_formats > 0)
context->audin->dst_format = &context->audin->server_formats[0];
context->audin->ReceiveVersion = sf_peer_audin_receive_version;
context->audin->ReceiveFormats = sf_peer_audin_receive_formats;
context->audin->OpenReply = sf_peer_audin_open_reply;
context->audin->IncomingData = sf_peer_audin_incoming_data;
context->audin->Data = sf_peer_audin_data;
context->audin->ReceiveFormatChange = sf_peer_audin_receive_format_change;
context->audin->Opening = sf_peer_audin_opening;
context->audin->OpenResult = sf_peer_audin_open_result;
context->audin->ReceiveSamples = sf_peer_audin_receive_samples;
context->audin_n_server_formats = server_audin_get_formats(&context->audin_server_formats);
#endif
}
@ -139,7 +230,15 @@ BOOL sf_peer_audin_running(testPeerContext* context)
void sf_peer_audin_uninit(testPeerContext* context)
{
WINPR_ASSERT(context);
#if defined(CHANNEL_AUDIN_SERVER)
if (context->audin)
{
audio_formats_free(context->audin_server_formats, context->audin_n_server_formats);
context->audin_server_formats = NULL;
audin_server_context_free(context->audin);
context->audin = NULL;
}
#endif
}

View File

@ -60,6 +60,10 @@ struct test_peer_context
HANDLE debug_channel_thread;
#if defined(CHANNEL_AUDIN_SERVER)
audin_server_context* audin;
AUDIO_FORMAT* audin_server_formats;
size_t audin_n_server_formats;
AUDIO_FORMAT* audin_negotiated_format;
UINT32 audin_client_format_idx;
#endif
BOOL audin_open;
#if defined(CHANNEL_AINPUT_SERVER)

View File

@ -2,6 +2,7 @@
* FreeRDP: A Remote Desktop Protocol Implementation
*
* Copyright 2015 Jiang Zihao <zihao.jiang@yahoo.com>
* Copyright 2023 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.
@ -19,7 +20,6 @@
#include <freerdp/config.h>
#include <freerdp/log.h>
#include <freerdp/codec/dsp.h>
#include "shadow.h"
#include "shadow_audin.h"
@ -32,75 +32,161 @@
#define TAG SERVER_TAG("shadow")
#if defined(CHANNEL_AUDIN_SERVER)
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT AudinServerOpening(audin_server_context* context)
static UINT AudinServerReceiveVersion(audin_server_context* audin, const SNDIN_VERSION* version)
{
AUDIO_FORMAT* agreed_format = NULL;
size_t i = 0, j = 0;
rdpShadowClient* client;
SNDIN_FORMATS formats = { 0 };
for (i = 0; i < context->num_client_formats; i++)
WINPR_ASSERT(audin);
WINPR_ASSERT(version);
client = audin->userdata;
WINPR_ASSERT(client);
if (version->Version == 0)
{
for (j = 0; j < context->num_server_formats; j++)
{
if (audio_format_compatible(&context->server_formats[j], &context->client_formats[i]))
{
agreed_format = &context->server_formats[j];
break;
}
WLog_ERR(TAG, "Received invalid AUDIO_INPUT version from client");
return ERROR_INVALID_DATA;
}
if (agreed_format != NULL)
break;
}
WLog_DBG(TAG, "AUDIO_INPUT version of client: %u", version->Version);
if (agreed_format == NULL)
{
WLog_ERR(TAG, "Could not agree on a audio format with the server\n");
return CHANNEL_RC_OK;
}
formats.NumFormats = client->audin_n_server_formats;
formats.SoundFormats = client->audin_server_formats;
return IFCALLRESULT(ERROR_CALL_NOT_IMPLEMENTED, context->SelectFormat, context, i);
return audin->SendFormats(audin, &formats);
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT AudinServerOpenResult(audin_server_context* context, UINT32 result)
static UINT send_open(audin_server_context* audin)
{
/* TODO: Implement */
WLog_WARN(TAG, "not implemented");
WLog_INFO(TAG, "AUDIN open result %" PRIu32 ".\n", result);
rdpShadowClient* client = audin->userdata;
SNDIN_OPEN open = { 0 };
WINPR_ASSERT(audin);
client = audin->userdata;
WINPR_ASSERT(client);
open.FramesPerPacket = 441;
open.initialFormat = client->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;
return audin->SendOpen(audin, &open);
}
static UINT AudinServerReceiveFormats(audin_server_context* audin, const SNDIN_FORMATS* formats)
{
rdpShadowClient* client;
WINPR_ASSERT(audin);
WINPR_ASSERT(formats);
client = audin->userdata;
WINPR_ASSERT(client);
if (client->audin_negotiated_format)
{
WLog_ERR(TAG, "Received client formats, but negotiation was already done");
return ERROR_INVALID_DATA;
}
for (size_t i = 0; i < client->audin_n_server_formats; ++i)
{
for (UINT32 j = 0; j < formats->NumFormats; ++j)
{
if (audio_format_compatible(&client->audin_server_formats[i],
&formats->SoundFormats[j]))
{
client->audin_negotiated_format = &client->audin_server_formats[i];
client->audin_client_format_idx = i;
return send_open(audin);
}
}
}
WLog_ERR(TAG, "Could not agree on a audio format with the server");
return ERROR_INVALID_DATA;
}
static UINT AudinServerOpenReply(audin_server_context* audin, const SNDIN_OPEN_REPLY* open_reply)
{
WINPR_ASSERT(audin);
WINPR_ASSERT(open_reply);
/* TODO: Implement failure handling */
WLog_DBG(TAG, "Open Reply PDU: Result: %i", open_reply->Result);
return CHANNEL_RC_OK;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT AudinServerReceiveSamples(audin_server_context* context, const AUDIO_FORMAT* format,
wStream* buf, size_t nframes)
static UINT AudinServerIncomingData(audin_server_context* audin,
const SNDIN_DATA_INCOMING* data_incoming)
{
rdpShadowClient* client = (rdpShadowClient*)context->data;
rdpShadowSubsystem* subsystem = client->server->subsystem;
WINPR_ASSERT(audin);
WINPR_ASSERT(data_incoming);
/* TODO: Implement bandwidth measure of clients uplink */
WLog_DBG(TAG, "Received Incoming Data PDU");
return CHANNEL_RC_OK;
}
static UINT AudinServerData(audin_server_context* audin, const SNDIN_DATA* data)
{
rdpShadowClient* client;
rdpShadowSubsystem* subsystem;
WINPR_ASSERT(audin);
WINPR_ASSERT(data);
client = audin->userdata;
WINPR_ASSERT(client);
WINPR_ASSERT(client->server);
subsystem = client->server->subsystem;
WINPR_ASSERT(subsystem);
if (!client->mayInteract)
return CHANNEL_RC_OK;
if (!IFCALLRESULT(TRUE, subsystem->AudinServerReceiveSamples, subsystem, client, format, buf,
nframes))
if (!IFCALLRESULT(TRUE, subsystem->AudinServerReceiveSamples, subsystem, client,
client->audin_negotiated_format, data->Data))
return ERROR_INTERNAL_ERROR;
return CHANNEL_RC_OK;
}
static UINT AudinServerReceiveFormatChange(audin_server_context* audin,
const SNDIN_FORMATCHANGE* format_change)
{
rdpShadowClient* client;
WINPR_ASSERT(audin);
WINPR_ASSERT(format_change);
client = audin->userdata;
WINPR_ASSERT(client);
if (format_change->NewFormat != client->audin_client_format_idx)
{
WLog_ERR(TAG, "NewFormat in FormatChange differs from requested format");
return ERROR_INVALID_DATA;
}
WLog_DBG(TAG, "Received Format Change PDU: %u", format_change->NewFormat);
return CHANNEL_RC_OK;
}
#endif
BOOL shadow_client_audin_init(rdpShadowClient* client)
{
WINPR_ASSERT(client);
#if defined(CHANNEL_AUDIN_SERVER)
audin_server_context* audin;
audin = client->audin = audin_server_context_new(client->vcm);
@ -108,36 +194,42 @@ BOOL shadow_client_audin_init(rdpShadowClient* client)
if (!audin)
return FALSE;
audin->data = client;
audin->userdata = client;
audin->ReceiveVersion = AudinServerReceiveVersion;
audin->ReceiveFormats = AudinServerReceiveFormats;
audin->OpenReply = AudinServerOpenReply;
audin->IncomingData = AudinServerIncomingData;
audin->Data = AudinServerData;
audin->ReceiveFormatChange = AudinServerReceiveFormatChange;
if (client->subsystem->audinFormats)
{
size_t x;
audin->server_formats = audio_formats_new(client->subsystem->nAudinFormats);
client->audin_server_formats = audio_formats_new(client->subsystem->nAudinFormats);
if (!audin->server_formats)
if (!client->audin_server_formats)
goto fail;
for (x = 0; x < client->subsystem->nAudinFormats; x++)
{
if (!audio_format_copy(&client->subsystem->audinFormats[x], &audin->server_formats[x]))
if (!audio_format_copy(&client->subsystem->audinFormats[x],
&client->audin_server_formats[x]))
goto fail;
}
audin->num_server_formats = client->subsystem->nAudinFormats;
client->audin_n_server_formats = client->subsystem->nAudinFormats;
}
else
{
audin->num_server_formats = server_audin_get_formats(&audin->server_formats);
client->audin_n_server_formats = server_audin_get_formats(&client->audin_server_formats);
}
if (audin->num_server_formats < 1)
if (client->audin_n_server_formats < 1)
goto fail;
audin->dst_format = &audin->server_formats[0];
audin->Opening = AudinServerOpening;
audin->OpenResult = AudinServerOpenResult;
audin->ReceiveSamples = AudinServerReceiveSamples;
client->audin_negotiated_format = NULL;
return TRUE;
fail:
audin_server_context_free(audin);
@ -148,9 +240,13 @@ fail:
void shadow_client_audin_uninit(rdpShadowClient* client)
{
WINPR_ASSERT(client);
#if defined(CHANNEL_AUDIN_SERVER)
if (client->audin)
{
audio_formats_free(client->audin_server_formats, client->audin_n_server_formats);
client->audin_server_formats = NULL;
audin_server_context_free(client->audin);
client->audin = NULL;
}