diff options
-rw-r--r-- | common/Makefile.am | 7 | ||||
-rw-r--r-- | common/snd_codec.c | 277 | ||||
-rw-r--r-- | common/snd_codec.h | 72 | ||||
-rw-r--r-- | configure.ac | 16 |
4 files changed, 372 insertions, 0 deletions
diff --git a/common/Makefile.am b/common/Makefile.am index 45568c6..c79f596 100644 --- a/common/Makefile.am +++ b/common/Makefile.am @@ -52,6 +52,8 @@ libspice_common_la_SOURCES = \ ring.h \ rop3.c \ rop3.h \ + snd_codec.c \ + snd_codec.h \ spice_common.h \ ssl_verify.c \ ssl_verify.h \ @@ -81,6 +83,7 @@ endif AM_CPPFLAGS = \ $(GL_CFLAGS) \ $(PIXMAN_CFLAGS) \ + $(CELT051_CFLAGS) \ $(PROTOCOL_CFLAGS) \ $(SMARTCARD_CFLAGS) \ $(VISIBILITY_HIDDEN_CFLAGS) \ @@ -88,6 +91,9 @@ AM_CPPFLAGS = \ -std=gnu99 \ $(NULL) +libspice_common_la_LIBADD = \ + $(CELT051_LIBS) + MARSHALLERS_DEPS = \ $(top_srcdir)/python_modules/__init__.py \ $(top_srcdir)/python_modules/codegen.py \ @@ -142,6 +148,7 @@ EXTRA_DIST = \ quic_family_tmpl.c \ quic_rgb_tmpl.c \ quic_tmpl.c \ + snd_codec.h \ sw_canvas.c \ sw_canvas.h \ $(NULL) diff --git a/common/snd_codec.c b/common/snd_codec.c new file mode 100644 index 0000000..6dcadb7 --- /dev/null +++ b/common/snd_codec.c @@ -0,0 +1,277 @@ +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* + Copyright (C) 2013 Jeremy White + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see <http://www.gnu.org/licenses/>. +*/ + +/* snd_codec.c + General purpose sound codec routines for use by Spice. + These routines abstract the work of picking a codec and + encoding and decoding the buffers. + Note: these routines have some peculiarities that come from + wanting to provide full backwards compatibility with the original + Spice celt 0.51 implementation. It has some hard requirements + (fixed sample size, fixed compressed buffer size). + + See below for documentation of the public routines. +*/ + +#include "config.h" +#include <stdio.h> +#include <string.h> +#include <spice/macros.h> +#include <spice/enums.h> + + +#include "snd_codec.h" +#include "mem.h" +#include "log.h" + +typedef struct +{ + int mode; + int frequency; +#if HAVE_CELT051 + CELTMode *celt_mode; + CELTEncoder *celt_encoder; + CELTDecoder *celt_decoder; +#endif +} SndCodecInternal; + + + +/* celt 0.51 specific support routines */ +#if HAVE_CELT051 +static void snd_codec_destroy_celt051(SndCodecInternal *codec) +{ + if (codec->celt_decoder) { + celt051_decoder_destroy(codec->celt_decoder); + codec->celt_decoder = NULL; + } + + if (codec->celt_encoder) { + celt051_encoder_destroy(codec->celt_encoder); + codec->celt_encoder = NULL; + } + + if (codec->celt_mode) { + celt051_mode_destroy(codec->celt_mode); + codec->celt_mode = NULL; + } +} + +static int snd_codec_create_celt051(SndCodecInternal *codec, int purpose) +{ + int celt_error; + + codec->celt_mode = celt051_mode_create(codec->frequency, + SND_CODEC_CELT_PLAYBACK_CHAN, + SND_CODEC_CELT_FRAME_SIZE, &celt_error); + if (! codec->celt_mode) { + spice_printerr("create celt mode failed %d", celt_error); + return SND_CODEC_UNAVAILABLE; + } + + if (purpose & SND_CODEC_ENCODE) { + codec->celt_encoder = celt051_encoder_create(codec->celt_mode); + if (! codec->celt_encoder) { + spice_printerr("create celt encoder failed"); + goto error; + } + } + + if (purpose & SND_CODEC_DECODE) { + codec->celt_decoder = celt051_decoder_create(codec->celt_mode); + if (! codec->celt_decoder) { + spice_printerr("create celt decoder failed"); + goto error; + } + } + + codec->mode = SPICE_AUDIO_DATA_MODE_CELT_0_5_1; + return SND_CODEC_OK; + +error: + snd_codec_destroy_celt051(codec); + return SND_CODEC_UNAVAILABLE; +} + +static int snd_codec_encode_celt051(SndCodecInternal *codec, uint8_t *in_ptr, int in_size, uint8_t *out_ptr, int *out_size) +{ + int n; + if (in_size != SND_CODEC_CELT_FRAME_SIZE * SND_CODEC_CELT_PLAYBACK_CHAN * 2) + return SND_CODEC_INVALID_ENCODE_SIZE; + n = celt051_encode(codec->celt_encoder, (celt_int16_t *) in_ptr, NULL, out_ptr, *out_size); + if (n < 0) { + spice_printerr("celt051_encode failed %d\n", n); + return SND_CODEC_ENCODE_FAILED; + } + *out_size = n; + return SND_CODEC_OK; +} + +static int snd_codec_decode_celt051(SndCodecInternal *codec, uint8_t *in_ptr, int in_size, uint8_t *out_ptr, int *out_size) +{ + int n; + n = celt051_decode(codec->celt_decoder, in_ptr, in_size, (celt_int16_t *) out_ptr); + if (n < 0) { + spice_printerr("celt051_decode failed %d\n", n); + return SND_CODEC_DECODE_FAILED; + } + *out_size = SND_CODEC_CELT_FRAME_SIZE * SND_CODEC_CELT_PLAYBACK_CHAN * 2 /* 16 fmt */; + return SND_CODEC_OK; +} +#endif + + + +/*---------------------------------------------------------------------------- +** PUBLIC INTERFACE +**--------------------------------------------------------------------------*/ + +/* + snd_codec_is_capable + Returns TRUE if the current spice implementation can + use the given codec, FALSE otherwise. + mode must be a SPICE_AUDIO_DATA_MODE_XXX enum from spice/enum.h + */ +int snd_codec_is_capable(int mode) +{ +#if HAVE_CELT051 + if (mode == SPICE_AUDIO_DATA_MODE_CELT_0_5_1) + return TRUE; +#else + return FALSE; +#endif +} + +/* + snd_codec_create + Create a codec control. Required for most functions in this library. + Parameters: + 1. codec Pointer to preallocated codec control + 2. mode SPICE_AUDIO_DATA_MODE_XXX enum from spice/enum.h + 3. encode TRUE if encoding is desired + 4. decode TRUE if decoding is desired + Returns: + SND_CODEC_OK if all went well; a different code if not. + + snd_codec_destroy is the obvious partner of snd_codec_create. + */ +int snd_codec_create(SndCodec *codec, int mode, int frequency, int purpose) +{ + int rc = SND_CODEC_UNAVAILABLE; + SndCodecInternal **c = (SndCodecInternal **) codec; + + *c = spice_new0(SndCodecInternal, 1); + (*c)->frequency = frequency; + +#if HAVE_CELT051 + if (mode == SPICE_AUDIO_DATA_MODE_CELT_0_5_1) + rc = snd_codec_create_celt051(*c, purpose); +#endif + + return rc; +} + +/* + snd_codec_destroy + The obvious companion to snd_codec_create +*/ +void snd_codec_destroy(SndCodec *codec) +{ + SndCodecInternal **c = (SndCodecInternal **) codec; + if (! c || ! *c) + return; + +#if HAVE_CELT051 + snd_codec_destroy_celt051(*c); +#endif + + free(*c); + *c = NULL; +} + +/* + snd_codec_frame_size + Returns the size, in frames, of the raw PCM frame buffer + required by this codec. To get bytes, you'll need + to multiply by channels and sample width. + */ +int snd_codec_frame_size(SndCodec codec) +{ + SndCodecInternal *c = (SndCodecInternal *) codec; +#if HAVE_CELT051 + if (c && c->mode == SPICE_AUDIO_DATA_MODE_CELT_0_5_1) + return SND_CODEC_CELT_FRAME_SIZE; +#endif + return SND_CODEC_MAX_FRAME_SIZE; +} + +/* + snd_codec_encode + Encode a block of data to a compressed buffer. + + Parameters: + 1. codec Pointer to codec control previously allocated + created + 2. in_data Pointer to uncompressed PCM data + 3. in_size Input size (for celt, this must be a + particular size, governed by the frame size) + 4. out_ptr Pointer to area to write encoded data + 5. out_size On input, the maximum size of the output buffer; on + successful return, it will hold the number of bytes + returned. For celt, this must be set to a particular + size to ensure compatibility. + + Returns: + SND_CODEC_OK if all went well +*/ +int snd_codec_encode(SndCodec codec, uint8_t *in_ptr, int in_size, uint8_t *out_ptr, int *out_size) +{ + SndCodecInternal *c = (SndCodecInternal *) codec; +#if HAVE_CELT051 + if (c && c->mode == SPICE_AUDIO_DATA_MODE_CELT_0_5_1) + return snd_codec_encode_celt051(c, in_ptr, in_size, out_ptr, out_size); +#endif + + return SND_CODEC_ENCODER_UNAVAILABLE; +} + +/* + snd_codec_decode + Decode a block of data from a compressed buffer. + + Parameters: + 1. codec Pointer to codec control previously allocated + created + 2. in_data Pointer to compressed data + 3. in_size Input size + 4. out_ptr Pointer to area to write decoded data + 5. out_size On input, the maximum size of the output buffer; on + successful return, it will hold the number of bytes + returned. + + Returns: + SND_CODEC_OK if all went well +*/ +int snd_codec_decode(SndCodec codec, uint8_t *in_ptr, int in_size, uint8_t *out_ptr, int *out_size) +{ + SndCodecInternal *c = (SndCodecInternal *) codec; +#if HAVE_CELT051 + if (c && c->mode == SPICE_AUDIO_DATA_MODE_CELT_0_5_1) + return snd_codec_decode_celt051(c, in_ptr, in_size, out_ptr, out_size); +#endif + + return SND_CODEC_DECODER_UNAVAILABLE; +} diff --git a/common/snd_codec.h b/common/snd_codec.h new file mode 100644 index 0000000..752e79d --- /dev/null +++ b/common/snd_codec.h @@ -0,0 +1,72 @@ +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* + Copyright (C) 2013 Jeremy White <jwhite@codeweavers.com> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef _H_SND_CODEC +#define _H_SND_CODEC + + +#if HAVE_CELT051 +#include <celt051/celt.h> +#endif + +/* Spice uses a very fixed protocol when transmitting CELT audio; + audio must be transmitted in frames of 256, and we must compress + data down to a fairly specific size (47, computation below). + While the protocol doesn't inherently specify this, the expectation + of older clients and server mandates it. +*/ +#define SND_CODEC_CELT_FRAME_SIZE 256 +#define SND_CODEC_CELT_BIT_RATE (64 * 1024) +#define SND_CODEC_CELT_PLAYBACK_FREQ 44100 +#define SND_CODEC_CELT_PLAYBACK_CHAN 2 +#define SND_CODEC_CELT_COMPRESSED_FRAME_BYTES (SND_CODEC_CELT_FRAME_SIZE * SND_CODEC_CELT_BIT_RATE / \ + SND_CODEC_CELT_PLAYBACK_FREQ / 8) + + +#define SND_CODEC_MAX_FRAME_SIZE SND_CODEC_CELT_FRAME_SIZE +#define SND_CODEC_MAX_FRAME_BYTES (SND_CODEC_MAX_FRAME_SIZE * SND_CODEC_CELT_PLAYBACK_CHAN * 2 /* FMT_S16 */) +#define SND_CODEC_MAX_COMPRESSED_BYTES SND_CODEC_CELT_COMPRESSED_FRAME_BYTES + +#define SND_CODEC_OK 0 +#define SND_CODEC_UNAVAILABLE 1 +#define SND_CODEC_ENCODER_UNAVAILABLE 2 +#define SND_CODEC_DECODER_UNAVAILABLE 3 +#define SND_CODEC_ENCODE_FAILED 4 +#define SND_CODEC_DECODE_FAILED 5 +#define SND_CODEC_INVALID_ENCODE_SIZE 6 + +#define SND_CODEC_ENCODE 0x0001 +#define SND_CODEC_DECODE 0x0002 + +SPICE_BEGIN_DECLS + +typedef struct SndCodecInternal * SndCodec; + +int snd_codec_is_capable(int mode); + +int snd_codec_create(SndCodec *codec, int mode, int frequency, int purpose); +void snd_codec_destroy(SndCodec *codec); + +int snd_codec_frame_size(SndCodec codec); + +int snd_codec_encode(SndCodec codec, uint8_t *in_ptr, int in_size, uint8_t *out_ptr, int *out_size); +int snd_codec_decode(SndCodec codec, uint8_t *in_ptr, int in_size, uint8_t *out_ptr, int *out_size); + +SPICE_END_DECLS + +#endif diff --git a/configure.ac b/configure.ac index acb7626..3443334 100644 --- a/configure.ac +++ b/configure.ac @@ -51,6 +51,22 @@ if test "x$enable_smartcard" != "xno"; then fi AM_CONDITIONAL([WITH_SMARTCARD], [test "x$have_smartcard" = "xyes"]) +AC_ARG_ENABLE(celt051, +[ --disable-celt051 Disable celt051 audio codec (enabled by default)],, +[enable_celt051="yes"]) + +if test "x$enable_celt051" = "xyes"; then + PKG_CHECK_MODULES(CELT051, celt051 >= 0.5.1.1, have_celt051=yes, have_celt051=no) + AC_SUBST(CELT051_CFLAGS) + AC_SUBST(CELT051_LIBS) + AC_SUBST(CELT051_LIBDIR) +else + have_celt051=no +fi + +AM_CONDITIONAL([HAVE_CELT051], [test "x$have_celt051" = "xyes"]) +AM_COND_IF([HAVE_CELT051], AC_DEFINE([HAVE_CELT051], 1, [Define if we have celt051 codec])) + AC_ARG_ENABLE([opengl], AS_HELP_STRING([--enable-opengl=@<:@yes/no@:>@], [Enable opengl support (not recommended) @<:@default=no@:>@]), |