Merge pull request #90 from glyptodon/new-media-streams
GUAC-1354: Implement raw encoder.
This commit is contained in:
commit
3dddc8c5a4
@ -67,7 +67,7 @@ noinst_HEADERS = \
|
|||||||
encode-jpeg.h \
|
encode-jpeg.h \
|
||||||
encode-png.h \
|
encode-png.h \
|
||||||
palette.h \
|
palette.h \
|
||||||
wav_encoder.h
|
raw_encoder.h
|
||||||
|
|
||||||
libguac_la_SOURCES = \
|
libguac_la_SOURCES = \
|
||||||
audio.c \
|
audio.c \
|
||||||
@ -82,18 +82,12 @@ libguac_la_SOURCES = \
|
|||||||
plugin.c \
|
plugin.c \
|
||||||
pool.c \
|
pool.c \
|
||||||
protocol.c \
|
protocol.c \
|
||||||
|
raw_encoder.c \
|
||||||
socket.c \
|
socket.c \
|
||||||
socket-fd.c \
|
socket-fd.c \
|
||||||
socket-nest.c \
|
socket-nest.c \
|
||||||
timestamp.c \
|
timestamp.c \
|
||||||
unicode.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
|
|
||||||
|
|
||||||
# Compile WebP support if available
|
# Compile WebP support if available
|
||||||
if ENABLE_WEBP
|
if ENABLE_WEBP
|
||||||
|
@ -22,11 +22,7 @@
|
|||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
#ifdef ENABLE_OGG
|
#include "raw_encoder.h"
|
||||||
#include "ogg_encoder.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "wav_encoder.h"
|
|
||||||
|
|
||||||
#include <guacamole/audio.h>
|
#include <guacamole/audio.h>
|
||||||
#include <guacamole/client.h>
|
#include <guacamole/client.h>
|
||||||
@ -36,7 +32,8 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
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;
|
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];
|
const char* mimetype = client->info.audio_mimetypes[i];
|
||||||
|
|
||||||
#ifdef ENABLE_OGG
|
/* If 16-bit raw audio is supported, done. */
|
||||||
/* If Ogg is supported, done. */
|
if (bps == 16 && strcmp(mimetype, raw16_encoder->mimetype) == 0) {
|
||||||
if (strcmp(mimetype, ogg_encoder->mimetype) == 0) {
|
encoder = raw16_encoder;
|
||||||
encoder = ogg_encoder;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
/* If wav is supported, done. */
|
/* If 8-bit raw audio is supported, done. */
|
||||||
if (strcmp(mimetype, wav_encoder->mimetype) == 0) {
|
if (bps == 8 && strcmp(mimetype, raw8_encoder->mimetype) == 0) {
|
||||||
encoder = wav_encoder;
|
encoder = raw8_encoder;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,130 +68,84 @@ guac_audio_stream* guac_audio_stream_alloc(guac_client* client, guac_audio_encod
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Allocate stream */
|
/* Allocate stream */
|
||||||
audio = (guac_audio_stream*) malloc(sizeof(guac_audio_stream));
|
audio = (guac_audio_stream*) calloc(1, sizeof(guac_audio_stream));
|
||||||
audio->client = client;
|
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 */
|
/* Assign encoder */
|
||||||
audio->encoder = encoder;
|
audio->encoder = encoder;
|
||||||
audio->stream = guac_client_alloc_stream(client);
|
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 */
|
/* Load PCM properties */
|
||||||
audio->rate = rate;
|
audio->rate = rate;
|
||||||
audio->channels = channels;
|
audio->channels = channels;
|
||||||
audio->bps = bps;
|
audio->bps = bps;
|
||||||
|
|
||||||
/* Reset write counter */
|
/* Call handler, if defined */
|
||||||
audio->pcm_bytes_written = 0;
|
if (audio->encoder->begin_handler)
|
||||||
|
audio->encoder->begin_handler(audio);
|
||||||
|
|
||||||
/* Call handler */
|
return audio;
|
||||||
audio->encoder->begin_handler(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 */
|
/* Free old encoder data */
|
||||||
guac_audio_stream_flush(audio);
|
if (audio->encoder->end_handler)
|
||||||
audio->encoder->end_handler(audio);
|
audio->encoder->end_handler(audio);
|
||||||
|
|
||||||
/* Calculate duration of PCM data */
|
/* Assign new encoder, if changed */
|
||||||
duration = ((double) (audio->pcm_bytes_written * 1000 * 8))
|
if (encoder != NULL)
|
||||||
/ audio->rate / audio->channels / audio->bps;
|
audio->encoder = encoder;
|
||||||
|
|
||||||
/* Send audio */
|
/* Set PCM properties */
|
||||||
guac_protocol_send_audio(audio->client->socket, audio->stream,
|
audio->rate = rate;
|
||||||
audio->stream->index, audio->encoder->mimetype, duration);
|
audio->channels = channels;
|
||||||
|
audio->bps = bps;
|
||||||
|
|
||||||
guac_protocol_send_blob(audio->client->socket, audio->stream,
|
/* Init encoder with new data */
|
||||||
audio->encoded_data, audio->encoded_data_used);
|
if (audio->encoder->begin_handler)
|
||||||
|
audio->encoder->begin_handler(audio);
|
||||||
guac_protocol_send_end(audio->client->socket, audio->stream);
|
|
||||||
|
|
||||||
/* Clear data */
|
|
||||||
audio->encoded_data_used = 0;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void guac_audio_stream_free(guac_audio_stream* 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);
|
free(audio);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void guac_audio_stream_write_pcm(guac_audio_stream* audio,
|
void guac_audio_stream_write_pcm(guac_audio_stream* audio,
|
||||||
const unsigned char* data, int length) {
|
const unsigned char* data, int length) {
|
||||||
|
|
||||||
/* Update counter */
|
/* Write data */
|
||||||
audio->pcm_bytes_written += length;
|
if (audio->encoder->write_handler)
|
||||||
|
audio->encoder->write_handler(audio, data, 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;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void guac_audio_stream_flush(guac_audio_stream* audio) {
|
void guac_audio_stream_flush(guac_audio_stream* audio) {
|
||||||
|
|
||||||
/* If data in buffer */
|
/* Flush any buffered data */
|
||||||
if (audio->used != 0) {
|
if (audio->encoder->flush_handler)
|
||||||
|
audio->encoder->flush_handler(audio);
|
||||||
/* 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;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
* 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);
|
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.
|
* Handler which is called when the audio stream is closed.
|
||||||
*/
|
*/
|
||||||
typedef void guac_audio_encoder_end_handler(guac_audio_stream* audio);
|
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,
|
typedef void guac_audio_encoder_write_handler(guac_audio_stream* audio,
|
||||||
const unsigned char* pcm_data, int length);
|
const unsigned char* pcm_data, int length);
|
||||||
|
@ -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
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
@ -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
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
@ -44,15 +44,21 @@ struct guac_audio_encoder {
|
|||||||
const char* mimetype;
|
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;
|
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;
|
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.
|
* Handler which will be called when the audio stream is closed.
|
||||||
*/
|
*/
|
||||||
@ -63,38 +69,7 @@ struct guac_audio_encoder {
|
|||||||
struct guac_audio_stream {
|
struct guac_audio_stream {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PCM data buffer, 16-bit samples, 2-channel, 44100 Hz.
|
* Arbitrary codec encoder which will receive raw PCM data.
|
||||||
*/
|
|
||||||
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.
|
|
||||||
*/
|
*/
|
||||||
guac_audio_encoder* encoder;
|
guac_audio_encoder* encoder;
|
||||||
|
|
||||||
@ -125,11 +100,6 @@ struct guac_audio_stream {
|
|||||||
*/
|
*/
|
||||||
int bps;
|
int bps;
|
||||||
|
|
||||||
/**
|
|
||||||
* The number of PCM bytes written since the audio chunk began.
|
|
||||||
*/
|
|
||||||
int pcm_bytes_written;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encoder-specific state data.
|
* Encoder-specific state data.
|
||||||
*/
|
*/
|
||||||
@ -141,79 +111,83 @@ struct guac_audio_stream {
|
|||||||
* Allocates a new audio stream which encodes audio data using the given
|
* Allocates a new audio stream which encodes audio data using the given
|
||||||
* encoder. If NULL is specified for the encoder, an appropriate encoder
|
* encoder. If NULL is specified for the encoder, an appropriate encoder
|
||||||
* will be selected based on the encoders built into libguac and the level
|
* 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
|
* @param client
|
||||||
* allocated.
|
* 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
|
* @param encoder
|
||||||
* encoder on its own.
|
* The guac_audio_encoder to use when encoding audio, or NULL if libguac
|
||||||
* @return The newly allocated guac_audio_stream, or NULL if no audio
|
* should select an appropriate built-in encoder on its own.
|
||||||
* stream could be allocated due to lack of client support.
|
*
|
||||||
|
* @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_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);
|
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
|
* Writes PCM data to the given audio stream. This PCM data will be
|
||||||
* automatically encoded by the audio encoder associated with this stream. This
|
* automatically encoded by the audio encoder associated with this stream. The
|
||||||
* function must only be called after an audio packet has been started with
|
* PCM data must be 2-channel, 44100 Hz, with signed 16-bit samples.
|
||||||
* guac_audio_stream_begin().
|
|
||||||
*
|
*
|
||||||
* @param stream The guac_audio_stream to write PCM data through.
|
* @param stream
|
||||||
* @param data The PCM data to write.
|
* The guac_audio_stream to write PCM data through.
|
||||||
* @param length The number of bytes of PCM data provided.
|
*
|
||||||
|
* @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,
|
void guac_audio_stream_write_pcm(guac_audio_stream* stream,
|
||||||
const unsigned char* data, int length);
|
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);
|
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
|
#endif
|
||||||
|
|
||||||
|
@ -295,15 +295,20 @@ int guac_protocol_send_undefine(guac_socket* socket,
|
|||||||
* If an error occurs sending the instruction, a non-zero value is
|
* If an error occurs sending the instruction, a non-zero value is
|
||||||
* returned, and guac_error is set appropriately.
|
* returned, and guac_error is set appropriately.
|
||||||
*
|
*
|
||||||
* @param socket The guac_socket connection to use.
|
* @param socket
|
||||||
* @param stream The stream to use.
|
* The guac_socket connection to use when sending the audio instruction.
|
||||||
* @param channel The index of the audio channel to use.
|
*
|
||||||
* @param mimetype The mimetype of the data being sent.
|
* @param stream
|
||||||
* @param duration The duration of the sound being sent, in milliseconds.
|
* The stream to use for future audio data.
|
||||||
* @return Zero on success, non-zero on error.
|
*
|
||||||
|
* @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 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.
|
* 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.
|
* @return Zero on success, non-zero on error.
|
||||||
*/
|
*/
|
||||||
int guac_protocol_send_blob(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);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends an end instruction over the given guac_socket connection.
|
* Sends an end instruction over the given guac_socket connection.
|
||||||
|
@ -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 <vorbis/vorbisenc.h>
|
|
||||||
|
|
||||||
#include <stdlib.h>
|
|
||||||
|
|
||||||
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; i<samples; i++) {
|
|
||||||
|
|
||||||
/* FIXME: For now, assume 2 channels, 16-bit */
|
|
||||||
int left = ((readbuffer[i*4+1]<<8)|(0x00ff&(int)readbuffer[i*4]));
|
|
||||||
int right = ((readbuffer[i*4+3]<<8)|(0x00ff&(int)readbuffer[i*4+2]));
|
|
||||||
|
|
||||||
/* Store sample in buffer */
|
|
||||||
buffer[0][i] = left / 32768.f;
|
|
||||||
buffer[1][i] = right / 32768.f;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Submit data */
|
|
||||||
vorbis_analysis_wrote(&(state->vorbis_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;
|
|
||||||
|
|
@ -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 guac_protocol_send_audio(guac_socket* socket, const guac_stream* stream,
|
||||||
int channel, const char* mimetype, double duration) {
|
const char* mimetype) {
|
||||||
|
|
||||||
int ret_val;
|
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_string(socket, "5.audio,")
|
||||||
|| __guac_socket_write_length_int(socket, stream->index)
|
|| __guac_socket_write_length_int(socket, stream->index)
|
||||||
|| guac_socket_write_string(socket, ",")
|
|| 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_length_string(socket, mimetype)
|
||||||
|| guac_socket_write_string(socket, ",")
|
|
||||||
|| __guac_socket_write_length_double(socket, duration)
|
|
||||||
|| guac_socket_write_string(socket, ";");
|
|| guac_socket_write_string(socket, ";");
|
||||||
guac_socket_instruction_end(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,
|
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;
|
int base64_length = (count + 2) / 3 * 4;
|
||||||
|
|
||||||
|
161
src/libguac/raw_encoder.c
Normal file
161
src/libguac/raw_encoder.c
Normal file
@ -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 <guacamole/audio.h>
|
||||||
|
#include <guacamole/client.h>
|
||||||
|
#include <guacamole/protocol.h>
|
||||||
|
#include <guacamole/socket.h>
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
@ -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
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
@ -21,35 +21,58 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
#ifndef __GUAC_OGG_ENCODER_H
|
#ifndef GUAC_RAW_ENCODER_H
|
||||||
#define __GUAC_OGG_ENCODER_H
|
#define GUAC_RAW_ENCODER_H
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
#include "audio.h"
|
#include "audio.h"
|
||||||
|
|
||||||
#include <vorbis/vorbisenc.h>
|
/**
|
||||||
|
* 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;
|
unsigned char* buffer;
|
||||||
ogg_page ogg_page;
|
|
||||||
ogg_packet ogg_packet;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Vorbis state
|
* Size of the PCM buffer, in bytes.
|
||||||
*/
|
*/
|
||||||
vorbis_info info;
|
int length;
|
||||||
vorbis_comment comment;
|
|
||||||
vorbis_dsp_state vorbis_state;
|
|
||||||
vorbis_block vorbis_block;
|
|
||||||
|
|
||||||
} 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
|
#endif
|
||||||
|
|
@ -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 <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#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<length; offset++) {
|
|
||||||
|
|
||||||
/* Store byte */
|
|
||||||
*buffer = value & 0xFF;
|
|
||||||
|
|
||||||
/* Move to next byte */
|
|
||||||
value >>= 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;
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -286,7 +286,10 @@ BOOL rdp_freerdp_pre_connect(freerdp* instance) {
|
|||||||
/* If audio enabled, choose an encoder */
|
/* If audio enabled, choose an encoder */
|
||||||
if (guac_client_data->settings.audio_enabled) {
|
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 an encoding is available, load the sound plugin */
|
||||||
if (guac_client_data->audio != NULL) {
|
if (guac_client_data->audio != NULL) {
|
||||||
|
@ -87,6 +87,27 @@
|
|||||||
*/
|
*/
|
||||||
#define GUAC_RDP_CLIPBOARD_MAX_LENGTH 262144
|
#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.
|
* Client data that will remain accessible through the guac_client.
|
||||||
* This should generally include data commonly used by Guacamole handlers.
|
* This should generally include data commonly used by Guacamole handlers.
|
||||||
|
@ -133,6 +133,9 @@ void guac_rdpsnd_formats_handler(guac_rdpsndPlugin* rdpsnd,
|
|||||||
"%i Hz",
|
"%i Hz",
|
||||||
bps, channels, rate);
|
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 */
|
/* Queue format for sending as accepted */
|
||||||
Stream_EnsureRemainingCapacity(output_stream, 18 + body_size);
|
Stream_EnsureRemainingCapacity(output_stream, 18 + body_size);
|
||||||
Stream_Write(output_stream, format_start, 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_audio_stream* audio, wStream* input_stream,
|
||||||
guac_rdpsnd_pdu_header* header) {
|
guac_rdpsnd_pdu_header* header) {
|
||||||
|
|
||||||
unsigned char buffer[4];
|
|
||||||
int format;
|
int format;
|
||||||
|
|
||||||
/* Read wave information */
|
/* Read wave information */
|
||||||
@ -233,7 +235,7 @@ void guac_rdpsnd_wave_info_handler(guac_rdpsndPlugin* rdpsnd,
|
|||||||
Stream_Read_UINT16(input_stream, format);
|
Stream_Read_UINT16(input_stream, format);
|
||||||
Stream_Read_UINT8(input_stream, rdpsnd->waveinfo_block_number);
|
Stream_Read_UINT8(input_stream, rdpsnd->waveinfo_block_number);
|
||||||
Stream_Seek(input_stream, 3);
|
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
|
* 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 */
|
/* Read wave in next iteration */
|
||||||
rdpsnd->next_pdu_is_wave = TRUE;
|
rdpsnd->next_pdu_is_wave = TRUE;
|
||||||
|
|
||||||
/* Init stream with requested format */
|
/* Reset audio stream if format has changed */
|
||||||
guac_audio_stream_begin(audio,
|
guac_audio_stream_reset(audio, NULL,
|
||||||
rdpsnd->formats[format].rate,
|
rdpsnd->formats[format].rate,
|
||||||
rdpsnd->formats[format].channels,
|
rdpsnd->formats[format].channels,
|
||||||
rdpsnd->formats[format].bps);
|
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,
|
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);
|
wStream* output_stream = Stream_New(NULL, 8);
|
||||||
|
|
||||||
/* Get wave data */
|
/* 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 */
|
/* Write rest of audio packet */
|
||||||
guac_audio_stream_write_pcm(audio, buffer, rdpsnd->incoming_wave_size);
|
guac_audio_stream_write_pcm(audio, buffer, rdpsnd->incoming_wave_size + 4);
|
||||||
guac_audio_stream_end(audio);
|
guac_audio_stream_flush(audio);
|
||||||
|
|
||||||
/* Write Wave Confirmation PDU */
|
/* Write Wave Confirmation PDU */
|
||||||
Stream_Write_UINT8(output_stream, SNDC_WAVECONFIRM);
|
Stream_Write_UINT8(output_stream, SNDC_WAVECONFIRM);
|
||||||
|
@ -95,8 +95,16 @@ typedef struct guac_rdpsndPlugin {
|
|||||||
*/
|
*/
|
||||||
int next_pdu_is_wave;
|
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.
|
* 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;
|
int incoming_wave_size;
|
||||||
|
|
||||||
|
@ -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 an encoding is available, load an audio stream */
|
||||||
if (guac_client_data->audio_enabled) {
|
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 */
|
/* Load servername if specified */
|
||||||
if (argv[IDX_AUDIO_SERVERNAME][0] != '\0')
|
if (argv[IDX_AUDIO_SERVERNAME][0] != '\0')
|
||||||
|
@ -74,23 +74,13 @@ static void __stream_read_callback(pa_stream* stream, size_t length,
|
|||||||
/* Read data */
|
/* Read data */
|
||||||
pa_stream_peek(stream, &buffer, &length);
|
pa_stream_peek(stream, &buffer, &length);
|
||||||
|
|
||||||
/* Avoid sending silence unless data is waiting to be flushed */
|
/* Continuously write received PCM data */
|
||||||
if (audio->pcm_bytes_written != 0 || !guac_pa_is_silence(buffer, length)) {
|
if (!guac_pa_is_silence(buffer, length))
|
||||||
|
|
||||||
/* Write data */
|
|
||||||
guac_audio_stream_write_pcm(audio, buffer, length);
|
guac_audio_stream_write_pcm(audio, buffer, length);
|
||||||
|
|
||||||
/* Flush occasionally */
|
/* Flush upon silence */
|
||||||
if (audio->pcm_bytes_written > GUAC_VNC_PCM_WRITE_RATE) {
|
else
|
||||||
guac_audio_stream_end(audio);
|
guac_audio_stream_flush(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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Advance buffer */
|
/* Advance buffer */
|
||||||
pa_stream_drop(stream);
|
pa_stream_drop(stream);
|
||||||
@ -245,10 +235,6 @@ void guac_pa_start_stream(guac_client* client) {
|
|||||||
pa_context* context;
|
pa_context* context;
|
||||||
|
|
||||||
guac_client_log(client, GUAC_LOG_INFO, "Starting audio stream");
|
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 */
|
/* Init main loop */
|
||||||
client_data->pa_mainloop = pa_threaded_mainloop_new();
|
client_data->pa_mainloop = pa_threaded_mainloop_new();
|
||||||
|
Loading…
Reference in New Issue
Block a user