GUACAMOLE-25: Merge RDP audio input addition."

This commit is contained in:
James Muehlner 2016-06-02 11:44:16 -07:00
commit 995b6d669a
22 changed files with 2440 additions and 35 deletions

View File

@ -436,6 +436,7 @@ then
have_freerdp=yes have_freerdp=yes
legacy_freerdp_extensions=no legacy_freerdp_extensions=no
rdpsettings_interface=unknown rdpsettings_interface=unknown
rdpsettings_audiocapture=yes
rdpsettings_audioplayback=yes rdpsettings_audioplayback=yes
rdpsettings_deviceredirection=yes rdpsettings_deviceredirection=yes
freerdp_interface=unknown freerdp_interface=unknown
@ -578,6 +579,15 @@ then
[#include <freerdp/channels/channels.h>]) [#include <freerdp/channels/channels.h>])
fi 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 # FreeRDP: WinPR
# #
@ -686,6 +696,11 @@ then
[rdpsettings_audioplayback=no], [rdpsettings_audioplayback=no],
[[#include <freerdp/freerdp.h>]]) [[#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 # Legacy interface may not have DeviceRedirection settings
AC_CHECK_MEMBERS([rdpSettings.device_redirection],, AC_CHECK_MEMBERS([rdpSettings.device_redirection],,
[rdpsettings_deviceredirection=no], [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]) [Whether the rdpSettings structure has AudioPlayback settings])
fi 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 # Activate device redirection settings if present
if test "x${have_freerdp}" = "xyes" -a "x${rdpsettings_deviceredirection}" = "xyes"; then if test "x${have_freerdp}" = "xyes" -a "x${rdpsettings_deviceredirection}" = "xyes"; then
AC_DEFINE([HAVE_RDPSETTINGS_DEVICEREDIRECTION],, AC_DEFINE([HAVE_RDPSETTINGS_DEVICEREDIRECTION],,
@ -752,6 +773,32 @@ then
[Whether the legacy rdpBitmap API was found])]) [Whether the legacy rdpBitmap API was found])])
fi 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 # FreeRDP: Decompression function variants
# #

View File

@ -87,6 +87,12 @@ typedef enum guac_protocol_status {
*/ */
GUAC_PROTOCOL_STATUS_RESOURCE_CONFLICT = 0x205, 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 * The operation could not be performed because bad parameters were
* given. * given.

View File

@ -24,8 +24,11 @@ lib_LTLIBRARIES = libguac-client-rdp.la
libguac_client_rdp_la_SOURCES = \ libguac_client_rdp_la_SOURCES = \
_generated_keymaps.c \ _generated_keymaps.c \
audio_input.c \
client.c \ client.c \
dvc.c \
input.c \ input.c \
ptr_string.c \
rdp.c \ rdp.c \
rdp_bitmap.c \ rdp_bitmap.c \
rdp_cliprdr.c \ rdp_cliprdr.c \
@ -44,6 +47,12 @@ libguac_client_rdp_la_SOURCES = \
unicode.c \ unicode.c \
user.c user.c
guacai_sources = \
audio_input.c \
guac_ai/ai_messages.c \
guac_ai/ai_service.c \
ptr_string.c
guacsvc_sources = \ guacsvc_sources = \
guac_svc/svc_service.c \ guac_svc/svc_service.c \
rdp_svc.c rdp_svc.c
@ -68,6 +77,8 @@ guacdr_sources = \
noinst_HEADERS = \ noinst_HEADERS = \
compat/client-cliprdr.h \ compat/client-cliprdr.h \
compat/rail.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.h \
guac_rdpdr/rdpdr_fs_messages_dir_info.h \ guac_rdpdr/rdpdr_fs_messages_dir_info.h \
guac_rdpdr/rdpdr_fs_messages_file_info.h \ guac_rdpdr/rdpdr_fs_messages_file_info.h \
@ -79,8 +90,11 @@ noinst_HEADERS = \
guac_rdpsnd/rdpsnd_messages.h \ guac_rdpsnd/rdpsnd_messages.h \
guac_rdpsnd/rdpsnd_service.h \ guac_rdpsnd/rdpsnd_service.h \
guac_svc/svc_service.h \ guac_svc/svc_service.h \
audio_input.h \
client.h \ client.h \
dvc.h \
input.h \ input.h \
ptr_string.h \
rdp.h \ rdp.h \
rdp_bitmap.h \ rdp_bitmap.h \
rdp_cliprdr.h \ rdp_cliprdr.h \
@ -104,6 +118,7 @@ noinst_HEADERS = \
if ! ENABLE_WINPR if ! ENABLE_WINPR
noinst_HEADERS += compat/winpr-stream.h compat/winpr-wtypes.h noinst_HEADERS += compat/winpr-stream.h compat/winpr-wtypes.h
libguac_client_rdp_la_SOURCES += compat/winpr-stream.c libguac_client_rdp_la_SOURCES += compat/winpr-stream.c
guacai_sources += compat/winpr-stream.c
guacsvc_sources += compat/winpr-stream.c guacsvc_sources += compat/winpr-stream.c
guacsnd_sources += compat/winpr-stream.c guacsnd_sources += compat/winpr-stream.c
guacdr_sources += compat/winpr-stream.c guacdr_sources += compat/winpr-stream.c
@ -148,6 +163,25 @@ guacdr_libadd = \
@COMMON_LTLIB@ \ @COMMON_LTLIB@ \
@LIBGUAC_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 # RDPSND
# #
@ -224,10 +258,16 @@ if LEGACY_FREERDP_EXTENSIONS
# FreeRDP 1.0-style extensions # FreeRDP 1.0-style extensions
freerdp_LTLIBRARIES = \ freerdp_LTLIBRARIES = \
guacai.la \
guacdr.la \ guacdr.la \
guacsnd.la \ guacsnd.la \
guacsvc.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_SOURCES = ${guacdr_sources}
guacdr_la_CFLAGS = ${guacdr_cflags} guacdr_la_CFLAGS = ${guacdr_cflags}
guacdr_la_LDFLAGS = ${guacdr_ldflags} guacdr_la_LDFLAGS = ${guacdr_ldflags}
@ -247,10 +287,16 @@ else
# FreeRDP 1.1 (and hopefully onward) extensions # FreeRDP 1.1 (and hopefully onward) extensions
freerdp_LTLIBRARIES = \ freerdp_LTLIBRARIES = \
guacai-client.la \
guacdr-client.la \ guacdr-client.la \
guacsnd-client.la \ guacsnd-client.la \
guacsvc-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_SOURCES = ${guacdr_sources}
guacdr_client_la_CFLAGS = ${guacdr_cflags} guacdr_client_la_CFLAGS = ${guacdr_cflags}
guacdr_client_la_LDFLAGS = ${guacdr_ldflags} guacdr_client_la_LDFLAGS = ${guacdr_ldflags}

View 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);
}

