1 /* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /*
3 Copyright (C) 2013 Jeremy White
4
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Lesser General Public
7 License as published by the Free Software Foundation; either
8 version 2.1 of the License, or (at your option) any later version.
9
10 This library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public
16 License along with this library; if not, see <http://www.gnu.org/licenses/>.
17 */
18
19 /* snd_codec.c
20 General purpose sound codec routines for use by Spice.
21 These routines abstract the work of picking a codec and
22 encoding and decoding the buffers.
23 Note: these routines have some peculiarities that come from
24 wanting to provide full backwards compatibility with the original
25 Spice celt 0.51 implementation. It has some hard requirements
26 (fixed sample size, fixed compressed buffer size).
27
28 See below for documentation of the public routines.
29 */
30
31 #include "config.h"
32 #include <stdio.h>
33 #include <string.h>
34 #include <spice/macros.h>
35 #include <spice/enums.h>
36
37
38 #include "snd_codec.h"
39 #include "mem.h"
40 #include "log.h"
41
42 typedef struct
43 {
44 int mode;
45 int frequency;
46 #if HAVE_CELT051
47 CELTMode *celt_mode;
48 CELTEncoder *celt_encoder;
49 CELTDecoder *celt_decoder;
50 #endif
51
52 #if HAVE_OPUS
53 OpusEncoder *opus_encoder;
54 OpusDecoder *opus_decoder;
55 #endif
56 } SndCodecInternal;
57
58
59
60 /* celt 0.51 specific support routines */
61 #if HAVE_CELT051
snd_codec_destroy_celt051(SndCodecInternal * codec)62 static void snd_codec_destroy_celt051(SndCodecInternal *codec)
63 {
64 if (codec->celt_decoder) {
65 celt051_decoder_destroy(codec->celt_decoder);
66 codec->celt_decoder = NULL;
67 }
68
69 if (codec->celt_encoder) {
70 celt051_encoder_destroy(codec->celt_encoder);
71 codec->celt_encoder = NULL;
72 }
73
74 if (codec->celt_mode) {
75 celt051_mode_destroy(codec->celt_mode);
76 codec->celt_mode = NULL;
77 }
78 }
79
snd_codec_create_celt051(SndCodecInternal * codec,int purpose)80 static int snd_codec_create_celt051(SndCodecInternal *codec, int purpose)
81 {
82 int celt_error;
83
84 codec->celt_mode = celt051_mode_create(codec->frequency,
85 SND_CODEC_PLAYBACK_CHAN,
86 SND_CODEC_CELT_FRAME_SIZE, &celt_error);
87 if (! codec->celt_mode) {
88 g_warning("create celt mode failed %d", celt_error);
89 return SND_CODEC_UNAVAILABLE;
90 }
91
92 if (purpose & SND_CODEC_ENCODE) {
93 codec->celt_encoder = celt051_encoder_create(codec->celt_mode);
94 if (! codec->celt_encoder) {
95 g_warning("create celt encoder failed");
96 goto error;
97 }
98 }
99
100 if (purpose & SND_CODEC_DECODE) {
101 codec->celt_decoder = celt051_decoder_create(codec->celt_mode);
102 if (! codec->celt_decoder) {
103 g_warning("create celt decoder failed");
104 goto error;
105 }
106 }
107
108 codec->mode = SPICE_AUDIO_DATA_MODE_CELT_0_5_1;
109 return SND_CODEC_OK;
110
111 error:
112 snd_codec_destroy_celt051(codec);
113 return SND_CODEC_UNAVAILABLE;
114 }
115
snd_codec_encode_celt051(SndCodecInternal * codec,uint8_t * in_ptr,int in_size,uint8_t * out_ptr,int * out_size)116 static int snd_codec_encode_celt051(SndCodecInternal *codec, uint8_t *in_ptr, int in_size, uint8_t *out_ptr, int *out_size)
117 {
118 int n;
119 if (in_size != SND_CODEC_CELT_FRAME_SIZE * SND_CODEC_PLAYBACK_CHAN * 2)
120 return SND_CODEC_INVALID_ENCODE_SIZE;
121 n = celt051_encode(codec->celt_encoder, (celt_int16_t *) in_ptr, NULL, out_ptr, *out_size);
122 if (n < 0) {
123 g_warning("celt051_encode failed %d", n);
124 return SND_CODEC_ENCODE_FAILED;
125 }
126 *out_size = n;
127 return SND_CODEC_OK;
128 }
129
snd_codec_decode_celt051(SndCodecInternal * codec,uint8_t * in_ptr,int in_size,uint8_t * out_ptr,int * out_size)130 static int snd_codec_decode_celt051(SndCodecInternal *codec, uint8_t *in_ptr, int in_size, uint8_t *out_ptr, int *out_size)
131 {
132 int n;
133 n = celt051_decode(codec->celt_decoder, in_ptr, in_size, (celt_int16_t *) out_ptr);
134 if (n < 0) {
135 g_warning("celt051_decode failed %d", n);
136 return SND_CODEC_DECODE_FAILED;
137 }
138 *out_size = SND_CODEC_CELT_FRAME_SIZE * SND_CODEC_PLAYBACK_CHAN * 2 /* 16 fmt */;
139 return SND_CODEC_OK;
140 }
141 #endif
142
143
144 /* Opus support routines */
145 #if HAVE_OPUS
snd_codec_destroy_opus(SndCodecInternal * codec)146 static void snd_codec_destroy_opus(SndCodecInternal *codec)
147 {
148 if (codec->opus_decoder) {
149 opus_decoder_destroy(codec->opus_decoder);
150 codec->opus_decoder = NULL;
151 }
152
153 if (codec->opus_encoder) {
154 opus_encoder_destroy(codec->opus_encoder);
155 codec->opus_encoder = NULL;
156 }
157
158 }
159
snd_codec_create_opus(SndCodecInternal * codec,int purpose)160 static int snd_codec_create_opus(SndCodecInternal *codec, int purpose)
161 {
162 int opus_error;
163
164 if (purpose & SND_CODEC_ENCODE) {
165 codec->opus_encoder = opus_encoder_create(codec->frequency,
166 SND_CODEC_PLAYBACK_CHAN,
167 OPUS_APPLICATION_AUDIO, &opus_error);
168 if (! codec->opus_encoder) {
169 g_warning("create opus encoder failed; error %d", opus_error);
170 goto error;
171 }
172 }
173
174 if (purpose & SND_CODEC_DECODE) {
175 codec->opus_decoder = opus_decoder_create(codec->frequency,
176 SND_CODEC_PLAYBACK_CHAN, &opus_error);
177 if (! codec->opus_decoder) {
178 g_warning("create opus decoder failed; error %d", opus_error);
179 goto error;
180 }
181 }
182
183 codec->mode = SPICE_AUDIO_DATA_MODE_OPUS;
184 return SND_CODEC_OK;
185
186 error:
187 snd_codec_destroy_opus(codec);
188 return SND_CODEC_UNAVAILABLE;
189 }
190
snd_codec_encode_opus(SndCodecInternal * codec,uint8_t * in_ptr,int in_size,uint8_t * out_ptr,int * out_size)191 static int snd_codec_encode_opus(SndCodecInternal *codec, uint8_t *in_ptr, int in_size, uint8_t *out_ptr, int *out_size)
192 {
193 int n;
194 if (in_size != SND_CODEC_OPUS_FRAME_SIZE * SND_CODEC_PLAYBACK_CHAN * 2)
195 return SND_CODEC_INVALID_ENCODE_SIZE;
196 n = opus_encode(codec->opus_encoder, (opus_int16 *) in_ptr, SND_CODEC_OPUS_FRAME_SIZE, out_ptr, *out_size);
197 if (n < 0) {
198 g_warning("opus_encode failed %d", n);
199 return SND_CODEC_ENCODE_FAILED;
200 }
201 *out_size = n;
202 return SND_CODEC_OK;
203 }
204
snd_codec_decode_opus(SndCodecInternal * codec,uint8_t * in_ptr,int in_size,uint8_t * out_ptr,int * out_size)205 static int snd_codec_decode_opus(SndCodecInternal *codec, uint8_t *in_ptr, int in_size, uint8_t *out_ptr, int *out_size)
206 {
207 int n;
208 n = opus_decode(codec->opus_decoder, in_ptr, in_size, (opus_int16 *) out_ptr,
209 *out_size / SND_CODEC_PLAYBACK_CHAN / 2, 0);
210 if (n < 0) {
211 g_warning("opus_decode failed %d", n);
212 return SND_CODEC_DECODE_FAILED;
213 }
214 *out_size = n * SND_CODEC_PLAYBACK_CHAN * 2 /* 16 fmt */;
215 return SND_CODEC_OK;
216 }
217 #endif
218
219
220 /*----------------------------------------------------------------------------
221 ** PUBLIC INTERFACE
222 **--------------------------------------------------------------------------*/
223
224 /*
225 snd_codec_is_capable
226 Returns TRUE if the current spice implementation can
227 use the given codec, FALSE otherwise.
228 mode must be a SPICE_AUDIO_DATA_MODE_XXX enum from spice/enum.h
229 */
snd_codec_is_capable(int mode,int frequency)230 int snd_codec_is_capable(int mode, int frequency)
231 {
232 #if HAVE_CELT051
233 if (mode == SPICE_AUDIO_DATA_MODE_CELT_0_5_1)
234 return TRUE;
235 #endif
236
237 #if HAVE_OPUS
238 if (mode == SPICE_AUDIO_DATA_MODE_OPUS &&
239 (frequency == SND_CODEC_ANY_FREQUENCY ||
240 frequency == 48000 || frequency == 24000 ||
241 frequency == 16000 || frequency == 12000 ||
242 frequency == 8000) )
243 return TRUE;
244 #endif
245
246 return FALSE;
247 }
248
249 /*
250 snd_codec_create
251 Create a codec control. Required for most functions in this library.
252 Parameters:
253 1. codec Pointer to preallocated codec control
254 2. mode SPICE_AUDIO_DATA_MODE_XXX enum from spice/enum.h
255 3. encode TRUE if encoding is desired
256 4. decode TRUE if decoding is desired
257 Returns:
258 SND_CODEC_OK if all went well; a different code if not.
259
260 snd_codec_destroy is the obvious partner of snd_codec_create.
261 */
snd_codec_create(SndCodec * codec,int mode,int frequency,int purpose)262 int snd_codec_create(SndCodec *codec, int mode, int frequency, int purpose)
263 {
264 int rc = SND_CODEC_UNAVAILABLE;
265 SndCodecInternal **c = (SndCodecInternal **) codec;
266
267 *c = spice_new0(SndCodecInternal, 1);
268 (*c)->frequency = frequency;
269
270 #if HAVE_CELT051
271 if (mode == SPICE_AUDIO_DATA_MODE_CELT_0_5_1)
272 rc = snd_codec_create_celt051(*c, purpose);
273 #endif
274
275 #if HAVE_OPUS
276 if (mode == SPICE_AUDIO_DATA_MODE_OPUS)
277 rc = snd_codec_create_opus(*c, purpose);
278 #endif
279
280 return rc;
281 }
282
283 /*
284 snd_codec_destroy
285 The obvious companion to snd_codec_create
286 */
snd_codec_destroy(SndCodec * codec)287 void snd_codec_destroy(SndCodec *codec)
288 {
289 SndCodecInternal **c = (SndCodecInternal **) codec;
290 if (! c || ! *c)
291 return;
292
293 #if HAVE_CELT051
294 snd_codec_destroy_celt051(*c);
295 #endif
296
297 #if HAVE_OPUS
298 snd_codec_destroy_opus(*c);
299 #endif
300
301 free(*c);
302 *c = NULL;
303 }
304
305 /*
306 snd_codec_frame_size
307 Returns the size, in frames, of the raw PCM frame buffer
308 required by this codec. To get bytes, you'll need
309 to multiply by channels and sample width.
310 */
snd_codec_frame_size(SndCodec codec)311 int snd_codec_frame_size(SndCodec codec)
312 {
313 #if defined(HAVE_CELT051) || defined(HAVE_OPUS)
314 SndCodecInternal *c = (SndCodecInternal *) codec;
315 #endif
316 #if HAVE_CELT051
317 if (c && c->mode == SPICE_AUDIO_DATA_MODE_CELT_0_5_1)
318 return SND_CODEC_CELT_FRAME_SIZE;
319 #endif
320 #if HAVE_OPUS
321 if (c && c->mode == SPICE_AUDIO_DATA_MODE_OPUS)
322 return SND_CODEC_OPUS_FRAME_SIZE;
323 #endif
324 return SND_CODEC_MAX_FRAME_SIZE;
325 }
326
327 /*
328 snd_codec_encode
329 Encode a block of data to a compressed buffer.
330
331 Parameters:
332 1. codec Pointer to codec control previously allocated + created
333 2. in_data Pointer to uncompressed PCM data
334 3. in_size Input size (for celt, this must be a
335 particular size, governed by the frame size)
336 4. out_ptr Pointer to area to write encoded data
337 5. out_size On input, the maximum size of the output buffer; on
338 successful return, it will hold the number of bytes
339 returned. For celt, this must be set to a particular
340 size to ensure compatibility.
341
342 Returns:
343 SND_CODEC_OK if all went well
344 */
snd_codec_encode(SndCodec codec,uint8_t * in_ptr,int in_size,uint8_t * out_ptr,int * out_size)345 int snd_codec_encode(SndCodec codec, uint8_t *in_ptr, int in_size, uint8_t *out_ptr, int *out_size)
346 {
347 #if defined(HAVE_CELT051) || defined(HAVE_OPUS)
348 SndCodecInternal *c = (SndCodecInternal *) codec;
349 #endif
350 #if HAVE_CELT051
351 if (c && c->mode == SPICE_AUDIO_DATA_MODE_CELT_0_5_1) {
352 /* The output buffer size in celt determines the compression,
353 and so is essentially mandatory to use a certain value (47) */
354 if (*out_size > SND_CODEC_CELT_COMPRESSED_FRAME_BYTES)
355 *out_size = SND_CODEC_CELT_COMPRESSED_FRAME_BYTES;
356 return snd_codec_encode_celt051(c, in_ptr, in_size, out_ptr, out_size);
357 }
358 #endif
359
360 #if HAVE_OPUS
361 if (c && c->mode == SPICE_AUDIO_DATA_MODE_OPUS)
362 return snd_codec_encode_opus(c, in_ptr, in_size, out_ptr, out_size);
363 #endif
364
365 return SND_CODEC_ENCODER_UNAVAILABLE;
366 }
367
368 /*
369 snd_codec_decode
370 Decode a block of data from a compressed buffer.
371
372 Parameters:
373 1. codec Pointer to codec control previously allocated + created
374 2. in_data Pointer to compressed data
375 3. in_size Input size
376 4. out_ptr Pointer to area to write decoded data
377 5. out_size On input, the maximum size of the output buffer; on
378 successful return, it will hold the number of bytes
379 returned.
380
381 Returns:
382 SND_CODEC_OK if all went well
383 */
snd_codec_decode(SndCodec codec,uint8_t * in_ptr,int in_size,uint8_t * out_ptr,int * out_size)384 int snd_codec_decode(SndCodec codec, uint8_t *in_ptr, int in_size, uint8_t *out_ptr, int *out_size)
385 {
386 #if defined(HAVE_CELT051) || defined(HAVE_OPUS)
387 SndCodecInternal *c = (SndCodecInternal *) codec;
388 #endif
389 #if HAVE_CELT051
390 if (c && c->mode == SPICE_AUDIO_DATA_MODE_CELT_0_5_1)
391 return snd_codec_decode_celt051(c, in_ptr, in_size, out_ptr, out_size);
392 #endif
393
394 #if HAVE_OPUS
395 if (c && c->mode == SPICE_AUDIO_DATA_MODE_OPUS)
396 return snd_codec_decode_opus(c, in_ptr, in_size, out_ptr, out_size);
397 #endif
398
399 return SND_CODEC_DECODER_UNAVAILABLE;
400 }
401