Mercurial > SDL_sound_CoreAudio
view decoders/speex.c @ 513:006e3d6cd2fe
Patch to get mpglib going again, from Sam.
author | Ryan C. Gordon <icculus@icculus.org> |
---|---|
date | Fri, 12 May 2006 14:43:27 +0000 |
parents | 1841bdcf2122 |
children | 2df1f5c62d38 |
line wrap: on
line source
/* * SDL_sound -- An abstract sound format decoding API. * Copyright (C) 2001 Ryan C. Gordon. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* * Speex decoder for SDL_sound. * * This driver handles Speex audio data. Speex is a codec for speech that is * meant to be transmitted over narrowband network connections. Epic Games * estimates that their VoIP solution, built on top of Speex, uses around * 500 bytes per second or less to transmit relatively good sounding speech. * * This decoder processes the .spx files that the speexenc program produces. * * Speex isn't meant for general audio compression. Something like Ogg Vorbis * will give better results in that case. * * Further Speex information can be found at http://www.speex.org/ * * This code is based on speexdec.c (see the Speex website). * * Please see the file COPYING in the source's root directory. * * This file written by Ryan C. Gordon. (icculus@clutteredmind.org) */ #if HAVE_CONFIG_H # include <config.h> #endif #ifdef SOUND_SUPPORTS_SPEEX #include <stdio.h> #include <stdlib.h> #include <string.h> #include <assert.h> #include <ogg/ogg.h> #include <speex.h> #include <speex_header.h> #include "SDL_sound.h" #define __SDL_SOUND_INTERNAL__ #include "SDL_sound_internal.h" static int SPEEX_init(void); static void SPEEX_quit(void); static int SPEEX_open(Sound_Sample *sample, const char *ext); static void SPEEX_close(Sound_Sample *sample); static Uint32 SPEEX_read(Sound_Sample *sample); static int SPEEX_rewind(Sound_Sample *sample); static int SPEEX_seek(Sound_Sample *sample, Uint32 ms); static const char *extensions_speex[] = { "spx", NULL }; const Sound_DecoderFunctions __Sound_DecoderFunctions_SPEEX = { { extensions_speex, "SPEEX speech compression format", "Ryan C. Gordon <icculus@clutteredmind.org>", "http://www.icculus.org/SDL_sound/" }, SPEEX_init, /* init() method */ SPEEX_quit, /* quit() method */ SPEEX_open, /* open() method */ SPEEX_close, /* close() method */ SPEEX_read, /* read() method */ SPEEX_rewind, /* rewind() method */ SPEEX_seek /* seek() method */ }; #define SPEEX_USE_PERCEPTUAL_ENHANCER 1 #define SPEEX_MAGIC 0x5367674F /* "OggS" in ASCII (littleendian) */ #define SPEEX_OGG_BUFSIZE 200 /* this is what we store in our internal->decoder_private field... */ typedef struct { ogg_sync_state oy; ogg_page og; ogg_packet op; ogg_stream_state os; void *state; SpeexBits bits; int header_count; int frame_size; int nframes; int frames_avail; float *decode_buf; int decode_total; int decode_pos; int have_ogg_packet; } speex_t; static int SPEEX_init(void) { return(1); /* no-op. */ } /* SPEEX_init */ static void SPEEX_quit(void) { /* no-op. */ } /* SPEEX_quit */ static int process_header(speex_t *speex, Sound_Sample *sample) { SpeexMode *mode; SpeexHeader *hptr; SpeexHeader header; int enh_enabled = SPEEX_USE_PERCEPTUAL_ENHANCER; int tmp; hptr = speex_packet_to_header((char*) speex->op.packet, speex->op.bytes); BAIL_IF_MACRO(!hptr, "SPEEX: Cannot read header", 0); memcpy(&header, hptr, sizeof (SpeexHeader)); /* move to stack. */ free(hptr); /* lame that this forces you to malloc... */ BAIL_IF_MACRO(header.mode >= SPEEX_NB_MODES, "SPEEX: Unknown mode", 0); mode = speex_mode_list[header.mode]; BAIL_IF_MACRO(header.speex_version_id > 1, "SPEEX: Unknown version", 0); BAIL_IF_MACRO(mode->bitstream_version < header.mode_bitstream_version, "SPEEX: Unsupported bitstream version", 0); BAIL_IF_MACRO(mode->bitstream_version > header.mode_bitstream_version, "SPEEX: Unsupported bitstream version", 0); speex->state = speex_decoder_init(mode); BAIL_IF_MACRO(!speex->state, "SPEEX: Decoder initialization error", 0); speex_decoder_ctl(speex->state, SPEEX_SET_ENH, &enh_enabled); speex_decoder_ctl(speex->state, SPEEX_GET_FRAME_SIZE, &speex->frame_size); speex->decode_buf = (float *) malloc(speex->frame_size * sizeof (float)); BAIL_IF_MACRO(!speex->decode_buf, ERR_OUT_OF_MEMORY, 0); speex->nframes = header.frames_per_packet; if (!speex->nframes) speex->nframes = 1; /* !!! FIXME: Write converters to match desired format. !!! FIXME: We have to convert from Float32 anyhow. */ /* !!! FIXME: Is it a performance hit to alter sampling rate? !!! FIXME: If not, try to match desired rate. */ /* !!! FIXME: We force mono output, but speexdec.c has code for stereo. !!! FIXME: Use that if sample->desired.channels == 2? */ tmp = header.rate; speex_decoder_ctl(speex->state, SPEEX_SET_SAMPLING_RATE, &tmp); speex_decoder_ctl(speex->state, SPEEX_GET_SAMPLING_RATE, &tmp); sample->actual.rate = tmp; sample->actual.channels = 1; sample->actual.format = AUDIO_S16SYS; SNDDBG(("SPEEX: %dHz, mono, %svbr, %s mode.\n", (int) sample->actual.rate, header.vbr ? "" : "not ", mode->modeName)); /* plus 2: one for this header, one for the comment header. */ speex->header_count = header.extra_headers + 2; return(1); } /* process_header */ /* !!! FIXME: this code sucks. */ static int SPEEX_open(Sound_Sample *sample, const char *ext) { int set_error_str = 1; int bitstream_initialized = 0; Uint8 *buffer = NULL; int packet_count = 0; speex_t *speex = NULL; Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque; SDL_RWops *rw = internal->rw; Uint32 magic; /* Quick rejection. */ /* * !!! FIXME: If (ext) is .spx, ignore bad magic number and assume * !!! FIXME: this is a corrupted file...try to sync up further in * !!! FIXME: stream. But for general purposes we can't read the * !!! FIXME: whole RWops here in case it's not a Speex file at all. */ magic = SDL_ReadLE32(rw); /* make sure this is an ogg stream. */ BAIL_IF_MACRO(magic != SPEEX_MAGIC, "SPEEX: Not a complete ogg stream", 0); BAIL_IF_MACRO(SDL_RWseek(rw, -4, SEEK_CUR) < 0, ERR_IO_ERROR, 0); speex = (speex_t *) malloc(sizeof (speex_t)); BAIL_IF_MACRO(speex == NULL, ERR_OUT_OF_MEMORY, 0); memset(speex, '\0', sizeof (speex_t)); speex_bits_init(&speex->bits); if (ogg_sync_init(&speex->oy) != 0) goto speex_open_failed; while (1) { int rc; Uint8 *buffer = (Uint8*)ogg_sync_buffer(&speex->oy, SPEEX_OGG_BUFSIZE); if (buffer == NULL) goto speex_open_failed; rc = SDL_RWread(rw, buffer, 1, SPEEX_OGG_BUFSIZE); if (rc <= 0) goto speex_open_failed; if (ogg_sync_wrote(&speex->oy, rc) != 0) goto speex_open_failed; while (ogg_sync_pageout(&speex->oy, &speex->og) == 1) { if (!bitstream_initialized) { if (ogg_stream_init(&speex->os, ogg_page_serialno(&speex->og))) goto speex_open_failed; bitstream_initialized = 1; } /* if */ if (ogg_stream_pagein(&speex->os, &speex->og) != 0) goto speex_open_failed; while (ogg_stream_packetout(&speex->os, &speex->op) == 1) { if (speex->op.e_o_s) goto speex_open_failed; /* end of stream already?! */ packet_count++; if (packet_count == 1) /* need speex header. */ { if (!process_header(speex, sample)) { set_error_str = 0; /* process_header will set error string. */ goto speex_open_failed; } /* if */ } /* if */ if (packet_count > speex->header_count) { /* if you made it here, you're ready to get a waveform. */ SNDDBG(("SPEEX: Accepting data stream.\n")); /* sample->actual is configured in process_header()... */ speex->have_ogg_packet = 1; sample->flags = SOUND_SAMPLEFLAG_NONE; internal->decoder_private = speex; return(1); /* we'll handle this data. */ } /* if */ } /* while */ } /* while */ } /* while */ assert(0); /* shouldn't hit this point. */ speex_open_failed: if (speex != NULL) { if (speex->state != NULL) speex_decoder_destroy(speex->state); if (bitstream_initialized) ogg_stream_clear(&speex->os); speex_bits_destroy(&speex->bits); ogg_sync_clear(&speex->oy); free(speex->decode_buf); free(speex); } /* if */ if (set_error_str) BAIL_MACRO("SPEEX: decoding error", 0); return(0); } /* SPEEX_open */ static void SPEEX_close(Sound_Sample *sample) { Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque; speex_t *speex = (speex_t *) internal->decoder_private; speex_decoder_destroy(speex->state); ogg_stream_clear(&speex->os); speex_bits_destroy(&speex->bits); ogg_sync_clear(&speex->oy); free(speex->decode_buf); free(speex); } /* SPEEX_close */ static Uint32 copy_from_decoded(speex_t *speex, Sound_SampleInternal *internal, Uint32 _cpypos) { /* * !!! FIXME: Obviously, this all needs to change if we allow for * !!! FIXME: more than mono, S16SYS data. */ Uint32 cpypos = _cpypos >> 1; Sint16 *dst = ((Sint16 *) internal->buffer) + cpypos; Sint16 *max; Uint32 maxoutput = (internal->buffer_size >> 1) - cpypos; Uint32 maxavail = speex->decode_total - speex->decode_pos; float *src = speex->decode_buf + speex->decode_pos; if (maxavail < maxoutput) maxoutput = maxavail; speex->decode_pos += maxoutput; cpypos += maxoutput; for (max = dst + maxoutput; dst < max; dst++, src++) { /* !!! FIXME: This screams for vectorization. */ register float f = *src; if (f > 32000.0f) /* eh, speexdec.c clamps like this, too. */ f = 32000.0f; else if (f < -32000.0f) f = -32000.0f; *dst = (Sint16) (0.5f + f); } /* for */ return(cpypos << 1); } /* copy_from_decoded */ /* !!! FIXME: this code sucks. */ static Uint32 SPEEX_read(Sound_Sample *sample) { Uint32 retval = 0; Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque; speex_t *speex = (speex_t *) internal->decoder_private; SDL_RWops *rw = internal->rw; Uint8 *buffer; int rc; while (1) { /* see if there's some already-decoded leftovers... */ if (speex->decode_total != speex->decode_pos) { retval = copy_from_decoded(speex, internal, retval); if (retval >= internal->buffer_size) return(retval); /* whee. */ } /* if */ /* okay, decoded buffer is spent. What else do we have? */ speex->decode_total = speex->decode_pos = 0; if (speex->frames_avail) /* have more frames to decode? */ { rc = speex_decode(speex->state, &speex->bits, speex->decode_buf); if (rc < 0) goto speex_read_failed; if (speex_bits_remaining(&speex->bits) < 0) goto speex_read_failed; speex->frames_avail--; speex->decode_total = speex->frame_size; continue; /* go fill the output buffer... */ } /* if */ /* need to get more speex frames from available ogg packets... */ if (speex->have_ogg_packet) { speex_bits_read_from(&speex->bits, (char *) speex->op.packet, speex->op.bytes); speex->frames_avail += speex->nframes; if (ogg_stream_packetout(&speex->os, &speex->op) <= 0) speex->have_ogg_packet = 0; continue; /* go decode these frames. */ } /* if */ /* need to get more ogg packets from bitstream... */ if (speex->op.e_o_s) /* okay, we're really spent. */ { sample->flags |= SOUND_SAMPLEFLAG_EOF; return(retval); } /* if */ while ((!speex->op.e_o_s) && (!speex->have_ogg_packet)) { buffer = (Uint8 *) ogg_sync_buffer(&speex->oy, SPEEX_OGG_BUFSIZE); if (buffer == NULL) goto speex_read_failed; rc = SDL_RWread(rw, buffer, 1, SPEEX_OGG_BUFSIZE); if (rc <= 0) goto speex_read_failed; if (ogg_sync_wrote(&speex->oy, rc) != 0) goto speex_read_failed; /* got complete ogg page? */ if (ogg_sync_pageout(&speex->oy, &speex->og) == 1) { if (ogg_stream_pagein(&speex->os, &speex->og) != 0) goto speex_read_failed; /* got complete ogg packet? */ if (ogg_stream_packetout(&speex->os, &speex->op) == 1) speex->have_ogg_packet = 1; } /* if */ } /* while */ } /* while */ assert(0); /* never hit this. Either return or goto speex_read_failed */ speex_read_failed: sample->flags |= SOUND_SAMPLEFLAG_ERROR; /* !!! FIXME: "i/o error" is better in some situations. */ BAIL_MACRO("SPEEX: Decoding error", retval); } /* SPEEX_read */ static int SPEEX_rewind(Sound_Sample *sample) { Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque; /* * !!! FIXME: This is really unacceptable; state should be reset and * !!! FIXME: the RWops should be pointed to the start of the data * !!! FIXME: to decode. The below kludge adds unneeded overhead and * !!! FIXME: risk of failure. */ BAIL_IF_MACRO(SDL_RWseek(internal->rw, 0, SEEK_SET) != 0, ERR_IO_ERROR, 0); SPEEX_close(sample); return(SPEEX_open(sample, "SPX")); } /* SPEEX_rewind */ static int SPEEX_seek(Sound_Sample *sample, Uint32 ms) { /* !!! FIXME */ return(0); } /* SPEEX_seek */ #endif /* SOUND_SUPPORTS_SPEEX */ /* end of speex.c ... */