451
|
1 /*
|
|
2 * SDL_sound -- An abstract sound format decoding API.
|
|
3 * Copyright (C) 2001 Ryan C. Gordon.
|
|
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, write to the Free Software
|
|
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
18 */
|
|
19
|
|
20 /*
|
|
21 * Speex decoder for SDL_sound.
|
|
22 *
|
|
23 * This driver handles Speex audio data. Speex is a codec for speech that is
|
|
24 * meant to be transmitted over narrowband network connections. Epic Games
|
|
25 * estimates that their VoIP solution, built on top of Speex, uses around
|
|
26 * 500 bytes per second or less to transmit relatively good sounding speech.
|
|
27 *
|
|
28 * This decoder processes the .spx files that the speexenc program produces.
|
|
29 *
|
|
30 * Speex isn't meant for general audio compression. Something like Ogg Vorbis
|
|
31 * will give better results in that case.
|
|
32 *
|
|
33 * Further Speex information can be found at http://www.speex.org/
|
|
34 *
|
|
35 * This code is based on speexdec.c (see the Speex website).
|
|
36 *
|
|
37 * Please see the file COPYING in the source's root directory.
|
|
38 *
|
|
39 * This file written by Ryan C. Gordon. (icculus@clutteredmind.org)
|
|
40 */
|
|
41
|
|
42 #if HAVE_CONFIG_H
|
|
43 # include <config.h>
|
|
44 #endif
|
|
45
|
|
46 #ifdef SOUND_SUPPORTS_SPEEX
|
|
47
|
|
48 #include <stdio.h>
|
|
49 #include <stdlib.h>
|
|
50 #include <string.h>
|
|
51 #include <assert.h>
|
|
52
|
|
53 #include <ogg/ogg.h>
|
|
54 #include <speex.h>
|
|
55 #include <speex_header.h>
|
|
56
|
|
57 #include "SDL_sound.h"
|
|
58
|
|
59 #define __SDL_SOUND_INTERNAL__
|
|
60 #include "SDL_sound_internal.h"
|
|
61
|
|
62 static int SPEEX_init(void);
|
|
63 static void SPEEX_quit(void);
|
|
64 static int SPEEX_open(Sound_Sample *sample, const char *ext);
|
|
65 static void SPEEX_close(Sound_Sample *sample);
|
|
66 static Uint32 SPEEX_read(Sound_Sample *sample);
|
|
67 static int SPEEX_rewind(Sound_Sample *sample);
|
|
68 static int SPEEX_seek(Sound_Sample *sample, Uint32 ms);
|
|
69
|
|
70 static const char *extensions_speex[] = { "spx", NULL };
|
|
71 const Sound_DecoderFunctions __Sound_DecoderFunctions_SPEEX =
|
|
72 {
|
|
73 {
|
|
74 extensions_speex,
|
|
75 "SPEEX speech compression format",
|
|
76 "Ryan C. Gordon <icculus@clutteredmind.org>",
|
|
77 "http://www.icculus.org/SDL_sound/"
|
|
78 },
|
|
79
|
|
80 SPEEX_init, /* init() method */
|
|
81 SPEEX_quit, /* quit() method */
|
|
82 SPEEX_open, /* open() method */
|
|
83 SPEEX_close, /* close() method */
|
|
84 SPEEX_read, /* read() method */
|
|
85 SPEEX_rewind, /* rewind() method */
|
|
86 SPEEX_seek /* seek() method */
|
|
87 };
|
|
88
|
|
89 #define SPEEX_USE_PERCEPTUAL_ENHANCER 1
|
|
90 #define SPEEX_MAGIC 0x5367674F /* "OggS" in ASCII (littleendian) */
|
|
91 #define SPEEX_OGG_BUFSIZE 200
|
|
92
|
|
93 /* this is what we store in our internal->decoder_private field... */
|
|
94 typedef struct
|
|
95 {
|
|
96 ogg_sync_state oy;
|
|
97 ogg_page og;
|
|
98 ogg_packet op;
|
|
99 ogg_stream_state os;
|
|
100 void *state;
|
|
101 SpeexBits bits;
|
|
102 int header_count;
|
|
103 int frame_size;
|
|
104 int nframes;
|
|
105 int frames_avail;
|
|
106 float *decode_buf;
|
|
107 int decode_total;
|
|
108 int decode_pos;
|
|
109 int have_ogg_packet;
|
|
110 } speex_t;
|
|
111
|
|
112
|
|
113 static int SPEEX_init(void)
|
|
114 {
|
|
115 return(1); /* no-op. */
|
|
116 } /* SPEEX_init */
|
|
117
|
|
118
|
|
119 static void SPEEX_quit(void)
|
|
120 {
|
|
121 /* no-op. */
|
|
122 } /* SPEEX_quit */
|
|
123
|
|
124
|
|
125 static int process_header(speex_t *speex, Sound_Sample *sample)
|
|
126 {
|
|
127 SpeexMode *mode;
|
|
128 SpeexHeader *hptr;
|
|
129 SpeexHeader header;
|
|
130 int enh_enabled = SPEEX_USE_PERCEPTUAL_ENHANCER;
|
|
131 int tmp;
|
|
132
|
|
133 hptr = speex_packet_to_header((char*) speex->op.packet, speex->op.bytes);
|
|
134 BAIL_IF_MACRO(!hptr, "SPEEX: Cannot read header", 0);
|
|
135 memcpy(&header, hptr, sizeof (SpeexHeader)); /* move to stack. */
|
|
136 free(hptr); /* lame that this forces you to malloc... */
|
|
137
|
|
138 BAIL_IF_MACRO(header.mode >= SPEEX_NB_MODES, "SPEEX: Unknown mode", 0);
|
|
139 mode = speex_mode_list[header.mode];
|
|
140 BAIL_IF_MACRO(header.speex_version_id > 1, "SPEEX: Unknown version", 0);
|
|
141 BAIL_IF_MACRO(mode->bitstream_version < header.mode_bitstream_version,
|
|
142 "SPEEX: Unsupported bitstream version", 0);
|
|
143 BAIL_IF_MACRO(mode->bitstream_version > header.mode_bitstream_version,
|
|
144 "SPEEX: Unsupported bitstream version", 0);
|
|
145
|
|
146 speex->state = speex_decoder_init(mode);
|
|
147 BAIL_IF_MACRO(!speex->state, "SPEEX: Decoder initialization error", 0);
|
|
148
|
|
149 speex_decoder_ctl(speex->state, SPEEX_SET_ENH, &enh_enabled);
|
|
150 speex_decoder_ctl(speex->state, SPEEX_GET_FRAME_SIZE, &speex->frame_size);
|
|
151
|
|
152 speex->decode_buf = (float *) malloc(speex->frame_size * sizeof (float));
|
|
153 BAIL_IF_MACRO(!speex->decode_buf, ERR_OUT_OF_MEMORY, 0);
|
|
154
|
|
155 speex->nframes = header.frames_per_packet;
|
|
156 if (!speex->nframes)
|
|
157 speex->nframes = 1;
|
|
158
|
|
159 /* !!! FIXME: Write converters to match desired format.
|
|
160 !!! FIXME: We have to convert from Float32 anyhow. */
|
|
161 /* !!! FIXME: Is it a performance hit to alter sampling rate?
|
|
162 !!! FIXME: If not, try to match desired rate. */
|
|
163 /* !!! FIXME: We force mono output, but speexdec.c has code for stereo.
|
|
164 !!! FIXME: Use that if sample->desired.channels == 2? */
|
|
165 tmp = header.rate;
|
|
166 speex_decoder_ctl(speex->state, SPEEX_SET_SAMPLING_RATE, &tmp);
|
|
167 speex_decoder_ctl(speex->state, SPEEX_GET_SAMPLING_RATE, &tmp);
|
|
168 sample->actual.rate = tmp;
|
|
169 sample->actual.channels = 1;
|
|
170 sample->actual.format = AUDIO_S16SYS;
|
|
171
|
|
172 SNDDBG(("SPEEX: %dHz, mono, %svbr, %s mode.\n",
|
|
173 (int) sample->actual.rate,
|
|
174 header.vbr ? "" : "not ",
|
|
175 mode->modeName));
|
|
176
|
|
177 /* plus 2: one for this header, one for the comment header. */
|
|
178 speex->header_count = header.extra_headers + 2;
|
|
179 return(1);
|
|
180 } /* process_header */
|
|
181
|
|
182
|
|
183 /* !!! FIXME: this code sucks. */
|
|
184 static int SPEEX_open(Sound_Sample *sample, const char *ext)
|
|
185 {
|
|
186 int set_error_str = 1;
|
|
187 int bitstream_initialized = 0;
|
|
188 Uint8 *buffer = NULL;
|
|
189 int packet_count = 0;
|
|
190 speex_t *speex = NULL;
|
|
191 Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque;
|
|
192 SDL_RWops *rw = internal->rw;
|
|
193 Uint32 magic;
|
|
194
|
|
195 /* Quick rejection. */
|
|
196 /*
|
|
197 * !!! FIXME: If (ext) is .spx, ignore bad magic number and assume
|
|
198 * !!! FIXME: this is a corrupted file...try to sync up further in
|
|
199 * !!! FIXME: stream. But for general purposes we can't read the
|
|
200 * !!! FIXME: whole RWops here in case it's not a Speex file at all.
|
|
201 */
|
|
202 magic = SDL_ReadLE32(rw); /* make sure this is an ogg stream. */
|
|
203 BAIL_IF_MACRO(magic != SPEEX_MAGIC, "SPEEX: Not a complete ogg stream", 0);
|
|
204 BAIL_IF_MACRO(SDL_RWseek(rw, -4, SEEK_CUR) < 0, ERR_IO_ERROR, 0);
|
|
205
|
|
206 speex = (speex_t *) malloc(sizeof (speex_t));
|
|
207 BAIL_IF_MACRO(speex == NULL, ERR_OUT_OF_MEMORY, 0);
|
|
208 memset(speex, '\0', sizeof (speex_t));
|
|
209
|
|
210 speex_bits_init(&speex->bits);
|
|
211 if (ogg_sync_init(&speex->oy) != 0) goto speex_open_failed;
|
|
212
|
|
213 while (1)
|
|
214 {
|
|
215 int rc;
|
|
216 Uint8 *buffer = (Uint8*)ogg_sync_buffer(&speex->oy, SPEEX_OGG_BUFSIZE);
|
|
217 if (buffer == NULL) goto speex_open_failed;
|
|
218 rc = SDL_RWread(rw, buffer, 1, SPEEX_OGG_BUFSIZE);
|
|
219 if (rc <= 0) goto speex_open_failed;
|
|
220 if (ogg_sync_wrote(&speex->oy, rc) != 0) goto speex_open_failed;
|
|
221 while (ogg_sync_pageout(&speex->oy, &speex->og) == 1)
|
|
222 {
|
|
223 if (!bitstream_initialized)
|
|
224 {
|
|
225 if (ogg_stream_init(&speex->os, ogg_page_serialno(&speex->og)))
|
|
226 goto speex_open_failed;
|
|
227 bitstream_initialized = 1;
|
|
228 } /* if */
|
|
229
|
|
230 if (ogg_stream_pagein(&speex->os, &speex->og) != 0)
|
|
231 goto speex_open_failed;
|
|
232
|
|
233 while (ogg_stream_packetout(&speex->os, &speex->op) == 1)
|
|
234 {
|
|
235 if (speex->op.e_o_s)
|
|
236 goto speex_open_failed; /* end of stream already?! */
|
|
237
|
|
238 packet_count++;
|
|
239 if (packet_count == 1) /* need speex header. */
|
|
240 {
|
|
241 if (!process_header(speex, sample))
|
|
242 {
|
|
243 set_error_str = 0; /* process_header will set error string. */
|
|
244 goto speex_open_failed;
|
|
245 } /* if */
|
|
246 } /* if */
|
|
247
|
|
248 if (packet_count > speex->header_count)
|
|
249 {
|
|
250 /* if you made it here, you're ready to get a waveform. */
|
|
251 SNDDBG(("SPEEX: Accepting data stream.\n"));
|
|
252
|
|
253 /* sample->actual is configured in process_header()... */
|
|
254 speex->have_ogg_packet = 1;
|
|
255 sample->flags = SOUND_SAMPLEFLAG_NONE;
|
|
256 internal->decoder_private = speex;
|
|
257 return(1); /* we'll handle this data. */
|
|
258 } /* if */
|
|
259 } /* while */
|
|
260
|
|
261 } /* while */
|
|
262
|
|
263 } /* while */
|
|
264
|
|
265 assert(0); /* shouldn't hit this point. */
|
|
266
|
|
267 speex_open_failed:
|
|
268 if (speex != NULL)
|
|
269 {
|
|
270 if (speex->state != NULL)
|
|
271 speex_decoder_destroy(speex->state);
|
|
272 if (bitstream_initialized)
|
|
273 ogg_stream_clear(&speex->os);
|
|
274 speex_bits_destroy(&speex->bits);
|
|
275 ogg_sync_clear(&speex->oy);
|
|
276 free(speex->decode_buf);
|
|
277 free(speex);
|
|
278 } /* if */
|
|
279
|
|
280 if (set_error_str)
|
|
281 BAIL_MACRO("SPEEX: decoding error", 0);
|
|
282
|
|
283 return(0);
|
|
284 } /* SPEEX_open */
|
|
285
|
|
286
|
|
287 static void SPEEX_close(Sound_Sample *sample)
|
|
288 {
|
|
289 Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque;
|
|
290 speex_t *speex = (speex_t *) internal->decoder_private;
|
|
291 speex_decoder_destroy(speex->state);
|
|
292 ogg_stream_clear(&speex->os);
|
|
293 speex_bits_destroy(&speex->bits);
|
|
294 ogg_sync_clear(&speex->oy);
|
|
295 free(speex->decode_buf);
|
|
296 free(speex);
|
|
297 } /* SPEEX_close */
|
|
298
|
|
299
|
|
300 static Uint32 copy_from_decoded(speex_t *speex,
|
|
301 Sound_SampleInternal *internal,
|
|
302 Uint32 _cpypos)
|
|
303 {
|
|
304 /*
|
|
305 * !!! FIXME: Obviously, this all needs to change if we allow for
|
|
306 * !!! FIXME: more than mono, S16SYS data.
|
|
307 */
|
|
308 Uint32 cpypos = _cpypos >> 1;
|
|
309 Sint16 *dst = ((Sint16 *) internal->buffer) + cpypos;
|
|
310 Sint16 *max;
|
|
311 Uint32 maxoutput = (internal->buffer_size >> 1) - cpypos;
|
|
312 Uint32 maxavail = speex->decode_total - speex->decode_pos;
|
|
313 float *src = speex->decode_buf + speex->decode_pos;
|
|
314
|
|
315 if (maxavail < maxoutput)
|
|
316 maxoutput = maxavail;
|
|
317
|
|
318 speex->decode_pos += maxoutput;
|
|
319 cpypos += maxoutput;
|
|
320
|
|
321 for (max = dst + maxoutput; dst < max; dst++, src++)
|
|
322 {
|
|
323 register float f = *src;
|
|
324 if (f > 32000.0f) /* eh, speexdec.c clamps like this, too. */
|
|
325 f = 32000.0f;
|
|
326 else if (f < -32000.0f)
|
|
327 f = -32000.0f;
|
|
328 *dst = //(Sint16) round(f);
|
|
329 // (Sint16) floor(.5f+f);
|
|
330 (Sint16) 0.5f+f;
|
|
331 } /* for */
|
|
332
|
|
333 return(cpypos << 1);
|
|
334 } /* copy_from_decoded */
|
|
335
|
|
336
|
|
337 /* !!! FIXME: this code sucks. */
|
|
338 static Uint32 SPEEX_read(Sound_Sample *sample)
|
|
339 {
|
|
340 Uint32 retval = 0;
|
|
341 Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque;
|
|
342 speex_t *speex = (speex_t *) internal->decoder_private;
|
|
343 SDL_RWops *rw = internal->rw;
|
|
344 Uint8 *buffer;
|
|
345 int rc;
|
|
346
|
|
347 while (1)
|
|
348 {
|
|
349 /* see if there's some already-decoded leftovers... */
|
|
350 if (speex->decode_total != speex->decode_pos)
|
|
351 {
|
|
352 retval = copy_from_decoded(speex, internal, retval);
|
|
353 if (retval >= internal->buffer_size)
|
|
354 return(retval); /* whee. */
|
|
355 } /* if */
|
|
356
|
|
357 /* okay, decoded buffer is spent. What else do we have? */
|
|
358 speex->decode_total = speex->decode_pos = 0;
|
|
359
|
|
360 if (speex->frames_avail) /* have more frames to decode? */
|
|
361 {
|
|
362 rc = speex_decode(speex->state, &speex->bits, speex->decode_buf);
|
|
363 if (rc < 0) goto speex_read_failed;
|
|
364 if (speex_bits_remaining(&speex->bits) < 0) goto speex_read_failed;
|
|
365 speex->frames_avail--;
|
|
366 speex->decode_total = speex->frame_size;
|
|
367 continue; /* go fill the output buffer... */
|
|
368 } /* if */
|
|
369
|
|
370 /* need to get more speex frames from available ogg packets... */
|
|
371 if (speex->have_ogg_packet)
|
|
372 {
|
|
373 speex_bits_read_from(&speex->bits,
|
|
374 (char *) speex->op.packet,
|
|
375 speex->op.bytes);
|
|
376
|
|
377 speex->frames_avail += speex->nframes;
|
|
378 if (ogg_stream_packetout(&speex->os, &speex->op) <= 0)
|
|
379 speex->have_ogg_packet = 0;
|
|
380 continue; /* go decode these frames. */
|
|
381 } /* if */
|
|
382
|
|
383 /* need to get more ogg packets from bitstream... */
|
|
384
|
|
385 if (speex->op.e_o_s) /* okay, we're really spent. */
|
|
386 {
|
|
387 sample->flags |= SOUND_SAMPLEFLAG_EOF;
|
|
388 return(retval);
|
|
389 } /* if */
|
|
390
|
|
391 while ((!speex->op.e_o_s) && (!speex->have_ogg_packet))
|
|
392 {
|
|
393 buffer = (Uint8 *) ogg_sync_buffer(&speex->oy, SPEEX_OGG_BUFSIZE);
|
|
394 if (buffer == NULL) goto speex_read_failed;
|
|
395 rc = SDL_RWread(rw, buffer, 1, SPEEX_OGG_BUFSIZE);
|
|
396 if (rc <= 0) goto speex_read_failed;
|
|
397 if (ogg_sync_wrote(&speex->oy, rc) != 0) goto speex_read_failed;
|
|
398
|
|
399 /* got complete ogg page? */
|
|
400 if (ogg_sync_pageout(&speex->oy, &speex->og) == 1)
|
|
401 {
|
|
402 if (ogg_stream_pagein(&speex->os, &speex->og) != 0)
|
|
403 goto speex_read_failed;
|
|
404
|
|
405 /* got complete ogg packet? */
|
|
406 if (ogg_stream_packetout(&speex->os, &speex->op) == 1)
|
|
407 speex->have_ogg_packet = 1;
|
|
408 } /* if */
|
|
409 } /* while */
|
|
410 } /* while */
|
|
411
|
|
412 assert(0); /* never hit this. Either return or goto speex_read_failed */
|
|
413
|
|
414 speex_read_failed:
|
|
415 sample->flags |= SOUND_SAMPLEFLAG_ERROR;
|
|
416 /* !!! FIXME: "i/o error" is better in some situations. */
|
|
417 BAIL_MACRO("SPEEX: Decoding error", retval);
|
|
418 } /* SPEEX_read */
|
|
419
|
|
420
|
|
421 static int SPEEX_rewind(Sound_Sample *sample)
|
|
422 {
|
|
423 /* !!! FIXME */ return(0);
|
|
424 } /* SPEEX_rewind */
|
|
425
|
|
426
|
|
427 static int SPEEX_seek(Sound_Sample *sample, Uint32 ms)
|
|
428 {
|
|
429 /* !!! FIXME */ return(0);
|
|
430 } /* SPEEX_seek */
|
|
431
|
|
432
|
|
433 #endif /* SOUND_SUPPORTS_SPEEX */
|
|
434
|
|
435 /* end of speex.c ... */
|
|
436
|