Mercurial > SDL_sound_CoreAudio
annotate decoders/speex.c @ 566:74405e7be04b
Moved SNDDBG output a little later.
author | Ryan C. Gordon <icculus@icculus.org> |
---|---|
date | Fri, 30 Jan 2009 19:54:58 -0500 |
parents | a737d89af74e |
children |
rev | line source |
---|---|
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 * | |
552
2e8907ff98e9
Replaced references to COPYING with references to LICENSE.txt ...
Ryan C. Gordon <icculus@icculus.org>
parents:
534
diff
changeset
|
37 * Please see the file LICENSE.txt in the source's root directory. |
451 | 38 * |
526
2df1f5c62d38
Updated my email address.
Ryan C. Gordon <icculus@icculus.org>
parents:
510
diff
changeset
|
39 * This file written by Ryan C. Gordon. (icculus@icculus.org) |
451 | 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> | |
534
a116d8f628a0
Merged r541:543 from branches/stable-1.0: Speex include directory fix.
Ryan C. Gordon <icculus@icculus.org>
parents:
527
diff
changeset
|
54 #include <speex/speex.h> |
a116d8f628a0
Merged r541:543 from branches/stable-1.0: Speex include directory fix.
Ryan C. Gordon <icculus@icculus.org>
parents:
527
diff
changeset
|
55 #include <speex/speex_header.h> |
451 | 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 | |
565
a737d89af74e
Capitalized some lowercase extensions, to match all the others.
Ryan C. Gordon <icculus@icculus.org>
parents:
552
diff
changeset
|
70 static const char *extensions_speex[] = { "SPX", NULL }; |
451 | 71 const Sound_DecoderFunctions __Sound_DecoderFunctions_SPEEX = |
72 { | |
73 { | |
74 extensions_speex, | |
75 "SPEEX speech compression format", | |
526
2df1f5c62d38
Updated my email address.
Ryan C. Gordon <icculus@icculus.org>
parents:
510
diff
changeset
|
76 "Ryan C. Gordon <icculus@icculus.org>", |
451 | 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); | |
527
b5ddeebce808
Check if Speex header has bogus data (CVE-2008-1686).
Ryan C. Gordon <icculus@icculus.org>
parents:
526
diff
changeset
|
139 BAIL_IF_MACRO(header.mode < 0, "SPEEX: Unknown mode", 0); |
451 | 140 mode = speex_mode_list[header.mode]; |
141 BAIL_IF_MACRO(header.speex_version_id > 1, "SPEEX: Unknown version", 0); | |
142 BAIL_IF_MACRO(mode->bitstream_version < header.mode_bitstream_version, | |
143 "SPEEX: Unsupported bitstream version", 0); | |
144 BAIL_IF_MACRO(mode->bitstream_version > header.mode_bitstream_version, | |
145 "SPEEX: Unsupported bitstream version", 0); | |
146 | |
147 speex->state = speex_decoder_init(mode); | |
148 BAIL_IF_MACRO(!speex->state, "SPEEX: Decoder initialization error", 0); | |
149 | |
150 speex_decoder_ctl(speex->state, SPEEX_SET_ENH, &enh_enabled); | |
151 speex_decoder_ctl(speex->state, SPEEX_GET_FRAME_SIZE, &speex->frame_size); | |
152 | |
153 speex->decode_buf = (float *) malloc(speex->frame_size * sizeof (float)); | |
154 BAIL_IF_MACRO(!speex->decode_buf, ERR_OUT_OF_MEMORY, 0); | |
155 | |
156 speex->nframes = header.frames_per_packet; | |
157 if (!speex->nframes) | |
158 speex->nframes = 1; | |
159 | |
160 /* !!! FIXME: Write converters to match desired format. | |
161 !!! FIXME: We have to convert from Float32 anyhow. */ | |
162 /* !!! FIXME: Is it a performance hit to alter sampling rate? | |
163 !!! FIXME: If not, try to match desired rate. */ | |
164 /* !!! FIXME: We force mono output, but speexdec.c has code for stereo. | |
165 !!! FIXME: Use that if sample->desired.channels == 2? */ | |
166 tmp = header.rate; | |
167 speex_decoder_ctl(speex->state, SPEEX_SET_SAMPLING_RATE, &tmp); | |
168 speex_decoder_ctl(speex->state, SPEEX_GET_SAMPLING_RATE, &tmp); | |
169 sample->actual.rate = tmp; | |
170 sample->actual.channels = 1; | |
171 sample->actual.format = AUDIO_S16SYS; | |
172 | |
173 SNDDBG(("SPEEX: %dHz, mono, %svbr, %s mode.\n", | |
174 (int) sample->actual.rate, | |
175 header.vbr ? "" : "not ", | |
176 mode->modeName)); | |
177 | |
178 /* plus 2: one for this header, one for the comment header. */ | |
179 speex->header_count = header.extra_headers + 2; | |
180 return(1); | |
181 } /* process_header */ | |
182 | |
183 | |
184 /* !!! FIXME: this code sucks. */ | |
185 static int SPEEX_open(Sound_Sample *sample, const char *ext) | |
186 { | |
187 int set_error_str = 1; | |
188 int bitstream_initialized = 0; | |
189 Uint8 *buffer = NULL; | |
190 int packet_count = 0; | |
191 speex_t *speex = NULL; | |
192 Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque; | |
193 SDL_RWops *rw = internal->rw; | |
194 Uint32 magic; | |
195 | |
196 /* Quick rejection. */ | |
197 /* | |
198 * !!! FIXME: If (ext) is .spx, ignore bad magic number and assume | |
199 * !!! FIXME: this is a corrupted file...try to sync up further in | |
200 * !!! FIXME: stream. But for general purposes we can't read the | |
201 * !!! FIXME: whole RWops here in case it's not a Speex file at all. | |
202 */ | |
203 magic = SDL_ReadLE32(rw); /* make sure this is an ogg stream. */ | |
204 BAIL_IF_MACRO(magic != SPEEX_MAGIC, "SPEEX: Not a complete ogg stream", 0); | |
205 BAIL_IF_MACRO(SDL_RWseek(rw, -4, SEEK_CUR) < 0, ERR_IO_ERROR, 0); | |
206 | |
207 speex = (speex_t *) malloc(sizeof (speex_t)); | |
208 BAIL_IF_MACRO(speex == NULL, ERR_OUT_OF_MEMORY, 0); | |
209 memset(speex, '\0', sizeof (speex_t)); | |
210 | |
211 speex_bits_init(&speex->bits); | |
212 if (ogg_sync_init(&speex->oy) != 0) goto speex_open_failed; | |
213 | |
214 while (1) | |
215 { | |
216 int rc; | |
217 Uint8 *buffer = (Uint8*)ogg_sync_buffer(&speex->oy, SPEEX_OGG_BUFSIZE); | |
218 if (buffer == NULL) goto speex_open_failed; | |
219 rc = SDL_RWread(rw, buffer, 1, SPEEX_OGG_BUFSIZE); | |
220 if (rc <= 0) goto speex_open_failed; | |
221 if (ogg_sync_wrote(&speex->oy, rc) != 0) goto speex_open_failed; | |
222 while (ogg_sync_pageout(&speex->oy, &speex->og) == 1) | |
223 { | |
224 if (!bitstream_initialized) | |
225 { | |
226 if (ogg_stream_init(&speex->os, ogg_page_serialno(&speex->og))) | |
227 goto speex_open_failed; | |
228 bitstream_initialized = 1; | |
229 } /* if */ | |
230 | |
231 if (ogg_stream_pagein(&speex->os, &speex->og) != 0) | |
232 goto speex_open_failed; | |
233 | |
234 while (ogg_stream_packetout(&speex->os, &speex->op) == 1) | |
235 { | |
236 if (speex->op.e_o_s) | |
237 goto speex_open_failed; /* end of stream already?! */ | |
238 | |
239 packet_count++; | |
240 if (packet_count == 1) /* need speex header. */ | |
241 { | |
242 if (!process_header(speex, sample)) | |
243 { | |
244 set_error_str = 0; /* process_header will set error string. */ | |
245 goto speex_open_failed; | |
246 } /* if */ | |
247 } /* if */ | |
248 | |
249 if (packet_count > speex->header_count) | |
250 { | |
251 /* if you made it here, you're ready to get a waveform. */ | |
252 SNDDBG(("SPEEX: Accepting data stream.\n")); | |
253 | |
254 /* sample->actual is configured in process_header()... */ | |
255 speex->have_ogg_packet = 1; | |
256 sample->flags = SOUND_SAMPLEFLAG_NONE; | |
257 internal->decoder_private = speex; | |
258 return(1); /* we'll handle this data. */ | |
259 } /* if */ | |
260 } /* while */ | |
261 | |
262 } /* while */ | |
263 | |
264 } /* while */ | |
265 | |
266 assert(0); /* shouldn't hit this point. */ | |
267 | |
268 speex_open_failed: | |
269 if (speex != NULL) | |
270 { | |
271 if (speex->state != NULL) | |
272 speex_decoder_destroy(speex->state); | |
273 if (bitstream_initialized) | |
274 ogg_stream_clear(&speex->os); | |
275 speex_bits_destroy(&speex->bits); | |
276 ogg_sync_clear(&speex->oy); | |
277 free(speex->decode_buf); | |
278 free(speex); | |
279 } /* if */ | |
280 | |
281 if (set_error_str) | |
282 BAIL_MACRO("SPEEX: decoding error", 0); | |
283 | |
284 return(0); | |
285 } /* SPEEX_open */ | |
286 | |
287 | |
288 static void SPEEX_close(Sound_Sample *sample) | |
289 { | |
290 Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque; | |
291 speex_t *speex = (speex_t *) internal->decoder_private; | |
292 speex_decoder_destroy(speex->state); | |
293 ogg_stream_clear(&speex->os); | |
294 speex_bits_destroy(&speex->bits); | |
295 ogg_sync_clear(&speex->oy); | |
296 free(speex->decode_buf); | |
297 free(speex); | |
298 } /* SPEEX_close */ | |
299 | |
300 | |
301 static Uint32 copy_from_decoded(speex_t *speex, | |
302 Sound_SampleInternal *internal, | |
303 Uint32 _cpypos) | |
304 { | |
305 /* | |
306 * !!! FIXME: Obviously, this all needs to change if we allow for | |
307 * !!! FIXME: more than mono, S16SYS data. | |
308 */ | |
309 Uint32 cpypos = _cpypos >> 1; | |
310 Sint16 *dst = ((Sint16 *) internal->buffer) + cpypos; | |
311 Sint16 *max; | |
312 Uint32 maxoutput = (internal->buffer_size >> 1) - cpypos; | |
313 Uint32 maxavail = speex->decode_total - speex->decode_pos; | |
314 float *src = speex->decode_buf + speex->decode_pos; | |
315 | |
316 if (maxavail < maxoutput) | |
317 maxoutput = maxavail; | |
318 | |
319 speex->decode_pos += maxoutput; | |
320 cpypos += maxoutput; | |
321 | |
322 for (max = dst + maxoutput; dst < max; dst++, src++) | |
323 { | |
454
6bd7ca7d218b
Took out a C++-style comment.
Ryan C. Gordon <icculus@icculus.org>
parents:
451
diff
changeset
|
324 /* !!! FIXME: This screams for vectorization. */ |
451 | 325 register float f = *src; |
326 if (f > 32000.0f) /* eh, speexdec.c clamps like this, too. */ | |
327 f = 32000.0f; | |
328 else if (f < -32000.0f) | |
329 f = -32000.0f; | |
454
6bd7ca7d218b
Took out a C++-style comment.
Ryan C. Gordon <icculus@icculus.org>
parents:
451
diff
changeset
|
330 *dst = (Sint16) (0.5f + f); |
451 | 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 { | |
510
1841bdcf2122
Completely unacceptable hack for Speex rewinding.
Ryan C. Gordon <icculus@icculus.org>
parents:
454
diff
changeset
|
423 Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque; |
1841bdcf2122
Completely unacceptable hack for Speex rewinding.
Ryan C. Gordon <icculus@icculus.org>
parents:
454
diff
changeset
|
424 /* |
1841bdcf2122
Completely unacceptable hack for Speex rewinding.
Ryan C. Gordon <icculus@icculus.org>
parents:
454
diff
changeset
|
425 * !!! FIXME: This is really unacceptable; state should be reset and |
1841bdcf2122
Completely unacceptable hack for Speex rewinding.
Ryan C. Gordon <icculus@icculus.org>
parents:
454
diff
changeset
|
426 * !!! FIXME: the RWops should be pointed to the start of the data |
1841bdcf2122
Completely unacceptable hack for Speex rewinding.
Ryan C. Gordon <icculus@icculus.org>
parents:
454
diff
changeset
|
427 * !!! FIXME: to decode. The below kludge adds unneeded overhead and |
1841bdcf2122
Completely unacceptable hack for Speex rewinding.
Ryan C. Gordon <icculus@icculus.org>
parents:
454
diff
changeset
|
428 * !!! FIXME: risk of failure. |
1841bdcf2122
Completely unacceptable hack for Speex rewinding.
Ryan C. Gordon <icculus@icculus.org>
parents:
454
diff
changeset
|
429 */ |
1841bdcf2122
Completely unacceptable hack for Speex rewinding.
Ryan C. Gordon <icculus@icculus.org>
parents:
454
diff
changeset
|
430 BAIL_IF_MACRO(SDL_RWseek(internal->rw, 0, SEEK_SET) != 0, ERR_IO_ERROR, 0); |
1841bdcf2122
Completely unacceptable hack for Speex rewinding.
Ryan C. Gordon <icculus@icculus.org>
parents:
454
diff
changeset
|
431 SPEEX_close(sample); |
1841bdcf2122
Completely unacceptable hack for Speex rewinding.
Ryan C. Gordon <icculus@icculus.org>
parents:
454
diff
changeset
|
432 return(SPEEX_open(sample, "SPX")); |
451 | 433 } /* SPEEX_rewind */ |
434 | |
435 | |
436 static int SPEEX_seek(Sound_Sample *sample, Uint32 ms) | |
437 { | |
438 /* !!! FIXME */ return(0); | |
439 } /* SPEEX_seek */ | |
440 | |
441 | |
442 #endif /* SOUND_SUPPORTS_SPEEX */ | |
443 | |
444 /* end of speex.c ... */ | |
445 |