diff --git a/src/protocols/rdp/Makefile.am b/src/protocols/rdp/Makefile.am index 0d07a747..f76c33ef 100644 --- a/src/protocols/rdp/Makefile.am +++ b/src/protocols/rdp/Makefile.am @@ -47,6 +47,7 @@ libguac_client_rdp_la_SOURCES = \ user.c guacai_sources = \ + audio_input.c \ guac_ai/ai_messages.c \ guac_ai/ai_service.c \ ptr_string.c diff --git a/src/protocols/rdp/audio_input.c b/src/protocols/rdp/audio_input.c index 9d5d5055..1bea175b 100644 --- a/src/protocols/rdp/audio_input.c +++ b/src/protocols/rdp/audio_input.c @@ -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, 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; } 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; } @@ -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); +} + diff --git a/src/protocols/rdp/audio_input.h b/src/protocols/rdp/audio_input.h index d32cda1e..c89de153 100644 --- a/src/protocols/rdp/audio_input.h +++ b/src/protocols/rdp/audio_input.h @@ -25,6 +25,132 @@ #include #include +/** + * 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). */ diff --git a/src/protocols/rdp/client.c b/src/protocols/rdp/client.c index 51321257..362f0aad 100644 --- a/src/protocols/rdp/client.c +++ b/src/protocols/rdp/client.c @@ -19,6 +19,7 @@ #include "config.h" +#include "audio_input.h" #include "client.h" #include "rdp.h" #include "rdp_disp.h" @@ -129,6 +130,10 @@ int guac_rdp_client_free_handler(guac_client* client) { if (rdp_client->audio != NULL) guac_audio_stream_free(rdp_client->audio); + /* Clean up audio input buffer, if allocated */ + if (rdp_client->audio_input != NULL) + guac_rdp_audio_buffer_free(rdp_client->audio_input); + /* Free client data */ guac_common_clipboard_free(rdp_client->clipboard); free(rdp_client); diff --git a/src/protocols/rdp/guac_ai/ai_messages.c b/src/protocols/rdp/guac_ai/ai_messages.c index 8b2d0d71..b0eb6cfc 100644 --- a/src/protocols/rdp/guac_ai/ai_messages.c +++ b/src/protocols/rdp/guac_ai/ai_messages.c @@ -20,6 +20,7 @@ #include "config.h" #include "ai_messages.h" +#include "audio_input.h" #include "rdp.h" #include @@ -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 * 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, 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; @@ -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_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, 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"); } diff --git a/src/protocols/rdp/guac_ai/ai_service.c b/src/protocols/rdp/guac_ai/ai_service.c index d6538bcd..945f0398 100644 --- a/src/protocols/rdp/guac_ai/ai_service.c +++ b/src/protocols/rdp/guac_ai/ai_service.c @@ -21,6 +21,7 @@ #include "ai_messages.h" #include "ai_service.h" +#include "audio_input.h" #include "ptr_string.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*) 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(ai_channel_callback->client, GUAC_LOG_DEBUG, + 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; diff --git a/src/protocols/rdp/rdp.c b/src/protocols/rdp/rdp.c index 7859e209..f4613692 100644 --- a/src/protocols/rdp/rdp.c +++ b/src/protocols/rdp/rdp.c @@ -244,8 +244,10 @@ BOOL rdp_freerdp_pre_connect(freerdp* instance) { #endif /* 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); + } } diff --git a/src/protocols/rdp/rdp.h b/src/protocols/rdp/rdp.h index 9325a183..02d9ac6e 100644 --- a/src/protocols/rdp/rdp.h +++ b/src/protocols/rdp/rdp.h @@ -22,6 +22,7 @@ #include "config.h" +#include "audio_input.h" #include "guac_clipboard.h" #include "guac_display.h" #include "guac_surface.h" @@ -120,6 +121,11 @@ typedef struct guac_rdp_client { */ guac_audio_stream* audio; + /** + * Audio input buffer, if audio input is enabled. + */ + guac_rdp_audio_buffer* audio_input; + /** * The filesystem being shared, if any. */