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@icculus.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/speex.h> 55 #include <speex/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@icculus.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 BAIL_IF_MACRO(header.mode < 0, "SPEEX: Unknown mode", 0); 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 { 324 /* !!! FIXME: This screams for vectorization. */ 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; 330 *dst = (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 437