GUACAMOLE-179: Merge move of PulseAudio support into library.
This commit is contained in:
commit
b8f4f19c36
@ -28,6 +28,7 @@ DIST_SUBDIRS = \
|
|||||||
src/terminal \
|
src/terminal \
|
||||||
src/guacd \
|
src/guacd \
|
||||||
src/guacenc \
|
src/guacenc \
|
||||||
|
src/pulse \
|
||||||
src/protocols/rdp \
|
src/protocols/rdp \
|
||||||
src/protocols/ssh \
|
src/protocols/ssh \
|
||||||
src/protocols/telnet \
|
src/protocols/telnet \
|
||||||
@ -48,6 +49,10 @@ if ENABLE_TERMINAL
|
|||||||
SUBDIRS += src/terminal
|
SUBDIRS += src/terminal
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
if ENABLE_PULSE
|
||||||
|
SUBDIRS += src/pulse
|
||||||
|
endif
|
||||||
|
|
||||||
if ENABLE_RDP
|
if ENABLE_RDP
|
||||||
SUBDIRS += src/protocols/rdp
|
SUBDIRS += src/protocols/rdp
|
||||||
endif
|
endif
|
||||||
|
@ -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_LTLIB], '$(top_builddir)/src/common/libguac_common.la')
|
||||||
AC_SUBST([COMMON_INCLUDE], '-I$(top_srcdir)/src/common')
|
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
|
# Common utility library for guacd implementations
|
||||||
AC_SUBST([LIBGUACD_LTLIB], '$(top_builddir)/src/libguacd/libguacd.la')
|
AC_SUBST([LIBGUACD_LTLIB], '$(top_builddir)/src/libguacd/libguacd.la')
|
||||||
AC_SUBST([LIBGUACD_INCLUDE], '-I$(top_srcdir)/src/libguacd')
|
AC_SUBST([LIBGUACD_INCLUDE], '-I$(top_srcdir)/src/libguacd')
|
||||||
@ -1108,6 +1112,7 @@ AC_CONFIG_FILES([Makefile
|
|||||||
src/libguacd/Makefile
|
src/libguacd/Makefile
|
||||||
src/guacd/Makefile
|
src/guacd/Makefile
|
||||||
src/guacenc/Makefile
|
src/guacenc/Makefile
|
||||||
|
src/pulse/Makefile
|
||||||
src/protocols/rdp/Makefile
|
src/protocols/rdp/Makefile
|
||||||
src/protocols/ssh/Makefile
|
src/protocols/ssh/Makefile
|
||||||
src/protocols/telnet/Makefile
|
src/protocols/telnet/Makefile
|
||||||
|
@ -30,16 +30,38 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the encoder associated with the given guac_audio_stream, automatically
|
||||||
|
* invoking its begin_handler. The guac_audio_stream MUST NOT already be
|
||||||
|
* associated with an encoder.
|
||||||
|
*
|
||||||
|
* @param audio
|
||||||
|
* The guac_audio_stream whose encoder is being set.
|
||||||
|
*
|
||||||
|
* @param encoder
|
||||||
|
* The encoder to associate with the given guac_audio_stream.
|
||||||
|
*/
|
||||||
|
static void guac_audio_stream_set_encoder(guac_audio_stream* audio,
|
||||||
|
guac_audio_encoder* encoder) {
|
||||||
|
|
||||||
|
/* Call handler, if defined */
|
||||||
|
if (encoder != NULL && encoder->begin_handler)
|
||||||
|
encoder->begin_handler(audio);
|
||||||
|
|
||||||
|
/* Assign encoder, which may be NULL */
|
||||||
|
audio->encoder = encoder;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assigns a new audio encoder to the given guac_audio_stream based on the
|
* Assigns a new audio encoder to the given guac_audio_stream based on the
|
||||||
* audio mimetypes declared as supported by the given user. If no audio encoder
|
* audio mimetypes declared as supported by the given user. If no audio encoder
|
||||||
* can be found, no new audio encoder is assigned, and the existing encoder is
|
* can be found, no new audio encoder is assigned, and the existing encoder is
|
||||||
* left untouched (if any).
|
* left untouched (if any).
|
||||||
*
|
*
|
||||||
* @param owner
|
* @param user
|
||||||
* The user whose supported audio mimetypes should determine the audio
|
* The user whose supported audio mimetypes should determine the audio
|
||||||
* encoder selected. It is expected that this user will be the owner of
|
* encoder selected.
|
||||||
* the connection.
|
|
||||||
*
|
*
|
||||||
* @param data
|
* @param data
|
||||||
* The guac_audio_stream to which the new encoder should be assigned.
|
* The guac_audio_stream to which the new encoder should be assigned.
|
||||||
@ -50,31 +72,32 @@
|
|||||||
* The assigned audio encoder. If no new audio encoder can be assigned,
|
* The assigned audio encoder. If no new audio encoder can be assigned,
|
||||||
* this will be the currently-assigned audio encoder (which may be NULL).
|
* this will be the currently-assigned audio encoder (which may be NULL).
|
||||||
*/
|
*/
|
||||||
static void* guac_audio_assign_encoder(guac_user* owner, void* data) {
|
static void* guac_audio_assign_encoder(guac_user* user, void* data) {
|
||||||
|
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
guac_audio_stream* audio = (guac_audio_stream*) data;
|
guac_audio_stream* audio = (guac_audio_stream*) data;
|
||||||
int bps = audio->bps;
|
int bps = audio->bps;
|
||||||
|
|
||||||
/* If there is no owner, do not attempt to assign a new encoder */
|
/* If no user is provided, or an encoder has already been assigned,
|
||||||
if (owner == NULL)
|
* do not attempt to assign a new encoder */
|
||||||
|
if (user == NULL || audio->encoder != NULL)
|
||||||
return audio->encoder;
|
return audio->encoder;
|
||||||
|
|
||||||
/* For each supported mimetype, check for an associated encoder */
|
/* For each supported mimetype, check for an associated encoder */
|
||||||
for (i=0; owner->info.audio_mimetypes[i] != NULL; i++) {
|
for (i=0; user->info.audio_mimetypes[i] != NULL; i++) {
|
||||||
|
|
||||||
const char* mimetype = owner->info.audio_mimetypes[i];
|
const char* mimetype = user->info.audio_mimetypes[i];
|
||||||
|
|
||||||
/* If 16-bit raw audio is supported, done. */
|
/* If 16-bit raw audio is supported, done. */
|
||||||
if (bps == 16 && strcmp(mimetype, raw16_encoder->mimetype) == 0) {
|
if (bps == 16 && strcmp(mimetype, raw16_encoder->mimetype) == 0) {
|
||||||
audio->encoder = raw16_encoder;
|
guac_audio_stream_set_encoder(audio, raw16_encoder);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* If 8-bit raw audio is supported, done. */
|
/* If 8-bit raw audio is supported, done. */
|
||||||
if (bps == 8 && strcmp(mimetype, raw8_encoder->mimetype) == 0) {
|
if (bps == 8 && strcmp(mimetype, raw8_encoder->mimetype) == 0) {
|
||||||
audio->encoder = raw8_encoder;
|
guac_audio_stream_set_encoder(audio, raw8_encoder);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,16 +123,17 @@ guac_audio_stream* guac_audio_stream_alloc(guac_client* client,
|
|||||||
audio->channels = channels;
|
audio->channels = channels;
|
||||||
audio->bps = bps;
|
audio->bps = bps;
|
||||||
|
|
||||||
/* Assign encoder for owner, abort if no encoder can be found */
|
/* Assign encoder if explicitly provided */
|
||||||
if (!guac_client_for_owner(client, guac_audio_assign_encoder, audio)) {
|
if (encoder != NULL)
|
||||||
guac_client_free_stream(client, audio->stream);
|
guac_audio_stream_set_encoder(audio, encoder);
|
||||||
free(audio);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Call handler, if defined */
|
/* Otherwise, attempt to automatically assign encoder for owner */
|
||||||
if (audio->encoder->begin_handler)
|
if (audio->encoder == NULL)
|
||||||
audio->encoder->begin_handler(audio);
|
guac_client_for_owner(client, guac_audio_assign_encoder, audio);
|
||||||
|
|
||||||
|
/* Failing that, attempt to assign encoder for ANY connected user */
|
||||||
|
if (audio->encoder == NULL)
|
||||||
|
guac_client_foreach_user(client, guac_audio_assign_encoder, audio);
|
||||||
|
|
||||||
return audio;
|
return audio;
|
||||||
|
|
||||||
@ -118,8 +142,12 @@ guac_audio_stream* guac_audio_stream_alloc(guac_client* client,
|
|||||||
void guac_audio_stream_reset(guac_audio_stream* audio,
|
void guac_audio_stream_reset(guac_audio_stream* audio,
|
||||||
guac_audio_encoder* encoder, int rate, int channels, int bps) {
|
guac_audio_encoder* encoder, int rate, int channels, int bps) {
|
||||||
|
|
||||||
|
/* Pull assigned encoder if no other encoder is requested */
|
||||||
|
if (encoder == NULL)
|
||||||
|
encoder = audio->encoder;
|
||||||
|
|
||||||
/* Do nothing if nothing is changing */
|
/* Do nothing if nothing is changing */
|
||||||
if ((encoder == NULL || encoder == audio->encoder)
|
if (encoder == audio->encoder
|
||||||
&& rate == audio->rate
|
&& rate == audio->rate
|
||||||
&& channels == audio->channels
|
&& channels == audio->channels
|
||||||
&& bps == audio->bps) {
|
&& bps == audio->bps) {
|
||||||
@ -127,31 +155,28 @@ void guac_audio_stream_reset(guac_audio_stream* audio,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Free old encoder data */
|
/* Free old encoder data */
|
||||||
if (audio->encoder->end_handler)
|
if (audio->encoder != NULL && audio->encoder->end_handler)
|
||||||
audio->encoder->end_handler(audio);
|
audio->encoder->end_handler(audio);
|
||||||
|
|
||||||
/* Assign new encoder, if changed */
|
|
||||||
if (encoder != NULL)
|
|
||||||
audio->encoder = encoder;
|
|
||||||
|
|
||||||
/* Set PCM properties */
|
/* Set PCM properties */
|
||||||
audio->rate = rate;
|
audio->rate = rate;
|
||||||
audio->channels = channels;
|
audio->channels = channels;
|
||||||
audio->bps = bps;
|
audio->bps = bps;
|
||||||
|
|
||||||
/* Init encoder with new data */
|
/* Re-init encoder */
|
||||||
if (audio->encoder->begin_handler)
|
guac_audio_stream_set_encoder(audio, encoder);
|
||||||
audio->encoder->begin_handler(audio);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void guac_audio_stream_add_user(guac_audio_stream* audio, guac_user* user) {
|
void guac_audio_stream_add_user(guac_audio_stream* audio, guac_user* user) {
|
||||||
|
|
||||||
guac_audio_encoder* encoder = audio->encoder;
|
/* Attempt to assign encoder if no encoder has yet been assigned */
|
||||||
|
if (audio->encoder == NULL)
|
||||||
|
guac_audio_assign_encoder(user, audio);
|
||||||
|
|
||||||
/* Notify encoder that a new user is present */
|
/* Notify encoder that a new user is present */
|
||||||
if (encoder->join_handler)
|
if (audio->encoder != NULL && audio->encoder->join_handler)
|
||||||
encoder->join_handler(audio, user);
|
audio->encoder->join_handler(audio, user);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -161,7 +186,7 @@ void guac_audio_stream_free(guac_audio_stream* audio) {
|
|||||||
guac_audio_stream_flush(audio);
|
guac_audio_stream_flush(audio);
|
||||||
|
|
||||||
/* Clean up encoder */
|
/* Clean up encoder */
|
||||||
if (audio->encoder->end_handler)
|
if (audio->encoder != NULL && audio->encoder->end_handler)
|
||||||
audio->encoder->end_handler(audio);
|
audio->encoder->end_handler(audio);
|
||||||
|
|
||||||
/* Free associated data */
|
/* Free associated data */
|
||||||
@ -173,7 +198,7 @@ void guac_audio_stream_write_pcm(guac_audio_stream* audio,
|
|||||||
const unsigned char* data, int length) {
|
const unsigned char* data, int length) {
|
||||||
|
|
||||||
/* Write data */
|
/* Write data */
|
||||||
if (audio->encoder->write_handler)
|
if (audio->encoder != NULL && audio->encoder->write_handler)
|
||||||
audio->encoder->write_handler(audio, data, length);
|
audio->encoder->write_handler(audio, data, length);
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -181,7 +206,7 @@ void guac_audio_stream_write_pcm(guac_audio_stream* audio,
|
|||||||
void guac_audio_stream_flush(guac_audio_stream* audio) {
|
void guac_audio_stream_flush(guac_audio_stream* audio) {
|
||||||
|
|
||||||
/* Flush any buffered data */
|
/* Flush any buffered data */
|
||||||
if (audio->encoder->flush_handler)
|
if (audio->encoder != NULL && audio->encoder->flush_handler)
|
||||||
audio->encoder->flush_handler(audio);
|
audio->encoder->flush_handler(audio);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -114,7 +114,7 @@ struct guac_audio_stream {
|
|||||||
* Allocates a new audio stream at the client level which encodes audio data
|
* Allocates a new audio stream at the client level which encodes audio data
|
||||||
* using the given encoder. If NULL is specified for the encoder, an
|
* using the given encoder. If NULL is specified for the encoder, an
|
||||||
* appropriate encoder will be selected based on the encoders built into
|
* appropriate encoder will be selected based on the encoders built into
|
||||||
* libguac and the level of support declared by the owner associated with the
|
* libguac and the level of support declared by users associated with the
|
||||||
* given guac_client. The PCM format specified here (via rate, channels, and
|
* given guac_client. The PCM format specified here (via rate, channels, and
|
||||||
* bps) must be the format used for all PCM data provided to the audio stream.
|
* 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().
|
* The format may only be changed using guac_audio_stream_reset().
|
||||||
@ -125,10 +125,10 @@ struct guac_audio_stream {
|
|||||||
* for the new user.
|
* for the new user.
|
||||||
*
|
*
|
||||||
* @param client
|
* @param client
|
||||||
* The guac_client for which this audio stream is being allocated. Only the
|
* The guac_client for which this audio stream is being allocated. The
|
||||||
* connection owner is used to determine the level of audio support, and it
|
* connection owner is given priority when determining the level of audio
|
||||||
* is currently assumed that all other joining users on the connection will
|
* support. It is currently assumed that all other joining users on the
|
||||||
* have the same level of audio support.
|
* connection will have the same level of audio support.
|
||||||
*
|
*
|
||||||
* @param encoder
|
* @param encoder
|
||||||
* The guac_audio_encoder to use when encoding audio, or NULL if libguac
|
* The guac_audio_encoder to use when encoding audio, or NULL if libguac
|
||||||
|
@ -46,22 +46,16 @@ noinst_HEADERS = \
|
|||||||
user.h \
|
user.h \
|
||||||
vnc.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 = \
|
libguac_client_vnc_la_CFLAGS = \
|
||||||
-Werror -Wall -pedantic -Iinclude \
|
-Werror -Wall -pedantic -Iinclude \
|
||||||
@COMMON_INCLUDE@ \
|
@COMMON_INCLUDE@ \
|
||||||
@COMMON_SSH_INCLUDE@ \
|
@COMMON_SSH_INCLUDE@ \
|
||||||
@LIBGUAC_INCLUDE@
|
@LIBGUAC_INCLUDE@ \
|
||||||
|
@PULSE_INCLUDE@
|
||||||
|
|
||||||
libguac_client_vnc_la_LDFLAGS = \
|
libguac_client_vnc_la_LDFLAGS = \
|
||||||
-version-info 0:0:0 \
|
-version-info 0:0:0 \
|
||||||
@CAIRO_LIBS@ \
|
@CAIRO_LIBS@ \
|
||||||
@PULSE_LIBS@ \
|
|
||||||
@VNC_LIBS@
|
@VNC_LIBS@
|
||||||
|
|
||||||
libguac_client_vnc_la_LIBADD = \
|
libguac_client_vnc_la_LIBADD = \
|
||||||
@ -75,3 +69,8 @@ noinst_HEADERS += sftp.h
|
|||||||
libguac_client_vnc_la_LIBADD += @COMMON_SSH_LTLIB@
|
libguac_client_vnc_la_LIBADD += @COMMON_SSH_LTLIB@
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
# Optional PulseAudio support
|
||||||
|
if ENABLE_PULSE
|
||||||
|
libguac_client_vnc_la_LIBADD += @PULSE_LTLIB@
|
||||||
|
endif
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef ENABLE_PULSE
|
#ifdef ENABLE_PULSE
|
||||||
#include "pulse.h"
|
#include "pulse/pulse.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <guacamole/client.h>
|
#include <guacamole/client.h>
|
||||||
@ -110,20 +110,16 @@ int guac_vnc_client_free_handler(guac_client* client) {
|
|||||||
if (vnc_client->display != NULL)
|
if (vnc_client->display != NULL)
|
||||||
guac_common_display_free(vnc_client->display);
|
guac_common_display_free(vnc_client->display);
|
||||||
|
|
||||||
/* Free settings-dependend data */
|
|
||||||
if (settings != NULL) {
|
|
||||||
|
|
||||||
#ifdef ENABLE_PULSE
|
#ifdef ENABLE_PULSE
|
||||||
/* If audio enabled, stop streaming */
|
/* If audio enabled, stop streaming */
|
||||||
if (settings->audio_enabled)
|
if (vnc_client->audio)
|
||||||
guac_pa_stop_stream(client);
|
guac_pa_stream_free(vnc_client->audio);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* Free parsed settings */
|
/* Free parsed settings */
|
||||||
|
if (settings != NULL)
|
||||||
guac_vnc_settings_free(settings);
|
guac_vnc_settings_free(settings);
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Free generic data struct */
|
/* Free generic data struct */
|
||||||
free(client->data);
|
free(client->data);
|
||||||
|
|
||||||
|
@ -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 <guacamole/client.h>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
|
||||||
|
|
@ -28,6 +28,10 @@
|
|||||||
#include "sftp.h"
|
#include "sftp.h"
|
||||||
#include "vnc.h"
|
#include "vnc.h"
|
||||||
|
|
||||||
|
#ifdef ENABLE_PULSE
|
||||||
|
#include "pulse/pulse.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <guacamole/audio.h>
|
#include <guacamole/audio.h>
|
||||||
#include <guacamole/client.h>
|
#include <guacamole/client.h>
|
||||||
#include <guacamole/socket.h>
|
#include <guacamole/socket.h>
|
||||||
@ -75,7 +79,7 @@ int guac_vnc_user_join_handler(guac_user* user, int argc, char** argv) {
|
|||||||
#ifdef ENABLE_PULSE
|
#ifdef ENABLE_PULSE
|
||||||
/* Synchronize an audio stream */
|
/* Synchronize an audio stream */
|
||||||
if (vnc_client->audio)
|
if (vnc_client->audio)
|
||||||
guac_audio_stream_add_user(vnc_client->audio, user);
|
guac_pa_stream_add_user(vnc_client->audio, user);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* Synchronize with current display */
|
/* Synchronize with current display */
|
||||||
|
@ -33,7 +33,7 @@
|
|||||||
#include "vnc.h"
|
#include "vnc.h"
|
||||||
|
|
||||||
#ifdef ENABLE_PULSE
|
#ifdef ENABLE_PULSE
|
||||||
#include "pulse.h"
|
#include "pulse/pulse.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef ENABLE_COMMON_SSH
|
#ifdef ENABLE_COMMON_SSH
|
||||||
@ -207,32 +207,10 @@ void* guac_vnc_client_thread(void* data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifdef ENABLE_PULSE
|
#ifdef ENABLE_PULSE
|
||||||
/* If an encoding is available, load an audio stream */
|
/* If audio is enabled, start streaming via PulseAudio */
|
||||||
if (settings->audio_enabled) {
|
if (settings->audio_enabled)
|
||||||
|
vnc_client->audio = guac_pa_stream_alloc(client,
|
||||||
vnc_client->audio = guac_audio_stream_alloc(client, NULL,
|
settings->pa_servername);
|
||||||
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 */
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef ENABLE_COMMON_SSH
|
#ifdef ENABLE_COMMON_SSH
|
||||||
|
@ -33,8 +33,7 @@
|
|||||||
#include <rfb/rfbclient.h>
|
#include <rfb/rfbclient.h>
|
||||||
|
|
||||||
#ifdef ENABLE_PULSE
|
#ifdef ENABLE_PULSE
|
||||||
#include <guacamole/audio.h>
|
#include "pulse/pulse.h"
|
||||||
#include <pulse/pulseaudio.h>
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef ENABLE_COMMON_SSH
|
#ifdef ENABLE_COMMON_SSH
|
||||||
@ -89,14 +88,9 @@ typedef struct guac_vnc_client {
|
|||||||
|
|
||||||
#ifdef ENABLE_PULSE
|
#ifdef ENABLE_PULSE
|
||||||
/**
|
/**
|
||||||
* Audio output, if any.
|
* PulseAudio output, if any.
|
||||||
*/
|
*/
|
||||||
guac_audio_stream* audio;
|
guac_pa_stream* audio;
|
||||||
|
|
||||||
/**
|
|
||||||
* PulseAudio event loop.
|
|
||||||
*/
|
|
||||||
pa_threaded_mainloop* pa_mainloop;
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef ENABLE_COMMON_SSH
|
#ifdef ENABLE_COMMON_SSH
|
||||||
|
40
src/pulse/Makefile.am
Normal file
40
src/pulse/Makefile.am
Normal file
@ -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@
|
||||||
|
|
@ -19,12 +19,11 @@
|
|||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
#include "pulse.h"
|
#include "pulse/pulse.h"
|
||||||
#include "vnc.h"
|
|
||||||
|
|
||||||
#include <guacamole/audio.h>
|
#include <guacamole/audio.h>
|
||||||
#include <guacamole/client.h>
|
#include <guacamole/client.h>
|
||||||
#include <guacamole/socket.h>
|
#include <guacamole/user.h>
|
||||||
#include <pulse/pulseaudio.h>
|
#include <pulse/pulseaudio.h>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -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,
|
static void __stream_read_callback(pa_stream* stream, size_t length,
|
||||||
void* data) {
|
void* data) {
|
||||||
|
|
||||||
guac_client* client = (guac_client*) data;
|
guac_pa_stream* guac_stream = (guac_pa_stream*) data;
|
||||||
guac_vnc_client* vnc_client = (guac_vnc_client*) client->data;
|
guac_audio_stream* audio = guac_stream->audio;
|
||||||
guac_audio_stream* audio = vnc_client->audio;
|
|
||||||
|
|
||||||
const void* buffer;
|
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) {
|
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)) {
|
switch (pa_stream_get_state(stream)) {
|
||||||
|
|
||||||
@ -137,11 +136,11 @@ static void __context_get_sink_info_callback(pa_context* context,
|
|||||||
|
|
||||||
/* Set format */
|
/* Set format */
|
||||||
spec.format = PA_SAMPLE_S16LE;
|
spec.format = PA_SAMPLE_S16LE;
|
||||||
spec.rate = GUAC_VNC_AUDIO_RATE;
|
spec.rate = GUAC_PULSE_AUDIO_RATE;
|
||||||
spec.channels = GUAC_VNC_AUDIO_CHANNELS;
|
spec.channels = GUAC_PULSE_AUDIO_CHANNELS;
|
||||||
|
|
||||||
attr.maxlength = -1;
|
attr.maxlength = -1;
|
||||||
attr.fragsize = GUAC_VNC_AUDIO_FRAGMENT_SIZE;
|
attr.fragsize = GUAC_PULSE_AUDIO_FRAGMENT_SIZE;
|
||||||
|
|
||||||
/* Create stream */
|
/* Create stream */
|
||||||
stream = pa_stream_new(context, "Guacamole Audio", &spec, NULL);
|
stream = pa_stream_new(context, "Guacamole Audio", &spec, NULL);
|
||||||
@ -226,41 +225,54 @@ 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_audio_stream* audio = guac_audio_stream_alloc(client, NULL,
|
||||||
guac_vnc_settings* settings = vnc_client->settings;
|
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)
|
||||||
guac_client_log(client, GUAC_LOG_INFO, "Starting audio stream");
|
return NULL;
|
||||||
|
|
||||||
/* Init main loop */
|
/* 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 */
|
/* Create context */
|
||||||
context = pa_context_new(
|
pa_context* context = pa_context_new(
|
||||||
pa_threaded_mainloop_get_api(vnc_client->pa_mainloop),
|
pa_threaded_mainloop_get_api(stream->pa_mainloop),
|
||||||
"Guacamole Audio");
|
"Guacamole Audio");
|
||||||
|
|
||||||
/* Set up context */
|
/* Set up context */
|
||||||
pa_context_set_state_callback(context, __context_state_callback, client);
|
pa_context_set_state_callback(context, __context_state_callback, stream);
|
||||||
pa_context_connect(context, settings->pa_servername,
|
pa_context_connect(context, server_name, PA_CONTEXT_NOAUTOSPAWN, NULL);
|
||||||
PA_CONTEXT_NOAUTOSPAWN, NULL);
|
|
||||||
|
|
||||||
/* Start loop */
|
/* 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 */
|
/* 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);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
124
src/pulse/pulse/pulse.h
Normal file
124
src/pulse/pulse/pulse.h
Normal file
@ -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 <guacamole/audio.h>
|
||||||
|
#include <guacamole/client.h>
|
||||||
|
#include <guacamole/user.h>
|
||||||
|
#include <pulse/pulseaudio.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
|
Loading…
Reference in New Issue
Block a user