Mercurial > SDL_sound_CoreAudio
annotate decoders/speex.c @ 562:7e08477b0fc1
MP3 decoder upgrade work.
Ripped out SMPEG and mpglib support, replaced it with "mpg123.c" and libmpg123.
libmpg123 is a much better version of mpglib, so it should solve all the
problems about MP3's not seeking, or most modern MP3's not playing at all,
etc. Since you no longer have to make a tradeoff with SMPEG for features, and
SMPEG is basically rotting, I removed it from the project.
There is still work to be done with libmpg123...there are MMX, 3DNow, SSE,
Altivec, etc decoders which we don't have enabled at the moment, and the
build system could use some work to make this compile more cleanly, etc.
Still: huge win.
author | Ryan C. Gordon <icculus@icculus.org> |
---|---|
date | Fri, 30 Jan 2009 02:44:47 -0500 |
parents | 2e8907ff98e9 |
children | a737d89af74e |
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 | |
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", | |
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 |