diff --git a/src/libguac/Makefile.am b/src/libguac/Makefile.am index 6fdc0ceb..55e98af5 100644 --- a/src/libguac/Makefile.am +++ b/src/libguac/Makefile.am @@ -67,7 +67,7 @@ noinst_HEADERS = \ encode-jpeg.h \ encode-png.h \ palette.h \ - wav_encoder.h + raw_encoder.h libguac_la_SOURCES = \ audio.c \ @@ -82,18 +82,12 @@ libguac_la_SOURCES = \ plugin.c \ pool.c \ protocol.c \ + raw_encoder.c \ socket.c \ socket-fd.c \ socket-nest.c \ timestamp.c \ - unicode.c \ - wav_encoder.c - -# Compile OGG support if available -if ENABLE_OGG -libguac_la_SOURCES += ogg_encoder.c -noinst_HEADERS += ogg_encoder.h -endif + unicode.c # Compile WebP support if available if ENABLE_WEBP diff --git a/src/libguac/audio.c b/src/libguac/audio.c index 923ab969..8d340be8 100644 --- a/src/libguac/audio.c +++ b/src/libguac/audio.c @@ -22,11 +22,7 @@ #include "config.h" -#ifdef ENABLE_OGG -#include "ogg_encoder.h" -#endif - -#include "wav_encoder.h" +#include "raw_encoder.h" #include #include @@ -36,7 +32,8 @@ #include #include -guac_audio_stream* guac_audio_stream_alloc(guac_client* client, guac_audio_encoder* encoder) { +guac_audio_stream* guac_audio_stream_alloc(guac_client* client, + guac_audio_encoder* encoder, int rate, int channels, int bps) { guac_audio_stream* audio; @@ -50,17 +47,15 @@ guac_audio_stream* guac_audio_stream_alloc(guac_client* client, guac_audio_encod const char* mimetype = client->info.audio_mimetypes[i]; -#ifdef ENABLE_OGG - /* If Ogg is supported, done. */ - if (strcmp(mimetype, ogg_encoder->mimetype) == 0) { - encoder = ogg_encoder; + /* If 16-bit raw audio is supported, done. */ + if (bps == 16 && strcmp(mimetype, raw16_encoder->mimetype) == 0) { + encoder = raw16_encoder; break; } -#endif - /* If wav is supported, done. */ - if (strcmp(mimetype, wav_encoder->mimetype) == 0) { - encoder = wav_encoder; + /* If 8-bit raw audio is supported, done. */ + if (bps == 8 && strcmp(mimetype, raw8_encoder->mimetype) == 0) { + encoder = raw8_encoder; break; } @@ -73,130 +68,84 @@ guac_audio_stream* guac_audio_stream_alloc(guac_client* client, guac_audio_encod } /* Allocate stream */ - audio = (guac_audio_stream*) malloc(sizeof(guac_audio_stream)); + audio = (guac_audio_stream*) calloc(1, sizeof(guac_audio_stream)); audio->client = client; - /* Reset buffer stats */ - audio->used = 0; - audio->length = 0x40000; - - audio->encoded_data_used = 0; - audio->encoded_data_length = 0x40000; - - /* Allocate buffers */ - audio->pcm_data = malloc(audio->length); - audio->encoded_data = malloc(audio->encoded_data_length); - /* Assign encoder */ audio->encoder = encoder; audio->stream = guac_client_alloc_stream(client); - return audio; -} - -void guac_audio_stream_begin(guac_audio_stream* audio, int rate, int channels, int bps) { - /* Load PCM properties */ audio->rate = rate; audio->channels = channels; audio->bps = bps; - /* Reset write counter */ - audio->pcm_bytes_written = 0; + /* Call handler, if defined */ + if (audio->encoder->begin_handler) + audio->encoder->begin_handler(audio); - /* Call handler */ - audio->encoder->begin_handler(audio); + return audio; } -void guac_audio_stream_end(guac_audio_stream* audio) { +void guac_audio_stream_reset(guac_audio_stream* audio, + guac_audio_encoder* encoder, int rate, int channels, int bps) { - double duration; + /* Do nothing if nothing is changing */ + if ((encoder == NULL || encoder == audio->encoder) + && rate == audio->rate + && channels == audio->channels + && bps == audio->bps) { + return; + } - /* Flush stream and finish encoding */ - guac_audio_stream_flush(audio); - audio->encoder->end_handler(audio); + /* Free old encoder data */ + if (audio->encoder->end_handler) + audio->encoder->end_handler(audio); - /* Calculate duration of PCM data */ - duration = ((double) (audio->pcm_bytes_written * 1000 * 8)) - / audio->rate / audio->channels / audio->bps; + /* Assign new encoder, if changed */ + if (encoder != NULL) + audio->encoder = encoder; - /* Send audio */ - guac_protocol_send_audio(audio->client->socket, audio->stream, - audio->stream->index, audio->encoder->mimetype, duration); + /* Set PCM properties */ + audio->rate = rate; + audio->channels = channels; + audio->bps = bps; - guac_protocol_send_blob(audio->client->socket, audio->stream, - audio->encoded_data, audio->encoded_data_used); - - guac_protocol_send_end(audio->client->socket, audio->stream); - - /* Clear data */ - audio->encoded_data_used = 0; + /* Init encoder with new data */ + if (audio->encoder->begin_handler) + audio->encoder->begin_handler(audio); } void guac_audio_stream_free(guac_audio_stream* audio) { - free(audio->pcm_data); + + /* Flush stream encoding */ + guac_audio_stream_flush(audio); + + /* Clean up encoder */ + if (audio->encoder->end_handler) + audio->encoder->end_handler(audio); + + /* Free associated data */ free(audio); + } void guac_audio_stream_write_pcm(guac_audio_stream* audio, const unsigned char* data, int length) { - /* Update counter */ - audio->pcm_bytes_written += length; - - /* Resize audio buffer if necessary */ - if (length > audio->length) { - - /* Resize to double provided length */ - audio->length = length*2; - audio->pcm_data = realloc(audio->pcm_data, audio->length); - - } - - /* Flush if necessary */ - if (audio->used + length > audio->length) - guac_audio_stream_flush(audio); - - /* Append to buffer */ - memcpy(&(audio->pcm_data[audio->used]), data, length); - audio->used += length; + /* Write data */ + if (audio->encoder->write_handler) + audio->encoder->write_handler(audio, data, length); } void guac_audio_stream_flush(guac_audio_stream* audio) { - /* If data in buffer */ - if (audio->used != 0) { - - /* Write data */ - audio->encoder->write_handler(audio, - audio->pcm_data, audio->used); - - /* Reset buffer */ - audio->used = 0; - - } - -} - -void guac_audio_stream_write_encoded(guac_audio_stream* audio, - const unsigned char* data, int length) { - - /* Resize audio buffer if necessary */ - if (audio->encoded_data_used + length > audio->encoded_data_length) { - - /* Increase to double concatenated size to accomodate */ - audio->encoded_data_length = (audio->encoded_data_length + length)*2; - audio->encoded_data = realloc(audio->encoded_data, - audio->encoded_data_length); - - } - - /* Append to buffer */ - memcpy(&(audio->encoded_data[audio->encoded_data_used]), data, length); - audio->encoded_data_used += length; + /* Flush any buffered data */ + if (audio->encoder->flush_handler) + audio->encoder->flush_handler(audio); } diff --git a/src/libguac/guacamole/audio-fntypes.h b/src/libguac/guacamole/audio-fntypes.h index 0b30a7d9..7880eac7 100644 --- a/src/libguac/guacamole/audio-fntypes.h +++ b/src/libguac/guacamole/audio-fntypes.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 Glyptodon LLC + * Copyright (C) 2015 Glyptodon LLC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -36,13 +36,18 @@ */ typedef void guac_audio_encoder_begin_handler(guac_audio_stream* audio); +/** + * Handler which is called when the audio stream needs to be flushed. + */ +typedef void guac_audio_encoder_flush_handler(guac_audio_stream* audio); + /** * Handler which is called when the audio stream is closed. */ typedef void guac_audio_encoder_end_handler(guac_audio_stream* audio); /** - * Handler which is called when the audio stream is flushed. + * Handler which is called when PCM data is written to the audio stream. */ typedef void guac_audio_encoder_write_handler(guac_audio_stream* audio, const unsigned char* pcm_data, int length); diff --git a/src/libguac/guacamole/audio-types.h b/src/libguac/guacamole/audio-types.h index 2294b80a..de63c57a 100644 --- a/src/libguac/guacamole/audio-types.h +++ b/src/libguac/guacamole/audio-types.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 Glyptodon LLC + * Copyright (C) 2015 Glyptodon LLC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/src/libguac/guacamole/audio.h b/src/libguac/guacamole/audio.h index 36f5df09..a8449a26 100644 --- a/src/libguac/guacamole/audio.h +++ b/src/libguac/guacamole/audio.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Glyptodon LLC + * Copyright (C) 2015 Glyptodon LLC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -44,15 +44,21 @@ struct guac_audio_encoder { const char* mimetype; /** - * Handler which will be called when the audio stream is opened. + * Handler which will be called when the audio stream is first created. */ guac_audio_encoder_begin_handler* begin_handler; /** - * Handler which will be called when the audio stream is flushed. + * Handler which will be called when PCM data is written to the audio + * stream for encoding. */ guac_audio_encoder_write_handler* write_handler; + /** + * Handler which will be called when the audio stream is flushed. + */ + guac_audio_encoder_flush_handler* flush_handler; + /** * Handler which will be called when the audio stream is closed. */ @@ -63,38 +69,7 @@ struct guac_audio_encoder { struct guac_audio_stream { /** - * PCM data buffer, 16-bit samples, 2-channel, 44100 Hz. - */ - unsigned char* pcm_data; - - /** - * Number of bytes in buffer. - */ - int used; - - /** - * Maximum number of bytes in buffer. - */ - int length; - - /** - * Encoded audio data buffer, as written by the encoder. - */ - unsigned char* encoded_data; - - /** - * Number of bytes in the encoded data buffer. - */ - int encoded_data_used; - - /** - * Maximum number of bytes in the encoded data buffer. - */ - int encoded_data_length; - - /** - * Arbitrary codec encoder. When the PCM buffer is flushed, PCM data will - * be sent to this encoder. + * Arbitrary codec encoder which will receive raw PCM data. */ guac_audio_encoder* encoder; @@ -125,11 +100,6 @@ struct guac_audio_stream { */ int bps; - /** - * The number of PCM bytes written since the audio chunk began. - */ - int pcm_bytes_written; - /** * Encoder-specific state data. */ @@ -141,79 +111,83 @@ struct guac_audio_stream { * Allocates a new audio stream which encodes audio data using the given * encoder. If NULL is specified for the encoder, an appropriate encoder * will be selected based on the encoders built into libguac and the level - * of client support. + * of client support. The PCM format specified here (via rate, channels, and + * bps) must be the format used for all PCM data provided to the audio stream. + * The format may only be changed using guac_audio_stream_reset(). * - * @param client The guac_client for which this audio stream is being - * allocated. - * @param encoder The guac_audio_encoder to use when encoding audio, or - * NULL if libguac should select an appropriate built-in - * encoder on its own. - * @return The newly allocated guac_audio_stream, or NULL if no audio - * stream could be allocated due to lack of client support. + * @param client + * The guac_client for which this audio stream is being allocated. + * + * @param encoder + * The guac_audio_encoder to use when encoding audio, or NULL if libguac + * should select an appropriate built-in encoder on its own. + * + * @param rate + * The number of samples per second of PCM data sent to this stream. + * + * @param channels + * The number of audio channels per sample of PCM data. Legal values are + * 1 or 2. + * + * @param bps + * The number of bits per sample per channel for PCM data. Legal values are + * 8 or 16. + * + * @return + * The newly allocated guac_audio_stream, or NULL if no audio stream could + * be allocated due to lack of client support. */ guac_audio_stream* guac_audio_stream_alloc(guac_client* client, - guac_audio_encoder* encoder); + guac_audio_encoder* encoder, int rate, int channels, int bps); /** - * Frees the given audio stream. + * Resets the given audio stream, switching to the given encoder, rate, + * channels, and bits per sample. If NULL is specified for the encoder, the + * encoder is left unchanged. If the encoder, rate, channels, and bits per + * sample are all identical to the current settings, this function has no + * effect. * - * @param stream The guac_audio_stream to free. + * @param encoder + * The guac_audio_encoder to use when encoding audio, or NULL to leave this + * unchanged. + */ +void guac_audio_stream_reset(guac_audio_stream* audio, + guac_audio_encoder* encoder, int rate, int channels, int bps); + +/** + * Closes and frees the given audio stream. + * + * @param stream + * The guac_audio_stream to free. */ void guac_audio_stream_free(guac_audio_stream* stream); -/** - * Begins a new audio packet within the given audio stream. This packet will be - * built up with repeated writes of PCM data, finally being sent when complete - * via guac_audio_stream_end(). - * - * @param stream The guac_audio_stream which should start a new audio packet. - * @param rate The audio rate of the packet, in Hz. - * @param channels The number of audio channels. - * @param bps The number of bits per audio sample. - */ -void guac_audio_stream_begin(guac_audio_stream* stream, int rate, int channels, int bps); - -/** - * Ends the current audio packet, writing the finished packet as an audio - * instruction. - * - * @param stream The guac_audio_stream whose current audio packet should be - * completed and sent. - */ -void guac_audio_stream_end(guac_audio_stream* stream); - /** * Writes PCM data to the given audio stream. This PCM data will be - * automatically encoded by the audio encoder associated with this stream. This - * function must only be called after an audio packet has been started with - * guac_audio_stream_begin(). + * automatically encoded by the audio encoder associated with this stream. The + * PCM data must be 2-channel, 44100 Hz, with signed 16-bit samples. * - * @param stream The guac_audio_stream to write PCM data through. - * @param data The PCM data to write. - * @param length The number of bytes of PCM data provided. + * @param stream + * The guac_audio_stream to write PCM data through. + * + * @param data + * The PCM data to write. + * + * @param length + * The number of bytes of PCM data provided. */ void guac_audio_stream_write_pcm(guac_audio_stream* stream, const unsigned char* data, int length); /** - * Flushes the given audio stream. + * Flushes the underlying audio buffer, if any, ensuring that all audio + * previously written via guac_audio_stream_write_pcm() has been encoded and + * sent to the client. * - * @param stream The guac_audio_stream to flush. + * @param stream + * The guac_audio_stream whose audio buffers should be flushed. */ void guac_audio_stream_flush(guac_audio_stream* stream); -/** - * Appends arbitrarily-encoded data to the encoded_data buffer within the given - * audio stream. This data must be encoded in the output format of the encoder - * used by the stream. This function is mainly for use by encoder - * implementations. - * - * @param audio The guac_audio_stream to write data through. - * @param data Arbitrary encoded data to write through the audio stream. - * @param length The number of bytes of data provided. - */ -void guac_audio_stream_write_encoded(guac_audio_stream* audio, - const unsigned char* data, int length); - #endif diff --git a/src/libguac/guacamole/protocol.h b/src/libguac/guacamole/protocol.h index 5aa7f128..ce9c9cba 100644 --- a/src/libguac/guacamole/protocol.h +++ b/src/libguac/guacamole/protocol.h @@ -295,15 +295,20 @@ int guac_protocol_send_undefine(guac_socket* socket, * If an error occurs sending the instruction, a non-zero value is * returned, and guac_error is set appropriately. * - * @param socket The guac_socket connection to use. - * @param stream The stream to use. - * @param channel The index of the audio channel to use. - * @param mimetype The mimetype of the data being sent. - * @param duration The duration of the sound being sent, in milliseconds. - * @return Zero on success, non-zero on error. + * @param socket + * The guac_socket connection to use when sending the audio instruction. + * + * @param stream + * The stream to use for future audio data. + * + * @param mimetype + * The mimetype of the audio data which will be sent over the given stream. + * + * @return + * Zero on success, non-zero on error. */ int guac_protocol_send_audio(guac_socket* socket, const guac_stream* stream, - int channel, const char* mimetype, double duration); + const char* mimetype); /** * Sends a file instruction over the given guac_socket connection. @@ -350,7 +355,7 @@ int guac_protocol_send_pipe(guac_socket* socket, const guac_stream* stream, * @return Zero on success, non-zero on error. */ int guac_protocol_send_blob(guac_socket* socket, const guac_stream* stream, - void* data, int count); + const void* data, int count); /** * Sends an end instruction over the given guac_socket connection. diff --git a/src/libguac/ogg_encoder.c b/src/libguac/ogg_encoder.c deleted file mode 100644 index 32bf69e8..00000000 --- a/src/libguac/ogg_encoder.c +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright (C) 2013 Glyptodon LLC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -#include "config.h" - -#include "audio.h" -#include "ogg_encoder.h" - -#include - -#include - -void ogg_encoder_begin_handler(guac_audio_stream* audio) { - - /* Allocate stream state */ - ogg_encoder_state* state = (ogg_encoder_state*) - malloc(sizeof(ogg_encoder_state)); - - /* Init state */ - vorbis_info_init(&(state->info)); - vorbis_encode_init_vbr(&(state->info), audio->channels, audio->rate, 0.4); - - vorbis_analysis_init(&(state->vorbis_state), &(state->info)); - vorbis_block_init(&(state->vorbis_state), &(state->vorbis_block)); - - vorbis_comment_init(&(state->comment)); - vorbis_comment_add_tag(&(state->comment), "ENCODER", "libguac"); - - ogg_stream_init(&(state->ogg_state), rand()); - - /* Write headers */ - { - ogg_packet header; - ogg_packet header_comm; - ogg_packet header_code; - - vorbis_analysis_headerout( - &(state->vorbis_state), - &(state->comment), - &header, &header_comm, &header_code); - - ogg_stream_packetin(&(state->ogg_state), &header); - ogg_stream_packetin(&(state->ogg_state), &header_comm); - ogg_stream_packetin(&(state->ogg_state), &header_code); - - /* For each packet */ - while (ogg_stream_flush(&(state->ogg_state), &(state->ogg_page)) != 0) { - - /* Write packet header */ - guac_audio_stream_write_encoded(audio, - state->ogg_page.header, - state->ogg_page.header_len); - - /* Write packet body */ - guac_audio_stream_write_encoded(audio, - state->ogg_page.body, - state->ogg_page.body_len); - } - - } - - audio->data = state; - -} - -void ogg_encoder_write_blocks(guac_audio_stream* audio) { - - /* Get state */ - ogg_encoder_state* state = (ogg_encoder_state*) audio->data; - - while (vorbis_analysis_blockout(&(state->vorbis_state), - &(state->vorbis_block)) == 1) { - - /* Analyze */ - vorbis_analysis(&(state->vorbis_block), NULL); - vorbis_bitrate_addblock(&(state->vorbis_block)); - - /* Flush Ogg pages */ - while (vorbis_bitrate_flushpacket(&(state->vorbis_state), - &(state->ogg_packet))) { - - /* Weld packet into bitstream */ - ogg_stream_packetin(&(state->ogg_state), &(state->ogg_packet)); - - /* Write out pages */ - while (ogg_stream_pageout(&(state->ogg_state), - &(state->ogg_page)) != 0) { - - /* Write packet header */ - guac_audio_stream_write_encoded(audio, - state->ogg_page.header, - state->ogg_page.header_len); - - /* Write packet body */ - guac_audio_stream_write_encoded(audio, - state->ogg_page.body, - state->ogg_page.body_len); - - if (ogg_page_eos(&(state->ogg_page))) - break; - - } - - } - - } - -} - -void ogg_encoder_end_handler(guac_audio_stream* audio) { - - /* Get state */ - ogg_encoder_state* state = (ogg_encoder_state*) audio->data; - - /* Write end-of-stream */ - vorbis_analysis_wrote(&(state->vorbis_state), 0); - ogg_encoder_write_blocks(audio); - - /* Clean up encoder */ - ogg_stream_clear(&(state->ogg_state)); - vorbis_block_clear(&(state->vorbis_block)); - vorbis_dsp_clear(&(state->vorbis_state)); - vorbis_comment_clear(&(state->comment)); - vorbis_info_clear(&(state->info)); - - /* Free stream state */ - free(audio->data); - -} - -void ogg_encoder_write_handler(guac_audio_stream* audio, - const unsigned char* pcm_data, int length) { - - /* Get state */ - ogg_encoder_state* state = (ogg_encoder_state*) audio->data; - - /* Calculate samples */ - int samples = length / audio->channels * 8 / audio->bps; - int i; - - /* Get buffer */ - float** buffer = vorbis_analysis_buffer(&(state->vorbis_state), samples); - - signed char* readbuffer = (signed char*) pcm_data; - - for (i=0; ivorbis_state), samples); - - /* Write data */ - ogg_encoder_write_blocks(audio); - -} - -/* Encoder handlers */ -guac_audio_encoder _ogg_encoder = { - .mimetype = "audio/ogg", - .begin_handler = ogg_encoder_begin_handler, - .write_handler = ogg_encoder_write_handler, - .end_handler = ogg_encoder_end_handler -}; - -/* Actual encoder */ -guac_audio_encoder* ogg_encoder = &_ogg_encoder; - diff --git a/src/libguac/protocol.c b/src/libguac/protocol.c index 00aa8bbd..f7e09099 100644 --- a/src/libguac/protocol.c +++ b/src/libguac/protocol.c @@ -153,7 +153,7 @@ int guac_protocol_send_arc(guac_socket* socket, const guac_layer* layer, } int guac_protocol_send_audio(guac_socket* socket, const guac_stream* stream, - int channel, const char* mimetype, double duration) { + const char* mimetype) { int ret_val; @@ -162,11 +162,7 @@ int guac_protocol_send_audio(guac_socket* socket, const guac_stream* stream, guac_socket_write_string(socket, "5.audio,") || __guac_socket_write_length_int(socket, stream->index) || guac_socket_write_string(socket, ",") - || __guac_socket_write_length_int(socket, channel) - || guac_socket_write_string(socket, ",") || __guac_socket_write_length_string(socket, mimetype) - || guac_socket_write_string(socket, ",") - || __guac_socket_write_length_double(socket, duration) || guac_socket_write_string(socket, ";"); guac_socket_instruction_end(socket); @@ -175,7 +171,7 @@ int guac_protocol_send_audio(guac_socket* socket, const guac_stream* stream, } int guac_protocol_send_blob(guac_socket* socket, const guac_stream* stream, - void* data, int count) { + const void* data, int count) { int base64_length = (count + 2) / 3 * 4; diff --git a/src/libguac/raw_encoder.c b/src/libguac/raw_encoder.c new file mode 100644 index 00000000..91efdc0f --- /dev/null +++ b/src/libguac/raw_encoder.c @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2015 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "config.h" + +#include "audio.h" +#include "raw_encoder.h" + +#include +#include +#include +#include + +#include +#include +#include + +static void raw_encoder_begin_handler(guac_audio_stream* audio) { + + raw_encoder_state* state; + char mimetype[256]; + + /* Produce mimetype string from format info */ + snprintf(mimetype, sizeof(mimetype), "audio/L%i;rate=%i,channels=%i", + audio->bps, audio->rate, audio->channels); + + /* Associate stream */ + guac_protocol_send_audio(audio->client->socket, audio->stream, mimetype); + + /* Allocate and init encoder state */ + audio->data = state = malloc(sizeof(raw_encoder_state)); + state->written = 0; + state->length = GUAC_RAW_ENCODER_BUFFER_SIZE + * audio->rate * audio->channels * audio->bps + / 8 / 1000; + + state->buffer = malloc(state->length); + + guac_client_log(audio->client, GUAC_LOG_DEBUG, + "Using raw encoder (%s) with a %i byte buffer.", + mimetype, state->length); + +} + +static void raw_encoder_end_handler(guac_audio_stream* audio) { + + raw_encoder_state* state = (raw_encoder_state*) audio->data; + + /* Send end of stream */ + guac_protocol_send_end(audio->client->socket, audio->stream); + + /* Free state information */ + free(state->buffer); + free(state); + +} + +static void raw_encoder_write_handler(guac_audio_stream* audio, + const unsigned char* pcm_data, int length) { + + raw_encoder_state* state = (raw_encoder_state*) audio->data; + + while (length > 0) { + + /* Prefer to copy a chunk of equal size to available buffer space */ + int chunk_size = state->length - state->written; + + /* If no space remains, flush and retry */ + if (chunk_size == 0) { + guac_audio_stream_flush(audio); + continue; + } + + /* Do not copy more data than is available in source PCM */ + if (chunk_size > length) + chunk_size = length; + + /* Copy block of PCM data into buffer */ + memcpy(state->buffer + state->written, pcm_data, chunk_size); + + /* Advance to next block */ + state->written += chunk_size; + pcm_data += chunk_size; + length -= chunk_size; + + } + +} + +static void raw_encoder_flush_handler(guac_audio_stream* audio) { + + raw_encoder_state* state = (raw_encoder_state*) audio->data; + guac_socket* socket = audio->client->socket; + guac_stream* stream = audio->stream; + + unsigned char* current = state->buffer; + int remaining = state->written; + + /* Flush all data in buffer as blobs */ + while (remaining > 0) { + + /* Determine size of blob to be written */ + int chunk_size = remaining; + if (chunk_size > 6048) + chunk_size = 6048; + + /* Send audio data */ + guac_protocol_send_blob(socket, stream, current, chunk_size); + + /* Advance to next blob */ + current += chunk_size; + remaining -= chunk_size; + + } + + /* All data has been flushed */ + state->written = 0; + +} + +/* 8-bit raw encoder handlers */ +guac_audio_encoder _raw8_encoder = { + .mimetype = "audio/L8", + .begin_handler = raw_encoder_begin_handler, + .write_handler = raw_encoder_write_handler, + .flush_handler = raw_encoder_flush_handler, + .end_handler = raw_encoder_end_handler +}; + +/* 16-bit raw encoder handlers */ +guac_audio_encoder _raw16_encoder = { + .mimetype = "audio/L16", + .begin_handler = raw_encoder_begin_handler, + .write_handler = raw_encoder_write_handler, + .flush_handler = raw_encoder_flush_handler, + .end_handler = raw_encoder_end_handler +}; + +/* Actual encoder definitions */ +guac_audio_encoder* raw8_encoder = &_raw8_encoder; +guac_audio_encoder* raw16_encoder = &_raw16_encoder; + diff --git a/src/libguac/ogg_encoder.h b/src/libguac/raw_encoder.h similarity index 51% rename from src/libguac/ogg_encoder.h rename to src/libguac/raw_encoder.h index ccb7233c..2ebbf067 100644 --- a/src/libguac/ogg_encoder.h +++ b/src/libguac/raw_encoder.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Glyptodon LLC + * Copyright (C) 2015 Glyptodon LLC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,35 +21,58 @@ */ -#ifndef __GUAC_OGG_ENCODER_H -#define __GUAC_OGG_ENCODER_H +#ifndef GUAC_RAW_ENCODER_H +#define GUAC_RAW_ENCODER_H #include "config.h" #include "audio.h" -#include +/** + * The number of bytes to send in each audio blob. + */ +#define GUAC_RAW_ENCODER_BLOB_SIZE 6048 -typedef struct ogg_encoder_state { +/** + * The size of the raw encoder output PCM buffer, in milliseconds. The + * equivalent size in bytes will vary by PCM rate, number of channels, and bits + * per sample. + */ +#define GUAC_RAW_ENCODER_BUFFER_SIZE 250 + +/** + * The current state of the raw encoder. The raw encoder performs very minimal + * processing, buffering provided PCM data only as necessary to ensure audio + * packet sizes are reasonable. + */ +typedef struct raw_encoder_state { /** - * Ogg state + * Buffer of not-yet-written raw PCM data. */ - ogg_stream_state ogg_state; - ogg_page ogg_page; - ogg_packet ogg_packet; + unsigned char* buffer; /** - * Vorbis state + * Size of the PCM buffer, in bytes. */ - vorbis_info info; - vorbis_comment comment; - vorbis_dsp_state vorbis_state; - vorbis_block vorbis_block; + int length; -} ogg_encoder_state; + /** + * The current number of bytes stored within the PCM buffer. + */ + int written; -extern guac_audio_encoder* ogg_encoder; +} raw_encoder_state; + +/** + * Audio encoder which writes 8-bit raw PCM (one byte per sample). + */ +extern guac_audio_encoder* raw8_encoder; + +/** + * Audio encoder which writes 16-bit raw PCM (two bytes per sample). + */ +extern guac_audio_encoder* raw16_encoder; #endif diff --git a/src/libguac/wav_encoder.c b/src/libguac/wav_encoder.c deleted file mode 100644 index a389374c..00000000 --- a/src/libguac/wav_encoder.c +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright (C) 2013 Glyptodon LLC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -#include "config.h" - -#include "audio.h" -#include "wav_encoder.h" - -#include -#include - -#define WAV_BUFFER_SIZE 0x4000 - -void wav_encoder_begin_handler(guac_audio_stream* audio) { - - /* Allocate stream state */ - wav_encoder_state* state = (wav_encoder_state*) - malloc(sizeof(wav_encoder_state)); - - /* Initialize buffer */ - state->length = WAV_BUFFER_SIZE; - state->used = 0; - state->data_buffer = (unsigned char*) malloc(state->length); - - audio->data = state; - -} - -void _wav_encoder_write_le(unsigned char* buffer, int value, int length) { - - int offset; - - /* Write all bytes in the given value in little-endian byte order */ - for (offset=0; offset>= 8; - buffer++; - - } - -} - -void wav_encoder_end_handler(guac_audio_stream* audio) { - - /* - * Static header init - */ - - wav_encoder_riff_header riff_header = { - .chunk_id = "RIFF", - .chunk_format = "WAVE" - }; - - wav_encoder_fmt_header fmt_header = { - .subchunk_id = "fmt ", - .subchunk_size = {0x10, 0x00, 0x00, 0x00}, /* 16 */ - .subchunk_format = {0x01, 0x00} /* 1 = PCM */ - }; - - wav_encoder_data_header data_header = { - .subchunk_id = "data" - }; - - /* Get state */ - wav_encoder_state* state = (wav_encoder_state*) audio->data; - - /* - * RIFF HEADER - */ - - /* Chunk size */ - _wav_encoder_write_le(riff_header.chunk_size, - 4 + sizeof(fmt_header) + sizeof(data_header) + state->used, - sizeof(riff_header.chunk_size)); - - guac_audio_stream_write_encoded(audio, - (unsigned char*) &riff_header, - sizeof(riff_header)); - - /* - * FMT HEADER - */ - - /* Channels */ - _wav_encoder_write_le(fmt_header.subchunk_channels, - audio->channels, sizeof(fmt_header.subchunk_channels)); - - /* Sample rate */ - _wav_encoder_write_le(fmt_header.subchunk_sample_rate, - audio->rate, sizeof(fmt_header.subchunk_sample_rate)); - - /* Byte rate */ - _wav_encoder_write_le(fmt_header.subchunk_byte_rate, - audio->rate * audio->channels * audio->bps / 8, - sizeof(fmt_header.subchunk_byte_rate)); - - /* Block align */ - _wav_encoder_write_le(fmt_header.subchunk_block_align, - audio->channels * audio->bps / 8, - sizeof(fmt_header.subchunk_block_align)); - - /* Bits per second */ - _wav_encoder_write_le(fmt_header.subchunk_bps, - audio->bps, sizeof(fmt_header.subchunk_bps)); - - guac_audio_stream_write_encoded(audio, - (unsigned char*) &fmt_header, - sizeof(fmt_header)); - - /* - * DATA HEADER - */ - - /* PCM data size */ - _wav_encoder_write_le(data_header.subchunk_size, - state->used, sizeof(data_header.subchunk_size)); - - guac_audio_stream_write_encoded(audio, - (unsigned char*) &data_header, - sizeof(data_header)); - - /* Write .wav data */ - guac_audio_stream_write_encoded(audio, state->data_buffer, state->used); - - /* Free stream state */ - free(state); - -} - -void wav_encoder_write_handler(guac_audio_stream* audio, - const unsigned char* pcm_data, int length) { - - /* Get state */ - wav_encoder_state* state = (wav_encoder_state*) audio->data; - - /* Increase size of buffer if necessary */ - if (state->used + length > state->length) { - - /* Increase to double concatenated size to accomodate */ - state->length = (state->length + length)*2; - state->data_buffer = realloc(state->data_buffer, - state->length); - - } - - /* Append to buffer */ - memcpy(&(state->data_buffer[state->used]), pcm_data, length); - state->used += length; - -} - -/* Encoder handlers */ -guac_audio_encoder _wav_encoder = { - .mimetype = "audio/wav", - .begin_handler = wav_encoder_begin_handler, - .write_handler = wav_encoder_write_handler, - .end_handler = wav_encoder_end_handler -}; - -/* Actual encoder */ -guac_audio_encoder* wav_encoder = &_wav_encoder; - diff --git a/src/libguac/wav_encoder.h b/src/libguac/wav_encoder.h deleted file mode 100644 index 13f9ef0f..00000000 --- a/src/libguac/wav_encoder.h +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright (C) 2013 Glyptodon LLC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - - -#ifndef __GUAC_WAV_ENCODER_H -#define __GUAC_WAV_ENCODER_H - -#include "config.h" - -#include "audio.h" - -typedef struct wav_encoder_riff_header { - - /** - * The RIFF chunk header, normally the string "RIFF". - */ - unsigned char chunk_id[4]; - - /** - * Size of the entire file, not including chunk_id or chunk_size. - */ - unsigned char chunk_size[4]; - - /** - * The format of this file, normally the string "WAVE". - */ - unsigned char chunk_format[4]; - -} wav_encoder_riff_header; - -typedef struct wav_encoder_fmt_header { - - /** - * ID of this subchunk. For the fmt subchunk, this should be "fmt ". - */ - unsigned char subchunk_id[4]; - - /** - * The size of the rest of this subchunk. For PCM, this will be 16. - */ - unsigned char subchunk_size[4]; - - /** - * Format of this subchunk. For PCM, this will be 1. - */ - unsigned char subchunk_format[2]; - - /** - * The number of channels in the PCM data. - */ - unsigned char subchunk_channels[2]; - - /** - * The sample rate of the PCM data. - */ - unsigned char subchunk_sample_rate[4]; - - /** - * The sample rate of the PCM data in bytes per second. - */ - unsigned char subchunk_byte_rate[4]; - - /** - * The number of bytes per sample. - */ - unsigned char subchunk_block_align[2]; - - /** - * The number of bits per sample. - */ - unsigned char subchunk_bps[2]; - -} wav_encoder_fmt_header; - -typedef struct wav_encoder_state { - - /** - * Arbitrary PCM data available for writing when the overall WAV is - * flushed. - */ - unsigned char* data_buffer; - - /** - * The number of bytes currently present in the data buffer. - */ - int used; - - /** - * The total number of bytes that can be written into the data buffer - * without requiring resizing. - */ - int length; - -} wav_encoder_state; - -typedef struct wav_encoder_data_header { - - /** - * ID of this subchunk. For the data subchunk, this should be "data". - */ - unsigned char subchunk_id[4]; - - /** - * The number of bytes in the PCM data. - */ - unsigned char subchunk_size[4]; - -} wav_encoder_data_header; - -extern guac_audio_encoder* wav_encoder; - -#endif - diff --git a/src/protocols/rdp/client.c b/src/protocols/rdp/client.c index ccd6776d..a669a101 100644 --- a/src/protocols/rdp/client.c +++ b/src/protocols/rdp/client.c @@ -286,7 +286,10 @@ BOOL rdp_freerdp_pre_connect(freerdp* instance) { /* If audio enabled, choose an encoder */ if (guac_client_data->settings.audio_enabled) { - guac_client_data->audio = guac_audio_stream_alloc(client, NULL); + guac_client_data->audio = guac_audio_stream_alloc(client, NULL, + GUAC_RDP_AUDIO_RATE, + GUAC_RDP_AUDIO_CHANNELS, + GUAC_RDP_AUDIO_BPS); /* If an encoding is available, load the sound plugin */ if (guac_client_data->audio != NULL) { diff --git a/src/protocols/rdp/client.h b/src/protocols/rdp/client.h index 7bcefe4a..376a1619 100644 --- a/src/protocols/rdp/client.h +++ b/src/protocols/rdp/client.h @@ -87,6 +87,27 @@ */ #define GUAC_RDP_CLIPBOARD_MAX_LENGTH 262144 +/** + * Initial rate of audio to stream, in Hz. If the RDP server uses a different + * value, the Guacamole audio stream will simply be reset appropriately. + */ +#define GUAC_RDP_AUDIO_RATE 44100 + +/** + * The number of channels to stream for audio. If the RDP server uses a + * different value, the Guacamole audio stream will simply be reset + * appropriately. + */ +#define GUAC_RDP_AUDIO_CHANNELS 2 + +/** + * The number of bits per sample within the audio stream. If the RDP server + * uses a different value, the Guacamole audio stream will simply be reset + * appropriately. + */ +#define GUAC_RDP_AUDIO_BPS 16 + + /** * Client data that will remain accessible through the guac_client. * This should generally include data commonly used by Guacamole handlers. diff --git a/src/protocols/rdp/guac_rdpsnd/rdpsnd_messages.c b/src/protocols/rdp/guac_rdpsnd/rdpsnd_messages.c index 62e49fdb..b2488dfd 100644 --- a/src/protocols/rdp/guac_rdpsnd/rdpsnd_messages.c +++ b/src/protocols/rdp/guac_rdpsnd/rdpsnd_messages.c @@ -133,6 +133,9 @@ void guac_rdpsnd_formats_handler(guac_rdpsndPlugin* rdpsnd, "%i Hz", bps, channels, rate); + /* Ensure audio stream is configured to use accepted format */ + guac_audio_stream_reset(audio, NULL, rate, channels, bps); + /* Queue format for sending as accepted */ Stream_EnsureRemainingCapacity(output_stream, 18 + body_size); Stream_Write(output_stream, format_start, 18 + body_size); @@ -225,7 +228,6 @@ void guac_rdpsnd_wave_info_handler(guac_rdpsndPlugin* rdpsnd, guac_audio_stream* audio, wStream* input_stream, guac_rdpsnd_pdu_header* header) { - unsigned char buffer[4]; int format; /* Read wave information */ @@ -233,7 +235,7 @@ void guac_rdpsnd_wave_info_handler(guac_rdpsndPlugin* rdpsnd, Stream_Read_UINT16(input_stream, format); Stream_Read_UINT8(input_stream, rdpsnd->waveinfo_block_number); Stream_Seek(input_stream, 3); - Stream_Read(input_stream, buffer, 4); + Stream_Read(input_stream, rdpsnd->initial_wave_data, 4); /* * Size of incoming wave data is equal to the body size field of this @@ -245,15 +247,12 @@ void guac_rdpsnd_wave_info_handler(guac_rdpsndPlugin* rdpsnd, /* Read wave in next iteration */ rdpsnd->next_pdu_is_wave = TRUE; - /* Init stream with requested format */ - guac_audio_stream_begin(audio, + /* Reset audio stream if format has changed */ + guac_audio_stream_reset(audio, NULL, rdpsnd->formats[format].rate, rdpsnd->formats[format].channels, rdpsnd->formats[format].bps); - /* Write initial 4 bytes of data */ - guac_audio_stream_write_pcm(audio, buffer, 4); - } void guac_rdpsnd_wave_handler(guac_rdpsndPlugin* rdpsnd, @@ -269,11 +268,14 @@ void guac_rdpsnd_wave_handler(guac_rdpsndPlugin* rdpsnd, wStream* output_stream = Stream_New(NULL, 8); /* Get wave data */ - unsigned char* buffer = Stream_Buffer(input_stream) + 4; + unsigned char* buffer = Stream_Buffer(input_stream); + + /* Copy over first four bytes */ + memcpy(buffer, rdpsnd->initial_wave_data, 4); /* Write rest of audio packet */ - guac_audio_stream_write_pcm(audio, buffer, rdpsnd->incoming_wave_size); - guac_audio_stream_end(audio); + guac_audio_stream_write_pcm(audio, buffer, rdpsnd->incoming_wave_size + 4); + guac_audio_stream_flush(audio); /* Write Wave Confirmation PDU */ Stream_Write_UINT8(output_stream, SNDC_WAVECONFIRM); diff --git a/src/protocols/rdp/guac_rdpsnd/rdpsnd_service.h b/src/protocols/rdp/guac_rdpsnd/rdpsnd_service.h index 8b3849a2..8e6b976d 100644 --- a/src/protocols/rdp/guac_rdpsnd/rdpsnd_service.h +++ b/src/protocols/rdp/guac_rdpsnd/rdpsnd_service.h @@ -95,8 +95,16 @@ typedef struct guac_rdpsndPlugin { */ int next_pdu_is_wave; + /** + * The wave data received within the last SNDC_WAVE (WaveInfo) PDU. + */ + unsigned char initial_wave_data[4]; + /** * The size, in bytes, of the wave data in the coming Wave PDU, if any. + * This does not include the initial wave data received within the last + * SNDC_WAVE (WaveInfo) PDU, which is always the first four bytes of the + * actual wave data block. */ int incoming_wave_size; diff --git a/src/protocols/vnc/client.c b/src/protocols/vnc/client.c index 201cdcd8..7d999d4a 100644 --- a/src/protocols/vnc/client.c +++ b/src/protocols/vnc/client.c @@ -401,7 +401,10 @@ int guac_client_init(guac_client* client, int argc, char** argv) { /* If an encoding is available, load an audio stream */ if (guac_client_data->audio_enabled) { - guac_client_data->audio = guac_audio_stream_alloc(client, NULL); + guac_client_data->audio = guac_audio_stream_alloc(client, NULL, + GUAC_VNC_AUDIO_RATE, + GUAC_VNC_AUDIO_CHANNELS, + GUAC_VNC_AUDIO_BPS); /* Load servername if specified */ if (argv[IDX_AUDIO_SERVERNAME][0] != '\0') diff --git a/src/protocols/vnc/pulse.c b/src/protocols/vnc/pulse.c index 57ed10af..05418cd6 100644 --- a/src/protocols/vnc/pulse.c +++ b/src/protocols/vnc/pulse.c @@ -74,23 +74,13 @@ static void __stream_read_callback(pa_stream* stream, size_t length, /* Read data */ pa_stream_peek(stream, &buffer, &length); - /* Avoid sending silence unless data is waiting to be flushed */ - if (audio->pcm_bytes_written != 0 || !guac_pa_is_silence(buffer, length)) { - - /* Write data */ + /* Continuously write received PCM data */ + if (!guac_pa_is_silence(buffer, length)) guac_audio_stream_write_pcm(audio, buffer, length); - /* Flush occasionally */ - if (audio->pcm_bytes_written > GUAC_VNC_PCM_WRITE_RATE) { - guac_audio_stream_end(audio); - guac_audio_stream_begin(client_data->audio, - GUAC_VNC_AUDIO_RATE, - GUAC_VNC_AUDIO_CHANNELS, - GUAC_VNC_AUDIO_BPS); - guac_socket_flush(client->socket); - } - - } + /* Flush upon silence */ + else + guac_audio_stream_flush(audio); /* Advance buffer */ pa_stream_drop(stream); @@ -245,10 +235,6 @@ void guac_pa_start_stream(guac_client* client) { pa_context* context; guac_client_log(client, GUAC_LOG_INFO, "Starting audio stream"); - guac_audio_stream_begin(client_data->audio, - GUAC_VNC_AUDIO_RATE, - GUAC_VNC_AUDIO_CHANNELS, - GUAC_VNC_AUDIO_BPS); /* Init main loop */ client_data->pa_mainloop = pa_threaded_mainloop_new();