GUACAMOLE-25: Merge RDP audio input addition."
This commit is contained in:
commit
995b6d669a
47
configure.ac
47
configure.ac
@ -436,6 +436,7 @@ then
|
||||
have_freerdp=yes
|
||||
legacy_freerdp_extensions=no
|
||||
rdpsettings_interface=unknown
|
||||
rdpsettings_audiocapture=yes
|
||||
rdpsettings_audioplayback=yes
|
||||
rdpsettings_deviceredirection=yes
|
||||
freerdp_interface=unknown
|
||||
@ -578,6 +579,15 @@ then
|
||||
[#include <freerdp/channels/channels.h>])
|
||||
fi
|
||||
|
||||
# Availability of ADDIN_ARGV structure for configuring plugins
|
||||
if test "x${have_freerdp}" = "xyes"
|
||||
then
|
||||
AC_CHECK_TYPE([ADDIN_ARGV],
|
||||
[AC_DEFINE([HAVE_ADDIN_ARGV],,
|
||||
[Whether the ADDIN_ARGV type is available])],,
|
||||
[#include <freerdp/settings.h>])
|
||||
fi
|
||||
|
||||
#
|
||||
# FreeRDP: WinPR
|
||||
#
|
||||
@ -686,6 +696,11 @@ then
|
||||
[rdpsettings_audioplayback=no],
|
||||
[[#include <freerdp/freerdp.h>]])
|
||||
|
||||
# Legacy interface may not have AudioCapture settings
|
||||
AC_CHECK_MEMBERS([rdpSettings.audio_capture],,
|
||||
[rdpsettings_audiocapture=no],
|
||||
[[#include <freerdp/freerdp.h>]])
|
||||
|
||||
# Legacy interface may not have DeviceRedirection settings
|
||||
AC_CHECK_MEMBERS([rdpSettings.device_redirection],,
|
||||
[rdpsettings_deviceredirection=no],
|
||||
@ -707,6 +722,12 @@ if test "x${have_freerdp}" = "xyes" -a "x${rdpsettings_audioplayback}" = "xyes";
|
||||
[Whether the rdpSettings structure has AudioPlayback settings])
|
||||
fi
|
||||
|
||||
# Activate audio capture settings if present
|
||||
if test "x${have_freerdp}" = "xyes" -a "x${rdpsettings_audiocapture}" = "xyes"; then
|
||||
AC_DEFINE([HAVE_RDPSETTINGS_AUDIOCAPTURE],,
|
||||
[Whether the rdpSettings structure has AudioCapture settings])
|
||||
fi
|
||||
|
||||
# Activate device redirection settings if present
|
||||
if test "x${have_freerdp}" = "xyes" -a "x${rdpsettings_deviceredirection}" = "xyes"; then
|
||||
AC_DEFINE([HAVE_RDPSETTINGS_DEVICEREDIRECTION],,
|
||||
@ -752,6 +773,32 @@ then
|
||||
[Whether the legacy rdpBitmap API was found])])
|
||||
fi
|
||||
|
||||
#
|
||||
# FreeRDP: IWTSVirtualChannelCallback
|
||||
#
|
||||
|
||||
if test "x${have_freerdp}" = "xyes"
|
||||
then
|
||||
AC_MSG_CHECKING([whether IWTSVirtualChannelCallback.OnDataReceived() uses a wStream])
|
||||
AC_COMPILE_IFELSE([AC_LANG_SOURCE([[#include <winpr/wtypes.h>
|
||||
#include <freerdp/dvc.h>
|
||||
#include <freerdp/freerdp.h>
|
||||
int __data_received(
|
||||
IWTSVirtualChannelCallback* channel_callback,
|
||||
wStream* stream);
|
||||
IWTSVirtualChannelCallback cb = {
|
||||
.OnDataReceived = __data_received
|
||||
};
|
||||
int main() {
|
||||
return
|
||||
cb.OnDataReceived(NULL, NULL);
|
||||
}]])],
|
||||
[AC_MSG_RESULT([yes])],
|
||||
[AC_MSG_RESULT([no])
|
||||
AC_DEFINE([LEGACY_IWTSVIRTUALCHANNELCALLBACK],,
|
||||
[Whether the legacy IWTSVirtualChannelCallback API was found])])
|
||||
fi
|
||||
|
||||
#
|
||||
# FreeRDP: Decompression function variants
|
||||
#
|
||||
|
@ -87,6 +87,12 @@ typedef enum guac_protocol_status {
|
||||
*/
|
||||
GUAC_PROTOCOL_STATUS_RESOURCE_CONFLICT = 0x205,
|
||||
|
||||
/**
|
||||
* The operation could not be performed as the requested resource is now
|
||||
* closed.
|
||||
*/
|
||||
GUAC_PROTOCOL_STATUS_RESOURCE_CLOSED = 0x0206,
|
||||
|
||||
/**
|
||||
* The operation could not be performed because bad parameters were
|
||||
* given.
|
||||
|
@ -24,8 +24,11 @@ lib_LTLIBRARIES = libguac-client-rdp.la
|
||||
|
||||
libguac_client_rdp_la_SOURCES = \
|
||||
_generated_keymaps.c \
|
||||
audio_input.c \
|
||||
client.c \
|
||||
dvc.c \
|
||||
input.c \
|
||||
ptr_string.c \
|
||||
rdp.c \
|
||||
rdp_bitmap.c \
|
||||
rdp_cliprdr.c \
|
||||
@ -44,6 +47,12 @@ libguac_client_rdp_la_SOURCES = \
|
||||
unicode.c \
|
||||
user.c
|
||||
|
||||
guacai_sources = \
|
||||
audio_input.c \
|
||||
guac_ai/ai_messages.c \
|
||||
guac_ai/ai_service.c \
|
||||
ptr_string.c
|
||||
|
||||
guacsvc_sources = \
|
||||
guac_svc/svc_service.c \
|
||||
rdp_svc.c
|
||||
@ -68,6 +77,8 @@ guacdr_sources = \
|
||||
noinst_HEADERS = \
|
||||
compat/client-cliprdr.h \
|
||||
compat/rail.h \
|
||||
guac_ai/ai_messages.h \
|
||||
guac_ai/ai_service.h \
|
||||
guac_rdpdr/rdpdr_fs_messages.h \
|
||||
guac_rdpdr/rdpdr_fs_messages_dir_info.h \
|
||||
guac_rdpdr/rdpdr_fs_messages_file_info.h \
|
||||
@ -79,8 +90,11 @@ noinst_HEADERS = \
|
||||
guac_rdpsnd/rdpsnd_messages.h \
|
||||
guac_rdpsnd/rdpsnd_service.h \
|
||||
guac_svc/svc_service.h \
|
||||
audio_input.h \
|
||||
client.h \
|
||||
dvc.h \
|
||||
input.h \
|
||||
ptr_string.h \
|
||||
rdp.h \
|
||||
rdp_bitmap.h \
|
||||
rdp_cliprdr.h \
|
||||
@ -104,6 +118,7 @@ noinst_HEADERS = \
|
||||
if ! ENABLE_WINPR
|
||||
noinst_HEADERS += compat/winpr-stream.h compat/winpr-wtypes.h
|
||||
libguac_client_rdp_la_SOURCES += compat/winpr-stream.c
|
||||
guacai_sources += compat/winpr-stream.c
|
||||
guacsvc_sources += compat/winpr-stream.c
|
||||
guacsnd_sources += compat/winpr-stream.c
|
||||
guacdr_sources += compat/winpr-stream.c
|
||||
@ -148,6 +163,25 @@ guacdr_libadd = \
|
||||
@COMMON_LTLIB@ \
|
||||
@LIBGUAC_LTLIB@
|
||||
|
||||
#
|
||||
# Audio Input
|
||||
#
|
||||
|
||||
guacai_cflags = \
|
||||
-Werror -Wall -Iinclude \
|
||||
@COMMON_INCLUDE@ \
|
||||
@COMMON_SSH_INCLUDE@ \
|
||||
@LIBGUAC_INCLUDE@
|
||||
|
||||
guacai_ldflags = \
|
||||
-module -avoid-version -shared \
|
||||
@PTHREAD_LIBS@ \
|
||||
@RDP_LIBS@
|
||||
|
||||
guacai_libadd = \
|
||||
@COMMON_LTLIB@ \
|
||||
@LIBGUAC_LTLIB@
|
||||
|
||||
#
|
||||
# RDPSND
|
||||
#
|
||||
@ -224,10 +258,16 @@ if LEGACY_FREERDP_EXTENSIONS
|
||||
|
||||
# FreeRDP 1.0-style extensions
|
||||
freerdp_LTLIBRARIES = \
|
||||
guacai.la \
|
||||
guacdr.la \
|
||||
guacsnd.la \
|
||||
guacsvc.la
|
||||
|
||||
guacai_la_SOURCES = ${guacai_sources}
|
||||
guacai_la_CFLAGS = ${guacai_cflags}
|
||||
guacai_la_LDFLAGS = ${guacai_ldflags}
|
||||
guacai_la_LIBADD = ${guacai_libadd}
|
||||
|
||||
guacdr_la_SOURCES = ${guacdr_sources}
|
||||
guacdr_la_CFLAGS = ${guacdr_cflags}
|
||||
guacdr_la_LDFLAGS = ${guacdr_ldflags}
|
||||
@ -247,10 +287,16 @@ else
|
||||
|
||||
# FreeRDP 1.1 (and hopefully onward) extensions
|
||||
freerdp_LTLIBRARIES = \
|
||||
guacai-client.la \
|
||||
guacdr-client.la \
|
||||
guacsnd-client.la \
|
||||
guacsvc-client.la
|
||||
|
||||
guacai_client_la_SOURCES = ${guacai_sources}
|
||||
guacai_client_la_CFLAGS = ${guacai_cflags}
|
||||
guacai_client_la_LDFLAGS = ${guacai_ldflags}
|
||||
guacai_client_la_LIBADD = ${guacai_libadd}
|
||||
|
||||
guacdr_client_la_SOURCES = ${guacdr_sources}
|
||||
guacdr_client_la_CFLAGS = ${guacdr_cflags}
|
||||
guacdr_client_la_LDFLAGS = ${guacdr_ldflags}
|
||||
|
484
src/protocols/rdp/audio_input.c
Normal file
484
src/protocols/rdp/audio_input.c
Normal file
@ -0,0 +1,484 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "audio_input.h"
|
||||
#include "dvc.h"
|
||||
#include "ptr_string.h"
|
||||
#include "rdp.h"
|
||||
|
||||
#include <freerdp/freerdp.h>
|
||||
#include <freerdp/channels/channels.h>
|
||||
#include <guacamole/protocol.h>
|
||||
#include <guacamole/socket.h>
|
||||
#include <guacamole/stream.h>
|
||||
#include <guacamole/user.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <pthread.h>
|
||||
|
||||
/**
|
||||
* Parses the given raw audio mimetype, producing the corresponding rate,
|
||||
* number of channels, and bytes per sample.
|
||||
*
|
||||
* @param mimetype
|
||||
* The raw auduio mimetype to parse.
|
||||
*
|
||||
* @param rate
|
||||
* A pointer to an int where the sample rate for the PCM format described
|
||||
* by the given mimetype should be stored.
|
||||
*
|
||||
* @param channels
|
||||
* A pointer to an int where the number of channels used by the PCM format
|
||||
* described by the given mimetype should be stored.
|
||||
*
|
||||
* @param bps
|
||||
* A pointer to an int where the number of bytes used the PCM format for
|
||||
* each sample (independent of number of channels) described by the given
|
||||
* mimetype should be stored.
|
||||
*
|
||||
* @return
|
||||
* Zero if the given mimetype is a raw audio mimetype and has been parsed
|
||||
* successfully, non-zero otherwise.
|
||||
*/
|
||||
static int guac_rdp_audio_parse_mimetype(const char* mimetype,
|
||||
int* rate, int* channels, int* bps) {
|
||||
|
||||
int parsed_rate = -1;
|
||||
int parsed_channels = 1;
|
||||
int parsed_bps;
|
||||
|
||||
/* PCM audio with one byte per sample */
|
||||
if (strncmp(mimetype, "audio/L8;", 9) == 0) {
|
||||
mimetype += 8; /* Advance to semicolon ONLY */
|
||||
parsed_bps = 1;
|
||||
}
|
||||
|
||||
/* PCM audio with two bytes per sample */
|
||||
else if (strncmp(mimetype, "audio/L16;", 10) == 0) {
|
||||
mimetype += 9; /* Advance to semicolon ONLY */
|
||||
parsed_bps = 2;
|
||||
}
|
||||
|
||||
/* Unsupported mimetype */
|
||||
else
|
||||
return 1;
|
||||
|
||||
/* Parse each parameter name/value pair within the mimetype */
|
||||
do {
|
||||
|
||||
/* Advance to first character of parameter (current is either a
|
||||
* semicolon or a comma) */
|
||||
mimetype++;
|
||||
|
||||
/* Parse number of channels */
|
||||
if (strncmp(mimetype, "channels=", 9) == 0) {
|
||||
|
||||
mimetype += 9;
|
||||
parsed_channels = strtol(mimetype, (char**) &mimetype, 10);
|
||||
|
||||
/* Fail if value invalid / out of range */
|
||||
if (errno == EINVAL || errno == ERANGE)
|
||||
return 1;
|
||||
|
||||
}
|
||||
|
||||
/* Parse number of rate */
|
||||
else if (strncmp(mimetype, "rate=", 5) == 0) {
|
||||
|
||||
mimetype += 5;
|
||||
parsed_rate = strtol(mimetype, (char**) &mimetype, 10);
|
||||
|
||||
/* Fail if value invalid / out of range */
|
||||
if (errno == EINVAL || errno == ERANGE)
|
||||
return 1;
|
||||
|
||||
}
|
||||
|
||||
/* Advance to next parameter */
|
||||
mimetype = strchr(mimetype, ',');
|
||||
|
||||
} while (mimetype != NULL);
|
||||
|
||||
/* Mimetype is invalid if rate was not specified */
|
||||
if (parsed_rate == -1)
|
||||
return 1;
|
||||
|
||||
/* Parse success */
|
||||
*rate = parsed_rate;
|
||||
*channels = parsed_channels;
|
||||
*bps = parsed_bps;
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
int guac_rdp_audio_handler(guac_user* user, guac_stream* stream,
|
||||
char* mimetype) {
|
||||
|
||||
guac_client* client = user->client;
|
||||
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
|
||||
|
||||
int rate;
|
||||
int channels;
|
||||
int bps;
|
||||
|
||||
/* Parse mimetype, abort on parse error */
|
||||
if (guac_rdp_audio_parse_mimetype(mimetype, &rate, &channels, &bps)) {
|
||||
guac_user_log(user, GUAC_LOG_WARNING, "Denying user audio stream with "
|
||||
"unsupported mimetype: \"%s\"", mimetype);
|
||||
guac_protocol_send_ack(user->socket, stream, "Unsupported audio "
|
||||
"mimetype", GUAC_PROTOCOL_STATUS_CLIENT_BAD_TYPE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Init stream data */
|
||||
stream->blob_handler = guac_rdp_audio_blob_handler;
|
||||
stream->end_handler = guac_rdp_audio_end_handler;
|
||||
|
||||
/* Associate stream with audio buffer */
|
||||
guac_rdp_audio_buffer_set_stream(rdp_client->audio_input, user, stream,
|
||||
rate, channels, bps);
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
int guac_rdp_audio_blob_handler(guac_user* user, guac_stream* stream,
|
||||
void* data, int length) {
|
||||
|
||||
guac_client* client = user->client;
|
||||
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
|
||||
|
||||
/* Write blob to audio stream, buffering if necessary */
|
||||
guac_rdp_audio_buffer_write(rdp_client->audio_input, data, length);
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
int guac_rdp_audio_end_handler(guac_user* user, guac_stream* stream) {
|
||||
|
||||
/* Ignore - the AUDIO_INPUT channel will simply not receive anything */
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
void guac_rdp_audio_load_plugin(rdpContext* context, guac_rdp_dvc_list* list) {
|
||||
|
||||
guac_client* client = ((rdp_freerdp_context*) context)->client;
|
||||
|
||||
/* Add "AUDIO_INPUT" channel */
|
||||
guac_rdp_dvc_list_add(list, "guacai", guac_rdp_ptr_to_string(client), NULL);
|
||||
|
||||
}
|
||||
|
||||
guac_rdp_audio_buffer* guac_rdp_audio_buffer_alloc() {
|
||||
guac_rdp_audio_buffer* buffer = calloc(1, sizeof(guac_rdp_audio_buffer));
|
||||
pthread_mutex_init(&(buffer->lock), NULL);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an "ack" instruction over the socket associated with the Guacamole
|
||||
* stream over which audio data is being received. The "ack" instruction will
|
||||
* only be sent if the Guacamole audio stream has been established (through
|
||||
* receipt of an "audio" instruction), is still open (has not received an "end"
|
||||
* instruction nor been associated with an "ack" having an error code), and is
|
||||
* associated with an active RDP AUDIO_INPUT channel.
|
||||
*
|
||||
* @param audio_buffer
|
||||
* The audio buffer associated with the guac_stream for which the "ack"
|
||||
* instruction should be sent, if any. If there is no associated
|
||||
* guac_stream, this function has no effect.
|
||||
*
|
||||
* @param message
|
||||
* An arbitrary human-readable message to send along with the "ack".
|
||||
*
|
||||
* @param status
|
||||
* The Guacamole protocol status code to send with the "ack". This should
|
||||
* be GUAC_PROTOCOL_STATUS_SUCCESS if the audio stream has been set up
|
||||
* successfully or GUAC_PROTOCOL_STATUS_RESOURCE_CLOSED if the audio stream
|
||||
* has been closed (but may usable again if reopened).
|
||||
*/
|
||||
static void guac_rdp_audio_buffer_ack(guac_rdp_audio_buffer* audio_buffer,
|
||||
const char* message, guac_protocol_status status) {
|
||||
|
||||
guac_user* user = audio_buffer->user;
|
||||
guac_stream* stream = audio_buffer->stream;
|
||||
|
||||
/* Do not send ack unless both sides of the audio stream are ready */
|
||||
if (user == NULL || stream == NULL || audio_buffer->packet == NULL)
|
||||
return;
|
||||
|
||||
/* Send ack instruction */
|
||||
guac_protocol_send_ack(user->socket, stream, message, status);
|
||||
guac_socket_flush(user->socket);
|
||||
|
||||
}
|
||||
|
||||
void guac_rdp_audio_buffer_set_stream(guac_rdp_audio_buffer* audio_buffer,
|
||||
guac_user* user, guac_stream* stream, int rate, int channels, int bps) {
|
||||
|
||||
pthread_mutex_lock(&(audio_buffer->lock));
|
||||
|
||||
/* Associate received stream */
|
||||
audio_buffer->user = user;
|
||||
audio_buffer->stream = stream;
|
||||
audio_buffer->in_format.rate = rate;
|
||||
audio_buffer->in_format.channels = channels;
|
||||
audio_buffer->in_format.bps = bps;
|
||||
|
||||
/* Acknowledge stream creation (if buffer is ready to receive) */
|
||||
guac_rdp_audio_buffer_ack(audio_buffer,
|
||||
"OK", GUAC_PROTOCOL_STATUS_SUCCESS);
|
||||
|
||||
guac_user_log(user, GUAC_LOG_DEBUG, "User is requesting to provide audio "
|
||||
"input as %i-channel, %i Hz PCM audio at %i bytes/sample.",
|
||||
audio_buffer->in_format.channels,
|
||||
audio_buffer->in_format.rate,
|
||||
audio_buffer->in_format.bps);
|
||||
|
||||
pthread_mutex_unlock(&(audio_buffer->lock));
|
||||
|
||||
}
|
||||
|
||||
void guac_rdp_audio_buffer_set_output(guac_rdp_audio_buffer* audio_buffer,
|
||||
int rate, int channels, int bps) {
|
||||
|
||||
pthread_mutex_lock(&(audio_buffer->lock));
|
||||
|
||||
/* Set output format */
|
||||
audio_buffer->out_format.rate = rate;
|
||||
audio_buffer->out_format.channels = channels;
|
||||
audio_buffer->out_format.bps = bps;
|
||||
|
||||
pthread_mutex_unlock(&(audio_buffer->lock));
|
||||
|
||||
}
|
||||
|
||||
void guac_rdp_audio_buffer_begin(guac_rdp_audio_buffer* audio_buffer,
|
||||
int packet_frames, guac_rdp_audio_buffer_flush_handler* flush_handler,
|
||||
void* data) {
|
||||
|
||||
pthread_mutex_lock(&(audio_buffer->lock));
|
||||
|
||||
/* Reset buffer state to provided values */
|
||||
audio_buffer->bytes_written = 0;
|
||||
audio_buffer->flush_handler = flush_handler;
|
||||
audio_buffer->data = data;
|
||||
|
||||
/* Calculate size of each packet in bytes */
|
||||
audio_buffer->packet_size = packet_frames
|
||||
* audio_buffer->out_format.channels
|
||||
* audio_buffer->out_format.bps;
|
||||
|
||||
/* Allocate new buffer */
|
||||
free(audio_buffer->packet);
|
||||
audio_buffer->packet = malloc(audio_buffer->packet_size);
|
||||
|
||||
/* Acknowledge stream creation (if stream is ready to receive) */
|
||||
guac_rdp_audio_buffer_ack(audio_buffer,
|
||||
"OK", GUAC_PROTOCOL_STATUS_SUCCESS);
|
||||
|
||||
pthread_mutex_unlock(&(audio_buffer->lock));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a single sample from the given buffer of data, using the input
|
||||
* format defined within the given audio buffer. Each read sample is
|
||||
* translated to a signed 16-bit value, even if the input format is 8-bit.
|
||||
* The offset into the given buffer will be determined according to the
|
||||
* input and output formats, the number of bytes sent thus far, and the
|
||||
* number of bytes received (excluding the contents of the buffer).
|
||||
*
|
||||
* @param audio_buffer
|
||||
* The audio buffer dictating the format of the given data buffer, as
|
||||
* well as the offset from which the sample should be read.
|
||||
*
|
||||
* @param buffer
|
||||
* The buffer of raw PCM audio data from which the sample should be read.
|
||||
* This buffer MUST NOT contain data already taken into account by the
|
||||
* audio buffer's total_bytes_received counter.
|
||||
*
|
||||
* @param length
|
||||
* The number of bytes within the given buffer of PCM data.
|
||||
*
|
||||
* @param sample
|
||||
* A pointer to the int16_t in which the read sample should be stored. If
|
||||
* the input format is 8-bit, the sample will be shifted left by 8 bits
|
||||
* to produce a 16-bit sample.
|
||||
*
|
||||
* @return
|
||||
* Non-zero if a sample was successfully read, zero if no data remains
|
||||
* within the given buffer that has not already been mapped to an
|
||||
* output sample.
|
||||
*/
|
||||
static int guac_rdp_audio_buffer_read_sample(
|
||||
guac_rdp_audio_buffer* audio_buffer, const char* buffer, int length,
|
||||
int16_t* sample) {
|
||||
|
||||
int in_bps = audio_buffer->in_format.bps;
|
||||
int in_rate = audio_buffer->in_format.rate;
|
||||
int in_channels = audio_buffer->in_format.channels;
|
||||
|
||||
int out_bps = audio_buffer->out_format.bps;
|
||||
int out_rate = audio_buffer->out_format.rate;
|
||||
int out_channels = audio_buffer->out_format.channels;
|
||||
|
||||
/* Calculate position within audio output */
|
||||
int current_sample = audio_buffer->total_bytes_sent / out_bps;
|
||||
int current_frame = current_sample / out_channels;
|
||||
int current_channel = current_sample % out_channels;
|
||||
|
||||
/* Map output channel to input channel */
|
||||
if (current_channel >= in_channels)
|
||||
current_channel = in_channels - 1;
|
||||
|
||||
/* Transform output position to input position */
|
||||
current_frame = (int) current_frame * ((double) in_rate / out_rate);
|
||||
current_sample = current_frame * in_channels + current_channel;
|
||||
|
||||
/* Calculate offset within given buffer from absolute input position */
|
||||
int offset = current_sample * in_bps
|
||||
- audio_buffer->total_bytes_received;
|
||||
|
||||
/* It should be impossible for the offset to ever go negative */
|
||||
assert(offset >= 0);
|
||||
|
||||
/* Apply offset to buffer */
|
||||
buffer += offset;
|
||||
length -= offset;
|
||||
|
||||
/* Read only if sufficient data is present in the given buffer */
|
||||
if (length < in_bps)
|
||||
return 0;
|
||||
|
||||
/* Simply read sample directly if input is 16-bit */
|
||||
if (in_bps == 2) {
|
||||
*sample = *((int16_t*) buffer);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Translate to 16-bit if input is 8-bit */
|
||||
if (in_bps == 1) {
|
||||
*sample = *buffer << 8;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Accepted audio formats are required to be 8- or 16-bit */
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
void guac_rdp_audio_buffer_write(guac_rdp_audio_buffer* audio_buffer,
|
||||
char* buffer, int length) {
|
||||
|
||||
int16_t sample;
|
||||
|
||||
pthread_mutex_lock(&(audio_buffer->lock));
|
||||
|
||||
/* Ignore packet if there is no buffer */
|
||||
if (audio_buffer->packet_size == 0 || audio_buffer->packet == NULL) {
|
||||
pthread_mutex_unlock(&(audio_buffer->lock));
|
||||
return;
|
||||
}
|
||||
|
||||
int out_bps = audio_buffer->out_format.bps;
|
||||
|
||||
/* Continuously write packets until no data remains */
|
||||
while (guac_rdp_audio_buffer_read_sample(audio_buffer,
|
||||
buffer, length, &sample) > 0) {
|
||||
|
||||
char* current = audio_buffer->packet + audio_buffer->bytes_written;
|
||||
|
||||
/* Store as 16-bit or 8-bit, depending on output format */
|
||||
if (out_bps == 2)
|
||||
*((int16_t*) current) = sample;
|
||||
else if (out_bps == 1)
|
||||
*current = sample >> 8;
|
||||
|
||||
/* Accepted audio formats are required to be 8- or 16-bit */
|
||||
else
|
||||
assert(0);
|
||||
|
||||
/* Update byte counters */
|
||||
audio_buffer->bytes_written += out_bps;
|
||||
audio_buffer->total_bytes_sent += out_bps;
|
||||
|
||||
/* Invoke flush handler if full */
|
||||
if (audio_buffer->bytes_written == audio_buffer->packet_size) {
|
||||
|
||||
/* Only actually invoke if defined */
|
||||
if (audio_buffer->flush_handler)
|
||||
audio_buffer->flush_handler(audio_buffer->packet,
|
||||
audio_buffer->bytes_written, audio_buffer->data);
|
||||
|
||||
/* Reset buffer in all cases */
|
||||
audio_buffer->bytes_written = 0;
|
||||
|
||||
}
|
||||
|
||||
} /* end packet write loop */
|
||||
|
||||
/* Track current position in audio stream */
|
||||
audio_buffer->total_bytes_received += length;
|
||||
|
||||
pthread_mutex_unlock(&(audio_buffer->lock));
|
||||
|
||||
}
|
||||
|
||||
void guac_rdp_audio_buffer_end(guac_rdp_audio_buffer* audio_buffer) {
|
||||
|
||||
pthread_mutex_lock(&(audio_buffer->lock));
|
||||
|
||||
/* The stream is now closed */
|
||||
guac_rdp_audio_buffer_ack(audio_buffer,
|
||||
"CLOSED", GUAC_PROTOCOL_STATUS_RESOURCE_CLOSED);
|
||||
|
||||
/* Unset user and stream */
|
||||
audio_buffer->user = NULL;
|
||||
audio_buffer->stream = NULL;
|
||||
|
||||
/* Reset buffer state */
|
||||
audio_buffer->bytes_written = 0;
|
||||
audio_buffer->packet_size = 0;
|
||||
audio_buffer->flush_handler = NULL;
|
||||
|
||||
/* Reset I/O counters */
|
||||
audio_buffer->total_bytes_sent = 0;
|
||||
audio_buffer->total_bytes_received = 0;
|
||||
|
||||
/* Free packet (if any) */
|
||||
free(audio_buffer->packet);
|
||||
audio_buffer->packet = NULL;
|
||||
|
||||
pthread_mutex_unlock(&(audio_buffer->lock));
|
||||
|
||||
}
|
||||
|
||||
void guac_rdp_audio_buffer_free(guac_rdp_audio_buffer* audio_buffer) {
|
||||
pthread_mutex_destroy(&(audio_buffer->lock));
|
||||
free(audio_buffer->packet);
|
||||
free(audio_buffer);
|
||||
}
|
||||
|
313
src/protocols/rdp/audio_input.h
Normal file
313
src/protocols/rdp/audio_input.h
Normal file
@ -0,0 +1,313 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#ifndef GUAC_RDP_AUDIO_INPUT_H
|
||||
#define GUAC_RDP_AUDIO_INPUT_H
|
||||
|
||||
#include "config.h"
|
||||
#include "dvc.h"
|
||||
|
||||
#include <freerdp/freerdp.h>
|
||||
#include <guacamole/stream.h>
|
||||
#include <guacamole/user.h>
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
/**
|
||||
* Handler which is invoked when a guac_rdp_audio_buffer's internal packet
|
||||
* buffer has reached capacity and must be flushed.
|
||||
*
|
||||
* @param buffer
|
||||
* The buffer which needs to be flushed as an audio packet.
|
||||
*
|
||||
* @param length
|
||||
* The number of bytes stored within the buffer. This is guaranteed to be
|
||||
* identical to the packet_size value specified when the audio buffer was
|
||||
* initialized.
|
||||
*
|
||||
* @param data
|
||||
* The arbitrary data pointer provided when the audio buffer was
|
||||
* initialized.
|
||||
*/
|
||||
typedef void guac_rdp_audio_buffer_flush_handler(char* buffer, int length,
|
||||
void* data);
|
||||
|
||||
/**
|
||||
* A description of an arbitrary PCM audio format.
|
||||
*/
|
||||
typedef struct guac_rdp_audio_format {
|
||||
|
||||
/**
|
||||
* The rate of the audio data in samples per second.
|
||||
*/
|
||||
int rate;
|
||||
|
||||
/**
|
||||
* The number of channels included in the audio data. This will be 1 for
|
||||
* monaural audio and 2 for stereo.
|
||||
*/
|
||||
int channels;
|
||||
|
||||
/**
|
||||
* The size of each sample within the audio data, in bytes.
|
||||
*/
|
||||
int bps;
|
||||
|
||||
} guac_rdp_audio_format;
|
||||
|
||||
/**
|
||||
* A buffer of arbitrary audio data. Received audio data can be written to this
|
||||
* buffer, and will automatically be flushed via a given handler once the
|
||||
* internal buffer reaches capacity.
|
||||
*/
|
||||
typedef struct guac_rdp_audio_buffer {
|
||||
|
||||
/**
|
||||
* Lock which is acquired/released to ensure accesses to the audio buffer
|
||||
* are atomic.
|
||||
*/
|
||||
pthread_mutex_t lock;
|
||||
|
||||
/**
|
||||
* The user from which this audio buffer will receive data. If no user has
|
||||
* yet opened an associated audio stream, this will be NULL.
|
||||
*/
|
||||
guac_user* user;
|
||||
|
||||
/**
|
||||
* The stream from which this audio buffer will receive data. If no user
|
||||
* has yet opened an associated audio stream, this will be NULL.
|
||||
*/
|
||||
guac_stream* stream;
|
||||
|
||||
/**
|
||||
* The PCM format of the audio stream being received from the user, if any.
|
||||
* If no stream is yet associated, the values stored within this format are
|
||||
* undefined.
|
||||
*/
|
||||
guac_rdp_audio_format in_format;
|
||||
|
||||
/**
|
||||
* The PCM format of the audio stream expected by RDP, if any. If no audio
|
||||
* stream has yet been requested by the RDP server, the values stored
|
||||
* within this format are undefined.
|
||||
*/
|
||||
guac_rdp_audio_format out_format;
|
||||
|
||||
/**
|
||||
* The size that each audio packet must be, in bytes. The packet buffer
|
||||
* within this structure will be at least this size.
|
||||
*/
|
||||
int packet_size;
|
||||
|
||||
/**
|
||||
* The number of bytes currently stored within the packet buffer.
|
||||
*/
|
||||
int bytes_written;
|
||||
|
||||
/**
|
||||
* The total number of bytes having ever been received by the Guacamole
|
||||
* server for the current audio stream.
|
||||
*/
|
||||
int total_bytes_received;
|
||||
|
||||
/**
|
||||
* The total number of bytes having ever been sent to the RDP server for
|
||||
* the current audio stream.
|
||||
*/
|
||||
int total_bytes_sent;
|
||||
|
||||
/**
|
||||
* All audio data being prepared for sending to the AUDIO_INPUT channel.
|
||||
*/
|
||||
char* packet;
|
||||
|
||||
/**
|
||||
* Handler function which will be invoked when a full audio packet is
|
||||
* ready to be flushed to the AUDIO_INPUT channel, if defined. If NULL,
|
||||
* audio packets will simply be ignored.
|
||||
*/
|
||||
guac_rdp_audio_buffer_flush_handler* flush_handler;
|
||||
|
||||
/**
|
||||
* Arbitrary data assigned by the AUDIO_INPUT plugin implementation.
|
||||
*/
|
||||
void* data;
|
||||
|
||||
} guac_rdp_audio_buffer;
|
||||
|
||||
/**
|
||||
* Allocates a new audio buffer. The new audio buffer will ignore any received
|
||||
* data until guac_rdp_audio_buffer_begin() is invoked, and will resume
|
||||
* ignoring received data once guac_rdp_audio_buffer_end() is invoked.
|
||||
*
|
||||
* @return
|
||||
* A newly-allocated audio buffer.
|
||||
*/
|
||||
guac_rdp_audio_buffer* guac_rdp_audio_buffer_alloc();
|
||||
|
||||
/**
|
||||
* Associates the given audio buffer with the underlying audio stream which
|
||||
* has been received from the given Guacamole user. Once both the Guacamole
|
||||
* audio stream and the RDP audio stream are ready, an appropriate "ack"
|
||||
* message will be sent.
|
||||
*
|
||||
* @param audio_buffer
|
||||
* The audio buffer associated with the audio stream just received.
|
||||
*
|
||||
* @param user
|
||||
* The Guacamole user that created the audio stream.
|
||||
*
|
||||
* @param stream
|
||||
* The guac_stream object representing the audio stream.
|
||||
*
|
||||
* @param rate
|
||||
* The rate of the audio stream being received from the user, if any, in
|
||||
* samples per second.
|
||||
*
|
||||
* @param channels
|
||||
* The number of channels included in the audio stream being received from
|
||||
* the user, if any.
|
||||
*
|
||||
* @param bps
|
||||
* The size of each sample within the audio stream being received from the
|
||||
* user, if any, in bytes.
|
||||
*/
|
||||
void guac_rdp_audio_buffer_set_stream(guac_rdp_audio_buffer* audio_buffer,
|
||||
guac_user* user, guac_stream* stream, int rate, int channels, int bps);
|
||||
|
||||
/**
|
||||
* Defines the output format that should be used by the audio buffer when
|
||||
* flushing packets of audio data received via guac_rdp_audio_buffer_write().
|
||||
* As this format determines how the underlying packet buffer will be
|
||||
* allocated, this function MUST be called prior to the call to
|
||||
* guac_rdp_audio_buffer_begin().
|
||||
*
|
||||
* @param audio_buffer
|
||||
* The audio buffer to set the output format of.
|
||||
*
|
||||
* @param rate
|
||||
* The rate of the audio stream expected by RDP, in samples per second.
|
||||
*
|
||||
* @param channels
|
||||
* The number of channels included in the audio stream expected by RDP.
|
||||
*
|
||||
* @param bps
|
||||
* The size of each sample within the audio stream expected by RDP, in
|
||||
* bytes.
|
||||
*/
|
||||
void guac_rdp_audio_buffer_set_output(guac_rdp_audio_buffer* audio_buffer,
|
||||
int rate, int channels, int bps);
|
||||
|
||||
/**
|
||||
* Begins handling of audio data received via guac_rdp_audio_buffer_write() and
|
||||
* allocates the necessary underlying packet buffer. Audio packets having
|
||||
* exactly packet_frames frames will be flushed as available using the provided
|
||||
* flush_handler. An audio frame is a set of single samples, one sample per
|
||||
* channel. The guac_rdp_audio_buffer_set_output() function MUST have
|
||||
* been invoked first.
|
||||
*
|
||||
* @param audio_buffer
|
||||
* The audio buffer to begin.
|
||||
*
|
||||
* @param packet_frames
|
||||
* The exact number of frames (a set of samples, one for each channel)
|
||||
* which MUST be included in all audio packets provided to the
|
||||
* given flush_handler.
|
||||
*
|
||||
* @param flush_handler
|
||||
* The function to invoke when an audio packet must be flushed.
|
||||
*
|
||||
* @param data
|
||||
* Arbitrary data to provide to the flush_handler when an audio packet
|
||||
* needs to be flushed.
|
||||
*/
|
||||
void guac_rdp_audio_buffer_begin(guac_rdp_audio_buffer* audio_buffer,
|
||||
int packet_frames, guac_rdp_audio_buffer_flush_handler* flush_handler,
|
||||
void* data);
|
||||
|
||||
/**
|
||||
* Writes the given buffer of audio data to the given audio buffer. A new
|
||||
* packet will be flushed using the associated flush handler once sufficient
|
||||
* bytes have been accumulated.
|
||||
*
|
||||
* @param audio_buffer
|
||||
* The audio buffer to which the given audio data should be written.
|
||||
*
|
||||
* @param buffer
|
||||
* The buffer of audio data to write to the given audio buffer.
|
||||
*
|
||||
* @param length
|
||||
* The number of bytes to write.
|
||||
*/
|
||||
void guac_rdp_audio_buffer_write(guac_rdp_audio_buffer* audio_buffer,
|
||||
char* buffer, int length);
|
||||
|
||||
/**
|
||||
* Stops handling of audio data received via guac_rdp_audio_buffer_write() and
|
||||
* frees the underlying packet buffer. Further audio data will be ignored until
|
||||
* guac_rdp_audio_buffer_begin() is invoked again.
|
||||
*
|
||||
* @param audio_buffer
|
||||
* The audio buffer to end.
|
||||
*/
|
||||
void guac_rdp_audio_buffer_end(guac_rdp_audio_buffer* audio_buffer);
|
||||
|
||||
/**
|
||||
* Frees the given audio buffer. If guac_rdp_audio_buffer_end() has not yet
|
||||
* been called, its associated packet buffer will also be freed.
|
||||
*
|
||||
* @param audio_buffer
|
||||
* The audio buffer to free.
|
||||
*/
|
||||
void guac_rdp_audio_buffer_free(guac_rdp_audio_buffer* audio_buffer);
|
||||
|
||||
/**
|
||||
* Handler for inbound audio data (audio input).
|
||||
*/
|
||||
guac_user_audio_handler guac_rdp_audio_handler;
|
||||
|
||||
/**
|
||||
* Handler for stream data related to audio input.
|
||||
*/
|
||||
guac_user_blob_handler guac_rdp_audio_blob_handler;
|
||||
|
||||
/**
|
||||
* Handler for end-of-stream related to audio input.
|
||||
*/
|
||||
guac_user_end_handler guac_rdp_audio_end_handler;
|
||||
|
||||
/**
|
||||
* Adds Guacamole's "guacai" plugin to the list of dynamic virtual channel
|
||||
* plugins to be loaded by FreeRDP's "drdynvc" plugin. The plugin will only
|
||||
* be loaded once guac_rdp_load_drdynvc() is invoked with the guac_rdp_dvc_list
|
||||
* passed to this function. The "guacai" plugin ultimately adds support for the
|
||||
* "AUDIO_INPUT" dynamic virtual channel.
|
||||
*
|
||||
* @param context
|
||||
* The rdpContext associated with the active RDP session.
|
||||
*
|
||||
* @param list
|
||||
* The guac_rdp_dvc_list to which the "guacai" plugin should be added, such
|
||||
* that it may later be loaded by guac_rdp_load_drdynvc().
|
||||
*/
|
||||
void guac_rdp_audio_load_plugin(rdpContext* context, guac_rdp_dvc_list* list);
|
||||
|
||||
#endif
|
||||
|
@ -19,6 +19,7 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "audio_input.h"
|
||||
#include "client.h"
|
||||
#include "rdp.h"
|
||||
#include "rdp_disp.h"
|
||||
@ -129,6 +130,10 @@ int guac_rdp_client_free_handler(guac_client* client) {
|
||||
if (rdp_client->audio != NULL)
|
||||
guac_audio_stream_free(rdp_client->audio);
|
||||
|
||||
/* Clean up audio input buffer, if allocated */
|
||||
if (rdp_client->audio_input != NULL)
|
||||
guac_rdp_audio_buffer_free(rdp_client->audio_input);
|
||||
|
||||
/* Free client data */
|
||||
guac_common_clipboard_free(rdp_client->clipboard);
|
||||
free(rdp_client);
|
||||
|
@ -22,17 +22,26 @@
|
||||
#include "winpr-stream.h"
|
||||
#include "winpr-wtypes.h"
|
||||
|
||||
/*
|
||||
* NOTE: Because the old API did not support local allocation of the buffer
|
||||
* for each stream, these compatibility implementations ignore
|
||||
* the parameters of Stream_New() and Stream_Free() that provide them.
|
||||
*/
|
||||
|
||||
wStream* Stream_New(BYTE* buffer, size_t size) {
|
||||
|
||||
/* If no buffer is provided, allocate a new stream of the given size */
|
||||
if (buffer == NULL)
|
||||
return stream_new(size);
|
||||
|
||||
/* Otherwise allocate an empty stream and assign the given buffer */
|
||||
wStream* stream = stream_new(0);
|
||||
stream_attach(stream, buffer, size);
|
||||
return stream;
|
||||
|
||||
}
|
||||
|
||||
void Stream_Free(wStream* s, BOOL bFreeBuffer) {
|
||||
|
||||
/* Disassociate buffer if it will be freed externally */
|
||||
if (!bFreeBuffer)
|
||||
stream_detach(s);
|
||||
|
||||
stream_free(s);
|
||||
|
||||
}
|
||||
|
||||
|
183
src/protocols/rdp/dvc.c
Normal file
183
src/protocols/rdp/dvc.c
Normal file
@ -0,0 +1,183 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "dvc.h"
|
||||
#include "guac_list.h"
|
||||
#include "rdp.h"
|
||||
|
||||
#include <freerdp/channels/channels.h>
|
||||
#include <freerdp/freerdp.h>
|
||||
#include <guacamole/client.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
guac_rdp_dvc_list* guac_rdp_dvc_list_alloc() {
|
||||
|
||||
guac_rdp_dvc_list* list = malloc(sizeof(guac_rdp_dvc_list));
|
||||
|
||||
/* Initialize with empty backing list */
|
||||
list->channels = guac_common_list_alloc();
|
||||
list->channel_count = 0;
|
||||
|
||||
return list;
|
||||
|
||||
}
|
||||
|
||||
void guac_rdp_dvc_list_add(guac_rdp_dvc_list* list, const char* name, ...) {
|
||||
|
||||
va_list args;
|
||||
|
||||
guac_rdp_dvc* dvc = malloc(sizeof(guac_rdp_dvc));
|
||||
|
||||
va_start(args, name);
|
||||
|
||||
/* Count number of arguments (excluding terminating NULL) */
|
||||
dvc->argc = 1;
|
||||
while (va_arg(args, char*) != NULL)
|
||||
dvc->argc++;
|
||||
|
||||
/* Reset va_list */
|
||||
va_end(args);
|
||||
va_start(args, name);
|
||||
|
||||
/* Copy argument values into DVC entry */
|
||||
dvc->argv = malloc(sizeof(char*) * dvc->argc);
|
||||
dvc->argv[0] = strdup(name);
|
||||
int i;
|
||||
for (i = 1; i < dvc->argc; i++)
|
||||
dvc->argv[i] = strdup(va_arg(args, char*));
|
||||
|
||||
va_end(args);
|
||||
|
||||
/* Add entry to DVC list */
|
||||
guac_common_list_add(list->channels, dvc);
|
||||
|
||||
/* Update channel count */
|
||||
list->channel_count++;
|
||||
|
||||
}
|
||||
|
||||
void guac_rdp_dvc_list_free(guac_rdp_dvc_list* list) {
|
||||
|
||||
/* For each channel */
|
||||
guac_common_list_element* current = list->channels->head;
|
||||
while (current != NULL) {
|
||||
|
||||
/* Free arguments declaration for current channel */
|
||||
guac_rdp_dvc* dvc = (guac_rdp_dvc*) current->data;
|
||||
|
||||
/* Free the underlying arguments list if not delegated to FreeRDP */
|
||||
if (dvc->argv != NULL) {
|
||||
|
||||
/* Free each argument value */
|
||||
for (int i = 0; i < dvc->argc; i++)
|
||||
free(dvc->argv[i]);
|
||||
|
||||
free(dvc->argv);
|
||||
}
|
||||
|
||||
free(dvc);
|
||||
|
||||
current = current->next;
|
||||
|
||||
}
|
||||
|
||||
/* Free underlying list */
|
||||
guac_common_list_free(list->channels);
|
||||
|
||||
/* Free the DVC list itself */
|
||||
free(list);
|
||||
|
||||
}
|
||||
|
||||
int guac_rdp_load_drdynvc(rdpContext* context, guac_rdp_dvc_list* list) {
|
||||
|
||||
guac_client* client = ((rdp_freerdp_context*) context)->client;
|
||||
rdpChannels* channels = context->channels;
|
||||
|
||||
/* Skip if no channels will be loaded */
|
||||
if (list->channel_count == 0)
|
||||
return 0;
|
||||
|
||||
#ifndef HAVE_ADDIN_ARGV
|
||||
/* Allocate plugin data array */
|
||||
RDP_PLUGIN_DATA* all_plugin_data =
|
||||
calloc(list->channel_count + 1, sizeof(RDP_PLUGIN_DATA));
|
||||
|
||||
RDP_PLUGIN_DATA* current_plugin_data = all_plugin_data;
|
||||
#endif
|
||||
|
||||
/* For each channel */
|
||||
guac_common_list_element* current = list->channels->head;
|
||||
while (current != NULL) {
|
||||
|
||||
/* Get channel arguments */
|
||||
guac_rdp_dvc* dvc = (guac_rdp_dvc*) current->data;
|
||||
current = current->next;
|
||||
|
||||
/* guac_rdp_dvc_list_add() guarantees at one argument */
|
||||
assert(dvc->argc >= 1);
|
||||
|
||||
/* guac_rdp_load_drdynvc() MUST only be invoked once */
|
||||
assert(dvc->argv != NULL);
|
||||
|
||||
/* Log registration of plugin for current channel */
|
||||
guac_client_log(client, GUAC_LOG_DEBUG,
|
||||
"Registering DVC plugin \"%s\"", dvc->argv[0]);
|
||||
|
||||
#ifdef HAVE_ADDIN_ARGV
|
||||
/* Register plugin with FreeRDP */
|
||||
ADDIN_ARGV* args = malloc(sizeof(ADDIN_ARGV));
|
||||
args->argc = dvc->argc;
|
||||
args->argv = dvc->argv;
|
||||
freerdp_dynamic_channel_collection_add(context->settings, args);
|
||||
#else
|
||||
/* Copy all arguments */
|
||||
for (int i = 0; i < dvc->argc; i++)
|
||||
current_plugin_data->data[i] = dvc->argv[i];
|
||||
|
||||
/* Store size of entry */
|
||||
current_plugin_data->size = sizeof(*current_plugin_data);
|
||||
|
||||
/* Advance to next set of plugin data */
|
||||
current_plugin_data++;
|
||||
#endif
|
||||
|
||||
/* Rely on FreeRDP to free argv storage */
|
||||
dvc->argv = NULL;
|
||||
|
||||
}
|
||||
|
||||
#ifdef HAVE_ADDIN_ARGV
|
||||
/* Load virtual channel management plugin */
|
||||
return freerdp_channels_load_plugin(channels, context->instance->settings,
|
||||
"drdynvc", context->instance->settings);
|
||||
#else
|
||||
/* Terminate with empty RDP_PLUGIN_DATA element */
|
||||
current_plugin_data->size = 0;
|
||||
|
||||
/* Load virtual channel management plugin */
|
||||
return freerdp_channels_load_plugin(channels, context->instance->settings,
|
||||
"drdynvc", all_plugin_data);
|
||||
#endif
|
||||
|
||||
}
|
||||
|
138
src/protocols/rdp/dvc.h
Normal file
138
src/protocols/rdp/dvc.h
Normal file
@ -0,0 +1,138 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#ifndef GUAC_RDP_DVC_H
|
||||
#define GUAC_RDP_DVC_H
|
||||
|
||||
#include "config.h"
|
||||
#include "guac_list.h"
|
||||
|
||||
#include <freerdp/freerdp.h>
|
||||
|
||||
/**
|
||||
* The set of all arguments that should be passed to a given dynamic virtual
|
||||
* channel plugin, including the name of that plugin.
|
||||
*/
|
||||
typedef struct guac_rdp_dvc {
|
||||
|
||||
/**
|
||||
* The number of arguments in the argv array. This MUST be at least 1.
|
||||
*/
|
||||
int argc;
|
||||
|
||||
/**
|
||||
* The argument values being passed to the dynamic virtual channel plugin.
|
||||
* The first entry in this array is always the name of the plugin. If
|
||||
* guac_rdp_load_drdynvc() has been invoked, and freeing the argument
|
||||
* values is being delegated to FreeRDP, this will be NULL.
|
||||
*/
|
||||
char** argv;
|
||||
|
||||
} guac_rdp_dvc;
|
||||
|
||||
/**
|
||||
* A list of dynamic virtual channels which should be provided to the DRDYNVC
|
||||
* plugin once loaded via guac_rdp_load_drdynvc(). This interface exists purely
|
||||
* to bridge incompatibilities between differing versions of FreeRDP and its
|
||||
* DRDYNVC plugin. Any allocated guac_rdp_dvc_list is unlikely to be needed
|
||||
* after the DRDYNVC plugin has been loaded.
|
||||
*/
|
||||
typedef struct guac_rdp_dvc_list {
|
||||
|
||||
/**
|
||||
* Array of all dynamic virtual channels which should be registered with
|
||||
* the DRDYNVC plugin once loaded. Each list element will point to a
|
||||
* guac_rdp_dvc structure which must eventually be freed.
|
||||
*/
|
||||
guac_common_list* channels;
|
||||
|
||||
/**
|
||||
* The number of channels within the list.
|
||||
*/
|
||||
int channel_count;
|
||||
|
||||
} guac_rdp_dvc_list;
|
||||
|
||||
/**
|
||||
* Allocates a new, empty list of dynamic virtual channels. New channels may
|
||||
* be added via guac_rdp_dvc_list_add(). The loading of those channels'
|
||||
* associated plugins will be deferred until guac_rdp_load_drdynvc() is
|
||||
* invoked.
|
||||
*
|
||||
* @return
|
||||
* A newly-allocated, empty list of dynamic virtual channels.
|
||||
*/
|
||||
guac_rdp_dvc_list* guac_rdp_dvc_list_alloc();
|
||||
|
||||
/**
|
||||
* Adds the given dynamic virtual channel plugin name and associated arguments
|
||||
* to the list. The provied arguments list is NOT optional and MUST be
|
||||
* NULL-terminated, even if there are no arguments for the named dynamic
|
||||
* virtual channel plugin. Though FreeRDP requires that the arguments for a
|
||||
* dynamic virtual channel plugin contain the name of the plugin itself as the
|
||||
* first argument, the name must be excluded from the arguments provided here.
|
||||
* This function will automatically take care of adding the plugin name to
|
||||
* the arguments.
|
||||
*
|
||||
* @param list
|
||||
* The guac_rdp_dvc_list to which the given plugin name and arguments
|
||||
* should be added, for later bulk registration via
|
||||
* guac_rdp_load_drdynvc().
|
||||
*
|
||||
* @param name
|
||||
* The name of the dynamic virtual channel plugin that should be given
|
||||
* the provided arguments when guac_rdp_load_drdynvc() is invoked.
|
||||
*
|
||||
* @param ...
|
||||
* The string (char*) arguments which should be passed to the dynamic
|
||||
* virtual channel plugin when it is loaded via guac_rdp_load_drdynvc(),
|
||||
* excluding the plugin name itself.
|
||||
*/
|
||||
void guac_rdp_dvc_list_add(guac_rdp_dvc_list* list, const char* name, ...);
|
||||
|
||||
/**
|
||||
* Frees the given list of dynamic virtual channels. Note that, while each
|
||||
* individual entry within this list will be freed, it is partially up to
|
||||
* FreeRDP to free the storage associated with the arguments passed to the
|
||||
* virtual channels.
|
||||
*
|
||||
* @param list
|
||||
* The list to free.
|
||||
*/
|
||||
void guac_rdp_dvc_list_free(guac_rdp_dvc_list* list);
|
||||
|
||||
/**
|
||||
* Loads FreeRDP's DRDYNVC plugin and registers the dynamic virtual channel
|
||||
* plugins described by the given guac_rdp_dvc_list. This function MUST be
|
||||
* invoked no more than once per RDP connection. Invoking this function
|
||||
* multiple times, even if the guac_rdp_dvc_list is different each time, will
|
||||
* result in undefined behavior.
|
||||
*
|
||||
* @param context
|
||||
* The rdpContext associated with the RDP connection for which the DRDYNVC
|
||||
* plugin should be loaded.
|
||||
*
|
||||
* @param list
|
||||
* A guac_rdp_dvc_list describing the dynamic virtual channel plugins that
|
||||
* should be registered with the DRDYNVC plugin, along with any arguments.
|
||||
*/
|
||||
int guac_rdp_load_drdynvc(rdpContext* context, guac_rdp_dvc_list* list);
|
||||
|
||||
#endif
|
||||
|
336
src/protocols/rdp/guac_ai/ai_messages.c
Normal file
336
src/protocols/rdp/guac_ai/ai_messages.c
Normal file
@ -0,0 +1,336 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "ai_messages.h"
|
||||
#include "audio_input.h"
|
||||
#include "rdp.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <freerdp/freerdp.h>
|
||||
#include <freerdp/constants.h>
|
||||
#include <freerdp/dvc.h>
|
||||
#include <guacamole/client.h>
|
||||
|
||||
#ifdef ENABLE_WINPR
|
||||
#include <winpr/stream.h>
|
||||
#else
|
||||
#include "compat/winpr-stream.h"
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Reads AUDIO_FORMAT data from the given stream into the given struct.
|
||||
*
|
||||
* @param stream
|
||||
* The stream to read AUDIO_FORMAT data from.
|
||||
*
|
||||
* @param format
|
||||
* The structure to populate with data from the stream.
|
||||
*/
|
||||
static void guac_rdp_ai_read_format(wStream* stream,
|
||||
guac_rdp_ai_format* format) {
|
||||
|
||||
/* Read audio format into structure */
|
||||
Stream_Read_UINT16(stream, format->tag); /* wFormatTag */
|
||||
Stream_Read_UINT16(stream, format->channels); /* nChannels */
|
||||
Stream_Read_UINT32(stream, format->rate); /* nSamplesPerSec */
|
||||
Stream_Read_UINT32(stream, format->bytes_per_sec); /* nAvgBytesPerSec */
|
||||
Stream_Read_UINT16(stream, format->block_align); /* nBlockAlign */
|
||||
Stream_Read_UINT16(stream, format->bps); /* wBitsPerSample */
|
||||
Stream_Read_UINT16(stream, format->data_size); /* cbSize */
|
||||
|
||||
/* Read arbitrary data block (if applicable) */
|
||||
if (format->data_size != 0) {
|
||||
format->data = Stream_Pointer(stream); /* data */
|
||||
Stream_Seek(stream, format->data_size);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes AUDIO_FORMAT data to the given stream from the given struct.
|
||||
*
|
||||
* @param stream
|
||||
* The stream to write AUDIO_FORMAT data to.
|
||||
*
|
||||
* @param format
|
||||
* The structure containing the data that should be written to the stream.
|
||||
*/
|
||||
static void guac_rdp_ai_write_format(wStream* stream,
|
||||
guac_rdp_ai_format* format) {
|
||||
|
||||
/* Write audio format into structure */
|
||||
Stream_Write_UINT16(stream, format->tag); /* wFormatTag */
|
||||
Stream_Write_UINT16(stream, format->channels); /* nChannels */
|
||||
Stream_Write_UINT32(stream, format->rate); /* nSamplesPerSec */
|
||||
Stream_Write_UINT32(stream, format->bytes_per_sec); /* nAvgBytesPerSec */
|
||||
Stream_Write_UINT16(stream, format->block_align); /* nBlockAlign */
|
||||
Stream_Write_UINT16(stream, format->bps); /* wBitsPerSample */
|
||||
Stream_Write_UINT16(stream, format->data_size); /* cbSize */
|
||||
|
||||
/* Write arbitrary data block (if applicable) */
|
||||
if (format->data_size != 0)
|
||||
Stream_Write(stream, format->data, format->data_size);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a Data Incoming PDU along the given channel. A Data Incoming PDU is
|
||||
* used by the client to indicate to the server that format or audio data is
|
||||
* about to be sent.
|
||||
*
|
||||
* @param channel
|
||||
* The channel along which the PDU should be sent.
|
||||
*/
|
||||
static void guac_rdp_ai_send_incoming_data(IWTSVirtualChannel* channel) {
|
||||
|
||||
/* Build data incoming PDU */
|
||||
wStream* stream = Stream_New(NULL, 1);
|
||||
Stream_Write_UINT8(stream, GUAC_RDP_MSG_SNDIN_DATA_INCOMING); /* MessageId */
|
||||
|
||||
/* Send stream */
|
||||
channel->Write(channel, (UINT32) Stream_GetPosition(stream),
|
||||
Stream_Buffer(stream), NULL);
|
||||
Stream_Free(stream, TRUE);
|
||||
|
||||
}
|
||||
|
||||
static void guac_rdp_ai_send_data(IWTSVirtualChannel* channel,
|
||||
char* buffer, int length) {
|
||||
|
||||
/* Build data PDU */
|
||||
wStream* stream = Stream_New(NULL, length + 1);
|
||||
Stream_Write_UINT8(stream, GUAC_RDP_MSG_SNDIN_DATA); /* MessageId */
|
||||
Stream_Write(stream, buffer, length); /* Data */
|
||||
|
||||
/* Send stream */
|
||||
channel->Write(channel, (UINT32) Stream_GetPosition(stream),
|
||||
Stream_Buffer(stream), NULL);
|
||||
Stream_Free(stream, TRUE);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a Sound Formats PDU along the given channel. A Sound Formats PDU is
|
||||
* used by the client to indicate to the server which formats of audio it
|
||||
* supports (in response to the server sending exactly the same type of PDU).
|
||||
* This PDU MUST be preceded by the Data Incoming PDU.
|
||||
*
|
||||
* @param channel
|
||||
* The channel along which the PDU should be sent.
|
||||
*
|
||||
* @param formats
|
||||
* An array of all supported formats.
|
||||
*
|
||||
* @param num_formats
|
||||
* The number of entries in the formats array.
|
||||
*/
|
||||
static void guac_rdp_ai_send_formats(IWTSVirtualChannel* channel,
|
||||
guac_rdp_ai_format* formats, int num_formats) {
|
||||
|
||||
int index;
|
||||
int packet_size = 9;
|
||||
|
||||
/* Calculate packet size */
|
||||
for (index = 0; index < num_formats; index++)
|
||||
packet_size += 18 + formats[index].data_size;
|
||||
|
||||
wStream* stream = Stream_New(NULL, packet_size);
|
||||
|
||||
/* Write header */
|
||||
Stream_Write_UINT8(stream, GUAC_RDP_MSG_SNDIN_FORMATS); /* MessageId */
|
||||
Stream_Write_UINT32(stream, num_formats); /* NumFormats */
|
||||
Stream_Write_UINT32(stream, packet_size); /* cbSizeFormatsPacket */
|
||||
|
||||
/* Write all formats */
|
||||
for (index = 0; index < num_formats; index++)
|
||||
guac_rdp_ai_write_format(stream, &(formats[index]));
|
||||
|
||||
/* Send PDU */
|
||||
channel->Write(channel, (UINT32) Stream_GetPosition(stream),
|
||||
Stream_Buffer(stream), NULL);
|
||||
Stream_Free(stream, TRUE);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an Open Reply PDU along the given channel. An Open Reply PDU is
|
||||
* used by the client to acknowledge the successful opening of the AUDIO_INPUT
|
||||
* channel.
|
||||
*
|
||||
* @param channel
|
||||
* The channel along which the PDU should be sent.
|
||||
*
|
||||
* @param result
|
||||
* The HRESULT code to send to the server indicating success, failure, etc.
|
||||
*/
|
||||
static void guac_rdp_ai_send_open_reply(IWTSVirtualChannel* channel,
|
||||
UINT32 result) {
|
||||
|
||||
/* Build open reply PDU */
|
||||
wStream* stream = Stream_New(NULL, 5);
|
||||
Stream_Write_UINT8(stream, GUAC_RDP_MSG_SNDIN_OPEN_REPLY); /* MessageId */
|
||||
Stream_Write_UINT32(stream, result); /* Result */
|
||||
|
||||
/* Send stream */
|
||||
channel->Write(channel, (UINT32) Stream_GetPosition(stream),
|
||||
Stream_Buffer(stream), NULL);
|
||||
Stream_Free(stream, TRUE);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a Format Change PDU along the given channel. A Format Change PDU is
|
||||
* used by the client to acknowledge the format being used for data sent
|
||||
* along the AUDIO_INPUT channel.
|
||||
*
|
||||
* @param channel
|
||||
* The channel along which the PDU should be sent.
|
||||
*
|
||||
* @param format
|
||||
* The index of the format being acknowledged, which must be the index of
|
||||
* the format within the original Sound Formats PDU received from the
|
||||
* server.
|
||||
*/
|
||||
static void guac_rdp_ai_send_formatchange(IWTSVirtualChannel* channel,
|
||||
UINT32 format) {
|
||||
|
||||
/* Build format change PDU */
|
||||
wStream* stream = Stream_New(NULL, 5);
|
||||
Stream_Write_UINT8(stream, GUAC_RDP_MSG_SNDIN_FORMATCHANGE); /* MessageId */
|
||||
Stream_Write_UINT32(stream, format); /* NewFormat */
|
||||
|
||||
/* Send stream */
|
||||
channel->Write(channel, (UINT32) Stream_GetPosition(stream),
|
||||
Stream_Buffer(stream), NULL);
|
||||
Stream_Free(stream, TRUE);
|
||||
|
||||
}
|
||||
|
||||
void guac_rdp_ai_process_version(guac_client* client,
|
||||
IWTSVirtualChannel* channel, wStream* stream) {
|
||||
|
||||
UINT32 version;
|
||||
Stream_Read_UINT32(stream, version);
|
||||
|
||||
/* Warn if server's version number is incorrect */
|
||||
if (version != 1)
|
||||
guac_client_log(client, GUAC_LOG_WARNING,
|
||||
"Server reports AUDIO_INPUT version %i, not 1", version);
|
||||
|
||||
/* Build response version PDU */
|
||||
wStream* response = Stream_New(NULL, 5);
|
||||
Stream_Write_UINT8(response, GUAC_RDP_MSG_SNDIN_VERSION); /* MessageId */
|
||||
Stream_Write_UINT32(response, 1); /* Version */
|
||||
|
||||
/* Send response */
|
||||
channel->Write(channel, (UINT32) Stream_GetPosition(response),
|
||||
Stream_Buffer(response), NULL);
|
||||
Stream_Free(response, TRUE);
|
||||
|
||||
}
|
||||
|
||||
void guac_rdp_ai_process_formats(guac_client* client,
|
||||
IWTSVirtualChannel* channel, wStream* stream) {
|
||||
|
||||
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
|
||||
guac_rdp_audio_buffer* audio_buffer = rdp_client->audio_input;
|
||||
|
||||
UINT32 num_formats;
|
||||
Stream_Read_UINT32(stream, num_formats); /* NumFormats */
|
||||
Stream_Seek_UINT32(stream); /* cbSizeFormatsPacket (MUST BE IGNORED) */
|
||||
|
||||
UINT32 index;
|
||||
for (index = 0; index < num_formats; index++) {
|
||||
|
||||
guac_rdp_ai_format format;
|
||||
guac_rdp_ai_read_format(stream, &format);
|
||||
|
||||
/* Ignore anything but WAVE_FORMAT_PCM */
|
||||
if (format.tag != GUAC_RDP_WAVE_FORMAT_PCM)
|
||||
continue;
|
||||
|
||||
/* Set output format of internal audio buffer to match RDP server */
|
||||
guac_rdp_audio_buffer_set_output(audio_buffer, format.rate,
|
||||
format.channels, format.bps / 8);
|
||||
|
||||
/* Accept single format */
|
||||
guac_rdp_ai_send_incoming_data(channel);
|
||||
guac_rdp_ai_send_formats(channel, &format, 1);
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
/* No formats available */
|
||||
guac_client_log(client, GUAC_LOG_WARNING, "AUDIO_INPUT: No WAVE format.");
|
||||
guac_rdp_ai_send_incoming_data(channel);
|
||||
guac_rdp_ai_send_formats(channel, NULL, 0);
|
||||
|
||||
}
|
||||
|
||||
static void guac_rdp_ai_flush_packet(char* buffer, int length, void* data) {
|
||||
|
||||
IWTSVirtualChannel* channel = (IWTSVirtualChannel*) data;
|
||||
|
||||
/* Send data over channel */
|
||||
guac_rdp_ai_send_incoming_data(channel);
|
||||
guac_rdp_ai_send_data(channel, buffer, length);
|
||||
|
||||
}
|
||||
|
||||
void guac_rdp_ai_process_open(guac_client* client,
|
||||
IWTSVirtualChannel* channel, wStream* stream) {
|
||||
|
||||
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
|
||||
guac_rdp_audio_buffer* audio_buffer = rdp_client->audio_input;
|
||||
|
||||
UINT32 packet_frames;
|
||||
UINT32 initial_format;
|
||||
|
||||
Stream_Read_UINT32(stream, packet_frames); /* FramesPerPacket */
|
||||
Stream_Read_UINT32(stream, initial_format); /* InitialFormat */
|
||||
|
||||
guac_client_log(client, GUAC_LOG_DEBUG, "RDP server is accepting audio "
|
||||
"input as %i-channel, %i Hz PCM audio at %i bytes/sample.",
|
||||
audio_buffer->out_format.channels,
|
||||
audio_buffer->out_format.rate,
|
||||
audio_buffer->out_format.bps);
|
||||
|
||||
/* Success */
|
||||
guac_rdp_ai_send_formatchange(channel, initial_format);
|
||||
guac_rdp_ai_send_open_reply(channel, 0);
|
||||
|
||||
/* Begin receiving audio data */
|
||||
guac_rdp_audio_buffer_begin(audio_buffer, packet_frames,
|
||||
guac_rdp_ai_flush_packet, channel);
|
||||
|
||||
}
|
||||
|
||||
void guac_rdp_ai_process_formatchange(guac_client* client,
|
||||
IWTSVirtualChannel* channel, wStream* stream) {
|
||||
|
||||
/* Should not be called as we only accept one format */
|
||||
guac_client_log(client, GUAC_LOG_DEBUG,
|
||||
"RDP server requesting AUDIO_INPUT format change despite only one "
|
||||
"format available.");
|
||||
|
||||
}
|
||||
|
216
src/protocols/rdp/guac_ai/ai_messages.h
Normal file
216
src/protocols/rdp/guac_ai/ai_messages.h
Normal file
@ -0,0 +1,216 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#ifndef GUAC_RDP_AI_MESSAGES_H
|
||||
#define GUAC_RDP_AI_MESSAGES_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <freerdp/dvc.h>
|
||||
#include <guacamole/client.h>
|
||||
|
||||
#ifdef ENABLE_WINPR
|
||||
#include <winpr/stream.h>
|
||||
#else
|
||||
#include "compat/winpr-stream.h"
|
||||
#endif
|
||||
|
||||
/**
|
||||
* The format tag associated with raw wave audio (WAVE_FORMAT_PCM). This format
|
||||
* is required to be supported by all RDP servers.
|
||||
*/
|
||||
#define GUAC_RDP_WAVE_FORMAT_PCM 0x01
|
||||
|
||||
/**
|
||||
* The message ID associated with the AUDIO_INPUT Version PDU. The Version PDU
|
||||
* is sent by both the client and the server to indicate their version of the
|
||||
* AUDIO_INPUT channel protocol (which must always be 1).
|
||||
*/
|
||||
#define GUAC_RDP_MSG_SNDIN_VERSION 0x01
|
||||
|
||||
/**
|
||||
* The message ID associated with the AUDIO_INPUT Sound Formats PDU. The
|
||||
* Sound Formats PDU is sent by the client and the server to indicate the
|
||||
* formats of audio supported.
|
||||
*/
|
||||
#define GUAC_RDP_MSG_SNDIN_FORMATS 0x02
|
||||
|
||||
/**
|
||||
* The message ID associated with the AUDIO_INPUT Open PDU. The Open PDU is
|
||||
* sent by the server to inform the client that the AUDIO_INPUT channel is
|
||||
* now open.
|
||||
*/
|
||||
#define GUAC_RDP_MSG_SNDIN_OPEN 0x03
|
||||
|
||||
/**
|
||||
* The message ID associated with the AUDIO_INPUT Open Reply PDU. The Open
|
||||
* Reply PDU is sent by the client (after sending a Format Change PDU) to
|
||||
* acknowledge that the AUDIO_INPUT channel is open.
|
||||
*/
|
||||
#define GUAC_RDP_MSG_SNDIN_OPEN_REPLY 0x04
|
||||
|
||||
/**
|
||||
* The message ID associated with the AUDIO_INPUT Incoming Data PDU. The
|
||||
* Incoming Data PDU is sent by the client to inform the server of incoming
|
||||
* sound format or audio data.
|
||||
*/
|
||||
#define GUAC_RDP_MSG_SNDIN_DATA_INCOMING 0x05
|
||||
|
||||
/**
|
||||
* The message ID associated with the AUDIO_INPUT Data PDU. The Data PDU is
|
||||
* sent by the client and contains audio data read from the microphone.
|
||||
*/
|
||||
#define GUAC_RDP_MSG_SNDIN_DATA 0x06
|
||||
|
||||
/**
|
||||
* The message ID associated with the AUDIO_INPUT Format Change PDU. The Format
|
||||
* Change PDU is sent by the client to acknowledge the current sound format, or
|
||||
* by the server to request a different sound format.
|
||||
*/
|
||||
#define GUAC_RDP_MSG_SNDIN_FORMATCHANGE 0x07
|
||||
|
||||
/**
|
||||
* An AUDIO_INPUT format, analogous to the AUDIO_FORMAT structure defined
|
||||
* within Microsoft's RDP documentation.
|
||||
*/
|
||||
typedef struct guac_rdp_ai_format {
|
||||
|
||||
/**
|
||||
* The "format tag" denoting the overall format of audio data received,
|
||||
* such as WAVE_FORMAT_PCM.
|
||||
*/
|
||||
UINT16 tag;
|
||||
|
||||
/**
|
||||
* The number of audio channels.
|
||||
*/
|
||||
UINT16 channels;
|
||||
|
||||
/**
|
||||
* The number of samples per second.
|
||||
*/
|
||||
UINT32 rate;
|
||||
|
||||
/**
|
||||
* The average number of bytes required for one second of audio.
|
||||
*/
|
||||
UINT32 bytes_per_sec;
|
||||
|
||||
/**
|
||||
* The absolute minimum number of bytes required to process audio in this
|
||||
* format.
|
||||
*/
|
||||
UINT16 block_align;
|
||||
|
||||
/**
|
||||
* The number of bits per sample.
|
||||
*/
|
||||
UINT16 bps;
|
||||
|
||||
/**
|
||||
* The size of the arbitrary data block, if any. The meaning of the data
|
||||
* within the arbitrary data block is determined by the format tag.
|
||||
* WAVE_FORMAT_PCM audio has no associated arbitrary data.
|
||||
*/
|
||||
UINT16 data_size;
|
||||
|
||||
/**
|
||||
* Optional arbitrary data whose meaning is determined by the format tag.
|
||||
* WAVE_FORMAT_PCM audio has no associated arbitrary data.
|
||||
*/
|
||||
BYTE* data;
|
||||
|
||||
} guac_rdp_ai_format;
|
||||
|
||||
/**
|
||||
* Processes a Version PDU received from the RDP server. The Version PDU is
|
||||
* sent by the server to indicate its version of the AUDIO_INPUT channel
|
||||
* protocol (which must always be 1).
|
||||
*
|
||||
* @param client
|
||||
* The guac_client associated with the current RDP connection.
|
||||
*
|
||||
* @param channel
|
||||
* The IWTSVirtualChannel instance associated with the connected
|
||||
* AUDIO_INPUT channel.
|
||||
*
|
||||
* @param stream
|
||||
* The received PDU, with the read position just after the message ID field
|
||||
* common to all AUDIO_INPUT PDUs.
|
||||
*/
|
||||
void guac_rdp_ai_process_version(guac_client* client,
|
||||
IWTSVirtualChannel* channel, wStream* stream);
|
||||
|
||||
/**
|
||||
* Processes a Sound Formats PDU received from the RDP server. The Sound
|
||||
* Formats PDU is sent by the server to indicate the formats of audio
|
||||
* supported.
|
||||
*
|
||||
* @param client
|
||||
* The guac_client associated with the current RDP connection.
|
||||
*
|
||||
* @param channel
|
||||
* The IWTSVirtualChannel instance associated with the connected
|
||||
* AUDIO_INPUT channel.
|
||||
*
|
||||
* @param stream
|
||||
* The received PDU, with the read position just after the message ID field
|
||||
* common to all AUDIO_INPUT PDUs.
|
||||
*/
|
||||
void guac_rdp_ai_process_formats(guac_client* client,
|
||||
IWTSVirtualChannel* channel, wStream* stream);
|
||||
|
||||
/**
|
||||
* Processes a Open PDU received from the RDP server. The Open PDU is sent by
|
||||
* the server to inform the client that the AUDIO_INPUT channel is now open.
|
||||
*
|
||||
* @param client
|
||||
* The guac_client associated with the current RDP connection.
|
||||
*
|
||||
* @param channel
|
||||
* The IWTSVirtualChannel instance associated with the connected
|
||||
* AUDIO_INPUT channel.
|
||||
*
|
||||
* @param stream
|
||||
* The received PDU, with the read position just after the message ID field
|
||||
* common to all AUDIO_INPUT PDUs.
|
||||
*/
|
||||
void guac_rdp_ai_process_open(guac_client* client,
|
||||
IWTSVirtualChannel* channel, wStream* stream);
|
||||
|
||||
/**
|
||||
* Processes a Format Change PDU received from the RDP server. The Format
|
||||
* Change PDU is sent by the server to request a different sound format.
|
||||
*
|
||||
* @param client
|
||||
* The guac_client associated with the current RDP connection.
|
||||
*
|
||||
* @param channel
|
||||
* The IWTSVirtualChannel instance associated with the connected
|
||||
* AUDIO_INPUT channel.
|
||||
*
|
||||
* @param stream
|
||||
* The received PDU, with the read position just after the message ID field
|
||||
* common to all AUDIO_INPUT PDUs.
|
||||
*/
|
||||
void guac_rdp_ai_process_formatchange(guac_client* client,
|
||||
IWTSVirtualChannel* channel, wStream* stream);
|
||||
|
||||
#endif
|
||||
|
349
src/protocols/rdp/guac_ai/ai_service.c
Normal file
349
src/protocols/rdp/guac_ai/ai_service.c
Normal file
@ -0,0 +1,349 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "ai_messages.h"
|
||||
#include "ai_service.h"
|
||||
#include "audio_input.h"
|
||||
#include "ptr_string.h"
|
||||
#include "rdp.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <freerdp/freerdp.h>
|
||||
#include <freerdp/constants.h>
|
||||
#include <freerdp/dvc.h>
|
||||
#include <guacamole/client.h>
|
||||
|
||||
#ifdef ENABLE_WINPR
|
||||
#include <winpr/stream.h>
|
||||
#else
|
||||
#include "compat/winpr-stream.h"
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Handles the given data received along the AUDIO_INPUT channel of the RDP
|
||||
* connection associated with the given guac_client. This handler is
|
||||
* API-independent and is invoked by API-dependent guac_rdp_ai_data callback
|
||||
* specific to the version of FreeRDP installed.
|
||||
*
|
||||
* @param client
|
||||
* The guac_client associated with RDP connection having the AUDIO_INPUT
|
||||
* connection along which the given data was received.
|
||||
*
|
||||
* @param channel
|
||||
* A reference to the IWTSVirtualChannel instance along which responses
|
||||
* should be sent.
|
||||
*
|
||||
* @param stream
|
||||
* The data received along the AUDIO_INPUT channel.
|
||||
*/
|
||||
static void guac_rdp_ai_handle_data(guac_client* client,
|
||||
IWTSVirtualChannel* channel, wStream* stream) {
|
||||
|
||||
/* Read message ID from received PDU */
|
||||
BYTE message_id;
|
||||
Stream_Read_UINT8(stream, message_id);
|
||||
|
||||
/* Invoke appropriate message processor based on ID */
|
||||
switch (message_id) {
|
||||
|
||||
/* Version PDU */
|
||||
case GUAC_RDP_MSG_SNDIN_VERSION:
|
||||
guac_rdp_ai_process_version(client, channel, stream);
|
||||
break;
|
||||
|
||||
/* Sound Formats PDU */
|
||||
case GUAC_RDP_MSG_SNDIN_FORMATS:
|
||||
guac_rdp_ai_process_formats(client, channel, stream);
|
||||
break;
|
||||
|
||||
/* Open PDU */
|
||||
case GUAC_RDP_MSG_SNDIN_OPEN:
|
||||
guac_rdp_ai_process_open(client, channel, stream);
|
||||
break;
|
||||
|
||||
/* Format Change PDU */
|
||||
case GUAC_RDP_MSG_SNDIN_FORMATCHANGE:
|
||||
guac_rdp_ai_process_formatchange(client, channel, stream);
|
||||
break;
|
||||
|
||||
/* Log unknown message IDs */
|
||||
default:
|
||||
guac_client_log(client, GUAC_LOG_DEBUG,
|
||||
"Unknown AUDIO_INPUT message ID: 0x%x", message_id);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#ifdef LEGACY_IWTSVIRTUALCHANNELCALLBACK
|
||||
/**
|
||||
* Callback which is invoked when data is received along a connection to the
|
||||
* AUDIO_INPUT plugin. This callback is specific to FreeRDP 1.1 and older.
|
||||
*
|
||||
* @param channel_callback
|
||||
* The IWTSVirtualChannelCallback structure to which this callback was
|
||||
* originally assigned.
|
||||
*
|
||||
* @param size
|
||||
* The number of bytes received.
|
||||
*
|
||||
* @param buffer
|
||||
* A buffer containing all bytes received.
|
||||
*
|
||||
* @return
|
||||
* Always zero.
|
||||
*/
|
||||
static int guac_rdp_ai_data(IWTSVirtualChannelCallback* channel_callback,
|
||||
UINT32 size, BYTE* buffer) {
|
||||
|
||||
guac_rdp_ai_channel_callback* ai_channel_callback =
|
||||
(guac_rdp_ai_channel_callback*) channel_callback;
|
||||
IWTSVirtualChannel* channel = ai_channel_callback->channel;
|
||||
|
||||
/* Invoke generalized (API-independent) data handler */
|
||||
wStream* stream = Stream_New(buffer, size);
|
||||
guac_rdp_ai_handle_data(ai_channel_callback->client, channel, stream);
|
||||
Stream_Free(stream, FALSE);
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
#else
|
||||
/**
|
||||
* Callback which is invoked when data is received along a connection to the
|
||||
* AUDIO_INPUT plugin. This callback is specific to FreeRDP 1.2 and newer.
|
||||
*
|
||||
* @param channel_callback
|
||||
* The IWTSVirtualChannelCallback structure to which this callback was
|
||||
* originally assigned.
|
||||
*
|
||||
* @param stream
|
||||
* The data received.
|
||||
*
|
||||
* @return
|
||||
* Always zero.
|
||||
*/
|
||||
static int guac_rdp_ai_data(IWTSVirtualChannelCallback* channel_callback,
|
||||
wStream* stream) {
|
||||
|
||||
guac_rdp_ai_channel_callback* ai_channel_callback =
|
||||
(guac_rdp_ai_channel_callback*) channel_callback;
|
||||
IWTSVirtualChannel* channel = ai_channel_callback->channel;
|
||||
|
||||
/* Invoke generalized (API-independent) data handler */
|
||||
guac_rdp_ai_handle_data(ai_channel_callback->client, channel, stream);
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Callback which is invoked when a connection to the AUDIO_INPUT plugin is
|
||||
* closed.
|
||||
*
|
||||
* @param channel_callback
|
||||
* The IWTSVirtualChannelCallback structure to which this callback was
|
||||
* originally assigned.
|
||||
*
|
||||
* @return
|
||||
* Always zero.
|
||||
*/
|
||||
static int guac_rdp_ai_close(IWTSVirtualChannelCallback* channel_callback) {
|
||||
|
||||
guac_rdp_ai_channel_callback* ai_channel_callback =
|
||||
(guac_rdp_ai_channel_callback*) channel_callback;
|
||||
|
||||
guac_client* client = ai_channel_callback->client;
|
||||
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
|
||||
guac_rdp_audio_buffer* audio_buffer = rdp_client->audio_input;
|
||||
|
||||
/* Log closure of AUDIO_INPUT channel */
|
||||
guac_client_log(client, GUAC_LOG_DEBUG,
|
||||
"AUDIO_INPUT channel connection closed");
|
||||
|
||||
guac_rdp_audio_buffer_end(audio_buffer);
|
||||
free(ai_channel_callback);
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback which is invoked when a new connection is received by the
|
||||
* AUDIO_INPUT plugin. Additional callbacks required to handle received data
|
||||
* and closure of the connection must be installed at this point.
|
||||
*
|
||||
* @param listener_callback
|
||||
* The IWTSListenerCallback structure associated with the AUDIO_INPUT
|
||||
* plugin receiving the new connection.
|
||||
*
|
||||
* @param channel
|
||||
* A reference to the IWTSVirtualChannel instance along which data related
|
||||
* to the AUDIO_INPUT channel should be sent.
|
||||
*
|
||||
* @param data
|
||||
* Absolutely no idea. According to Microsoft's documentation for the
|
||||
* function prototype on which FreeRDP's API appears to be based: "This
|
||||
* parameter is not implemented and is reserved for future use."
|
||||
*
|
||||
* @param accept
|
||||
* Pointer to a flag which should be set to TRUE if the connection should
|
||||
* be accepted or FALSE otherwise. In the case of FreeRDP, this value
|
||||
* defaults to TRUE, and TRUE absolutely MUST be identically 1 or it will
|
||||
* be interpreted as FALSE.
|
||||
*
|
||||
* @param channel_callback
|
||||
* A pointer to the location that the new IWTSVirtualChannelCallback
|
||||
* structure containing the required callbacks should be assigned.
|
||||
*
|
||||
* @return
|
||||
* Always zero.
|
||||
*/
|
||||
static int guac_rdp_ai_new_connection(
|
||||
IWTSListenerCallback* listener_callback, IWTSVirtualChannel* channel,
|
||||
BYTE* data, int* accept,
|
||||
IWTSVirtualChannelCallback** channel_callback) {
|
||||
|
||||
guac_rdp_ai_listener_callback* ai_listener_callback =
|
||||
(guac_rdp_ai_listener_callback*) listener_callback;
|
||||
|
||||
/* Log new AUDIO_INPUT connection */
|
||||
guac_client_log(ai_listener_callback->client, GUAC_LOG_DEBUG,
|
||||
"New AUDIO_INPUT channel connection");
|
||||
|
||||
/* Allocate new channel callback */
|
||||
guac_rdp_ai_channel_callback* ai_channel_callback =
|
||||
calloc(1, sizeof(guac_rdp_ai_channel_callback));
|
||||
|
||||
/* Init listener callback with data from plugin */
|
||||
ai_channel_callback->client = ai_listener_callback->client;
|
||||
ai_channel_callback->channel = channel;
|
||||
ai_channel_callback->parent.OnDataReceived = guac_rdp_ai_data;
|
||||
ai_channel_callback->parent.OnClose = guac_rdp_ai_close;
|
||||
|
||||
/* Return callback through pointer */
|
||||
*channel_callback = (IWTSVirtualChannelCallback*) ai_channel_callback;
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback which is invoked when the AUDIO_INPUT plugin has been loaded and
|
||||
* needs to be initialized with other callbacks and data.
|
||||
*
|
||||
* @param plugin
|
||||
* The AUDIO_INPUT plugin that needs to be initialied.
|
||||
*
|
||||
* @param manager
|
||||
* The IWTSVirtualChannelManager instance with which the AUDIO_INPUT plugin
|
||||
* must be registered.
|
||||
*
|
||||
* @return
|
||||
* Always zero.
|
||||
*/
|
||||
static int guac_rdp_ai_initialize(IWTSPlugin* plugin,
|
||||
IWTSVirtualChannelManager* manager) {
|
||||
|
||||
/* Allocate new listener callback */
|
||||
guac_rdp_ai_listener_callback* ai_listener_callback =
|
||||
calloc(1, sizeof(guac_rdp_ai_listener_callback));
|
||||
|
||||
/* Ensure listener callback is freed when plugin is terminated */
|
||||
guac_rdp_ai_plugin* ai_plugin = (guac_rdp_ai_plugin*) plugin;
|
||||
ai_plugin->listener_callback = ai_listener_callback;
|
||||
|
||||
/* Init listener callback with data from plugin */
|
||||
ai_listener_callback->client = ai_plugin->client;
|
||||
ai_listener_callback->parent.OnNewChannelConnection =
|
||||
guac_rdp_ai_new_connection;
|
||||
|
||||
/* Register listener for "AUDIO_INPUT" channel */
|
||||
manager->CreateListener(manager, "AUDIO_INPUT", 0,
|
||||
(IWTSListenerCallback*) ai_listener_callback, NULL);
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback which is invoked when all connections to the AUDIO_INPUT plugin
|
||||
* have closed and the plugin is being unloaded.
|
||||
*
|
||||
* @param plugin
|
||||
* The AUDIO_INPUT plugin being unloaded.
|
||||
*
|
||||
* @return
|
||||
* Always zero.
|
||||
*/
|
||||
static int guac_rdp_ai_terminated(IWTSPlugin* plugin) {
|
||||
|
||||
guac_rdp_ai_plugin* ai_plugin = (guac_rdp_ai_plugin*) plugin;
|
||||
guac_client* client = ai_plugin->client;
|
||||
|
||||
/* Free all non-FreeRDP data */
|
||||
free(ai_plugin->listener_callback);
|
||||
free(ai_plugin);
|
||||
|
||||
guac_client_log(client, GUAC_LOG_DEBUG, "AUDIO_INPUT plugin unloaded.");
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry point for AUDIO_INPUT dynamic virtual channel.
|
||||
*/
|
||||
int DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints) {
|
||||
|
||||
/* Pull guac_client from arguments */
|
||||
#ifdef HAVE_ADDIN_ARGV
|
||||
ADDIN_ARGV* args = pEntryPoints->GetPluginData(pEntryPoints);
|
||||
guac_client* client = (guac_client*) guac_rdp_string_to_ptr(args->argv[1]);
|
||||
#else
|
||||
RDP_PLUGIN_DATA* data = pEntryPoints->GetPluginData(pEntryPoints);
|
||||
guac_client* client = (guac_client*) guac_rdp_string_to_ptr(data->data[1]);
|
||||
#endif
|
||||
|
||||
/* Pull previously-allocated plugin */
|
||||
guac_rdp_ai_plugin* ai_plugin = (guac_rdp_ai_plugin*)
|
||||
pEntryPoints->GetPlugin(pEntryPoints, "guacai");
|
||||
|
||||
/* If no such plugin allocated, allocate and register it now */
|
||||
if (ai_plugin == NULL) {
|
||||
|
||||
/* Init plugin callbacks and data */
|
||||
ai_plugin = calloc(1, sizeof(guac_rdp_ai_plugin));
|
||||
ai_plugin->parent.Initialize = guac_rdp_ai_initialize;
|
||||
ai_plugin->parent.Terminated = guac_rdp_ai_terminated;
|
||||
ai_plugin->client = client;
|
||||
|
||||
/* Register plugin as "guacai" for later retrieval */
|
||||
pEntryPoints->RegisterPlugin(pEntryPoints, "guacai",
|
||||
(IWTSPlugin*) ai_plugin);
|
||||
|
||||
guac_client_log(client, GUAC_LOG_DEBUG, "AUDIO_INPUT plugin loaded.");
|
||||
}
|
||||
|
||||
return 1;
|
||||
|
||||
}
|
||||
|
108
src/protocols/rdp/guac_ai/ai_service.h
Normal file
108
src/protocols/rdp/guac_ai/ai_service.h
Normal file
@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#ifndef GUAC_RDP_AI_SERVICE_H
|
||||
#define GUAC_RDP_AI_SERVICE_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <freerdp/freerdp.h>
|
||||
#include <freerdp/constants.h>
|
||||
#include <freerdp/dvc.h>
|
||||
#include <guacamole/client.h>
|
||||
|
||||
/**
|
||||
* Extended version of the IWTSListenerCallback structure, providing additional
|
||||
* access to Guacamole-specific data. The IWTSListenerCallback provides access
|
||||
* to callbacks related to the receipt of new connections to the AUDIO_INPUT
|
||||
* channel.
|
||||
*/
|
||||
typedef struct guac_rdp_ai_listener_callback {
|
||||
|
||||
/**
|
||||
* The parent IWTSListenerCallback structure that this structure extends.
|
||||
* THIS MEMBER MUST BE FIRST!
|
||||
*/
|
||||
IWTSListenerCallback parent;
|
||||
|
||||
/**
|
||||
* The guac_client instance associated with the RDP connection using the
|
||||
* AUDIO_INPUT plugin.
|
||||
*/
|
||||
guac_client* client;
|
||||
|
||||
} guac_rdp_ai_listener_callback;
|
||||
|
||||
/**
|
||||
* Extended version of the IWTSVirtualChannelCallback structure, providing
|
||||
* additional access to Guacamole-specific data. The IWTSVirtualChannelCallback
|
||||
* provides access to callbacks related to an active connection to the
|
||||
* AUDIO_INPUT channel, including receipt of data.
|
||||
*/
|
||||
typedef struct guac_rdp_ai_channel_callback {
|
||||
|
||||
/**
|
||||
* The parent IWTSVirtualChannelCallback structure that this structure
|
||||
* extends. THIS MEMBER MUST BE FIRST!
|
||||
*/
|
||||
IWTSVirtualChannelCallback parent;
|
||||
|
||||
/**
|
||||
* The actual virtual channel instance along which the AUDIO_INPUT plugin
|
||||
* should send any responses.
|
||||
*/
|
||||
IWTSVirtualChannel* channel;
|
||||
|
||||
/**
|
||||
* The guac_client instance associated with the RDP connection using the
|
||||
* AUDIO_INPUT plugin.
|
||||
*/
|
||||
guac_client* client;
|
||||
|
||||
} guac_rdp_ai_channel_callback;
|
||||
|
||||
/**
|
||||
* All data associated with Guacamole's AUDIO_INPUT plugin for FreeRDP.
|
||||
*/
|
||||
typedef struct guac_rdp_ai_plugin {
|
||||
|
||||
/**
|
||||
* The parent IWTSPlugin structure that this structure extends. THIS
|
||||
* MEMBER MUST BE FIRST!
|
||||
*/
|
||||
IWTSPlugin parent;
|
||||
|
||||
/**
|
||||
* The listener callback structure allocated when the AUDIO_INPUT plugin
|
||||
* was loaded, if any. If the plugin did not fully load, this will be NULL.
|
||||
* If non-NULL, this callback structure must be freed when the plugin is
|
||||
* terminated.
|
||||
*/
|
||||
guac_rdp_ai_listener_callback* listener_callback;
|
||||
|
||||
/**
|
||||
* The guac_client instance associated with the RDP connection using the
|
||||
* AUDIO_INPUT plugin.
|
||||
*/
|
||||
guac_client* client;
|
||||
|
||||
} guac_rdp_ai_plugin;
|
||||
|
||||
#endif
|
||||
|
55
src/protocols/rdp/ptr_string.c
Normal file
55
src/protocols/rdp/ptr_string.c
Normal file
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "ptr_string.h"
|
||||
|
||||
#include <guacamole/client.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/**
|
||||
* The maximum number of bytes required to represent a pointer printed using
|
||||
* printf()'s "%p". This will be the size of the hex prefix ("0x"), null
|
||||
* terminator, plus two bytes for every byte required by a pointer.
|
||||
*/
|
||||
#define GUAC_RDP_PTR_STRING_LENGTH (sizeof("0x") + (sizeof(void*) * 2))
|
||||
|
||||
char* guac_rdp_ptr_to_string(void* data) {
|
||||
|
||||
/* Convert pointer to string */
|
||||
char* str = malloc(GUAC_RDP_PTR_STRING_LENGTH);
|
||||
sprintf(str, "%p", data);
|
||||
|
||||
return str;
|
||||
|
||||
}
|
||||
|
||||
void* guac_rdp_string_to_ptr(const char* str) {
|
||||
|
||||
void* data;
|
||||
|
||||
/* Convert string to pointer */
|
||||
sscanf(str, "%p", &data);
|
||||
|
||||
return data;
|
||||
|
||||
}
|
||||
|
57
src/protocols/rdp/ptr_string.h
Normal file
57
src/protocols/rdp/ptr_string.h
Normal file
@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#ifndef GUAC_RDP_PTR_STRING_H
|
||||
#define GUAC_RDP_PTR_STRING_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <guacamole/client.h>
|
||||
|
||||
/**
|
||||
* Converts the given string back into a void pointer. The string MUST have
|
||||
* been produced via guac_rdp_ptr_to_string().
|
||||
*
|
||||
* @param str
|
||||
* The string to convert back to a pointer.
|
||||
*
|
||||
* @return
|
||||
* The pointer value of the given string, as originally passed to
|
||||
* guac_rdp_ptr_to_string().
|
||||
*/
|
||||
void* guac_rdp_string_to_ptr(const char* str);
|
||||
|
||||
/**
|
||||
* Converts a void pointer into a string representation, safe for use with
|
||||
* parts of the FreeRDP API which provide only for passing arbitrary strings,
|
||||
* despite being within the same memory area. The returned string must
|
||||
* eventually be freed with a call to free().
|
||||
*
|
||||
* @param data
|
||||
* The void pointer to convert to a string.
|
||||
*
|
||||
* @return
|
||||
* A newly-allocated string containing the string representation of the
|
||||
* given void pointer. This string must eventually be freed with a call to
|
||||
* free().
|
||||
*/
|
||||
char* guac_rdp_ptr_to_string(void* data);
|
||||
|
||||
#endif
|
||||
|
@ -19,7 +19,9 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "audio_input.h"
|
||||
#include "client.h"
|
||||
#include "dvc.h"
|
||||
#include "guac_cursor.h"
|
||||
#include "guac_display.h"
|
||||
#include "guac_recording.h"
|
||||
@ -211,6 +213,7 @@ BOOL rdp_freerdp_pre_connect(freerdp* instance) {
|
||||
rdpPrimaryUpdate* primary;
|
||||
CLRCONV* clrconv;
|
||||
|
||||
guac_rdp_dvc_list* dvc_list = guac_rdp_dvc_list_alloc();
|
||||
|
||||
#ifdef HAVE_FREERDP_REGISTER_ADDIN_PROVIDER
|
||||
/* Init FreeRDP add-in provider */
|
||||
@ -224,23 +227,17 @@ BOOL rdp_freerdp_pre_connect(freerdp* instance) {
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT
|
||||
/* Load required plugins if display update is enabled */
|
||||
if (settings->resize_method == GUAC_RESIZE_DISPLAY_UPDATE) {
|
||||
|
||||
/* Load virtual channel management plugin (needed by display update) */
|
||||
if (freerdp_channels_load_plugin(channels, instance->settings,
|
||||
"drdynvc", instance->settings))
|
||||
guac_client_log(client, GUAC_LOG_WARNING,
|
||||
"Failed to load drdynvc plugin. Display update support "
|
||||
"will be disabled.");
|
||||
|
||||
/* Init display update plugin if "drdynvc" was loaded successfully */
|
||||
else
|
||||
guac_rdp_disp_load_plugin(instance->context);
|
||||
|
||||
}
|
||||
/* Load "disp" plugin for display update */
|
||||
if (settings->resize_method == GUAC_RESIZE_DISPLAY_UPDATE)
|
||||
guac_rdp_disp_load_plugin(instance->context, dvc_list);
|
||||
#endif
|
||||
|
||||
/* Load "AUDIO_INPUT" plugin for audio input*/
|
||||
if (settings->enable_audio_input) {
|
||||
rdp_client->audio_input = guac_rdp_audio_buffer_alloc();
|
||||
guac_rdp_audio_load_plugin(instance->context, dvc_list);
|
||||
}
|
||||
|
||||
/* Load clipboard plugin */
|
||||
if (freerdp_channels_load_plugin(channels, instance->settings,
|
||||
"cliprdr", NULL))
|
||||
@ -326,6 +323,15 @@ BOOL rdp_freerdp_pre_connect(freerdp* instance) {
|
||||
|
||||
}
|
||||
|
||||
/* Load DRDYNVC plugin if required */
|
||||
if (guac_rdp_load_drdynvc(instance->context, dvc_list))
|
||||
guac_client_log(client, GUAC_LOG_WARNING,
|
||||
"Failed to load drdynvc plugin. Display update and audio "
|
||||
"input support will be disabled.");
|
||||
|
||||
/* Dynamic virtual channel list is no longer needed */
|
||||
guac_rdp_dvc_list_free(dvc_list);
|
||||
|
||||
/* Init color conversion structure */
|
||||
clrconv = calloc(1, sizeof(CLRCONV));
|
||||
clrconv->alpha = 1;
|
||||
|
@ -22,6 +22,7 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "audio_input.h"
|
||||
#include "guac_clipboard.h"
|
||||
#include "guac_display.h"
|
||||
#include "guac_surface.h"
|
||||
@ -120,6 +121,11 @@ typedef struct guac_rdp_client {
|
||||
*/
|
||||
guac_audio_stream* audio;
|
||||
|
||||
/**
|
||||
* Audio input buffer, if audio input is enabled.
|
||||
*/
|
||||
guac_rdp_audio_buffer* audio_input;
|
||||
|
||||
/**
|
||||
* The filesystem being shared, if any.
|
||||
*/
|
||||
|
@ -19,6 +19,7 @@
|
||||
|
||||
#include "config.h"
|
||||
#include "client.h"
|
||||
#include "dvc.h"
|
||||
#include "rdp.h"
|
||||
#include "rdp_disp.h"
|
||||
#include "rdp_settings.h"
|
||||
@ -54,20 +55,14 @@ void guac_rdp_disp_free(guac_rdp_disp* disp) {
|
||||
free(disp);
|
||||
}
|
||||
|
||||
void guac_rdp_disp_load_plugin(rdpContext* context) {
|
||||
void guac_rdp_disp_load_plugin(rdpContext* context, guac_rdp_dvc_list* list) {
|
||||
|
||||
#ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT
|
||||
#ifdef HAVE_RDPSETTINGS_SUPPORTDISPLAYCONTROL
|
||||
context->settings->SupportDisplayControl = TRUE;
|
||||
#endif
|
||||
|
||||
/* Add "disp" channel */
|
||||
ADDIN_ARGV* args = malloc(sizeof(ADDIN_ARGV));
|
||||
args->argc = 1;
|
||||
args->argv = malloc(sizeof(char**) * 1);
|
||||
args->argv[0] = strdup("disp");
|
||||
freerdp_dynamic_channel_collection_add(context->settings, args);
|
||||
#endif
|
||||
guac_rdp_dvc_list_add(list, "disp", NULL);
|
||||
|
||||
}
|
||||
|
||||
|
@ -20,6 +20,7 @@
|
||||
#ifndef GUAC_RDP_DISP_H
|
||||
#define GUAC_RDP_DISP_H
|
||||
|
||||
#include "dvc.h"
|
||||
#include "rdp_settings.h"
|
||||
|
||||
#include <freerdp/freerdp.h>
|
||||
@ -96,13 +97,25 @@ guac_rdp_disp* guac_rdp_disp_alloc();
|
||||
void guac_rdp_disp_free(guac_rdp_disp* disp);
|
||||
|
||||
/**
|
||||
* Loads the "disp" plugin for FreeRDP. It is still up to external code to
|
||||
* detect when the "disp" channel is connected, and update the guac_rdp_disp
|
||||
* with a call to guac_rdp_disp_connect().
|
||||
*
|
||||
* @param context The rdpContext associated with the active RDP session.
|
||||
*/
|
||||
void guac_rdp_disp_load_plugin(rdpContext* context);
|
||||
/**
|
||||
* Adds FreeRDP's "disp" plugin to the list of dynamic virtual channel plugins
|
||||
* to be loaded by FreeRDP's "drdynvc" plugin. The plugin will only be loaded
|
||||
* once guac_rdp_load_drdynvc() is invoked with the guac_rdp_dvc_list passed to
|
||||
* this function. The "disp" plugin ultimately adds support for the Display
|
||||
* Update channel. NOTE: It is still up to external code to detect when the
|
||||
* "disp" channel is connected, and update the guac_rdp_disp with a call to
|
||||
* guac_rdp_disp_connect().
|
||||
*
|
||||
* @param context
|
||||
* The rdpContext associated with the active RDP session.
|
||||
*
|
||||
* @param list
|
||||
* The guac_rdp_dvc_list to which the "disp" plugin should be added, such
|
||||
* that it may later be loaded by guac_rdp_load_drdynvc().
|
||||
*/
|
||||
void guac_rdp_disp_load_plugin(rdpContext* context, guac_rdp_dvc_list* list);
|
||||
|
||||
#ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT
|
||||
/**
|
||||
|
@ -90,6 +90,7 @@ const char* GUAC_RDP_CLIENT_ARGS[] = {
|
||||
"recording-name",
|
||||
"create-recording-path",
|
||||
"resize-method",
|
||||
"enable-audio-input",
|
||||
|
||||
NULL
|
||||
};
|
||||
@ -378,6 +379,12 @@ enum RDP_ARGS_IDX {
|
||||
*/
|
||||
IDX_RESIZE_METHOD,
|
||||
|
||||
/**
|
||||
* "true" if audio input (microphone) should be enabled for the RDP
|
||||
* connection, "false" or blank otherwise.
|
||||
*/
|
||||
IDX_ENABLE_AUDIO_INPUT,
|
||||
|
||||
RDP_ARGS_COUNT
|
||||
};
|
||||
|
||||
@ -739,6 +746,11 @@ guac_rdp_settings* guac_rdp_parse_args(guac_user* user,
|
||||
settings->resize_method = GUAC_RESIZE_NONE;
|
||||
}
|
||||
|
||||
/* Audio input enable/disable */
|
||||
settings->enable_audio_input =
|
||||
guac_user_parse_args_boolean(user, GUAC_RDP_CLIENT_ARGS, argv,
|
||||
IDX_ENABLE_AUDIO_INPUT, 0);
|
||||
|
||||
/* Success */
|
||||
return settings;
|
||||
|
||||
@ -981,6 +993,17 @@ void guac_rdp_push_settings(guac_rdp_settings* guac_settings, freerdp* rdp) {
|
||||
#ifdef HAVE_RDPSETTINGS_AUDIOPLAYBACK
|
||||
rdp_settings->AudioPlayback = guac_settings->audio_enabled;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/* Audio capture */
|
||||
#ifdef LEGACY_RDPSETTINGS
|
||||
#ifdef HAVE_RDPSETTINGS_AUDIOCAPTURE
|
||||
rdp_settings->audio_capture = guac_settings->enable_audio_input;
|
||||
#endif
|
||||
#else
|
||||
#ifdef HAVE_RDPSETTINGS_AUDIOCAPTURE
|
||||
rdp_settings->AudioCapture = guac_settings->enable_audio_input;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/* Device redirection */
|
||||
|
@ -378,6 +378,11 @@ typedef struct guac_rdp_settings {
|
||||
*/
|
||||
guac_rdp_resize_method resize_method;
|
||||
|
||||
/**
|
||||
* Whether audio input (microphone) is enabled.
|
||||
*/
|
||||
int enable_audio_input;
|
||||
|
||||
} guac_rdp_settings;
|
||||
|
||||
/**
|
||||
|
@ -19,6 +19,7 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "audio_input.h"
|
||||
#include "input.h"
|
||||
#include "guac_display.h"
|
||||
#include "user.h"
|
||||
@ -65,6 +66,10 @@ int guac_rdp_user_join_handler(guac_user* user, int argc, char** argv) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Handle inbound audio streams if audio input is enabled */
|
||||
if (settings->enable_audio_input)
|
||||
user->audio_handler = guac_rdp_audio_handler;
|
||||
|
||||
}
|
||||
|
||||
/* If not owner, synchronize with current state */
|
||||
|
Loading…
Reference in New Issue
Block a user