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:
parent
a659290bd9
commit
c5278c874f
@ -44,7 +44,7 @@
|
|||||||
|
|
||||||
#define SNDIN_VERSION 0x02
|
#define SNDIN_VERSION 0x02
|
||||||
|
|
||||||
enum MSG_SNDIN_CMD
|
typedef enum
|
||||||
{
|
{
|
||||||
MSG_SNDIN_VERSION = 0x01,
|
MSG_SNDIN_VERSION = 0x01,
|
||||||
MSG_SNDIN_FORMATS = 0x02,
|
MSG_SNDIN_FORMATS = 0x02,
|
||||||
@ -52,8 +52,8 @@ enum MSG_SNDIN_CMD
|
|||||||
MSG_SNDIN_OPEN_REPLY = 0x04,
|
MSG_SNDIN_OPEN_REPLY = 0x04,
|
||||||
MSG_SNDIN_DATA_INCOMING = 0x05,
|
MSG_SNDIN_DATA_INCOMING = 0x05,
|
||||||
MSG_SNDIN_DATA = 0x06,
|
MSG_SNDIN_DATA = 0x06,
|
||||||
MSG_SNDIN_FORMATCHANGE = 0x07
|
MSG_SNDIN_FORMATCHANGE = 0x07,
|
||||||
};
|
} MSG_SNDIN;
|
||||||
|
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
* Copyright 2012 Vic Lee
|
* Copyright 2012 Vic Lee
|
||||||
* Copyright 2015 Thincast Technologies GmbH
|
* Copyright 2015 Thincast Technologies GmbH
|
||||||
* Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
|
* 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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -21,10 +22,6 @@
|
|||||||
|
|
||||||
#include <freerdp/config.h>
|
#include <freerdp/config.h>
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include <winpr/crt.h>
|
#include <winpr/crt.h>
|
||||||
#include <winpr/assert.h>
|
#include <winpr/assert.h>
|
||||||
#include <winpr/synch.h>
|
#include <winpr/synch.h>
|
||||||
@ -32,337 +29,205 @@
|
|||||||
#include <winpr/stream.h>
|
#include <winpr/stream.h>
|
||||||
|
|
||||||
#include <freerdp/freerdp.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/server/audin.h>
|
||||||
#include <freerdp/channels/log.h>
|
#include <freerdp/channels/log.h>
|
||||||
|
|
||||||
#define TAG CHANNELS_TAG("audin.server")
|
#define TAG CHANNELS_TAG("audin.server")
|
||||||
#define MSG_SNDIN_VERSION 0x01
|
|
||||||
#define MSG_SNDIN_FORMATS 0x02
|
#define SNDIN_HEADER_SIZE 1
|
||||||
#define MSG_SNDIN_OPEN 0x03
|
|
||||||
#define MSG_SNDIN_OPEN_REPLY 0x04
|
typedef enum
|
||||||
#define MSG_SNDIN_DATA_INCOMING 0x05
|
{
|
||||||
#define MSG_SNDIN_DATA 0x06
|
MSG_SNDIN_VERSION = 0x01,
|
||||||
#define MSG_SNDIN_FORMATCHANGE 0x07
|
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
|
typedef struct
|
||||||
{
|
{
|
||||||
audin_server_context context;
|
audin_server_context context;
|
||||||
|
|
||||||
BOOL opened;
|
|
||||||
|
|
||||||
HANDLE stopEvent;
|
HANDLE stopEvent;
|
||||||
|
|
||||||
HANDLE thread;
|
HANDLE thread;
|
||||||
void* audin_channel;
|
void* audin_channel;
|
||||||
|
|
||||||
DWORD SessionId;
|
DWORD SessionId;
|
||||||
|
|
||||||
FREERDP_DSP_CONTEXT* dsp_context;
|
|
||||||
|
|
||||||
} audin_server;
|
} audin_server;
|
||||||
|
|
||||||
/**
|
static UINT audin_server_recv_version(audin_server_context* context, wStream* s,
|
||||||
* Function description
|
const SNDIN_PDU* header)
|
||||||
*
|
|
||||||
* @return 0 on success, otherwise a Win32 error code
|
|
||||||
*/
|
|
||||||
static UINT audin_server_select_format(audin_server_context* context, size_t client_format_index)
|
|
||||||
{
|
{
|
||||||
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)
|
pdu.Header = *header;
|
||||||
{
|
|
||||||
WLog_ERR(TAG, "error in protocol: client_format_index >= context->num_client_formats!");
|
|
||||||
return ERROR_INVALID_DATA;
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
Stream_Read_UINT32(s, pdu.Version);
|
||||||
&audin->context.client_formats[client_format_index], 0u))
|
|
||||||
{
|
|
||||||
WLog_ERR(TAG, "Failed to reset dsp context format!");
|
|
||||||
return ERROR_INTERNAL_ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (audin->opened)
|
IFCALLRET(context->ReceiveVersion, error, context, &pdu);
|
||||||
{
|
if (error)
|
||||||
/* TODO: send MSG_SNDIN_FORMATCHANGE */
|
WLog_ERR(TAG, "context->ReceiveVersion failed with error %" PRIu32 "", error);
|
||||||
}
|
|
||||||
|
|
||||||
return CHANNEL_RC_OK;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
static UINT audin_server_recv_formats(audin_server_context* context, wStream* s,
|
||||||
* Function description
|
const SNDIN_PDU* header)
|
||||||
*
|
|
||||||
* @return 0 on success, otherwise a Win32 error code
|
|
||||||
*/
|
|
||||||
static UINT audin_server_send_version(audin_server* audin, wStream* s)
|
|
||||||
{
|
{
|
||||||
ULONG written;
|
SNDIN_FORMATS pdu = { 0 };
|
||||||
WINPR_ASSERT(audin);
|
UINT error = CHANNEL_RC_OK;
|
||||||
|
|
||||||
Stream_Write_UINT8(s, MSG_SNDIN_VERSION);
|
WINPR_ASSERT(context);
|
||||||
Stream_Write_UINT32(s, 1); /* Version (4 bytes) */
|
WINPR_ASSERT(header);
|
||||||
|
|
||||||
WINPR_ASSERT(Stream_GetPosition(s) <= ULONG_MAX);
|
pdu.Header = *header;
|
||||||
if (!WTSVirtualChannelWrite(audin->audin_channel, (PCHAR)Stream_Buffer(s),
|
|
||||||
(ULONG)Stream_GetPosition(s), &written))
|
/* 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!");
|
WLog_ERR(TAG, "Sound Formats PDU contains no formats");
|
||||||
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);
|
|
||||||
return ERROR_INVALID_DATA;
|
return ERROR_INVALID_DATA;
|
||||||
}
|
}
|
||||||
|
|
||||||
Stream_Read_UINT32(s, Version);
|
pdu.SoundFormats = audio_formats_new(pdu.NumFormats);
|
||||||
|
if (!pdu.SoundFormats)
|
||||||
if (Version < 1)
|
|
||||||
{
|
{
|
||||||
WLog_ERR(TAG, "expected Version > 0 but got %" PRIu32 "", Version);
|
WLog_ERR(TAG, "Failed to allocate %u SoundFormats", pdu.NumFormats);
|
||||||
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)
|
|
||||||
return ERROR_NOT_ENOUGH_MEMORY;
|
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))
|
if (!audio_format_read(s, format))
|
||||||
{
|
{
|
||||||
audio_formats_free(audin->context.client_formats, i);
|
WLog_ERR(TAG, "Failed to read audio format");
|
||||||
audin->context.client_formats = NULL;
|
audio_formats_free(pdu.SoundFormats, i + i);
|
||||||
WLog_ERR(TAG, "expected length at least 18, but got %" PRIu32 "", length);
|
|
||||||
return ERROR_INVALID_DATA;
|
return ERROR_INVALID_DATA;
|
||||||
}
|
}
|
||||||
|
|
||||||
audio_format_print(WLog_Get(TAG), WLOG_DEBUG, format);
|
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)
|
pdu.ExtraDataSize = Stream_GetRemainingLength(s);
|
||||||
WLog_ERR(TAG, "context.Opening failed with error %" PRIu32 "", success);
|
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
static UINT audin_server_recv_open_reply(audin_server_context* context, wStream* s,
|
||||||
* Function description
|
const SNDIN_PDU* header)
|
||||||
*
|
|
||||||
* @return 0 on success, otherwise a Win32 error code
|
|
||||||
*/
|
|
||||||
static UINT audin_server_send_open(audin_server* audin, wStream* s)
|
|
||||||
{
|
{
|
||||||
ULONG written;
|
SNDIN_OPEN_REPLY pdu = { 0 };
|
||||||
|
UINT error = CHANNEL_RC_OK;
|
||||||
|
|
||||||
WINPR_ASSERT(audin);
|
WINPR_ASSERT(context);
|
||||||
if (audin->context.selected_client_format < 0)
|
WINPR_ASSERT(header);
|
||||||
{
|
|
||||||
WLog_ERR(TAG, "audin->context.selected_client_format = %" PRIdz,
|
|
||||||
audin->context.selected_client_format);
|
|
||||||
return ERROR_INVALID_DATA;
|
|
||||||
}
|
|
||||||
|
|
||||||
audin->opened = TRUE;
|
pdu.Header = *header;
|
||||||
Stream_SetPosition(s, 0);
|
|
||||||
Stream_Write_UINT8(s, MSG_SNDIN_OPEN);
|
if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
|
||||||
Stream_Write_UINT32(s, audin->context.frames_per_packet); /* FramesPerPacket (4 bytes) */
|
return ERROR_NO_DATA;
|
||||||
WINPR_ASSERT(audin->context.selected_client_format >= 0);
|
|
||||||
WINPR_ASSERT(audin->context.selected_client_format <= UINT32_MAX);
|
Stream_Read_UINT32(s, pdu.Result);
|
||||||
Stream_Write_UINT32(
|
|
||||||
s, (UINT32)audin->context.selected_client_format); /* initialFormat (4 bytes) */
|
IFCALLRET(context->OpenReply, error, context, &pdu);
|
||||||
/*
|
if (error)
|
||||||
* [MS-RDPEAI] 3.2.5.1.6
|
WLog_ERR(TAG, "context->OpenReply failed with error %" PRIu32 "", error);
|
||||||
* The second format specify the format that SHOULD be used to capture data from
|
|
||||||
* the actual audio input device.
|
return error;
|
||||||
*/
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
static UINT audin_server_recv_data_incoming(audin_server_context* context, wStream* s,
|
||||||
* Function description
|
const SNDIN_PDU* header)
|
||||||
*
|
|
||||||
* @return 0 on success, otherwise a Win32 error code
|
|
||||||
*/
|
|
||||||
static UINT audin_server_recv_open_reply(audin_server* audin, wStream* s, UINT32 length)
|
|
||||||
{
|
{
|
||||||
UINT32 Result;
|
SNDIN_DATA_INCOMING pdu = { 0 };
|
||||||
UINT success = CHANNEL_RC_OK;
|
UINT error = CHANNEL_RC_OK;
|
||||||
|
|
||||||
WINPR_ASSERT(audin);
|
WINPR_ASSERT(context);
|
||||||
if (length < 4)
|
WINPR_ASSERT(header);
|
||||||
{
|
|
||||||
WLog_ERR(TAG, "error parsing version info: expected at least 4 bytes, got %" PRIu32 "",
|
|
||||||
length);
|
|
||||||
return ERROR_INVALID_DATA;
|
|
||||||
}
|
|
||||||
|
|
||||||
Stream_Read_UINT32(s, Result);
|
pdu.Header = *header;
|
||||||
IFCALLRET(audin->context.OpenResult, success, &audin->context, Result);
|
|
||||||
|
|
||||||
if (success)
|
IFCALLRET(context->IncomingData, error, context, &pdu);
|
||||||
WLog_ERR(TAG, "context.OpenResult failed with error %" PRIu32 "", success);
|
if (error)
|
||||||
|
WLog_ERR(TAG, "context->IncomingData failed with error %" PRIu32 "", error);
|
||||||
|
|
||||||
return success;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
static UINT audin_server_recv_data(audin_server_context* context, wStream* s,
|
||||||
* Function description
|
const SNDIN_PDU* header)
|
||||||
*
|
|
||||||
* @return 0 on success, otherwise a Win32 error code
|
|
||||||
*/
|
|
||||||
static UINT audin_server_recv_data(audin_server* audin, wStream* s, UINT32 length)
|
|
||||||
{
|
{
|
||||||
AUDIO_FORMAT* format;
|
SNDIN_DATA pdu = { 0 };
|
||||||
size_t sbytes_per_sample;
|
wStream dataBuffer = { 0 };
|
||||||
size_t sbytes_per_frame;
|
UINT error = CHANNEL_RC_OK;
|
||||||
size_t frames;
|
|
||||||
wStream* out;
|
|
||||||
UINT success = ERROR_INTERNAL_ERROR;
|
|
||||||
|
|
||||||
WINPR_ASSERT(audin);
|
WINPR_ASSERT(context);
|
||||||
if (audin->context.selected_client_format < 0)
|
WINPR_ASSERT(header);
|
||||||
{
|
|
||||||
WLog_ERR(TAG, "audin->context.selected_client_format = %" PRIdz,
|
|
||||||
audin->context.selected_client_format);
|
|
||||||
return ERROR_INVALID_DATA;
|
|
||||||
}
|
|
||||||
|
|
||||||
out = Stream_New(NULL, 4096);
|
pdu.Header = *header;
|
||||||
|
|
||||||
if (!out)
|
pdu.Data = Stream_StaticInit(&dataBuffer, Stream_Pointer(s), Stream_GetRemainingLength(s));
|
||||||
return ERROR_OUTOFMEMORY;
|
|
||||||
|
|
||||||
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))
|
return error;
|
||||||
{
|
}
|
||||||
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);
|
|
||||||
|
|
||||||
if (success)
|
static UINT audin_server_recv_format_change(audin_server_context* context, wStream* s,
|
||||||
WLog_ERR(TAG, "context.ReceiveSamples failed with error %" PRIu32 "", success);
|
const SNDIN_PDU* header)
|
||||||
}
|
{
|
||||||
else
|
SNDIN_FORMATCHANGE pdu = { 0 };
|
||||||
WLog_ERR(TAG, "freerdp_dsp_decode failed!");
|
UINT error = CHANNEL_RC_OK;
|
||||||
|
|
||||||
Stream_Free(out, TRUE);
|
WINPR_ASSERT(context);
|
||||||
return success;
|
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)
|
static DWORD WINAPI audin_server_thread_func(LPVOID arg)
|
||||||
@ -370,7 +235,6 @@ static DWORD WINAPI audin_server_thread_func(LPVOID arg)
|
|||||||
wStream* s;
|
wStream* s;
|
||||||
void* buffer;
|
void* buffer;
|
||||||
DWORD nCount;
|
DWORD nCount;
|
||||||
BYTE MessageId;
|
|
||||||
HANDLE events[8];
|
HANDLE events[8];
|
||||||
BOOL ready = FALSE;
|
BOOL ready = FALSE;
|
||||||
HANDLE ChannelEvent;
|
HANDLE ChannelEvent;
|
||||||
@ -379,7 +243,6 @@ static DWORD WINAPI audin_server_thread_func(LPVOID arg)
|
|||||||
UINT error = CHANNEL_RC_OK;
|
UINT error = CHANNEL_RC_OK;
|
||||||
DWORD status;
|
DWORD status;
|
||||||
buffer = NULL;
|
buffer = NULL;
|
||||||
BytesReturned = 0;
|
|
||||||
ChannelEvent = NULL;
|
ChannelEvent = NULL;
|
||||||
|
|
||||||
WINPR_ASSERT(audin);
|
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);
|
WLog_ERR(TAG, "WaitForMultipleObjects failed with error %" PRIu32 "", error);
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
if (status == WAIT_OBJECT_0)
|
||||||
|
goto out;
|
||||||
|
|
||||||
if (WTSVirtualChannelQuery(audin->audin_channel, WTSVirtualChannelReady, &buffer,
|
if (WTSVirtualChannelQuery(audin->audin_channel, WTSVirtualChannelReady, &buffer,
|
||||||
&BytesReturned) == FALSE)
|
&BytesReturned) == FALSE)
|
||||||
@ -443,15 +308,21 @@ static DWORD WINAPI audin_server_thread_func(LPVOID arg)
|
|||||||
|
|
||||||
if (ready)
|
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;
|
goto out_capacity;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
while (ready)
|
while (ready)
|
||||||
{
|
{
|
||||||
|
SNDIN_PDU header = { 0 };
|
||||||
|
|
||||||
if ((status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE)) == WAIT_OBJECT_0)
|
if ((status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE)) == WAIT_OBJECT_0)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -459,8 +330,10 @@ static DWORD WINAPI audin_server_thread_func(LPVOID arg)
|
|||||||
{
|
{
|
||||||
error = GetLastError();
|
error = GetLastError();
|
||||||
WLog_ERR(TAG, "WaitForMultipleObjects failed with error %" PRIu32 "", error);
|
WLog_ERR(TAG, "WaitForMultipleObjects failed with error %" PRIu32 "", error);
|
||||||
goto out;
|
break;
|
||||||
}
|
}
|
||||||
|
if (status == WAIT_OBJECT_0)
|
||||||
|
break;
|
||||||
|
|
||||||
Stream_SetPosition(s, 0);
|
Stream_SetPosition(s, 0);
|
||||||
|
|
||||||
@ -486,73 +359,43 @@ static DWORD WINAPI audin_server_thread_func(LPVOID arg)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
Stream_Read_UINT8(s, MessageId);
|
Stream_SetLength(s, BytesReturned);
|
||||||
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:
|
case MSG_SNDIN_VERSION:
|
||||||
if ((error = audin_server_recv_version(audin, s, BytesReturned)))
|
error = audin_server_recv_version(&audin->context, s, &header);
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MSG_SNDIN_FORMATS:
|
case MSG_SNDIN_FORMATS:
|
||||||
if ((error = audin_server_recv_formats(audin, s, BytesReturned)))
|
error = audin_server_recv_formats(&audin->context, s, &header);
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MSG_SNDIN_OPEN_REPLY:
|
case MSG_SNDIN_OPEN_REPLY:
|
||||||
if ((error = audin_server_recv_open_reply(audin, s, BytesReturned)))
|
error = audin_server_recv_open_reply(&audin->context, s, &header);
|
||||||
{
|
|
||||||
WLog_ERR(TAG, "audin_server_recv_open_reply failed with error %" PRIu32 "!",
|
|
||||||
error);
|
|
||||||
goto out_capacity;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MSG_SNDIN_DATA_INCOMING:
|
case MSG_SNDIN_DATA_INCOMING:
|
||||||
|
error = audin_server_recv_data_incoming(&audin->context, s, &header);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MSG_SNDIN_DATA:
|
case MSG_SNDIN_DATA:
|
||||||
if ((error = audin_server_recv_data(audin, s, BytesReturned)))
|
error = audin_server_recv_data(&audin->context, s, &header);
|
||||||
{
|
|
||||||
WLog_ERR(TAG, "audin_server_recv_data failed with error %" PRIu32 "!", error);
|
|
||||||
goto out_capacity;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MSG_SNDIN_FORMATCHANGE:
|
case MSG_SNDIN_FORMATCHANGE:
|
||||||
|
error = audin_server_recv_format_change(&audin->context, s, &header);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
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;
|
break;
|
||||||
}
|
}
|
||||||
|
if (error)
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
out_capacity:
|
out_capacity:
|
||||||
@ -664,10 +507,166 @@ static BOOL audin_server_close(audin_server_context* context)
|
|||||||
audin->audin_channel = NULL;
|
audin->audin_channel = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
audin->context.selected_client_format = -1;
|
|
||||||
return TRUE;
|
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_context* audin_server_context_new(HANDLE vcm)
|
||||||
{
|
{
|
||||||
audin_server* audin;
|
audin_server* audin;
|
||||||
@ -680,20 +679,17 @@ audin_server_context* audin_server_context_new(HANDLE vcm)
|
|||||||
}
|
}
|
||||||
|
|
||||||
audin->context.vcm = 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.Open = audin_server_open;
|
||||||
audin->context.IsOpen = audin_server_is_open;
|
audin->context.IsOpen = audin_server_is_open;
|
||||||
audin->context.Close = audin_server_close;
|
audin->context.Close = audin_server_close;
|
||||||
audin->dsp_context = freerdp_dsp_context_new(FALSE);
|
|
||||||
|
|
||||||
if (!audin->dsp_context)
|
audin->context.SendVersion = audin_server_send_version;
|
||||||
{
|
audin->context.SendFormats = audin_server_send_formats;
|
||||||
WLog_ERR(TAG, "freerdp_dsp_context_new failed!");
|
audin->context.SendOpen = audin_server_send_open;
|
||||||
free(audin);
|
audin->context.SendFormatChange = audin_server_send_format_change;
|
||||||
return NULL;
|
|
||||||
}
|
/* Default values */
|
||||||
|
audin->context.serverVersion = SNDIN_VERSION_Version_2;
|
||||||
|
|
||||||
return (audin_server_context*)audin;
|
return (audin_server_context*)audin;
|
||||||
}
|
}
|
||||||
@ -706,8 +702,5 @@ void audin_server_context_free(audin_server_context* context)
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
audin_server_close(context);
|
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);
|
free(audin);
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
* Audio Input Redirection Virtual Channel
|
* Audio Input Redirection Virtual Channel
|
||||||
*
|
*
|
||||||
* Copyright 2010-2011 Vic Lee
|
* Copyright 2010-2011 Vic Lee
|
||||||
|
* Copyright 2023 Pascal Nowack <Pascal.Nowack@gmx.de>
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -21,9 +22,102 @@
|
|||||||
#define FREERDP_CHANNEL_AUDIN_H
|
#define FREERDP_CHANNEL_AUDIN_H
|
||||||
|
|
||||||
#include <freerdp/api.h>
|
#include <freerdp/api.h>
|
||||||
|
#include <freerdp/codec/audio.h>
|
||||||
#include <freerdp/dvc.h>
|
#include <freerdp/dvc.h>
|
||||||
#include <freerdp/types.h>
|
#include <freerdp/types.h>
|
||||||
|
|
||||||
#define AUDIN_DVC_CHANNEL_NAME "AUDIO_INPUT"
|
#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 */
|
#endif /* FREERDP_CHANNEL_AUDIN_H */
|
||||||
|
@ -30,8 +30,8 @@ extern "C"
|
|||||||
{
|
{
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
struct AUDIO_FORMAT
|
struct AUDIO_FORMAT
|
||||||
{
|
{
|
||||||
UINT16 wFormatTag;
|
UINT16 wFormatTag;
|
||||||
UINT16 nChannels;
|
UINT16 nChannels;
|
||||||
UINT32 nSamplesPerSec;
|
UINT32 nSamplesPerSec;
|
||||||
@ -40,8 +40,8 @@ struct AUDIO_FORMAT
|
|||||||
UINT16 wBitsPerSample;
|
UINT16 wBitsPerSample;
|
||||||
UINT16 cbSize;
|
UINT16 cbSize;
|
||||||
BYTE* data;
|
BYTE* data;
|
||||||
};
|
};
|
||||||
typedef struct AUDIO_FORMAT AUDIO_FORMAT;
|
typedef struct AUDIO_FORMAT AUDIO_FORMAT;
|
||||||
|
|
||||||
#define SNDC_CLOSE 1
|
#define SNDC_CLOSE 1
|
||||||
#define SNDC_WAVE 2
|
#define SNDC_WAVE 2
|
||||||
@ -65,7 +65,7 @@ typedef struct AUDIO_FORMAT AUDIO_FORMAT;
|
|||||||
#define MEDIUM_QUALITY 0x0001
|
#define MEDIUM_QUALITY 0x0001
|
||||||
#define HIGH_QUALITY 0x0002
|
#define HIGH_QUALITY 0x0002
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Format Tags:
|
* Format Tags:
|
||||||
* http://tools.ietf.org/html/rfc2361
|
* http://tools.ietf.org/html/rfc2361
|
||||||
*/
|
*/
|
||||||
@ -192,7 +192,9 @@ typedef struct AUDIO_FORMAT AUDIO_FORMAT;
|
|||||||
#endif /* !WAVE_FORMAT_LUCENT_G723 */
|
#endif /* !WAVE_FORMAT_LUCENT_G723 */
|
||||||
#define WAVE_FORMAT_AAC_MS 0xA106
|
#define WAVE_FORMAT_AAC_MS 0xA106
|
||||||
|
|
||||||
/**
|
#define WAVE_FORMAT_EXTENSIBLE 0xFFFE
|
||||||
|
|
||||||
|
/**
|
||||||
* Audio Format Functions
|
* Audio Format Functions
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
* Copyright 2012 Vic Lee
|
* Copyright 2012 Vic Lee
|
||||||
* Copyright 2015 Thincast Technologies GmbH
|
* Copyright 2015 Thincast Technologies GmbH
|
||||||
* Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
|
* 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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -23,9 +24,9 @@
|
|||||||
#define FREERDP_CHANNEL_AUDIN_SERVER_H
|
#define FREERDP_CHANNEL_AUDIN_SERVER_H
|
||||||
|
|
||||||
#include <freerdp/config.h>
|
#include <freerdp/config.h>
|
||||||
#include <freerdp/codec/audio.h>
|
|
||||||
|
#include <freerdp/channels/audin.h>
|
||||||
#include <freerdp/channels/wtsvc.h>
|
#include <freerdp/channels/wtsvc.h>
|
||||||
#include <freerdp/channels/rdpsnd.h>
|
|
||||||
|
|
||||||
#if !defined(CHANNEL_AUDIN_SERVER)
|
#if !defined(CHANNEL_AUDIN_SERVER)
|
||||||
#error "This header must not be included if CHANNEL_AUDIN_SERVER is not defined"
|
#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 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 BOOL (*psAudinServerChannelIdAssigned)(audin_server_context* context, UINT32 channelId);
|
||||||
|
|
||||||
typedef UINT (*psAudinServerSelectFormat)(audin_server_context* context,
|
typedef UINT (*psAudinServerVersion)(audin_server_context* context,
|
||||||
size_t client_format_index);
|
const SNDIN_VERSION* version);
|
||||||
typedef BOOL (*psAudinServerOpen)(audin_server_context* context);
|
typedef UINT (*psAudinServerFormats)(audin_server_context* context,
|
||||||
typedef BOOL (*psAudinServerIsOpen)(audin_server_context* context);
|
const SNDIN_FORMATS* formats);
|
||||||
typedef BOOL (*psAudinServerClose)(audin_server_context* context);
|
typedef UINT (*psAudinServerOpen)(audin_server_context* context, const SNDIN_OPEN* open);
|
||||||
|
typedef UINT (*psAudinServerOpenReply)(audin_server_context* context,
|
||||||
typedef UINT (*psAudinServerOpening)(audin_server_context* context);
|
const SNDIN_OPEN_REPLY* open_reply);
|
||||||
typedef UINT (*psAudinServerOpenResult)(audin_server_context* context, UINT32 result);
|
typedef UINT (*psAudinServerIncomingData)(audin_server_context* context,
|
||||||
typedef UINT (*psAudinServerReceiveSamples)(audin_server_context* context,
|
const SNDIN_DATA_INCOMING* data_incoming);
|
||||||
const AUDIO_FORMAT* format, wStream* buf,
|
typedef UINT (*psAudinServerData)(audin_server_context* context, const SNDIN_DATA* data);
|
||||||
size_t nframes);
|
typedef UINT (*psAudinServerFormatChange)(audin_server_context* context,
|
||||||
|
const SNDIN_FORMATCHANGE* format_change);
|
||||||
|
|
||||||
struct s_audin_server_context
|
struct s_audin_server_context
|
||||||
{
|
{
|
||||||
HANDLE vcm;
|
HANDLE vcm;
|
||||||
|
|
||||||
/* Server self-defined pointer. */
|
/* Server self-defined pointer. */
|
||||||
void* data;
|
void* userdata;
|
||||||
|
|
||||||
/* Server supported formats. Set by server. */
|
/**
|
||||||
AUDIO_FORMAT* server_formats;
|
* Server version to send to the client, when the DVC was successfully
|
||||||
size_t num_server_formats;
|
* opened.
|
||||||
|
**/
|
||||||
/* Server destination PCM audio format. Set by server. */
|
SNDIN_VERSION_Version serverVersion;
|
||||||
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;
|
|
||||||
|
|
||||||
/*** APIs called by the server. ***/
|
/*** 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. ***/
|
/*** 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.
|
* Callback, when the channel got its id assigned.
|
||||||
*/
|
*/
|
||||||
psAudinServerChannelIdAssigned ChannelIdAssigned;
|
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);
|
FREERDP_API audin_server_context* audin_server_context_new(HANDLE vcm);
|
||||||
|
@ -99,7 +99,7 @@ extern "C"
|
|||||||
typedef BOOL (*pfnShadowChannelAudinServerReceiveSamples)(rdpShadowSubsystem* subsystem,
|
typedef BOOL (*pfnShadowChannelAudinServerReceiveSamples)(rdpShadowSubsystem* subsystem,
|
||||||
rdpShadowClient* client,
|
rdpShadowClient* client,
|
||||||
const AUDIO_FORMAT* format,
|
const AUDIO_FORMAT* format,
|
||||||
wStream* buf, size_t nframes);
|
wStream* data);
|
||||||
|
|
||||||
struct rdp_shadow_client
|
struct rdp_shadow_client
|
||||||
{
|
{
|
||||||
@ -129,6 +129,10 @@ extern "C"
|
|||||||
RdpsndServerContext* rdpsnd;
|
RdpsndServerContext* rdpsnd;
|
||||||
#if defined(CHANNEL_AUDIN_SERVER)
|
#if defined(CHANNEL_AUDIN_SERVER)
|
||||||
audin_server_context* audin;
|
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
|
#endif
|
||||||
RdpgfxServerContext* rdpgfx;
|
RdpgfxServerContext* rdpgfx;
|
||||||
|
|
||||||
@ -137,8 +141,8 @@ extern "C"
|
|||||||
UINT32 resizeHeight;
|
UINT32 resizeHeight;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct rdp_shadow_server
|
struct rdp_shadow_server
|
||||||
{
|
{
|
||||||
void* ext;
|
void* ext;
|
||||||
HANDLE thread;
|
HANDLE thread;
|
||||||
HANDLE StopEvent;
|
HANDLE StopEvent;
|
||||||
@ -171,10 +175,10 @@ struct rdp_shadow_server
|
|||||||
char* PrivateKeyFile;
|
char* PrivateKeyFile;
|
||||||
CRITICAL_SECTION lock;
|
CRITICAL_SECTION lock;
|
||||||
freerdp_listener* listener;
|
freerdp_listener* listener;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct rdp_shadow_surface
|
struct rdp_shadow_surface
|
||||||
{
|
{
|
||||||
rdpShadowServer* server;
|
rdpShadowServer* server;
|
||||||
|
|
||||||
UINT16 x;
|
UINT16 x;
|
||||||
@ -187,10 +191,10 @@ struct rdp_shadow_surface
|
|||||||
|
|
||||||
CRITICAL_SECTION lock;
|
CRITICAL_SECTION lock;
|
||||||
REGION16 invalidRegion;
|
REGION16 invalidRegion;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct S_RDP_SHADOW_ENTRY_POINTS
|
struct S_RDP_SHADOW_ENTRY_POINTS
|
||||||
{
|
{
|
||||||
pfnShadowSubsystemNew New;
|
pfnShadowSubsystemNew New;
|
||||||
pfnShadowSubsystemFree Free;
|
pfnShadowSubsystemFree Free;
|
||||||
|
|
||||||
@ -201,10 +205,10 @@ struct S_RDP_SHADOW_ENTRY_POINTS
|
|||||||
pfnShadowSubsystemStop Stop;
|
pfnShadowSubsystemStop Stop;
|
||||||
|
|
||||||
pfnShadowEnumMonitors EnumMonitors;
|
pfnShadowEnumMonitors EnumMonitors;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct rdp_shadow_subsystem
|
struct rdp_shadow_subsystem
|
||||||
{
|
{
|
||||||
RDP_SHADOW_ENTRY_POINTS ep;
|
RDP_SHADOW_ENTRY_POINTS ep;
|
||||||
HANDLE event;
|
HANDLE event;
|
||||||
UINT32 numMonitors;
|
UINT32 numMonitors;
|
||||||
@ -240,35 +244,35 @@ struct rdp_shadow_subsystem
|
|||||||
pfnShadowClientCapabilities ClientCapabilities;
|
pfnShadowClientCapabilities ClientCapabilities;
|
||||||
|
|
||||||
rdpShadowServer* server;
|
rdpShadowServer* server;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Definition of message between subsystem and clients */
|
/* Definition of message between subsystem and clients */
|
||||||
#define SHADOW_MSG_IN_REFRESH_REQUEST_ID 1001
|
#define SHADOW_MSG_IN_REFRESH_REQUEST_ID 1001
|
||||||
|
|
||||||
typedef struct S_SHADOW_MSG_OUT SHADOW_MSG_OUT;
|
typedef struct S_SHADOW_MSG_OUT SHADOW_MSG_OUT;
|
||||||
typedef void (*MSG_OUT_FREE_FN)(UINT32 id,
|
typedef void (*MSG_OUT_FREE_FN)(UINT32 id,
|
||||||
SHADOW_MSG_OUT* msg); /* function to free SHADOW_MSG_OUT */
|
SHADOW_MSG_OUT* msg); /* function to free SHADOW_MSG_OUT */
|
||||||
|
|
||||||
struct S_SHADOW_MSG_OUT
|
struct S_SHADOW_MSG_OUT
|
||||||
{
|
{
|
||||||
int refCount;
|
int refCount;
|
||||||
MSG_OUT_FREE_FN Free;
|
MSG_OUT_FREE_FN Free;
|
||||||
};
|
};
|
||||||
|
|
||||||
#define SHADOW_MSG_OUT_POINTER_POSITION_UPDATE_ID 2001
|
#define SHADOW_MSG_OUT_POINTER_POSITION_UPDATE_ID 2001
|
||||||
#define SHADOW_MSG_OUT_POINTER_ALPHA_UPDATE_ID 2002
|
#define SHADOW_MSG_OUT_POINTER_ALPHA_UPDATE_ID 2002
|
||||||
#define SHADOW_MSG_OUT_AUDIO_OUT_SAMPLES_ID 2003
|
#define SHADOW_MSG_OUT_AUDIO_OUT_SAMPLES_ID 2003
|
||||||
#define SHADOW_MSG_OUT_AUDIO_OUT_VOLUME_ID 2004
|
#define SHADOW_MSG_OUT_AUDIO_OUT_VOLUME_ID 2004
|
||||||
|
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
SHADOW_MSG_OUT common;
|
SHADOW_MSG_OUT common;
|
||||||
UINT32 xPos;
|
UINT32 xPos;
|
||||||
UINT32 yPos;
|
UINT32 yPos;
|
||||||
} SHADOW_MSG_OUT_POINTER_POSITION_UPDATE;
|
} SHADOW_MSG_OUT_POINTER_POSITION_UPDATE;
|
||||||
|
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
SHADOW_MSG_OUT common;
|
SHADOW_MSG_OUT common;
|
||||||
UINT32 xHot;
|
UINT32 xHot;
|
||||||
UINT32 yHot;
|
UINT32 yHot;
|
||||||
@ -278,23 +282,23 @@ typedef struct
|
|||||||
UINT32 lengthXorMask;
|
UINT32 lengthXorMask;
|
||||||
BYTE* xorMaskData;
|
BYTE* xorMaskData;
|
||||||
BYTE* andMaskData;
|
BYTE* andMaskData;
|
||||||
} SHADOW_MSG_OUT_POINTER_ALPHA_UPDATE;
|
} SHADOW_MSG_OUT_POINTER_ALPHA_UPDATE;
|
||||||
|
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
SHADOW_MSG_OUT common;
|
SHADOW_MSG_OUT common;
|
||||||
AUDIO_FORMAT* audio_format;
|
AUDIO_FORMAT* audio_format;
|
||||||
void* buf;
|
void* buf;
|
||||||
size_t nFrames;
|
size_t nFrames;
|
||||||
UINT16 wTimestamp;
|
UINT16 wTimestamp;
|
||||||
} SHADOW_MSG_OUT_AUDIO_OUT_SAMPLES;
|
} SHADOW_MSG_OUT_AUDIO_OUT_SAMPLES;
|
||||||
|
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
SHADOW_MSG_OUT common;
|
SHADOW_MSG_OUT common;
|
||||||
UINT16 left;
|
UINT16 left;
|
||||||
UINT16 right;
|
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_builtin(const char* name);
|
||||||
FREERDP_API void shadow_subsystem_set_entry(pfnShadowSubsystemEntry pEntry);
|
FREERDP_API void shadow_subsystem_set_entry(pfnShadowSubsystemEntry pEntry);
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
* Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
|
* Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
|
||||||
* Copyright 2015 Thincast Technologies GmbH
|
* Copyright 2015 Thincast Technologies GmbH
|
||||||
* Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
|
* 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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -29,47 +30,170 @@
|
|||||||
#include <freerdp/log.h>
|
#include <freerdp/log.h>
|
||||||
#define TAG SERVER_TAG("mac")
|
#define TAG SERVER_TAG("mac")
|
||||||
|
|
||||||
/**
|
static UINT mf_peer_audin_receive_version(audin_server_context* audin, const SNDIN_VERSION* version)
|
||||||
* Function description
|
|
||||||
*
|
|
||||||
* @return 0 on success, otherwise a Win32 error code
|
|
||||||
*/
|
|
||||||
static UINT mf_peer_audin_opening(audin_server_context* context)
|
|
||||||
{
|
{
|
||||||
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;
|
return CHANNEL_RC_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
static UINT mf_peer_audin_incoming_data(audin_server_context* audin,
|
||||||
* Function description
|
const SNDIN_DATA_INCOMING* data_incoming)
|
||||||
*
|
|
||||||
* @return 0 on success, otherwise a Win32 error code
|
|
||||||
*/
|
|
||||||
static UINT mf_peer_audin_open_result(audin_server_context* context, UINT32 result)
|
|
||||||
{
|
{
|
||||||
|
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;
|
return CHANNEL_RC_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
static UINT mf_peer_audin_data(audin_server_context* audin, const SNDIN_DATA* data)
|
||||||
* 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)
|
|
||||||
{
|
{
|
||||||
|
/* 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;
|
return CHANNEL_RC_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
void mf_peer_audin_init(mfPeerContext* context)
|
void mf_peer_audin_init(mfPeerContext* context)
|
||||||
{
|
{
|
||||||
|
WINPR_ASSERT(context);
|
||||||
|
|
||||||
context->audin = audin_server_context_new(context->vcm);
|
context->audin = audin_server_context_new(context->vcm);
|
||||||
context->audin->rdpcontext = &context->_p;
|
context->audin->rdpcontext = &context->_p;
|
||||||
context->audin->data = context;
|
context->audin->userdata = context;
|
||||||
context->audin->num_server_formats = server_audin_get_formats(&context->audin->server_formats);
|
|
||||||
if (context->audin->num_server_formats > 0)
|
context->audin->ReceiveVersion = mf_peer_audin_receive_version;
|
||||||
context->audin->dst_format = &context->audin->server_formats[0];
|
context->audin->ReceiveFormats = mf_peer_audin_receive_formats;
|
||||||
context->audin->Opening = mf_peer_audin_opening;
|
context->audin->OpenReply = mf_peer_audin_open_reply;
|
||||||
context->audin->OpenResult = mf_peer_audin_open_result;
|
context->audin->IncomingData = mf_peer_audin_incoming_data;
|
||||||
context->audin->ReceiveSamples = mf_peer_audin_receive_samples;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,4 +29,6 @@
|
|||||||
|
|
||||||
void mf_peer_audin_init(mfPeerContext* context);
|
void mf_peer_audin_init(mfPeerContext* context);
|
||||||
|
|
||||||
|
void mf_peer_audin_uninit(mfPeerContext* context);
|
||||||
|
|
||||||
#endif /* FREERDP_SERVER_MAC_AUDIN_H */
|
#endif /* FREERDP_SERVER_MAC_AUDIN_H */
|
||||||
|
@ -30,19 +30,19 @@
|
|||||||
|
|
||||||
#include <winpr/crt.h>
|
#include <winpr/crt.h>
|
||||||
|
|
||||||
//#ifdef WITH_SERVER_CHANNELS
|
// #ifdef WITH_SERVER_CHANNELS
|
||||||
#include <freerdp/channels/wtsvc.h>
|
#include <freerdp/channels/wtsvc.h>
|
||||||
//#endif
|
// #endif
|
||||||
|
|
||||||
//#ifdef CHANNEL_RDPSND_SERVER
|
// #ifdef CHANNEL_RDPSND_SERVER
|
||||||
#include <freerdp/server/rdpsnd.h>
|
#include <freerdp/server/rdpsnd.h>
|
||||||
//#include "mf_rdpsnd.h"
|
// #include "mf_rdpsnd.h"
|
||||||
//#endif
|
// #endif
|
||||||
|
|
||||||
//#ifdef CHANNEL_AUDIN_SERVER
|
// #ifdef CHANNEL_AUDIN_SERVER
|
||||||
#include <freerdp/server/audin.h>
|
#include <freerdp/server/audin.h>
|
||||||
//#include "mf_audin.h"
|
// #include "mf_audin.h"
|
||||||
//#endif
|
// #endif
|
||||||
|
|
||||||
typedef struct mf_info mfInfo;
|
typedef struct mf_info mfInfo;
|
||||||
typedef struct mf_peer_context mfPeerContext;
|
typedef struct mf_peer_context mfPeerContext;
|
||||||
@ -59,16 +59,20 @@ struct mf_peer_context
|
|||||||
RFX_CONTEXT* rfx_context;
|
RFX_CONTEXT* rfx_context;
|
||||||
NSC_CONTEXT* nsc_context;
|
NSC_CONTEXT* nsc_context;
|
||||||
|
|
||||||
//#ifdef WITH_SERVER_CHANNELS
|
// #ifdef WITH_SERVER_CHANNELS
|
||||||
HANDLE vcm;
|
HANDLE vcm;
|
||||||
//#endif
|
// #endif
|
||||||
//#ifdef CHANNEL_AUDIN_SERVER
|
// #ifdef CHANNEL_AUDIN_SERVER
|
||||||
audin_server_context* audin;
|
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;
|
RdpsndServerContext* rdpsnd;
|
||||||
//#endif
|
// #endif
|
||||||
};
|
};
|
||||||
|
|
||||||
struct mf_info
|
struct mf_info
|
||||||
|
@ -215,8 +215,7 @@ static void mf_peer_context_free(freerdp_peer* client, rdpContext* context)
|
|||||||
// nsc_context_free(peer->nsc_context);
|
// nsc_context_free(peer->nsc_context);
|
||||||
#ifdef CHANNEL_AUDIN_SERVER
|
#ifdef CHANNEL_AUDIN_SERVER
|
||||||
|
|
||||||
if (peer->audin)
|
mf_peer_audin_uninit(peer);
|
||||||
audin_server_context_free(peer->audin);
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
#ifdef CHANNEL_RDPSND_SERVER
|
#ifdef CHANNEL_RDPSND_SERVER
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
* Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
|
* Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
|
||||||
* Copyright 2015 Thincast Technologies GmbH
|
* Copyright 2015 Thincast Technologies GmbH
|
||||||
* Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
|
* 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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -32,51 +33,140 @@
|
|||||||
#define TAG SERVER_TAG("sample")
|
#define TAG SERVER_TAG("sample")
|
||||||
|
|
||||||
#if defined(CHANNEL_AUDIN_SERVER)
|
#if defined(CHANNEL_AUDIN_SERVER)
|
||||||
/**
|
static UINT sf_peer_audin_receive_version(audin_server_context* audin, const SNDIN_VERSION* version)
|
||||||
* Function description
|
|
||||||
*
|
|
||||||
* @return 0 on success, otherwise a Win32 error code
|
|
||||||
*/
|
|
||||||
static UINT sf_peer_audin_opening(audin_server_context* context)
|
|
||||||
{
|
{
|
||||||
|
testPeerContext* context;
|
||||||
|
SNDIN_FORMATS formats = { 0 };
|
||||||
|
|
||||||
|
WINPR_ASSERT(audin);
|
||||||
|
WINPR_ASSERT(version);
|
||||||
|
|
||||||
|
context = audin->userdata;
|
||||||
WINPR_ASSERT(context);
|
WINPR_ASSERT(context);
|
||||||
|
|
||||||
WLog_DBG(TAG, "AUDIN opening.");
|
if (version->Version == 0)
|
||||||
/* Simply choose the first format supported by the client. */
|
{
|
||||||
context->SelectFormat(context, 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;
|
return CHANNEL_RC_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
static UINT sf_peer_audin_incoming_data(audin_server_context* audin,
|
||||||
* Function description
|
const SNDIN_DATA_INCOMING* data_incoming)
|
||||||
*
|
|
||||||
* @return 0 on success, otherwise a Win32 error code
|
|
||||||
*/
|
|
||||||
static UINT sf_peer_audin_open_result(audin_server_context* context, UINT32 result)
|
|
||||||
{
|
{
|
||||||
/* TODO: Implement */
|
WINPR_ASSERT(audin);
|
||||||
WINPR_ASSERT(context);
|
WINPR_ASSERT(data_incoming);
|
||||||
|
|
||||||
WLog_WARN(TAG, "not implemented");
|
/* TODO: Implement bandwidth measure of clients uplink */
|
||||||
WLog_DBG(TAG, "AUDIN open result %" PRIu32 ".", result);
|
WLog_DBG(TAG, "Received Incoming Data PDU");
|
||||||
return CHANNEL_RC_OK;
|
return CHANNEL_RC_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
static UINT sf_peer_audin_data(audin_server_context* audin, const SNDIN_DATA* data)
|
||||||
* 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)
|
|
||||||
{
|
{
|
||||||
/* TODO: Implement */
|
/* TODO: Implement */
|
||||||
WINPR_ASSERT(context);
|
WINPR_ASSERT(audin);
|
||||||
WINPR_ASSERT(format);
|
WINPR_ASSERT(data);
|
||||||
WINPR_ASSERT(buf);
|
|
||||||
|
|
||||||
WLog_WARN(TAG, "not implemented");
|
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;
|
return CHANNEL_RC_OK;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@ -89,15 +179,16 @@ void sf_peer_audin_init(testPeerContext* context)
|
|||||||
WINPR_ASSERT(context->audin);
|
WINPR_ASSERT(context->audin);
|
||||||
|
|
||||||
context->audin->rdpcontext = &context->_p;
|
context->audin->rdpcontext = &context->_p;
|
||||||
context->audin->data = context;
|
context->audin->userdata = context;
|
||||||
context->audin->num_server_formats = server_audin_get_formats(&context->audin->server_formats);
|
|
||||||
|
|
||||||
if (context->audin->num_server_formats > 0)
|
context->audin->ReceiveVersion = sf_peer_audin_receive_version;
|
||||||
context->audin->dst_format = &context->audin->server_formats[0];
|
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_n_server_formats = server_audin_get_formats(&context->audin_server_formats);
|
||||||
context->audin->OpenResult = sf_peer_audin_open_result;
|
|
||||||
context->audin->ReceiveSamples = sf_peer_audin_receive_samples;
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,7 +230,15 @@ BOOL sf_peer_audin_running(testPeerContext* context)
|
|||||||
|
|
||||||
void sf_peer_audin_uninit(testPeerContext* context)
|
void sf_peer_audin_uninit(testPeerContext* context)
|
||||||
{
|
{
|
||||||
|
WINPR_ASSERT(context);
|
||||||
|
|
||||||
#if defined(CHANNEL_AUDIN_SERVER)
|
#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);
|
audin_server_context_free(context->audin);
|
||||||
|
context->audin = NULL;
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
@ -60,6 +60,10 @@ struct test_peer_context
|
|||||||
HANDLE debug_channel_thread;
|
HANDLE debug_channel_thread;
|
||||||
#if defined(CHANNEL_AUDIN_SERVER)
|
#if defined(CHANNEL_AUDIN_SERVER)
|
||||||
audin_server_context* audin;
|
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
|
#endif
|
||||||
BOOL audin_open;
|
BOOL audin_open;
|
||||||
#if defined(CHANNEL_AINPUT_SERVER)
|
#if defined(CHANNEL_AINPUT_SERVER)
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
* FreeRDP: A Remote Desktop Protocol Implementation
|
* FreeRDP: A Remote Desktop Protocol Implementation
|
||||||
*
|
*
|
||||||
* Copyright 2015 Jiang Zihao <zihao.jiang@yahoo.com>
|
* 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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -19,7 +20,6 @@
|
|||||||
#include <freerdp/config.h>
|
#include <freerdp/config.h>
|
||||||
|
|
||||||
#include <freerdp/log.h>
|
#include <freerdp/log.h>
|
||||||
#include <freerdp/codec/dsp.h>
|
|
||||||
#include "shadow.h"
|
#include "shadow.h"
|
||||||
|
|
||||||
#include "shadow_audin.h"
|
#include "shadow_audin.h"
|
||||||
@ -32,75 +32,161 @@
|
|||||||
#define TAG SERVER_TAG("shadow")
|
#define TAG SERVER_TAG("shadow")
|
||||||
|
|
||||||
#if defined(CHANNEL_AUDIN_SERVER)
|
#if defined(CHANNEL_AUDIN_SERVER)
|
||||||
/**
|
|
||||||
* Function description
|
static UINT AudinServerReceiveVersion(audin_server_context* audin, const SNDIN_VERSION* version)
|
||||||
*
|
|
||||||
* @return 0 on success, otherwise a Win32 error code
|
|
||||||
*/
|
|
||||||
static UINT AudinServerOpening(audin_server_context* context)
|
|
||||||
{
|
{
|
||||||
AUDIO_FORMAT* agreed_format = NULL;
|
rdpShadowClient* client;
|
||||||
size_t i = 0, j = 0;
|
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++)
|
WLog_ERR(TAG, "Received invalid AUDIO_INPUT version from client");
|
||||||
{
|
return ERROR_INVALID_DATA;
|
||||||
if (audio_format_compatible(&context->server_formats[j], &context->client_formats[i]))
|
|
||||||
{
|
|
||||||
agreed_format = &context->server_formats[j];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (agreed_format != NULL)
|
WLog_DBG(TAG, "AUDIO_INPUT version of client: %u", version->Version);
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (agreed_format == NULL)
|
formats.NumFormats = client->audin_n_server_formats;
|
||||||
{
|
formats.SoundFormats = client->audin_server_formats;
|
||||||
WLog_ERR(TAG, "Could not agree on a audio format with the server\n");
|
|
||||||
return CHANNEL_RC_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
return IFCALLRESULT(ERROR_CALL_NOT_IMPLEMENTED, context->SelectFormat, context, i);
|
return audin->SendFormats(audin, &formats);
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* Function description
|
static UINT send_open(audin_server_context* audin)
|
||||||
*
|
|
||||||
* @return 0 on success, otherwise a Win32 error code
|
|
||||||
*/
|
|
||||||
static UINT AudinServerOpenResult(audin_server_context* context, UINT32 result)
|
|
||||||
{
|
{
|
||||||
/* TODO: Implement */
|
rdpShadowClient* client = audin->userdata;
|
||||||
WLog_WARN(TAG, "not implemented");
|
SNDIN_OPEN open = { 0 };
|
||||||
WLog_INFO(TAG, "AUDIN open result %" PRIu32 ".\n", result);
|
|
||||||
|
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;
|
return CHANNEL_RC_OK;
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* Function description
|
static UINT AudinServerIncomingData(audin_server_context* audin,
|
||||||
*
|
const SNDIN_DATA_INCOMING* data_incoming)
|
||||||
* @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)
|
|
||||||
{
|
{
|
||||||
rdpShadowClient* client = (rdpShadowClient*)context->data;
|
WINPR_ASSERT(audin);
|
||||||
rdpShadowSubsystem* subsystem = client->server->subsystem;
|
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)
|
if (!client->mayInteract)
|
||||||
return CHANNEL_RC_OK;
|
return CHANNEL_RC_OK;
|
||||||
|
|
||||||
if (!IFCALLRESULT(TRUE, subsystem->AudinServerReceiveSamples, subsystem, client, format, buf,
|
if (!IFCALLRESULT(TRUE, subsystem->AudinServerReceiveSamples, subsystem, client,
|
||||||
nframes))
|
client->audin_negotiated_format, data->Data))
|
||||||
return ERROR_INTERNAL_ERROR;
|
return ERROR_INTERNAL_ERROR;
|
||||||
|
|
||||||
return CHANNEL_RC_OK;
|
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
|
#endif
|
||||||
|
|
||||||
BOOL shadow_client_audin_init(rdpShadowClient* client)
|
BOOL shadow_client_audin_init(rdpShadowClient* client)
|
||||||
{
|
{
|
||||||
|
WINPR_ASSERT(client);
|
||||||
|
|
||||||
#if defined(CHANNEL_AUDIN_SERVER)
|
#if defined(CHANNEL_AUDIN_SERVER)
|
||||||
audin_server_context* audin;
|
audin_server_context* audin;
|
||||||
audin = client->audin = audin_server_context_new(client->vcm);
|
audin = client->audin = audin_server_context_new(client->vcm);
|
||||||
@ -108,36 +194,42 @@ BOOL shadow_client_audin_init(rdpShadowClient* client)
|
|||||||
if (!audin)
|
if (!audin)
|
||||||
return FALSE;
|
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)
|
if (client->subsystem->audinFormats)
|
||||||
{
|
{
|
||||||
size_t x;
|
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;
|
goto fail;
|
||||||
|
|
||||||
for (x = 0; x < client->subsystem->nAudinFormats; x++)
|
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;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
audin->num_server_formats = client->subsystem->nAudinFormats;
|
client->audin_n_server_formats = client->subsystem->nAudinFormats;
|
||||||
}
|
}
|
||||||
else
|
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;
|
goto fail;
|
||||||
|
|
||||||
audin->dst_format = &audin->server_formats[0];
|
client->audin_negotiated_format = NULL;
|
||||||
audin->Opening = AudinServerOpening;
|
|
||||||
audin->OpenResult = AudinServerOpenResult;
|
|
||||||
audin->ReceiveSamples = AudinServerReceiveSamples;
|
|
||||||
return TRUE;
|
return TRUE;
|
||||||
fail:
|
fail:
|
||||||
audin_server_context_free(audin);
|
audin_server_context_free(audin);
|
||||||
@ -148,9 +240,13 @@ fail:
|
|||||||
|
|
||||||
void shadow_client_audin_uninit(rdpShadowClient* client)
|
void shadow_client_audin_uninit(rdpShadowClient* client)
|
||||||
{
|
{
|
||||||
|
WINPR_ASSERT(client);
|
||||||
|
|
||||||
#if defined(CHANNEL_AUDIN_SERVER)
|
#if defined(CHANNEL_AUDIN_SERVER)
|
||||||
if (client->audin)
|
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);
|
audin_server_context_free(client->audin);
|
||||||
client->audin = NULL;
|
client->audin = NULL;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user