8184225473
git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@15 fc73d0e0-1445-4013-8a0c-d673dee63da5
497 lines
12 KiB
C
497 lines
12 KiB
C
//source file by david walton, derived from minimad.c
|
|
//modified slightly to use new library functions instead for single threaded asyncronous mp3 sound.
|
|
//in Quake!
|
|
|
|
//Be aware that MP3s are patented and thus not GPLable. This file is an interface to madlib which contains the decoder.
|
|
//This file is excluded from the main source tree because of this, and madlib is not distributed - in binary or source form.
|
|
//The GPL disallows distribution of patented software. You may reactive, but do not distribute binaries.
|
|
|
|
//Really madlib should come with the exception of the mp3 patent.
|
|
|
|
#include "quakedef.h"
|
|
|
|
#ifdef AVAIL_MP3
|
|
|
|
#include "winquake.h"
|
|
#undef channels
|
|
|
|
|
|
#ifdef _WIN32
|
|
#define inline _inline
|
|
#endif
|
|
|
|
//the problem with this is that we store the entire decoded file in memory.
|
|
//this takes a lot of mem.
|
|
|
|
//uses madlib
|
|
|
|
|
|
|
|
/*
|
|
* mad - MPEG audio decoder
|
|
* Copyright (C) 2000-2001 Robert Leslie
|
|
*
|
|
* This program 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.
|
|
*
|
|
* This program 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 this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*
|
|
*/
|
|
|
|
# include "../../mp3/libmad/mad.h"
|
|
|
|
|
|
/*
|
|
* This is perhaps the simplest example use of the MAD high-level API.
|
|
* Standard input is mapped into memory via mmap(), then the high-level API
|
|
* is invoked with three callbacks: input, output, and error. The output
|
|
* callback converts MAD's high-resolution PCM samples to 16 bits, then
|
|
* writes them to standard output in little-endian, stereo-interleaved
|
|
* format.
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
* This is a private message structure. A generic pointer to this structure
|
|
* is passed to each of the callback functions. Put here any data you need
|
|
* to access from within the callbacks.
|
|
*/
|
|
|
|
typedef struct {
|
|
unsigned char *start;
|
|
unsigned long length;
|
|
qboolean failed;
|
|
|
|
sfxcache_t mp3sc;
|
|
char *mp3aswavdata;
|
|
int mp3aswavpos;
|
|
int mp3aswavbuflen;
|
|
|
|
struct mad_decoder decoder;
|
|
|
|
sfx_t *s;
|
|
} decoderbuffer_t;
|
|
|
|
int mymad_run(struct mad_decoder *decoder);
|
|
int mymad_reset(struct mad_decoder *decoder);
|
|
void mymad_finish(struct mad_decoder *decoder);
|
|
int startdecode(unsigned char *start, unsigned long length, decoderbuffer_t *buffer);
|
|
|
|
#define BUFFERSIZEINC (2*1024*1024)
|
|
|
|
int DecodeSomeMP3(sfx_t *s, int minlength);
|
|
byte *COM_LoadFile (char *path, int usehunk);
|
|
int MP3_decode(unsigned char *start, unsigned long length, decoderbuffer_t *buffer);
|
|
void CancelDecoder(sfx_t *s);
|
|
sfxcache_t *S_LoadMP3Sound (sfx_t *s)
|
|
{
|
|
char namebuffer[MAX_OSPATH];
|
|
char *name;
|
|
decoderbuffer_t *buffer;
|
|
|
|
char *data;
|
|
qboolean telluser;
|
|
int len;
|
|
|
|
name = s->name;
|
|
|
|
if (name[0] == '#')
|
|
strcpy(namebuffer, &name[1]);
|
|
else
|
|
sprintf (namebuffer, "sound/%s", name);
|
|
|
|
len = strlen(namebuffer);
|
|
telluser = strcmp(namebuffer+len-4, ".wav");
|
|
if (!telluser)
|
|
strcpy(namebuffer+len-4, ".mp3");
|
|
|
|
//try opening from a quake path
|
|
data = COM_LoadFile(namebuffer, 5);
|
|
if (!data)
|
|
{//if that didn't work, try opening direct from exe - this is media after all.
|
|
FILE *f;
|
|
|
|
if (!telluser)
|
|
return NULL; //never go out of the quake path for a wav replacement.
|
|
#ifndef _WIN32
|
|
char unixname[128];
|
|
if (name[1] == ':' && name[2] == '\\') //convert from windows to a suitable alternative.
|
|
{
|
|
sprintf(unixname, "/mnt/%c/%s", name[0]-'A'+'a', name+3);
|
|
name = unixname;
|
|
while (*name)
|
|
{
|
|
if (*name == '\\')
|
|
*name = '/';
|
|
name++;
|
|
}
|
|
name = unixname;
|
|
}
|
|
#endif
|
|
if ((f = fopen(name, "rb")))
|
|
{
|
|
com_filesize = COM_filelength(f);
|
|
data = BZ_Malloc(com_filesize);
|
|
fread(data, 1, com_filesize, f);
|
|
fclose(f);
|
|
}
|
|
else
|
|
{
|
|
Con_SafePrintf ("Couldn't load %s\n", namebuffer);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
if (!s->decoder)
|
|
s->decoder = Z_Malloc(sizeof(decoderbuffer_t) + sizeof(sfxdecode_t));
|
|
buffer = (decoderbuffer_t*)(s->decoder+1);
|
|
|
|
buffer->mp3aswavpos=0;
|
|
buffer->mp3sc.length=0;
|
|
buffer->s = s;
|
|
s->decoder->buf = buffer;
|
|
s->decoder->decodemore = DecodeSomeMP3;
|
|
s->decoder->abort = CancelDecoder;
|
|
|
|
if (!startdecode(data, com_filesize, buffer))
|
|
{
|
|
if (buffer->mp3aswavdata)
|
|
{
|
|
BZ_Free(buffer->mp3aswavdata);
|
|
|
|
buffer->mp3aswavdata=NULL;
|
|
}
|
|
Con_SafePrintf ("Couldn't load %s - corrupt.\n", namebuffer);
|
|
return NULL;
|
|
}
|
|
|
|
s->decoder->decodemore(s, 100);
|
|
|
|
s->cache.fake=true;
|
|
return buffer->s->cache.data;
|
|
}
|
|
|
|
|
|
/*
|
|
* This is the input callback. The purpose of this callback is to (re)fill
|
|
* the stream buffer which is to be decoded. In this example, an entire file
|
|
* has been mapped into memory, so we just call mad_stream_buffer() with the
|
|
* address and length of the mapping. When this callback is called a second
|
|
* time, we are finished decoding.
|
|
*/
|
|
|
|
static
|
|
enum mad_flow input(void *data,
|
|
struct mad_stream *stream)
|
|
{
|
|
decoderbuffer_t *buffer = data;
|
|
|
|
if (!buffer->length)
|
|
return MAD_FLOW_STOP;
|
|
|
|
mad_stream_buffer(stream, buffer->start, buffer->length);
|
|
|
|
buffer->length = 0;
|
|
|
|
return MAD_FLOW_CONTINUE;
|
|
}
|
|
|
|
/*
|
|
* The following utility routine performs simple rounding, clipping, and
|
|
* scaling of MAD's high-resolution samples down to 16 bits. It does not
|
|
* perform any dithering or noise shaping, which would be recommended to
|
|
* obtain any exceptional audio quality. It is therefore not recommended to
|
|
* use this routine if high-quality output is desired.
|
|
*/
|
|
|
|
static inline
|
|
signed int scale(mad_fixed_t sample)
|
|
{
|
|
/* round */
|
|
sample += (1L << (MAD_F_FRACBITS - 16));
|
|
|
|
/* clip */
|
|
if (sample >= MAD_F_ONE)
|
|
sample = MAD_F_ONE - 1;
|
|
else if (sample < -MAD_F_ONE)
|
|
sample = -MAD_F_ONE;
|
|
|
|
/* quantize */
|
|
return sample >> (MAD_F_FRACBITS + 1 - 16);
|
|
}
|
|
|
|
/*
|
|
* This is the output callback function. It is called after each frame of
|
|
* MPEG audio data has been completely decoded. The purpose of this callback
|
|
* is to output (or play) the decoded PCM audio.
|
|
*/
|
|
|
|
static
|
|
enum mad_flow output(void *data,
|
|
struct mad_header const *header,
|
|
struct mad_pcm *pcm)
|
|
{
|
|
decoderbuffer_t *buffer = data;
|
|
sfxcache_t *cache;
|
|
|
|
unsigned int nchannels, nsamples;
|
|
mad_fixed_t const *left_ch, *right_ch;
|
|
|
|
char *outpos;
|
|
signed int sample;
|
|
float speedfactor;
|
|
|
|
speedfactor = (float)snd_speed/buffer->mp3sc.speed;
|
|
|
|
/* pcm->samplerate contains the sampling frequency */
|
|
|
|
nchannels = pcm->channels;
|
|
nsamples = pcm->length*speedfactor;
|
|
left_ch = pcm->samples[0];
|
|
right_ch = pcm->samples[1];
|
|
|
|
if (buffer->mp3aswavpos + nsamples*nchannels*sizeof(short) > buffer->mp3aswavbuflen || !buffer->mp3aswavdata)
|
|
{
|
|
int newsize = buffer->mp3aswavpos + nsamples*nchannels*sizeof(short) + BUFFERSIZEINC+sizeof(sfxcache_t);
|
|
char *newbuf = BZ_Malloc(newsize);
|
|
if (!newbuf)
|
|
return MAD_FLOW_STOP; //damn and blast!
|
|
|
|
if (buffer->mp3aswavdata) //if we had an old buffer, fill the new one and free the old
|
|
{
|
|
// memset(newbuf, 0, newsize);
|
|
memcpy(newbuf, buffer->mp3aswavdata, buffer->mp3aswavpos+sizeof(sfxcache_t));
|
|
// newbuf[newsize] = '\0';
|
|
BZ_Free(buffer->mp3aswavdata);
|
|
}
|
|
buffer->mp3aswavdata = newbuf;
|
|
buffer->mp3aswavbuflen = newsize;
|
|
|
|
buffer->s->cache.data = buffer->mp3aswavdata;
|
|
}
|
|
|
|
cache = (sfxcache_t *)buffer->mp3aswavdata;
|
|
outpos = buffer->mp3aswavdata+sizeof(sfxcache_t) + buffer->mp3aswavpos;
|
|
|
|
if (nchannels == 1)
|
|
{
|
|
if (speedfactor==1) //fast
|
|
{
|
|
while (nsamples--)
|
|
{
|
|
sample = scale(*left_ch++);
|
|
*outpos++ = ((sample >> 0) & 0xff);
|
|
*outpos++ = ((sample >> 8) & 0xff);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int src=0;
|
|
int pos=0;
|
|
|
|
while (pos < nsamples)
|
|
{
|
|
src = pos/speedfactor;
|
|
sample = scale(left_ch[src]);
|
|
*outpos++ = (((sample >> 0) & 0xff));
|
|
*outpos++ = (((sample >> 8) & 0xff));
|
|
|
|
pos++;
|
|
}
|
|
}
|
|
cache->stereo = 0;
|
|
}
|
|
else
|
|
{
|
|
if (speedfactor==1) //fast
|
|
{
|
|
while (nsamples--)
|
|
{ //merge channels?
|
|
sample = scale(*left_ch++);
|
|
*outpos++ = (((sample >> 0) & 0xff));
|
|
*outpos++ = (((sample >> 8) & 0xff));
|
|
|
|
sample = scale(*right_ch++)/2;
|
|
*outpos++ = (((sample >> 0) & 0xff));
|
|
*outpos++ = (((sample >> 8) & 0xff));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int src=0;
|
|
int pos=0;
|
|
|
|
while (pos < nsamples)
|
|
{
|
|
src = pos/speedfactor;
|
|
sample = scale(left_ch[src]);
|
|
*outpos++ = (((sample >> 0) & 0xff));
|
|
*outpos++ = (((sample >> 8) & 0xff));
|
|
|
|
sample = scale(right_ch[src]);
|
|
*outpos++ = (((sample >> 0) & 0xff));
|
|
*outpos++ = (((sample >> 8) & 0xff));
|
|
|
|
pos++;
|
|
}
|
|
}
|
|
cache->stereo = 1;
|
|
}
|
|
|
|
buffer->mp3aswavpos += pcm->length*speedfactor*sizeof(short)*nchannels;
|
|
buffer->mp3sc.length+= pcm->length*speedfactor;
|
|
|
|
cache->speed = snd_speed;
|
|
cache->length = buffer->s->decoder->decodedlen = buffer->mp3sc.length;
|
|
cache->loopstart = -1;//buffer->mp3sc.loopstart;
|
|
cache->width = buffer->mp3sc.width;
|
|
|
|
return MAD_FLOW_CONTINUE;
|
|
}
|
|
|
|
/*
|
|
* This is the error callback function. It is called whenever a decoding
|
|
* error occurs. The error is indicated by stream->error; the list of
|
|
* possible MAD_ERROR_* errors can be found in the mad.h (or
|
|
* libmad/stream.h) header file.
|
|
*/
|
|
|
|
static
|
|
enum mad_flow error(void *data,
|
|
struct mad_stream *stream,
|
|
struct mad_frame *frame)
|
|
{
|
|
decoderbuffer_t *buffer = data;
|
|
|
|
if (stream->error == 257)
|
|
return MAD_FLOW_IGNORE;
|
|
|
|
if (developer.value)
|
|
Con_SafePrintf("MP3Error: decoding error 0x%04x (%s) at byte offset %u\n",
|
|
stream->error, mad_stream_errorstr(stream),
|
|
stream->this_frame - buffer->start);
|
|
|
|
// buffer->failed = true;
|
|
|
|
return MAD_FLOW_IGNORE;
|
|
}
|
|
|
|
static enum mad_flow header(void *data,
|
|
struct mad_header const *header)
|
|
{
|
|
decoderbuffer_t *buffer = data;
|
|
|
|
buffer->mp3sc.speed = header->samplerate;
|
|
buffer->mp3sc.width = 2;
|
|
return MAD_FLOW_CONTINUE;
|
|
}
|
|
|
|
void CancelDecoder(sfx_t *s)
|
|
{
|
|
decoderbuffer_t *dec = s->decoder->buf;
|
|
|
|
mymad_finish(&dec->decoder);
|
|
mad_decoder_finish(&dec->decoder);
|
|
|
|
s->cache.fake = false; //give it a true cache now, and hope that we don't need to free it while it's still playing.
|
|
s->cache.data=NULL;
|
|
|
|
BZ_Free(dec->mp3aswavdata);
|
|
if (dec->start)
|
|
BZ_Free(dec->start);
|
|
|
|
Z_Free(s->decoder);
|
|
s->decoder = NULL;
|
|
}
|
|
|
|
/*
|
|
* This is the function called by main() above to perform all the
|
|
* decoding. It instantiates a decoder object and configures it with the
|
|
* input, output, and error callback functions above. A single call to
|
|
* mad_decoder_run() continues until a callback function returns
|
|
* MAD_FLOW_STOP (to stop decoding) or MAD_FLOW_BREAK (to stop decoding and
|
|
* signal an error).
|
|
*/
|
|
|
|
int DecodeSomeMP3(sfx_t *s, int minlength)
|
|
{
|
|
decoderbuffer_t *dec = s->decoder->buf;
|
|
|
|
// int oldlen;
|
|
|
|
if (!dec->start)
|
|
return 1;
|
|
|
|
while(dec->mp3sc.length < minlength)
|
|
{
|
|
if (!mymad_run(&dec->decoder) || dec->failed)
|
|
{
|
|
if (dec->mp3aswavbuflen>441000)//10 secs at highest quality
|
|
{ //big fat heffers of mp3s are kept like this and are flushed when they finish playing fully.
|
|
BZ_Free(dec->start);
|
|
dec->start = NULL;
|
|
}
|
|
else
|
|
{ //small mp3 files are now treated like wavs
|
|
void *newmem;
|
|
mymad_finish(&dec->decoder);
|
|
mad_decoder_finish(&dec->decoder);
|
|
|
|
s->cache.fake = false; //give it a true cache now, and hope that we don't need to free it while it's still playing.
|
|
s->cache.data=NULL;
|
|
newmem = Cache_Alloc(&s->cache, dec->mp3aswavbuflen+sizeof(sfxcache_t), s->name);
|
|
if (dec->mp3aswavdata)
|
|
{
|
|
memcpy(newmem, dec->mp3aswavdata, dec->mp3aswavbuflen+sizeof(sfxcache_t));
|
|
BZ_Free(dec->mp3aswavdata);
|
|
}
|
|
BZ_Free(dec->start);
|
|
|
|
Z_Free(s->decoder);
|
|
s->decoder = NULL;
|
|
}
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int mymad_decoder_run(struct mad_decoder *decoder, enum mad_decoder_mode mode);
|
|
static
|
|
int startdecode(unsigned char *start, unsigned long length, decoderbuffer_t *buffer)
|
|
{
|
|
/* initialize our private message structure */
|
|
|
|
buffer->start = start;
|
|
buffer->length = length;
|
|
buffer->failed = false;
|
|
|
|
/* configure input, output, and error functions */
|
|
|
|
mad_decoder_init(&buffer->decoder, buffer,
|
|
input, header, 0 /* filter */, output,
|
|
error, 0 /* message */);
|
|
|
|
/* start decoding */
|
|
|
|
mymad_reset(&buffer->decoder);
|
|
|
|
if (buffer->failed)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
|
|
|