From 48ebbe95ea14632c4006dc13f6d7a1135b6dabd6 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 25 Dec 2016 01:13:40 -0800 Subject: [PATCH] GUACAMOLE-179: Move PulseAudio support into separate utility library. --- Makefile.am | 5 ++ configure.ac | 5 ++ src/protocols/vnc/Makefile.am | 15 ++-- src/protocols/vnc/client.c | 16 ++-- src/protocols/vnc/pulse.h | 70 --------------- src/protocols/vnc/user.c | 6 +- src/protocols/vnc/vnc.c | 32 ++----- src/protocols/vnc/vnc.h | 12 +-- src/pulse/Makefile.am | 40 +++++++++ src/{protocols/vnc => pulse}/pulse.c | 67 +++++++++------ src/pulse/pulse/pulse.h | 124 +++++++++++++++++++++++++++ 11 files changed, 241 insertions(+), 151 deletions(-) delete mode 100644 src/protocols/vnc/pulse.h create mode 100644 src/pulse/Makefile.am rename src/{protocols/vnc => pulse}/pulse.c (78%) create mode 100644 src/pulse/pulse/pulse.h diff --git a/Makefile.am b/Makefile.am index 78f76e1d..d67db2a3 100644 --- a/Makefile.am +++ b/Makefile.am @@ -28,6 +28,7 @@ DIST_SUBDIRS = \ src/terminal \ src/guacd \ src/guacenc \ + src/pulse \ src/protocols/rdp \ src/protocols/ssh \ src/protocols/telnet \ @@ -48,6 +49,10 @@ if ENABLE_TERMINAL SUBDIRS += src/terminal endif +if ENABLE_PULSE +SUBDIRS += src/pulse +endif + if ENABLE_RDP SUBDIRS += src/protocols/rdp endif diff --git a/configure.ac b/configure.ac index a763d6a2..d7a4e4c1 100644 --- a/configure.ac +++ b/configure.ac @@ -119,6 +119,10 @@ AC_SUBST([LIBGUAC_INCLUDE], '-I$(top_srcdir)/src/libguac') AC_SUBST([COMMON_LTLIB], '$(top_builddir)/src/common/libguac_common.la') AC_SUBST([COMMON_INCLUDE], '-I$(top_srcdir)/src/common') +# Common PulseAudio utility library +AC_SUBST([PULSE_LTLIB], '$(top_builddir)/src/pulse/libguac_pulse.la') +AC_SUBST([PULSE_INCLUDE], '-I$(top_srcdir)/src/pulse') + # Common utility library for guacd implementations AC_SUBST([LIBGUACD_LTLIB], '$(top_builddir)/src/libguacd/libguacd.la') AC_SUBST([LIBGUACD_INCLUDE], '-I$(top_srcdir)/src/libguacd') @@ -1108,6 +1112,7 @@ AC_CONFIG_FILES([Makefile src/libguacd/Makefile src/guacd/Makefile src/guacenc/Makefile + src/pulse/Makefile src/protocols/rdp/Makefile src/protocols/ssh/Makefile src/protocols/telnet/Makefile diff --git a/src/protocols/vnc/Makefile.am b/src/protocols/vnc/Makefile.am index 314bd8ff..0c5ea478 100644 --- a/src/protocols/vnc/Makefile.am +++ b/src/protocols/vnc/Makefile.am @@ -46,22 +46,16 @@ noinst_HEADERS = \ user.h \ vnc.h -# Optional PulseAudio support -if ENABLE_PULSE -libguac_client_vnc_la_SOURCES += pulse.c -noinst_HEADERS += pulse.h -endif - libguac_client_vnc_la_CFLAGS = \ -Werror -Wall -pedantic -Iinclude \ @COMMON_INCLUDE@ \ @COMMON_SSH_INCLUDE@ \ - @LIBGUAC_INCLUDE@ + @LIBGUAC_INCLUDE@ \ + @PULSE_INCLUDE@ libguac_client_vnc_la_LDFLAGS = \ -version-info 0:0:0 \ @CAIRO_LIBS@ \ - @PULSE_LIBS@ \ @VNC_LIBS@ libguac_client_vnc_la_LIBADD = \ @@ -75,3 +69,8 @@ noinst_HEADERS += sftp.h libguac_client_vnc_la_LIBADD += @COMMON_SSH_LTLIB@ endif +# Optional PulseAudio support +if ENABLE_PULSE +libguac_client_vnc_la_LIBADD += @PULSE_LTLIB@ +endif + diff --git a/src/protocols/vnc/client.c b/src/protocols/vnc/client.c index f3840bb7..516d93fe 100644 --- a/src/protocols/vnc/client.c +++ b/src/protocols/vnc/client.c @@ -30,7 +30,7 @@ #endif #ifdef ENABLE_PULSE -#include "pulse.h" +#include "pulse/pulse.h" #endif #include @@ -110,20 +110,16 @@ int guac_vnc_client_free_handler(guac_client* client) { if (vnc_client->display != NULL) guac_common_display_free(vnc_client->display); - /* Free settings-dependend data */ - if (settings != NULL) { - #ifdef ENABLE_PULSE - /* If audio enabled, stop streaming */ - if (settings->audio_enabled) - guac_pa_stop_stream(client); + /* If audio enabled, stop streaming */ + if (vnc_client->audio) + guac_pa_stream_free(vnc_client->audio); #endif - /* Free parsed settings */ + /* Free parsed settings */ + if (settings != NULL) guac_vnc_settings_free(settings); - } - /* Free generic data struct */ free(client->data); diff --git a/src/protocols/vnc/pulse.h b/src/protocols/vnc/pulse.h deleted file mode 100644 index 09823644..00000000 --- a/src/protocols/vnc/pulse.h +++ /dev/null @@ -1,70 +0,0 @@ -/* - * 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_VNC_PULSE_H -#define __GUAC_VNC_PULSE_H - -#include "config.h" - -#include - -/** - * The number of bytes to request for the audio fragments received from - * PulseAudio. - */ -#define GUAC_VNC_AUDIO_FRAGMENT_SIZE 8192 - -/** - * The minimum number of PCM bytes to wait for before flushing an audio - * packet. The current value is 48K, which works out to be around 280ms. - */ -#define GUAC_VNC_PCM_WRITE_RATE 49152 - -/** - * Rate of audio to stream, in Hz. - */ -#define GUAC_VNC_AUDIO_RATE 44100 - -/** - * The number of channels to stream. - */ -#define GUAC_VNC_AUDIO_CHANNELS 2 - -/** - * The number of bits per sample. - */ -#define GUAC_VNC_AUDIO_BPS 16 - -/** - * Starts streaming audio from PulseAudio to the given Guacamole client. - * - * @param client The client to stream data to. - */ -void guac_pa_start_stream(guac_client* client); - -/** - * Stops streaming audio from PulseAudio to the given Guacamole client. - * - * @param client The client to stream data to. - */ -void guac_pa_stop_stream(guac_client* client); - -#endif - diff --git a/src/protocols/vnc/user.c b/src/protocols/vnc/user.c index aecf4e90..3786c271 100644 --- a/src/protocols/vnc/user.c +++ b/src/protocols/vnc/user.c @@ -28,6 +28,10 @@ #include "sftp.h" #include "vnc.h" +#ifdef ENABLE_PULSE +#include "pulse/pulse.h" +#endif + #include #include #include @@ -75,7 +79,7 @@ int guac_vnc_user_join_handler(guac_user* user, int argc, char** argv) { #ifdef ENABLE_PULSE /* Synchronize an audio stream */ if (vnc_client->audio) - guac_audio_stream_add_user(vnc_client->audio, user); + guac_pa_stream_add_user(vnc_client->audio, user); #endif /* Synchronize with current display */ diff --git a/src/protocols/vnc/vnc.c b/src/protocols/vnc/vnc.c index 9238ef13..992933d0 100644 --- a/src/protocols/vnc/vnc.c +++ b/src/protocols/vnc/vnc.c @@ -33,7 +33,7 @@ #include "vnc.h" #ifdef ENABLE_PULSE -#include "pulse.h" +#include "pulse/pulse.h" #endif #ifdef ENABLE_COMMON_SSH @@ -207,32 +207,10 @@ void* guac_vnc_client_thread(void* data) { } #ifdef ENABLE_PULSE - /* If an encoding is available, load an audio stream */ - if (settings->audio_enabled) { - - vnc_client->audio = guac_audio_stream_alloc(client, NULL, - GUAC_VNC_AUDIO_RATE, - GUAC_VNC_AUDIO_CHANNELS, - GUAC_VNC_AUDIO_BPS); - - /* If successful, init audio system */ - if (vnc_client->audio != NULL) { - - guac_client_log(client, GUAC_LOG_INFO, - "Audio will be encoded as %s", - vnc_client->audio->encoder->mimetype); - - /* Start audio stream */ - guac_pa_start_stream(client); - - } - - /* Otherwise, audio loading failed */ - else - guac_client_log(client, GUAC_LOG_INFO, - "No available audio encoding. Sound disabled."); - - } /* end if audio enabled */ + /* If audio is enabled, start streaming via PulseAudio */ + if (settings->audio_enabled) + vnc_client->audio = guac_pa_stream_alloc(client, + settings->pa_servername); #endif #ifdef ENABLE_COMMON_SSH diff --git a/src/protocols/vnc/vnc.h b/src/protocols/vnc/vnc.h index 7938d846..7ae8b720 100644 --- a/src/protocols/vnc/vnc.h +++ b/src/protocols/vnc/vnc.h @@ -33,8 +33,7 @@ #include #ifdef ENABLE_PULSE -#include -#include +#include "pulse/pulse.h" #endif #ifdef ENABLE_COMMON_SSH @@ -89,14 +88,9 @@ typedef struct guac_vnc_client { #ifdef ENABLE_PULSE /** - * Audio output, if any. + * PulseAudio output, if any. */ - guac_audio_stream* audio; - - /** - * PulseAudio event loop. - */ - pa_threaded_mainloop* pa_mainloop; + guac_pa_stream* audio; #endif #ifdef ENABLE_COMMON_SSH diff --git a/src/pulse/Makefile.am b/src/pulse/Makefile.am new file mode 100644 index 00000000..b7921245 --- /dev/null +++ b/src/pulse/Makefile.am @@ -0,0 +1,40 @@ +# +# 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. +# + +AUTOMAKE_OPTIONS = foreign +ACLOCAL_AMFLAGS = -I m4 + +noinst_LTLIBRARIES = libguac_pulse.la + +noinst_HEADERS = \ + pulse/pulse.h + +libguac_pulse_la_SOURCES = \ + pulse.c + +libguac_pulse_la_CFLAGS = \ + -Werror -Wall -pedantic \ + @LIBGUAC_INCLUDE@ + +libguac_pulse_la_LIBADD = \ + @LIBGUAC_LTLIB@ + +libguac_pulse_la_LDFLAGS = \ + @PULSE_LIBS@ + diff --git a/src/protocols/vnc/pulse.c b/src/pulse/pulse.c similarity index 78% rename from src/protocols/vnc/pulse.c rename to src/pulse/pulse.c index f81703b5..b2e433be 100644 --- a/src/protocols/vnc/pulse.c +++ b/src/pulse/pulse.c @@ -19,12 +19,11 @@ #include "config.h" -#include "pulse.h" -#include "vnc.h" +#include "pulse/pulse.h" #include #include -#include +#include #include /** @@ -62,9 +61,8 @@ static int guac_pa_is_silence(const void* buffer, size_t length) { static void __stream_read_callback(pa_stream* stream, size_t length, void* data) { - guac_client* client = (guac_client*) data; - guac_vnc_client* vnc_client = (guac_vnc_client*) client->data; - guac_audio_stream* audio = vnc_client->audio; + guac_pa_stream* guac_stream = (guac_pa_stream*) data; + guac_audio_stream* audio = guac_stream->audio; const void* buffer; @@ -86,7 +84,8 @@ static void __stream_read_callback(pa_stream* stream, size_t length, static void __stream_state_callback(pa_stream* stream, void* data) { - guac_client* client = (guac_client*) data; + guac_pa_stream* guac_stream = (guac_pa_stream*) data; + guac_client* client = guac_stream->client; switch (pa_stream_get_state(stream)) { @@ -137,11 +136,11 @@ static void __context_get_sink_info_callback(pa_context* context, /* Set format */ spec.format = PA_SAMPLE_S16LE; - spec.rate = GUAC_VNC_AUDIO_RATE; - spec.channels = GUAC_VNC_AUDIO_CHANNELS; + spec.rate = GUAC_PULSE_AUDIO_RATE; + spec.channels = GUAC_PULSE_AUDIO_CHANNELS; attr.maxlength = -1; - attr.fragsize = GUAC_VNC_AUDIO_FRAGMENT_SIZE; + attr.fragsize = GUAC_PULSE_AUDIO_FRAGMENT_SIZE; /* Create stream */ stream = pa_stream_new(context, "Guacamole Audio", &spec, NULL); @@ -226,41 +225,57 @@ static void __context_state_callback(pa_context* context, void* data) { } -void guac_pa_start_stream(guac_client* client) { +guac_pa_stream* guac_pa_stream_alloc(guac_client* client, + const char* server_name) { - guac_vnc_client* vnc_client = (guac_vnc_client*) client->data; - guac_vnc_settings* settings = vnc_client->settings; + guac_audio_stream* audio = guac_audio_stream_alloc(client, NULL, + GUAC_PULSE_AUDIO_RATE, GUAC_PULSE_AUDIO_CHANNELS, + GUAC_PULSE_AUDIO_BPS); - pa_context* context; + /* Abort if audio stream cannot be created */ + if (audio == NULL) + return NULL; - guac_client_log(client, GUAC_LOG_INFO, "Starting audio stream"); + guac_client_log(client, GUAC_LOG_INFO, "Audio will be encoded as %s", + audio->encoder->mimetype); /* Init main loop */ - vnc_client->pa_mainloop = pa_threaded_mainloop_new(); + guac_pa_stream* stream = malloc(sizeof(guac_pa_stream)); + stream->client = client; + stream->audio = audio; + stream->pa_mainloop = pa_threaded_mainloop_new(); /* Create context */ - context = pa_context_new( - pa_threaded_mainloop_get_api(vnc_client->pa_mainloop), + pa_context* context = pa_context_new( + pa_threaded_mainloop_get_api(stream->pa_mainloop), "Guacamole Audio"); /* Set up context */ - pa_context_set_state_callback(context, __context_state_callback, client); - pa_context_connect(context, settings->pa_servername, - PA_CONTEXT_NOAUTOSPAWN, NULL); + pa_context_set_state_callback(context, __context_state_callback, stream); + pa_context_connect(context, server_name, PA_CONTEXT_NOAUTOSPAWN, NULL); /* Start loop */ - pa_threaded_mainloop_start(vnc_client->pa_mainloop); + pa_threaded_mainloop_start(stream->pa_mainloop); + + return stream; } -void guac_pa_stop_stream(guac_client* client) { +void guac_pa_stream_add_user(guac_pa_stream* stream, guac_user* user) { + guac_audio_stream_add_user(stream->audio, user); +} - guac_vnc_client* vnc_client = (guac_vnc_client*) client->data; +void guac_pa_stream_free(guac_pa_stream* stream) { /* Stop loop */ - pa_threaded_mainloop_stop(vnc_client->pa_mainloop); + pa_threaded_mainloop_stop(stream->pa_mainloop); - guac_client_log(client, GUAC_LOG_INFO, "Audio stream finished"); + /* Free underlying audio stream */ + guac_audio_stream_free(stream->audio); + + /* Stream now ended */ + guac_client_log(stream->client, GUAC_LOG_INFO, "Audio stream finished"); + free(stream); } diff --git a/src/pulse/pulse/pulse.h b/src/pulse/pulse/pulse.h new file mode 100644 index 00000000..386d8cd9 --- /dev/null +++ b/src/pulse/pulse/pulse.h @@ -0,0 +1,124 @@ +/* + * 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_PULSE_H +#define GUAC_PULSE_H + +#include "config.h" + +#include +#include +#include +#include + +/** + * The number of bytes to request for the audio fragments received from + * PulseAudio. + */ +#define GUAC_PULSE_AUDIO_FRAGMENT_SIZE 8192 + +/** + * The minimum number of PCM bytes to wait for before flushing an audio + * packet. The current value is 48K, which works out to be around 280ms. + */ +#define GUAC_PULSE_PCM_WRITE_RATE 49152 + +/** + * Rate of audio to stream, in Hz. + */ +#define GUAC_PULSE_AUDIO_RATE 44100 + +/** + * The number of channels to stream. + */ +#define GUAC_PULSE_AUDIO_CHANNELS 2 + +/** + * The number of bits per sample. + */ +#define GUAC_PULSE_AUDIO_BPS 16 + +/** + * An audio stream which connects to a PulseAudio server and streams the + * received audio through a guac_client. + */ +typedef struct guac_pa_stream { + + /** + * The client associated with the audio stream. + */ + guac_client* client; + + /** + * Audio output stream. + */ + guac_audio_stream* audio; + + /** + * PulseAudio event loop. + */ + pa_threaded_mainloop* pa_mainloop; + +} guac_pa_stream; + +/** + * Allocates a new PulseAudio audio stream for the given Guacamole client and + * begins streaming. + * + * @param client + * The client to stream audio to. + * + * @param server_name + * The hostname of the PulseAudio server to connect to, or NULL to connect + * to the default (local) server. + * + * @return + * A newly-allocated PulseAudio stream, or NULL if audio cannot be + * streamed. + */ +guac_pa_stream* guac_pa_stream_alloc(guac_client* client, + const char* server_name); + +/** + * Notifies the given PulseAudio stream that a user has joined the connection. + * The audio stream itself may need to be restarted. and the audio stream will + * need to be created for the new user to ensure they can properly handle + * future data received along the stream. + * + * @param stream + * The guac_pa_stream associated with the Guacamole connection being + * joined. + * + * @param user + * The user that has joined the Guacamole connection. + */ +void guac_pa_stream_add_user(guac_pa_stream* stream, guac_user* user); + +/** + * Stops streaming audio from the given PulseAudio stream, freeing all + * associated resources. + * + * @param stream + * The PulseAudio stream to free. + */ +void guac_pa_stream_free(guac_pa_stream* stream); + +#endif +