View 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

View File

@ -19,6 +19,7 @@
#include "config.h" #include "config.h"
#include "audio_input.h"
#include "client.h" #include "client.h"
#include "rdp.h" #include "rdp.h"
#include "rdp_disp.h" #include "rdp_disp.h"
@ -129,6 +130,10 @@ int guac_rdp_client_free_handler(guac_client* client) {
if (rdp_client->audio != NULL) if (rdp_client->audio != NULL)
guac_audio_stream_free(rdp_client->audio); 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 */ /* Free client data */
guac_common_clipboard_free(rdp_client->clipboard); guac_common_clipboard_free(rdp_client->clipboard);
free(rdp_client); free(rdp_client);

View File

@ -22,17 +22,26 @@
#include "winpr-stream.h" #include "winpr-stream.h"
#include "winpr-wtypes.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) { wStream* Stream_New(BYTE* buffer, size_t size) {
return stream_new(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) { void Stream_Free(wStream* s, BOOL bFreeBuffer) {
/* Disassociate buffer if it will be freed externally */
if (!bFreeBuffer)
stream_detach(s);
stream_free(s); stream_free(s);
} }

183
src/protocols/rdp/dvc.c Normal file
View 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
View 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

View 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.");
}

View 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

View 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;
}

View 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

View 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;
}

View 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

View File

