GUACAMOLE-25: Buffer and send Data PDUs as necessary.
This commit is contained in:
parent
63cd2ce512
commit
9d5871a3c8
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -25,6 +25,132 @@
|
||||
#include <freerdp/freerdp.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).
|
||||
*/
|
||||
|
@ -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);
|
||||
|
@ -20,6 +20,7 @@
|
||||
#include "config.h"
|
||||
|
||||
#include "ai_messages.h"
|
||||
#include "audio_input.h"
|
||||
#include "rdp.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
|
||||
* 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");
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
*/
|
||||
|
Loading…
Reference in New Issue
Block a user