[WIP]: [SOUND] Most of the sound recording implementation.
This commit is contained in:
parent
136bd9a6ab
commit
4dae4fe5c1
@ -25,6 +25,9 @@
|
|||||||
#include <guacamole/audio.h>
|
#include <guacamole/audio.h>
|
||||||
#include <guacamole/client.h>
|
#include <guacamole/client.h>
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
void guac_spice_client_audio_playback_data_handler(
|
void guac_spice_client_audio_playback_data_handler(
|
||||||
SpicePlaybackChannel* channel, gpointer data, gint size,
|
SpicePlaybackChannel* channel, gpointer data, gint size,
|
||||||
guac_client* client) {
|
guac_client* client) {
|
||||||
@ -76,11 +79,221 @@ void guac_spice_client_audio_playback_stop_handler(
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the given raw audio mimetype, producing the corresponding rate,
|
||||||
|
* number of channels, and bytes per sample.
|
||||||
|
*
|
||||||
|
* @param mimetype
|
||||||
|
* The raw audio mimetype to parse.
|
||||||
|
*
|
||||||
|
* @param rate
|
||||||
|
* A pointer to an int where the sample rate for the PCM format described
|
||||||
|
* by the given mimetype should be stored.
|
||||||
|
*
|
||||||
|
* @param channels
|
||||||
|
* A pointer to an int where the number of channels used by the PCM format
|
||||||
|
* described by the given mimetype should be stored.
|
||||||
|
*
|
||||||
|
* @param bps
|
||||||
|
* A pointer to an int where the number of bytes used the PCM format for
|
||||||
|
* each sample (independent of number of channels) described by the given
|
||||||
|
* mimetype should be stored.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* Zero if the given mimetype is a raw audio mimetype and has been parsed
|
||||||
|
* successfully, non-zero otherwise.
|
||||||
|
*/
|
||||||
|
static int guac_spice_audio_parse_mimetype(const char* mimetype, int* rate,
|
||||||
|
int* channels, int* bps) {
|
||||||
|
|
||||||
|
int parsed_rate = -1;
|
||||||
|
int parsed_channels = 1;
|
||||||
|
int parsed_bps;
|
||||||
|
|
||||||
|
/* PCM audio with one byte per sample */
|
||||||
|
if (strncmp(mimetype, "audio/L8;", 9) == 0) {
|
||||||
|
mimetype += 8; /* Advance to semicolon ONLY */
|
||||||
|
parsed_bps = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* PCM audio with two bytes per sample */
|
||||||
|
else if (strncmp(mimetype, "audio/L16;", 10) == 0) {
|
||||||
|
mimetype += 9; /* Advance to semicolon ONLY */
|
||||||
|
parsed_bps = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Unsupported mimetype */
|
||||||
|
else
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
/* Parse each parameter name/value pair within the mimetype */
|
||||||
|
do {
|
||||||
|
|
||||||
|
/* Advance to first character of parameter (current is either a
|
||||||
|
* semicolon or a comma) */
|
||||||
|
mimetype++;
|
||||||
|
|
||||||
|
/* Parse number of channels */
|
||||||
|
if (strncmp(mimetype, "channels=", 9) == 0) {
|
||||||
|
|
||||||
|
mimetype += 9;
|
||||||
|
parsed_channels = strtol(mimetype, (char**) &mimetype, 10);
|
||||||
|
|
||||||
|
/* Fail if value invalid / out of range */
|
||||||
|
if (errno == EINVAL || errno == ERANGE)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Parse number of rate */
|
||||||
|
else if (strncmp(mimetype, "rate=", 5) == 0) {
|
||||||
|
|
||||||
|
mimetype += 5;
|
||||||
|
parsed_rate = strtol(mimetype, (char**) &mimetype, 10);
|
||||||
|
|
||||||
|
/* Fail if value invalid / out of range */
|
||||||
|
if (errno == EINVAL || errno == ERANGE)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Advance to next parameter */
|
||||||
|
mimetype = strchr(mimetype, ',');
|
||||||
|
|
||||||
|
} while (mimetype != NULL);
|
||||||
|
|
||||||
|
/* Mimetype is invalid if rate was not specified */
|
||||||
|
if (parsed_rate == -1)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
/* Parse success */
|
||||||
|
*rate = parsed_rate;
|
||||||
|
*channels = parsed_channels;
|
||||||
|
*bps = parsed_bps;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static int guac_spice_audio_blob_handler(guac_user* user, guac_stream* stream,
|
||||||
|
void* data, int length) {
|
||||||
|
|
||||||
|
guac_client* client = user->client;
|
||||||
|
guac_spice_client* spice_client = (guac_spice_client*) client->data;
|
||||||
|
|
||||||
|
/* Write blob to audio stream */
|
||||||
|
spice_record_channel_send_data(spice_client->record_channel, data, length, (unsigned long) time(NULL));
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static int guac_spice_audio_end_handler(guac_user* user, guac_stream* stream) {
|
||||||
|
|
||||||
|
/* Ignore - the RECORD_CHANNEL channel will simply not receive anything */
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int guac_spice_client_audio_record_handler(guac_user* user, guac_stream* stream,
|
||||||
|
char* mimetype) {
|
||||||
|
|
||||||
|
guac_user_log(user, GUAC_LOG_DEBUG, "Calling audio input handler.");
|
||||||
|
|
||||||
|
guac_client* client = user->client;
|
||||||
|
guac_spice_client* spice_client = (guac_spice_client*) client->data;
|
||||||
|
spice_client->audio_input = stream;
|
||||||
|
|
||||||
|
int rate;
|
||||||
|
int channels;
|
||||||
|
int bps;
|
||||||
|
|
||||||
|
/* Parse mimetype, abort on parse error */
|
||||||
|
if (guac_spice_audio_parse_mimetype(mimetype, &rate, &channels, &bps)) {
|
||||||
|
guac_user_log(user, GUAC_LOG_WARNING, "Denying user audio stream with "
|
||||||
|
"unsupported mimetype: \"%s\"", mimetype);
|
||||||
|
guac_protocol_send_ack(user->socket, stream, "Unsupported audio "
|
||||||
|
"mimetype", GUAC_PROTOCOL_STATUS_CLIENT_BAD_TYPE);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Init stream data */
|
||||||
|
stream->blob_handler = guac_spice_audio_blob_handler;
|
||||||
|
stream->end_handler = guac_spice_audio_end_handler;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends an "ack" instruction over the socket associated with the Guacamole
|
||||||
|
* stream over which audio data is being received. The "ack" instruction will
|
||||||
|
* only be sent if the Guacamole audio stream has been established (through
|
||||||
|
* receipt of an "audio" instruction), is still open (has not received an "end"
|
||||||
|
* instruction nor been associated with an "ack" having an error code), and is
|
||||||
|
* associated with an active Spice RECORD_CHANNEL channel.
|
||||||
|
*
|
||||||
|
* @param user
|
||||||
|
* The guac_user associated with the audio input stream.
|
||||||
|
*
|
||||||
|
* @param stream
|
||||||
|
* The guac_stream associated with the audio input for the client.
|
||||||
|
*
|
||||||
|
* @param message
|
||||||
|
* An arbitrary human-readable message to send along with the "ack".
|
||||||
|
*
|
||||||
|
* @param status
|
||||||
|
* The Guacamole protocol status code to send with the "ack". This should
|
||||||
|
* be GUAC_PROTOCOL_STATUS_SUCCESS if the audio stream has been set up
|
||||||
|
* successfully or GUAC_PROTOCOL_STATUS_RESOURCE_CLOSED if the audio stream
|
||||||
|
* has been closed (but may usable again if reopened).
|
||||||
|
*/
|
||||||
|
static void guac_spice_audio_stream_ack(guac_user* user, guac_stream* stream,
|
||||||
|
const char* message, guac_protocol_status status) {
|
||||||
|
|
||||||
|
/* Do not send ack unless both sides of the audio stream are ready */
|
||||||
|
if (user == NULL || stream == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* Send ack instruction */
|
||||||
|
guac_protocol_send_ack(user->socket, stream, message, status);
|
||||||
|
guac_socket_flush(user->socket);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static void* spice_client_record_start_callback(guac_user* owner, void* data) {
|
||||||
|
|
||||||
|
guac_spice_client* spice_client = (guac_spice_client*) data;
|
||||||
|
|
||||||
|
guac_spice_audio_stream_ack(owner, spice_client->audio_input, "OK",
|
||||||
|
GUAC_PROTOCOL_STATUS_SUCCESS);
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static void* spice_client_record_stop_callback(guac_user* owner, void* data) {
|
||||||
|
|
||||||
|
guac_spice_client* spice_client = (guac_spice_client*) data;
|
||||||
|
|
||||||
|
/* The stream is now closed */
|
||||||
|
guac_spice_audio_stream_ack(owner, spice_client->audio_input, "CLOSED",
|
||||||
|
GUAC_PROTOCOL_STATUS_RESOURCE_CLOSED);
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
void guac_spice_client_audio_record_start_handler(SpiceRecordChannel* channel,
|
void guac_spice_client_audio_record_start_handler(SpiceRecordChannel* channel,
|
||||||
gint format, gint channels, gint rate, guac_client* client) {
|
gint format, gint channels, gint rate, guac_client* client) {
|
||||||
|
|
||||||
guac_client_log(client, GUAC_LOG_DEBUG, "Calling audio record start handler.");
|
guac_client_log(client, GUAC_LOG_DEBUG, "Calling audio record start handler.");
|
||||||
|
|
||||||
|
guac_spice_client* spice_client = (guac_spice_client*) client->data;
|
||||||
|
guac_client_for_owner(client, spice_client_record_start_callback, spice_client);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void guac_spice_client_audio_record_stop_handler(SpiceRecordChannel* channel,
|
void guac_spice_client_audio_record_stop_handler(SpiceRecordChannel* channel,
|
||||||
@ -88,4 +301,7 @@ void guac_spice_client_audio_record_stop_handler(SpiceRecordChannel* channel,
|
|||||||
|
|
||||||
guac_client_log(client, GUAC_LOG_DEBUG, "Calling audio record stop handler.");
|
guac_client_log(client, GUAC_LOG_DEBUG, "Calling audio record stop handler.");
|
||||||
|
|
||||||
|
guac_spice_client* spice_client = (guac_spice_client*) client->data;
|
||||||
|
guac_client_for_owner(client, spice_client_record_stop_callback, spice_client);
|
||||||
|
|
||||||
}
|
}
|
@ -95,6 +95,11 @@ void guac_spice_client_audio_playback_start_handler(
|
|||||||
void guac_spice_client_audio_playback_stop_handler(
|
void guac_spice_client_audio_playback_stop_handler(
|
||||||
SpicePlaybackChannel* channel, guac_client* client);
|
SpicePlaybackChannel* channel, guac_client* client);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler for inbound audio data (audio input).
|
||||||
|
*/
|
||||||
|
guac_user_audio_handler guac_spice_client_audio_record_handler;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The callback function invoked by the client when the SPICE server requests
|
* The callback function invoked by the client when the SPICE server requests
|
||||||
* that the client begin recording audio data to send to the server.
|
* that the client begin recording audio data to send to the server.
|
||||||
|
@ -327,7 +327,7 @@ void guac_spice_client_channel_handler(SpiceSession *spice_session,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Check for audio recording channel and set up the channel. */
|
/* Check for audio recording channel and set up the channel. */
|
||||||
if (SPICE_IS_RECORD_CHANNEL(channel) && settings->audio_enabled) {
|
if (SPICE_IS_RECORD_CHANNEL(channel) && settings->audio_input_enabled) {
|
||||||
guac_client_log(client, GUAC_LOG_DEBUG, "Setting up audio record channel.");
|
guac_client_log(client, GUAC_LOG_DEBUG, "Setting up audio record channel.");
|
||||||
spice_client->record_channel = SPICE_RECORD_CHANNEL(channel);
|
spice_client->record_channel = SPICE_RECORD_CHANNEL(channel);
|
||||||
g_signal_connect(channel, SPICE_SIGNAL_RECORD_START,
|
g_signal_connect(channel, SPICE_SIGNAL_RECORD_START,
|
||||||
|
@ -55,6 +55,7 @@ const char* GUAC_SPICE_CLIENT_ARGS[] = {
|
|||||||
"clipboard-encoding",
|
"clipboard-encoding",
|
||||||
|
|
||||||
"enable-audio",
|
"enable-audio",
|
||||||
|
"enable-audio-input",
|
||||||
"file-transfer",
|
"file-transfer",
|
||||||
"file-directory",
|
"file-directory",
|
||||||
"file-transfer-ro",
|
"file-transfer-ro",
|
||||||
@ -198,6 +199,11 @@ enum SPICE_ARGS_IDX {
|
|||||||
*/
|
*/
|
||||||
IDX_ENABLE_AUDIO,
|
IDX_ENABLE_AUDIO,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* "true" if audio input should be enabled, "false" or blank otherwise.
|
||||||
|
*/
|
||||||
|
IDX_ENABLE_AUDIO_INPUT,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* "true" if file transfer should be enabled, "false" or blank otherwise.
|
* "true" if file transfer should be enabled, "false" or blank otherwise.
|
||||||
*/
|
*/
|
||||||
@ -474,6 +480,11 @@ guac_spice_settings* guac_spice_parse_args(guac_user* user,
|
|||||||
guac_user_parse_args_boolean(user, GUAC_SPICE_CLIENT_ARGS, argv,
|
guac_user_parse_args_boolean(user, GUAC_SPICE_CLIENT_ARGS, argv,
|
||||||
IDX_ENABLE_AUDIO, false);
|
IDX_ENABLE_AUDIO, false);
|
||||||
|
|
||||||
|
/* Audio input enable/disable */
|
||||||
|
settings->audio_input_enabled =
|
||||||
|
guac_user_parse_args_boolean(user, GUAC_SPICE_CLIENT_ARGS, argv,
|
||||||
|
IDX_ENABLE_AUDIO_INPUT, false);
|
||||||
|
|
||||||
/* File transfer enable/disable */
|
/* File transfer enable/disable */
|
||||||
settings->file_transfer =
|
settings->file_transfer =
|
||||||
guac_user_parse_args_boolean(user, GUAC_SPICE_CLIENT_ARGS, argv,
|
guac_user_parse_args_boolean(user, GUAC_SPICE_CLIENT_ARGS, argv,
|
||||||
|
@ -112,6 +112,11 @@ typedef struct guac_spice_settings {
|
|||||||
*/
|
*/
|
||||||
bool audio_enabled;
|
bool audio_enabled;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether audio input is enabled.
|
||||||
|
*/
|
||||||
|
bool audio_input_enabled;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If file transfer capability should be enabled.
|
* If file transfer capability should be enabled.
|
||||||
*/
|
*/
|
||||||
|
@ -163,10 +163,15 @@ typedef struct guac_spice_client {
|
|||||||
pthread_mutex_t message_lock;
|
pthread_mutex_t message_lock;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Audio output, if any.
|
* Audio output stream, if any.
|
||||||
*/
|
*/
|
||||||
guac_audio_stream* audio_playback;
|
guac_audio_stream* audio_playback;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Audio input stream, if any.
|
||||||
|
*/
|
||||||
|
guac_stream* audio_input;
|
||||||
|
|
||||||
} guac_spice_client;
|
} guac_spice_client;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
|
#include "channels/audio.h"
|
||||||
#include "channels/clipboard.h"
|
#include "channels/clipboard.h"
|
||||||
#include "input.h"
|
#include "input.h"
|
||||||
#include "common/display.h"
|
#include "common/display.h"
|
||||||
@ -66,6 +67,10 @@ int guac_spice_user_join_handler(guac_user* user, int argc, char** argv) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Handle inbound audio streams if audio input is enabled */
|
||||||
|
if (settings->audio_input_enabled)
|
||||||
|
user->audio_handler = guac_spice_client_audio_record_handler;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* If not owner, synchronize with current state */
|
/* If not owner, synchronize with current state */
|
||||||
|
Loading…
Reference in New Issue
Block a user