@ -19,7 +19,9 @@
#include "config.h" #include "config.h"
#include "audio_input.h"
#include "client.h" #include "client.h"
#include "dvc.h"
#include "guac_cursor.h" #include "guac_cursor.h"
#include "guac_display.h" #include "guac_display.h"
#include "guac_recording.h" #include "guac_recording.h"
@ -211,6 +213,7 @@ BOOL rdp_freerdp_pre_connect(freerdp* instance) {
rdpPrimaryUpdate* primary; rdpPrimaryUpdate* primary;
CLRCONV* clrconv; CLRCONV* clrconv;
guac_rdp_dvc_list* dvc_list = guac_rdp_dvc_list_alloc();
#ifdef HAVE_FREERDP_REGISTER_ADDIN_PROVIDER #ifdef HAVE_FREERDP_REGISTER_ADDIN_PROVIDER
/* Init FreeRDP add-in provider */ /* Init FreeRDP add-in provider */
@ -224,23 +227,17 @@ BOOL rdp_freerdp_pre_connect(freerdp* instance) {
#endif #endif
#ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT #ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT
/* Load required plugins if display update is enabled */ /* Load "disp" plugin for display update */
if (settings->resize_method == GUAC_RESIZE_DISPLAY_UPDATE) { if (settings->resize_method == GUAC_RESIZE_DISPLAY_UPDATE)
guac_rdp_disp_load_plugin(instance->context, dvc_list);
/* 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);
}
#endif #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 */ /* Load clipboard plugin */
if (freerdp_channels_load_plugin(channels, instance->settings, if (freerdp_channels_load_plugin(channels, instance->settings,
"cliprdr", NULL)) "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 */ /* Init color conversion structure */
clrconv = calloc(1, sizeof(CLRCONV)); clrconv = calloc(1, sizeof(CLRCONV));
clrconv->alpha = 1; clrconv->alpha = 1;

View File

@ -22,6 +22,7 @@
#include "config.h" #include "config.h"
#include "audio_input.h"
#include "guac_clipboard.h" #include "guac_clipboard.h"
#include "guac_display.h" #include "guac_display.h"
#include "guac_surface.h" #include "guac_surface.h"
@ -120,6 +121,11 @@ typedef struct guac_rdp_client {
*/ */
guac_audio_stream* audio; guac_audio_stream* audio;
/**
* Audio input buffer, if audio input is enabled.
*/
guac_rdp_audio_buffer* audio_input;
/** /**
* The filesystem being shared, if any. * The filesystem being shared, if any.
*/ */

View File

@ -19,6 +19,7 @@
#include "config.h" #include "config.h"
#include "client.h" #include "client.h"
#include "dvc.h"
#include "rdp.h" #include "rdp.h"
#include "rdp_disp.h" #include "rdp_disp.h"
#include "rdp_settings.h" #include "rdp_settings.h"
@ -54,20 +55,14 @@ void guac_rdp_disp_free(guac_rdp_disp* disp) {
free(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 #ifdef HAVE_RDPSETTINGS_SUPPORTDISPLAYCONTROL
context->settings->SupportDisplayControl = TRUE; context->settings->SupportDisplayControl = TRUE;
#endif #endif
/* Add "disp" channel */ /* Add "disp" channel */
ADDIN_ARGV* args = malloc(sizeof(ADDIN_ARGV)); guac_rdp_dvc_list_add(list, "disp", NULL);
args->argc = 1;
args->argv = malloc(sizeof(char**) * 1);
args->argv[0] = strdup("disp");
freerdp_dynamic_channel_collection_add(context->settings, args);
#endif
} }

View File

@ -20,6 +20,7 @@
#ifndef GUAC_RDP_DISP_H #ifndef GUAC_RDP_DISP_H
#define GUAC_RDP_DISP_H #define GUAC_RDP_DISP_H
#include "dvc.h"
#include "rdp_settings.h" #include "rdp_settings.h"
#include <freerdp/freerdp.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); 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. * @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 #ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT
/** /**

View File

@ -90,6 +90,7 @@ const char* GUAC_RDP_CLIENT_ARGS[] = {
"recording-name", "recording-name",
"create-recording-path", "create-recording-path",
"resize-method", "resize-method",
"enable-audio-input",
NULL NULL
}; };
@ -378,6 +379,12 @@ enum RDP_ARGS_IDX {
*/ */
IDX_RESIZE_METHOD, 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 RDP_ARGS_COUNT
}; };
@ -739,6 +746,11 @@ guac_rdp_settings* guac_rdp_parse_args(guac_user* user,
settings->resize_method = GUAC_RESIZE_NONE; 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 */ /* Success */
return settings; return settings;
@ -981,6 +993,17 @@ void guac_rdp_push_settings(guac_rdp_settings* guac_settings, freerdp* rdp) {
#ifdef HAVE_RDPSETTINGS_AUDIOPLAYBACK #ifdef HAVE_RDPSETTINGS_AUDIOPLAYBACK
rdp_settings->AudioPlayback = guac_settings->audio_enabled; rdp_settings->AudioPlayback = guac_settings->audio_enabled;
#endif #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 #endif
/* Device redirection */ /* Device redirection */

View File

@ -378,6 +378,11 @@ typedef struct guac_rdp_settings {
*/ */
guac_rdp_resize_method resize_method; guac_rdp_resize_method resize_method;
/**
* Whether audio input (microphone) is enabled.
*/
int enable_audio_input;
} guac_rdp_settings; } guac_rdp_settings;
/** /**

View File

@ -19,6 +19,7 @@
#include "config.h" #include "config.h"
#include "audio_input.h"
#include "input.h" #include "input.h"
#include "guac_display.h" #include "guac_display.h"
#include "user.h" #include "user.h"
@ -65,6 +66,10 @@ int guac_rdp_user_join_handler(guac_user* user, int argc, char** argv) {
return 1; 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 */ /* If not owner, synchronize with current state */