Index: code/client/snd_codec.c =================================================================== --- code/client/snd_codec.c (revision 917) +++ code/client/snd_codec.c (working copy) @@ -105,6 +105,9 @@ #if USE_CODEC_VORBIS S_CodecRegister(&ogg_codec); #endif +#if USE_CODEC_MP3 + S_CodecRegister(&mp3_codec); +#endif } /* Index: code/client/snd_codec.h =================================================================== --- code/client/snd_codec.h (revision 917) +++ code/client/snd_codec.h (working copy) @@ -95,4 +95,13 @@ int S_OGG_CodecReadStream(snd_stream_t *stream, int bytes, void *buffer); #endif // USE_CODEC_VORBIS +// MP3 codec +#ifdef USE_CODEC_MP3 +extern snd_codec_t mp3_codec; +void *S_MP3_CodecLoad(const char *filename, snd_info_t *info); +snd_stream_t *S_MP3_CodecOpenStream(const char *filename); +void S_MP3_CodecCloseStream(snd_stream_t *stream); +int S_MP3_CodecReadStream(snd_stream_t *stream, int bytes, void *buffer); +#endif // USE_CODEC_MP3 + #endif // !_SND_CODEC_H_ Index: code/client/snd_codec_mp3.c =================================================================== --- code/client/snd_codec_mp3.c (revision 0) +++ code/client/snd_codec_mp3.c (revision 0) @@ -0,0 +1,716 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2005 Stuart Dalton (badcdev@gmail.com) +Copyright (C) 2005-2006 Joerg Dietrich +Copyright (C) 2006 Thilo Schulz + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +// MP3 support is enabled by this define +#if USE_CODEC_MP3 + +// includes for the Q3 sound system +#include "client.h" +#include "snd_codec.h" + +// includes for the MP3 codec +#include + +#define MP3_SAMPLE_WIDTH 2 +#define MP3_PCMSAMPLES_PERSLICE 32 + +// buffer size used when reading through the mp3 +#define MP3_DATA_BUFSIZ 128*1024 + +// undefine this if you don't want any dithering. +#define MP3_DITHERING + +// Q3 MP3 codec +snd_codec_t mp3_codec = +{ + ".mp3", + S_MP3_CodecLoad, + S_MP3_CodecOpenStream, + S_MP3_CodecReadStream, + S_MP3_CodecCloseStream, + NULL +}; + +// structure used for info purposes +struct snd_codec_mp3_info +{ + byte encbuf[MP3_DATA_BUFSIZ]; // left over bytes not consumed + // by the decoder. + struct mad_stream madstream; // uses encbuf as buffer. + struct mad_frame madframe; // control structures for libmad. + struct mad_synth madsynth; + + byte *pcmbuf; // buffer for not-used samples. + int buflen; // length of buffer data. + int pcmbufsize; // amount of allocated memory for + // pcmbuf. This should have at least + // the size of a decoded mp3 frame. + + byte *dest; // copy decoded data here. + int destlen; // amount of already copied data. + int destsize; // amount of bytes we must decode. +}; + +/*************** MP3 utility functions ***************/ + +/* +================= +S_MP3_ReadData +================= +*/ + +// feed libmad with data +int S_MP3_ReadData(snd_stream_t *stream, struct mad_stream *madstream, byte *encbuf, int encbufsize) +{ + int retval; + int leftover; + + if(!stream) + return -1; + + leftover = madstream->bufend - madstream->next_frame; + if(leftover > 0) + memmove(encbuf, madstream->this_frame, leftover); + + + // Fill the buffer right to the end + + retval = FS_Read(&encbuf[leftover], encbufsize - leftover, stream->file); + + if(retval <= 0) + { + // EOF reached, that's ok. + return 0; + } + + mad_stream_buffer(madstream, encbuf, retval + leftover); + + return retval; +} + + +/* +================= +S_MP3_Scanfile + +to determine the samplecount, we apparently must get *all* headers :( +I basically used the xmms-mad plugin source to see how this stuff works. + +returns a value < 0 on error. +================= +*/ + +int S_MP3_Scanfile(snd_stream_t *stream) +{ + struct mad_stream madstream; + struct mad_header madheader; + int retval; + int samplecount; + byte encbuf[MP3_DATA_BUFSIZ]; + + // error out on invalid input. + if(!stream) + return -1; + + mad_stream_init(&madstream); + mad_header_init(&madheader); + + while(1) + { + retval = S_MP3_ReadData(stream, &madstream, encbuf, sizeof(encbuf)); + if(retval < 0) + return -1; + else if(retval == 0) + break; + + // Start decoding the headers. + while(1) + { + if((retval = mad_header_decode(&madheader, &madstream)) < 0) + { + if(madstream.error == MAD_ERROR_BUFLEN) + { + // We need to read more data + break; + } + + if(!MAD_RECOVERABLE (madstream.error)) + { + // unrecoverable error... we must bail out. + return retval; + } + + mad_stream_skip(&madstream, madstream.skiplen); + continue; + } + + // we got a valid header. + + if(madheader.layer != MAD_LAYER_III) + { + // we don't support non-mp3s + return -1; + } + + if(!stream->info.samples) + { + // This here is the very first frame. Set initial values now, + // that we expect to stay constant throughout the whole mp3. + + stream->info.rate = madheader.samplerate; + stream->info.width = MP3_SAMPLE_WIDTH; + stream->info.channels = MAD_NCHANNELS(&madheader); + stream->info.samples = 0; + stream->info.size = 0; // same here. + stream->info.dataofs = 0; + } + else + { + // Check whether something changed that shouldn't. + + if(stream->info.rate != madheader.samplerate || + stream->info.channels != MAD_NCHANNELS(&madheader)) + return -1; + } + + // Update the counters + samplecount = MAD_NSBSAMPLES(&madheader) * MP3_PCMSAMPLES_PERSLICE; + stream->info.samples += samplecount; + stream->info.size += samplecount * stream->info.channels * stream->info.width; + } + } + + // Reset the file pointer so we can do the real decoding. + FS_Seek(stream->file, 0, FS_SEEK_SET); + + return 0; +} + +/************************ dithering functions ***************************/ + +#ifdef MP3_DITHERING + +// All dithering done here is taken from the GPL'ed xmms-mad plugin. + +/* Copyright (C) 1997 Makoto Matsumoto and Takuji Nishimura. */ +/* Any feedback is very welcome. For any question, comments, */ +/* see http://www.math.keio.ac.jp/matumoto/emt.html or email */ +/* matumoto@math.keio.ac.jp */ + +/* Period parameters */ +#define MP3_DITH_N 624 +#define MP3_DITH_M 397 +#define MATRIX_A 0x9908b0df /* constant vector a */ +#define UPPER_MASK 0x80000000 /* most significant w-r bits */ +#define LOWER_MASK 0x7fffffff /* least significant r bits */ + +/* Tempering parameters */ +#define TEMPERING_MASK_B 0x9d2c5680 +#define TEMPERING_MASK_C 0xefc60000 +#define TEMPERING_SHIFT_U(y) (y >> 11) +#define TEMPERING_SHIFT_S(y) (y << 7) +#define TEMPERING_SHIFT_T(y) (y << 15) +#define TEMPERING_SHIFT_L(y) (y >> 18) + +static unsigned long mt[MP3_DITH_N]; /* the array for the state vector */ +static int mti=MP3_DITH_N+1; /* mti==MP3_DITH_N+1 means mt[MP3_DITH_N] is not initialized */ + +/* initializing the array with a NONZERO seed */ +void sgenrand(unsigned long seed) +{ + /* setting initial seeds to mt[MP3_DITH_N] using */ + /* the generator Line 25 of Table 1 in */ + /* [KNUTH 1981, The Art of Computer Programming */ + /* Vol. 2 (2nd Ed.), pp102] */ + mt[0]= seed & 0xffffffff; + for (mti=1; mti= MP3_DITH_N) { /* generate MP3_DITH_N words at one time */ + int kk; + + if (mti == MP3_DITH_N+1) /* if sgenrand() has not been called, */ + sgenrand(4357); /* a default initial seed is used */ + + for (kk=0;kk> 1) ^ mag01[y & 0x1]; + } + for (;kk> 1) ^ mag01[y & 0x1]; + } + y = (mt[MP3_DITH_N-1]&UPPER_MASK)|(mt[0]&LOWER_MASK); + mt[MP3_DITH_N-1] = mt[MP3_DITH_M-1] ^ (y >> 1) ^ mag01[y & 0x1]; + + mti = 0; + } + + y = mt[mti++]; + y ^= TEMPERING_SHIFT_U(y); + y ^= TEMPERING_SHIFT_S(y) & TEMPERING_MASK_B; + y ^= TEMPERING_SHIFT_T(y) & TEMPERING_MASK_C; + y ^= TEMPERING_SHIFT_L(y); + + return y; +} + +long triangular_dither_noise(int nbits) { + // parameter nbits : the peak-to-peak amplitude desired (in bits) + // use with nbits set to 2 + nber of bits to be trimmed. + // (because triangular is made from two uniformly distributed processes, + // it starts at 2 bits peak-to-peak amplitude) + // see The Theory of Dithered Quantization by Robert Alexander Wannamaker + // for complete proof of why that's optimal + + long v = (genrand()/2 - genrand()/2); // in ]-2^31, 2^31[ + //int signe = (v>0) ? 1 : -1; + long P = 1 << (32 - nbits); // the power of 2 + v /= P; + // now v in ]-2^(nbits-1), 2^(nbits-1) [ + + return v; +} + +#endif // MP3_DITHERING + +/************************ decoder functions ***************************/ + +/* +================= +S_MP3_Scale + +Converts the signal to 16 bit LE-PCM data and does dithering. + +- borrowed from xmms-mad plugin source. +================= +*/ + +/* + * xmms-mad - mp3 plugin for xmms + * Copyright (C) 2001-2002 Sam Clegg + */ + +signed int S_MP3_Scale(mad_fixed_t sample) +{ + int n_bits_to_loose = MAD_F_FRACBITS + 1 - 16; +#ifdef MP3_DITHERING + int dither; +#endif + + // round + sample += (1L << (n_bits_to_loose - 1)); + +#ifdef MP3_DITHERING + dither = triangular_dither_noise(n_bits_to_loose + 1); + sample += dither; +#endif + + /* clip */ + if (sample >= MAD_F_ONE) + sample = MAD_F_ONE - 1; + else if (sample < -MAD_F_ONE) + sample = -MAD_F_ONE; + + /* quantize */ + return sample >> n_bits_to_loose; +} + + + +/* +================= +S_MP3_PCMCopy + +Copy and convert pcm data until bytecount bytes have been written. +return the position in pcm->samples. +indicate the amount of actually written bytes in wrotecnt. +================= +*/ + +int S_MP3_PCMCopy(byte *buf, struct mad_pcm *pcm, int bufofs, + int sampleofs, int bytecount, int *wrotecnt) +{ + int written = 0; + signed int sample; + int framesize = pcm->channels * MP3_SAMPLE_WIDTH; + + // add new pcm data. + while(written < bytecount && sampleofs < pcm->length) + { + sample = S_MP3_Scale(pcm->samples[0][sampleofs]); + +#ifdef Q3_BIG_ENDIAN + // output to 16 bit big endian PCM + buf[bufofs++] = (sample >> 8) & 0xff; + buf[bufofs++] = sample & 0xff; +#else + // output to 16 bit little endian PCM + buf[bufofs++] = sample & 0xff; + buf[bufofs++] = (sample >> 8) & 0xff; +#endif + + if(pcm->channels == 2) + { + sample = S_MP3_Scale(pcm->samples[1][sampleofs]); + +#ifdef Q3_BIG_ENDIAN + buf[bufofs++] = (sample >> 8) & 0xff; + buf[bufofs++] = sample & 0xff; +#else + buf[bufofs++] = sample & 0xff; + buf[bufofs++] = (sample >> 8) & 0xff; +#endif + } + + sampleofs++; + written += framesize; + } + + if(wrotecnt) + *wrotecnt = written; + + return sampleofs; +} + + +/* +================= +S_MP3_Decode +================= +*/ + +// gets executed for every decoded frame. +int S_MP3_Decode(snd_stream_t *stream) +{ + struct snd_codec_mp3_info *mp3info; + struct mad_stream *madstream; + struct mad_frame *madframe; + struct mad_synth *madsynth; + struct mad_pcm *pcm; + int cursize; + int samplecount; + int needcount; + int wrote; + int retval; + + if(!stream) + return -1; + + mp3info = stream->ptr; + madstream = &mp3info->madstream; + madframe = &mp3info->madframe; + + if(mad_frame_decode(madframe, madstream)) + { + if(madstream->error == MAD_ERROR_BUFLEN) + { + // we need more data. Read another chunk. + retval = S_MP3_ReadData(stream, madstream, mp3info->encbuf, sizeof(mp3info->encbuf)); + + // call myself again now that buffer is full. + if(retval > 0) + retval = S_MP3_Decode(stream); + } + else if(MAD_RECOVERABLE(madstream->error)) + { + mad_stream_skip(madstream, madstream->skiplen); + return S_MP3_Decode(stream); + } + else + retval = -1; + + return retval; + } + + // check whether this really is an mp3 + if(madframe->header.layer != MAD_LAYER_III) + return -1; + + // generate pcm data + madsynth = &mp3info->madsynth; + mad_synth_frame(madsynth, madframe); + + pcm = &madsynth->pcm; + + // perform a few checks to see whether something changed that shouldn't. + + if(stream->info.rate != pcm->samplerate || + stream->info.channels != pcm->channels) + { + return -1; + } + // see whether we have got enough data now. + cursize = pcm->length * pcm->channels * stream->info.width; + needcount = mp3info->destsize - mp3info->destlen; + + // Copy exactly as many samples as required. + samplecount = S_MP3_PCMCopy(mp3info->dest, pcm, + mp3info->destlen, 0, needcount, &wrote); + mp3info->destlen += wrote; + + if(samplecount < pcm->length) + { + // Not all samples got copied. Copy the rest into the pcm buffer. + samplecount = S_MP3_PCMCopy(mp3info->pcmbuf, pcm, + mp3info->buflen, + samplecount, + mp3info->pcmbufsize - mp3info->buflen, + &wrote); + mp3info->buflen += wrote; + + + if(samplecount < pcm->length) + { + // The pcm buffer was not large enough. Make it bigger. + byte *newbuf = Z_Malloc(cursize); + + if(mp3info->pcmbuf) + { + memcpy(newbuf, mp3info->pcmbuf, mp3info->buflen); + Z_Free(mp3info->pcmbuf); + } + + mp3info->pcmbuf = newbuf; + mp3info->pcmbufsize = cursize; + + samplecount = S_MP3_PCMCopy(mp3info->pcmbuf, pcm, + mp3info->buflen, + samplecount, + mp3info->pcmbufsize - mp3info->buflen, + &wrote); + mp3info->buflen += wrote; + } + + // we're definitely done. + retval = 0; + } + else if(mp3info->destlen >= mp3info->destsize) + retval = 0; + else + retval = 1; + + return retval; +} + +/*************** Callback functions for quake3 ***************/ + +/* +================= +S_MP3_CodecOpenStream +================= +*/ + +snd_stream_t *S_MP3_CodecOpenStream(const char *filename) +{ + snd_stream_t *stream; + struct snd_codec_mp3_info *mp3info; + + // Open the stream + stream = S_CodecUtilOpen(filename, &mp3_codec); + if(!stream || stream->length <= 0) + return NULL; + + // We have to scan through the MP3 to determine the important mp3 info. + if(S_MP3_Scanfile(stream) < 0) + { + // scanning didn't work out... + S_CodecUtilClose(stream); + return NULL; + } + + // Initialize the mp3 info structure we need for streaming + mp3info = Z_Malloc(sizeof(*mp3info)); + if(!mp3info) + { + S_CodecUtilClose(stream); + return NULL; + } + + stream->ptr = mp3info; + + // initialize the libmad control structures. + mad_stream_init(&mp3info->madstream); + mad_frame_init(&mp3info->madframe); + mad_synth_init(&mp3info->madsynth); + + if(S_MP3_ReadData(stream, &mp3info->madstream, mp3info->encbuf, sizeof(mp3info->encbuf)) <= 0) + { + // we didnt read anything, that's bad. + S_MP3_CodecCloseStream(stream); + return NULL; + } + + return stream; +} + +/* +================= +S_MP3_CodecCloseStream +================= +*/ + +// free all memory we allocated. +void S_MP3_CodecCloseStream(snd_stream_t *stream) +{ + struct snd_codec_mp3_info *mp3info; + + if(!stream) + return; + + // free all data in our mp3info tree + + if(stream->ptr) + { + mp3info = stream->ptr; + + if(mp3info->pcmbuf) + Z_Free(mp3info->pcmbuf); + + mad_synth_finish(&mp3info->madsynth); + mad_frame_finish(&mp3info->madframe); + mad_stream_finish(&mp3info->madstream); + + Z_Free(stream->ptr); + } + + S_CodecUtilClose(stream); +} + +/* +================= +S_MP3_CodecReadStream +================= +*/ +int S_MP3_CodecReadStream(snd_stream_t *stream, int bytes, void *buffer) +{ + struct snd_codec_mp3_info *mp3info; + int retval; + + if(!stream) + return -1; + + mp3info = stream->ptr; + + // Make sure we get complete frames all the way through. + bytes -= bytes % (stream->info.channels * stream->info.width); + + if(mp3info->buflen) + { + if(bytes < mp3info->buflen) + { + // we still have enough bytes in our decoded pcm buffer + memcpy(buffer, mp3info->pcmbuf, bytes); + + // remove the portion from our buffer. + mp3info->buflen -= bytes; + memmove(mp3info->pcmbuf, &mp3info->pcmbuf[bytes], mp3info->buflen); + return bytes; + } + else + { + // copy over the samples we already have. + memcpy(buffer, mp3info->pcmbuf, mp3info->buflen); + mp3info->destlen = mp3info->buflen; + mp3info->buflen = 0; + } + } + else + mp3info->destlen = 0; + + mp3info->dest = buffer; + mp3info->destsize = bytes; + + do + { + retval = S_MP3_Decode(stream); + } while(retval > 0); + + // if there was an error return nothing. + if(retval < 0) + return 0; + + return mp3info->destlen; +} + +/* +===================================================================== +S_MP3_CodecLoad + +We handle S_MP3_CodecLoad as a special case of the streaming functions +where we read the whole stream at once. +====================================================================== +*/ +void *S_MP3_CodecLoad(const char *filename, snd_info_t *info) +{ + snd_stream_t *stream; + byte *pcmbuffer; + + // check if input is valid + if(!filename) + return NULL; + + stream = S_MP3_CodecOpenStream(filename); + + if(!stream) + return NULL; + + // copy over the info + info->rate = stream->info.rate; + info->width = stream->info.width; + info->channels = stream->info.channels; + info->samples = stream->info.samples; + info->dataofs = stream->info.dataofs; + + // allocate enough buffer for all pcm data + pcmbuffer = Z_Malloc(stream->info.size); + if(!pcmbuffer) + { + S_MP3_CodecCloseStream(stream); + return NULL; + } + + info->size = S_MP3_CodecReadStream(stream, stream->info.size, pcmbuffer); + + if(info->size <= 0) + { + // we didn't read anything at all. darn. + Z_Free(pcmbuffer); + pcmbuffer = NULL; + } + + S_MP3_CodecCloseStream(stream); + + return pcmbuffer; +} + +#endif // USE_CODEC_MP3