GUACAMOLE-25: Buffer and send Data PDUs as necessary.

This commit is contained in:
Michael Jumper 2016-04-18 00:32:51 -07:00
parent 63cd2ce512
commit 9d5871a3c8
8 changed files with 270 additions and 5 deletions

View File

@ -47,6 +47,7 @@ libguac_client_rdp_la_SOURCES = \
user.c user.c
guacai_sources = \ guacai_sources = \
audio_input.c \
guac_ai/ai_messages.c \ guac_ai/ai_messages.c \
guac_ai/ai_service.c \ guac_ai/ai_service.c \
ptr_string.c ptr_string.c

View File

@ -51,14 +51,19 @@ int guac_rdp_audio_handler(guac_user* user, guac_stream* stream,
int guac_rdp_audio_blob_handler(guac_user* user, guac_stream* stream, int guac_rdp_audio_blob_handler(guac_user* user, guac_stream* stream,
void* data, int length) { void* data, int length) {
/* STUB */ 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; return 0;
} }
int guac_rdp_audio_end_handler(guac_user* user, guac_stream* stream) { int guac_rdp_audio_end_handler(guac_user* user, guac_stream* stream) {
/* STUB */ /* Ignore - the AUDIO_INPUT channel will simply not receive anything */
return 0; return 0;
} }
@ -77,3 +82,84 @@ void guac_rdp_audio_load_plugin(rdpContext* context) {
} }
guac_rdp_audio_buffer* guac_rdp_audio_buffer_alloc() {
return calloc(1, sizeof(guac_rdp_audio_buffer));
}
void guac_rdp_audio_buffer_begin(guac_rdp_audio_buffer* audio_buffer,
int packet_size, guac_rdp_audio_buffer_flush_handler* flush_handler,
void* data) {
/* Reset buffer state to provided values */
audio_buffer->bytes_written = 0;
audio_buffer->packet_size = packet_size;
audio_buffer->flush_handler = flush_handler;
audio_buffer->data = data;
/* Allocate new buffer */
free(audio_buffer->packet);
audio_buffer->packet = malloc(packet_size);
}
void guac_rdp_audio_buffer_write(guac_rdp_audio_buffer* audio_buffer,
char* buffer, int length) {
/* Ignore packet if there is no buffer */
if (audio_buffer->packet_size == 0 || audio_buffer->packet == NULL)
return;
/* Continuously write packets until no data remains */
while (length > 0) {
/* Calculate ideal size of chunk based on available space */
int chunk_size = audio_buffer->packet_size
- audio_buffer->bytes_written;
/* Shrink chunk size if insufficient bytes are provided */
if (length < chunk_size)
chunk_size = length;
/* Append buffer */
memcpy(audio_buffer->packet + audio_buffer->bytes_written,
buffer, chunk_size);
/* Update byte counters */
length -= chunk_size;
audio_buffer->bytes_written += chunk_size;
/* 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 */
}
void guac_rdp_audio_buffer_end(guac_rdp_audio_buffer* audio_buffer) {
/* Reset buffer state */
audio_buffer->bytes_written = 0;
audio_buffer->packet_size = 0;
audio_buffer->flush_handler = NULL;
/* Free packet (if any) */
free(audio_buffer->packet);
audio_buffer->packet = NULL;
}
void guac_rdp_audio_buffer_free(guac_rdp_audio_buffer* audio_buffer) {
free(audio_buffer->packet);
free(audio_buffer);
}

View File

@ -25,6 +25,132 @@
#include <freerdp/freerdp.h> #include <freerdp/freerdp.h>
#include <guacamole/user.h> #include <guacamole/user.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 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 {
/**
* 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;
/**
* 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();
/**
* Begins handling of audio data received via guac_rdp_audio_buffer_write() and
* allocates the necessary underlying packet buffer. Audio packets of exactly
* packet_size bytes will be flushed as available using the provided
* flush_handler.
*
* @param audio_buffer
* The audio buffer to begin.
*
* @param packet_size
* The number of bytes to include 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_size, 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). * Handler for inbound audio data (audio input).
*/ */

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

@ -20,6 +20,7 @@
#include "config.h" #include "config.h"
#include "ai_messages.h" #include "ai_messages.h"
#include "audio_input.h"
#include "rdp.h" #include "rdp.h"
#include <stdlib.h> #include <stdlib.h>
@ -112,6 +113,21 @@ static void guac_rdp_ai_send_incoming_data(IWTSVirtualChannel* channel) {
} }
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 * 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 * used by the client to indicate to the server which formats of audio it
@ -263,9 +279,22 @@ void guac_rdp_ai_process_formats(guac_client* client,
} }
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, void guac_rdp_ai_process_open(guac_client* client,
IWTSVirtualChannel* channel, wStream* stream) { 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 packet_frames;
UINT32 initial_format; UINT32 initial_format;
@ -281,12 +310,16 @@ void guac_rdp_ai_process_open(guac_client* client,
guac_rdp_ai_send_formatchange(channel, initial_format); guac_rdp_ai_send_formatchange(channel, initial_format);
guac_rdp_ai_send_open_reply(channel, 0); guac_rdp_ai_send_open_reply(channel, 0);
/* FIXME: Assuming mimetype of 16-bit 44100 Hz stereo PCM */
guac_rdp_audio_buffer_begin(audio_buffer, packet_frames * 2 * 2,
guac_rdp_ai_flush_packet, channel);
} }
void guac_rdp_ai_process_formatchange(guac_client* client, void guac_rdp_ai_process_formatchange(guac_client* client,
IWTSVirtualChannel* channel, wStream* stream) { IWTSVirtualChannel* channel, wStream* stream) {
/* STUB */ /* STUB: Should not be called as we only accept one format */
guac_client_log(client, GUAC_LOG_DEBUG, "AUDIO_INPUT: formatchange"); guac_client_log(client, GUAC_LOG_DEBUG, "AUDIO_INPUT: formatchange");
} }

View File

@ -21,6 +21,7 @@
#include "ai_messages.h" #include "ai_messages.h"
#include "ai_service.h" #include "ai_service.h"
#include "audio_input.h"
#include "ptr_string.h" #include "ptr_string.h"
#include "rdp.h" #include "rdp.h"
@ -173,10 +174,15 @@ static int guac_rdp_ai_close(IWTSVirtualChannelCallback* channel_callback) {
guac_rdp_ai_channel_callback* ai_channel_callback = guac_rdp_ai_channel_callback* ai_channel_callback =
(guac_rdp_ai_channel_callback*) 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 */ /* Log closure of AUDIO_INPUT channel */
guac_client_log(ai_channel_callback->client, GUAC_LOG_DEBUG, guac_client_log(client, GUAC_LOG_DEBUG,
"AUDIO_INPUT channel connection closed"); "AUDIO_INPUT channel connection closed");
guac_rdp_audio_buffer_end(audio_buffer);
free(ai_channel_callback); free(ai_channel_callback);
return 0; return 0;

View File

@ -244,8 +244,10 @@ BOOL rdp_freerdp_pre_connect(freerdp* instance) {
#endif #endif
/* Load "AUDIO_INPUT" plugin for audio input*/ /* Load "AUDIO_INPUT" plugin for audio input*/
if (settings->enable_audio_input) if (settings->enable_audio_input) {
rdp_client->audio_input = guac_rdp_audio_buffer_alloc();
guac_rdp_audio_load_plugin(instance->context); guac_rdp_audio_load_plugin(instance->context);
}
} }

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.
*/ */