Backported external music files support using decoder libraries and the

new raw samples interface from Hammer of Thyrion (uhexen2) :
- bgmusic.c, bgmusic.h: New BGM interface for background music handling.
  Handles streaming music as raw sound samples.
- bgmnull.c: BGM source for cases where the engine is configured for no
  sound.
- cl_main.c: Include bgmusic.h. Call BGM_Stop() and CDAudio_Stop() in
  CL_Disconnect().
- cd_sdl.c: Moved bgmvolume boundary checking to bgmusic.c upon value
  changes.
- gl_vidnt.c, gl_vidsdl.c, cl_parse.c: Include bgmusic.h. Add BGM_Pause()
  and BGM_Resume() calls along with CDAudio_ counterparts.
- cl_parse.c: Replace CDAudio_Play() call by the new BGM_PlayCDtrack()
  which first tries CDAudio_Play() and then streaming music if it fails.
- host.c: Include bgmusic.h. Call BGM_Update() just before S_Update()
  in Host_Frame(). In Host_Init(), call BGM_Init() after other audio init
  calls. In Host_Shutdown(), call BGM_Shutdown() before all other audio
  shutdown calls.
- snd_dma.c: Include snd_codec.h and bgmusic.h. Call S_CodecInit() from
  S_Init(). Call S_CodecShutdown() from S_Shutdown().
- snd_codec.c, snd_codec.h: New public codec interface for streaming
  music as raw samples. Adapted from quake2 and ioquake3 with changes.
  Individual codecs are responsible for handling any necessary byte swap
  operations.
- snd_codeci.h: New header for snd_codec internals.
- snd_wave.c, snd_wave.h: Codec for WAV format streaming music. Adapted
  from ioquake3 with changes.
- snd_vorbis.c, snd_vorbis.h: Codec for Ogg/Vorbis format streaming music.
- snd_mp3.c, snd_mp3.h: Codec for MP3 format streaming music using libmad.
  Adapted from the SoX project with changes.
- Makefile: Adjusted for the new sources. Added switches USE_CODEC_WAVE,
  USE_CODEC_MP3, USE_CODEC_VORBIS for enabling and disabling individual
  codecs.
- Windows makefiles and project files as well as other CodeBlocks project
  files will be updated shortly.


git-svn-id: svn://svn.code.sf.net/p/quakespasm/code/trunk/quakespasm@374 af15c1b1-3010-417e-b628-4374ebc0bcbd
This commit is contained in:
Ozkan Sezer 2011-01-05 19:50:43 +00:00
parent ceb1dd2186
commit cbb7eb0428
20 changed files with 2001 additions and 10 deletions

View file

@ -19,6 +19,11 @@ check_gcc = $(shell if echo | $(CC) $(1) -S -o /dev/null -xc - > /dev/null 2>&1;
# ============================================================================
# Enable/disable codecs for streaming music support:
USE_CODEC_WAVE=yes
USE_CODEC_MP3=yes
USE_CODEC_VORBIS=yes
DEBUG ?= 0
SDLNET ?= 0
@ -109,9 +114,22 @@ else
NET_LIBS :=
endif
CODECLIBS :=
ifeq ($(USE_CODEC_WAVE),yes)
CFLAGS+= -DUSE_CODEC_WAVE
endif
ifeq ($(USE_CODEC_VORBIS),yes)
CFLAGS+= -DUSE_CODEC_VORBIS
CODECLIBS+= vorbis vorbisfile ogg
endif
ifeq ($(USE_CODEC_MP3),yes)
CFLAGS+= -DUSE_CODEC_MP3
CODECLIBS+= mad
endif
COMMON_LIBS:= m GL
LIBS := $(patsubst %,-l%,$(COMMON_LIBS) $(NET_LIBS))
LIBS := $(patsubst %,-l%,$(COMMON_LIBS) $(NET_LIBS) $(CODECLIBS))
# ---------------------------
# targets
@ -132,7 +150,12 @@ DEFAULT_TARGET := quakespasm
# objects
# ----------------------------------------------------------------------------
COMOBJ_SND := snd_dma.o snd_mix.o snd_mem.o
MUSIC_OBJS:= bgmusic.o \
snd_codec.o \
snd_wave.o \
snd_vorbis.o \
snd_mp3.o
COMOBJ_SND := snd_dma.o snd_mix.o snd_mem.o $(MUSIC_OBJS)
SYSOBJ_SND := snd_sdl.o
SYSOBJ_CDA := cd_sdl.o
SYSOBJ_INPUT := in_sdl.o

71
Quake/bgmnull.c Normal file
View file

@ -0,0 +1,71 @@
/*
* Background music handling for Hexen II: Hammer of Thyrion (uHexen2)
* Handle cases when we are configured for no sound and no midi driver,
* nada...
*
* Copyright (C) 1999-2005 Id Software, Inc.
* Copyright (C) 2010 O.Sezer <sezero@users.sourceforge.net>
*
* 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "quakedef.h"
#include "bgmusic.h"
qboolean bgmloop = true;
static float old_volume = -1.0f;
qboolean BGM_Init (void)
{
return false;
}
void BGM_Play (const char *filename)
{
}
void BGM_PlayCDtrack (byte track, qboolean looping)
{
bgmloop = looping;
CDAudio_Play(track, looping);
}
void BGM_Stop (void)
{
}
void BGM_Pause (void)
{
}
void BGM_Resume (void)
{
}
void BGM_Update (void)
{
if (old_volume != bgmvolume.value)
{
if (bgmvolume.value < 0)
Cvar_Set ("bgmvolume", "0");
else if (bgmvolume.value > 1)
Cvar_Set ("bgmvolume", "1");
old_volume = bgmvolume.value;
}
}

456
Quake/bgmusic.c Normal file
View file

@ -0,0 +1,456 @@
/*
* Background music handling for Hexen II: Hammer of Thyrion (uHexen2)
* Handles streaming music as raw sound samples and runs the midi driver
*
* Copyright (C) 1999-2005 Id Software, Inc.
* Copyright (C) 2010 O.Sezer <sezero@users.sourceforge.net>
*
* 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "quakedef.h"
#include "snd_codec.h"
#include "bgmusic.h"
/*
* MUSIC FILE DIRECTORIES:
*
* Midi music file have always lived under "midi" directory, we are
* not changing that. Any other music files, including cd-rips, are
* expected under the "music" directory.
*/
#define MUSIC_DIRNAME "music"
qboolean bgmloop;
static float old_volume = -1.0f;
typedef enum _bgm_player
{
BGM_NONE = -1,
BGM_MIDIDRV,
BGM_STREAMER,
} bgm_player_t;
typedef struct music_handler_s
{
unsigned int type; /* power of two (snd_codec.h) */
bgm_player_t player; /* Enumerated bgm player type */
int is_available; /* -1 means not present */
const char *ext; /* Expected file extension */
const char *dir; /* Where to look for music file */
struct music_handler_s *next;
} music_handler_t;
static music_handler_t wanted_handlers[] =
{
{ CODECTYPE_OGG, BGM_STREAMER, -1, ".ogg", MUSIC_DIRNAME, NULL },
{ CODECTYPE_MP3, BGM_STREAMER, -1, ".mp3", MUSIC_DIRNAME, NULL },
{ CODECTYPE_WAV, BGM_STREAMER, -1, ".wav", MUSIC_DIRNAME, NULL },
{ CODECTYPE_FLAC, BGM_STREAMER, -1, ".flac", MUSIC_DIRNAME, NULL },
{ CODECTYPE_NONE, BGM_NONE, -1, NULL, NULL, NULL }
};
static music_handler_t *music_handlers = NULL;
#define ANY_CODECTYPE 0xFFFFFFFF
#define CDRIP_TYPES (CODECTYPE_OGG | CODECTYPE_MP3 | CODECTYPE_WAV)
#define CDRIPTYPE(x) (((x) & CDRIP_TYPES) != 0)
static snd_stream_t *bgmstream = NULL;
static void BGM_Play_f (void)
{
if (Cmd_Argc() == 2)
{
BGM_Play (Cmd_Argv(1));
}
else
{
Con_Printf ("music <musicfile>\n");
return;
}
}
static void BGM_Pause_f (void)
{
BGM_Pause ();
}
static void BGM_Resume_f (void)
{
BGM_Resume ();
}
static void BGM_Loop_f (void)
{
if (Cmd_Argc() == 2)
{
if (Q_strcasecmp(Cmd_Argv(1), "0") == 0 ||
Q_strcasecmp(Cmd_Argv(1),"off") == 0)
bgmloop = false;
else if (Q_strcasecmp(Cmd_Argv(1), "1") == 0 ||
Q_strcasecmp(Cmd_Argv(1),"on") == 0)
bgmloop = true;
else if (Q_strcasecmp(Cmd_Argv(1),"toggle") == 0)
bgmloop = !bgmloop;
}
if (bgmloop)
Con_Printf("Music will be looped\n");
else
Con_Printf("Music will not be looped\n");
}
static void BGM_Stop_f (void)
{
BGM_Stop();
}
qboolean BGM_Init (void)
{
music_handler_t *handlers = NULL;
int i;
Cmd_AddCommand("music", BGM_Play_f);
Cmd_AddCommand("music_pause", BGM_Pause_f);
Cmd_AddCommand("music_resume", BGM_Resume_f);
Cmd_AddCommand("music_loop", BGM_Loop_f);
Cmd_AddCommand("music_stop", BGM_Stop_f);
bgmloop = true;
for (i = 0; wanted_handlers[i].type != CODECTYPE_NONE; i++)
{
switch (wanted_handlers[i].player)
{
case BGM_MIDIDRV:
/* not supported in quake */
break;
case BGM_STREAMER:
wanted_handlers[i].is_available =
S_CodecIsAvailable(wanted_handlers[i].type);
break;
case BGM_NONE:
default:
break;
}
if (wanted_handlers[i].is_available != -1)
{
if (handlers)
{
handlers->next = &wanted_handlers[i];
handlers = handlers->next;
}
else
{
music_handlers = &wanted_handlers[i];
handlers = music_handlers;
}
}
}
return true;
}
void BGM_Shutdown (void)
{
BGM_Stop();
/* sever our connections to
* midi_drv and snd_codec */
music_handlers = NULL;
}
static void BGM_Play_noext (const char *filename, unsigned int allowed_types)
{
char tmp[MAX_QPATH];
music_handler_t *handler;
handler = music_handlers;
while (handler)
{
if (! (handler->type & allowed_types))
{
handler = handler->next;
continue;
}
if (!handler->is_available)
{
/* skip handlers which failed to initialize */
/* TODO: implement re-init, make BGM aware of it */
handler = handler->next;
continue;
}
q_snprintf(tmp, sizeof(tmp), "%s/%s%s",
handler->dir, filename, handler->ext);
switch (handler->player)
{
case BGM_MIDIDRV:
/* not supported in quake */
break;
case BGM_STREAMER:
bgmstream = S_CodecOpenStreamType(tmp, handler->type);
if (bgmstream)
return; /* success */
break;
case BGM_NONE:
default:
break;
}
handler = handler->next;
}
Con_Printf("Couldn't handle music file %s\n", filename);
}
void BGM_Play (const char *filename)
{
char tmp[MAX_QPATH];
const char *ext;
music_handler_t *handler;
if (!filename || !*filename)
{
Con_DPrintf("null music file name\n");
return;
}
BGM_Stop();
ext = S_FileExtension(filename);
if (!ext) /* try all things */
{
BGM_Play_noext(filename, ANY_CODECTYPE);
return;
}
handler = music_handlers;
while (handler)
{
/* skip handlers which failed to initialize */
/* TODO: implement re-init, make BGM aware of it */
if (handler->is_available &&
!Q_strcasecmp(ext, handler->ext))
break;
handler = handler->next;
}
if (!handler)
{
Con_Printf("Unhandled extension for %s\n", filename);
return;
}
q_snprintf(tmp, sizeof(tmp), "%s/%s", handler->dir, filename);
switch (handler->player)
{
case BGM_MIDIDRV:
/* not supported in quake */
break;
case BGM_STREAMER:
bgmstream = S_CodecOpenStreamType(tmp, handler->type);
if (bgmstream)
return; /* success */
break;
case BGM_NONE:
default:
break;
}
Con_Printf("Couldn't handle music file %s\n", filename);
}
void BGM_PlayCDtrack (byte track, qboolean looping)
{
#if 0 /* see below */
char tmp[MAX_QPATH];
BGM_Stop();
bgmloop = looping;
if (CDAudio_Play(track, looping) == 0)
return; /* success */
q_snprintf(tmp, sizeof(tmp), "track%02d", (int)track);
BGM_Play_noext(tmp, CDRIP_TYPES);
#endif
/* instead of searching by the order of music_handlers, do so by
* the order of searchpath priority: the file from the searchpath
* with the highest path_id is most likely from our own gamedir
* itself. This way, if a mod has track02 as a *.mp3 file, which
* is below *.ogg in the music_handler order, the mp3 will still
* have priority over track02.ogg from, say, id1.
*/
char tmp[MAX_QPATH];
const char *ext;
unsigned int path_id, prev_id, type;
music_handler_t *handler;
BGM_Stop();
if (CDAudio_Play(track, looping) == 0)
return; /* success */
prev_id = 0;
type = 0;
ext = NULL;
handler = music_handlers;
while (handler)
{
if (! handler->is_available)
goto _next;
q_snprintf(tmp, sizeof(tmp), "%s/track%02d%s",
MUSIC_DIRNAME, (int)track, handler->ext);
if (COM_FileExists(tmp, &path_id) == -1)
goto _next;
if (path_id > prev_id)
{
prev_id = path_id;
type = handler->type;
ext = handler->ext;
}
_next:
handler = handler->next;
}
if (ext == NULL)
Con_Printf("Couldn't find a cdrip for track %d\n", (int)track);
else
{
q_snprintf(tmp, sizeof(tmp), "%s/track%02d%s",
MUSIC_DIRNAME, (int)track, ext);
bgmstream = S_CodecOpenStreamType(tmp, type);
if (! bgmstream)
Con_Printf("Couldn't handle music file %s\n", tmp);
}
}
void BGM_Stop (void)
{
if (bgmstream)
{
bgmstream->status = STREAM_NONE;
S_CodecCloseStream(bgmstream);
bgmstream = NULL;
s_rawend = 0;
}
}
void BGM_Pause (void)
{
if (bgmstream)
{
if (bgmstream->status == STREAM_PLAY)
bgmstream->status = STREAM_PAUSE;
}
}
void BGM_Resume (void)
{
if (bgmstream)
{
if (bgmstream->status == STREAM_PAUSE)
bgmstream->status = STREAM_PLAY;
}
}
static void BGM_UpdateStream (void)
{
int res; /* Number of bytes read. */
int bufferSamples;
int fileSamples;
int fileBytes;
byte raw[16384];
if (bgmstream->status != STREAM_PLAY)
return;
/* don't bother playing anything if musicvolume is 0 */
if (bgmvolume.value <= 0)
return;
/* see how many samples should be copied into the raw buffer */
if (s_rawend < paintedtime)
s_rawend = paintedtime;
while (s_rawend < paintedtime + MAX_RAW_SAMPLES)
{
bufferSamples = MAX_RAW_SAMPLES - (s_rawend - paintedtime);
/* decide how much data needs to be read from the file */
fileSamples = bufferSamples * bgmstream->info.rate / shm->speed;
if (!fileSamples)
return;
/* our max buffer size */
fileBytes = fileSamples * (bgmstream->info.width * bgmstream->info.channels);
if (fileBytes > sizeof(raw))
{
fileBytes = sizeof(raw);
fileSamples = fileBytes / (bgmstream->info.width * bgmstream->info.channels);
}
/* Read */
res = S_CodecReadStream(bgmstream, fileBytes, raw);
if (res < fileBytes)
{
fileBytes = res;
fileSamples = res / (bgmstream->info.width * bgmstream->info.channels);
}
if (res > 0) /* data: add to raw buffer */
{
S_RawSamples(fileSamples, bgmstream->info.rate,
bgmstream->info.width, bgmstream->info.channels, raw, bgmvolume.value);
}
else if (res == 0) /* EOF */
{
if (bgmloop)
{
if (S_CodecRewindStream(bgmstream) < 0)
{
BGM_Stop();
return;
}
}
else
{
BGM_Stop();
return;
}
}
else /* res < 0: some read error */
{
Con_Printf("Stream read error (%i), stopping.\n", res);
BGM_Stop();
return;
}
}
}
void BGM_Update (void)
{
if (old_volume != bgmvolume.value)
{
if (bgmvolume.value < 0)
Cvar_Set ("bgmvolume", "0");
else if (bgmvolume.value > 1)
Cvar_Set ("bgmvolume", "1");
old_volume = bgmvolume.value;
}
if (bgmstream)
BGM_UpdateStream ();
}

43
Quake/bgmusic.h Normal file
View file

@ -0,0 +1,43 @@
/*
* Background music handling for Hexen II: Hammer of Thyrion (uHexen2)
* Handles streaming music as raw sound samples and runs the midi driver
*
* Copyright (C) 1999-2005 Id Software, Inc.
* Copyright (C) 2010 O.Sezer <sezero@users.sourceforge.net>
*
* $Id: bgmusic.h 3818 2010-12-19 09:04:17Z sezero $
*
* 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifndef _BGMUSIC_H_
#define _BGMUSIC_H_
extern qboolean bgmloop;
qboolean BGM_Init (void);
void BGM_Shutdown (void);
void BGM_Play (const char *filename);
void BGM_PlayCDtrack (byte track, qboolean looping);
void BGM_Stop (void);
void BGM_Update (void);
void BGM_Pause (void);
void BGM_Resume (void);
#endif /* _BGMUSIC_H_ */

View file

@ -426,13 +426,7 @@ void CDAudio_Update(void)
return;
if (old_cdvolume != bgmvolume.value)
{
if (bgmvolume.value < 0)
Cvar_Set ("bgmvolume", "0.0");
else if (bgmvolume.value > 1)
Cvar_Set ("bgmvolume", "1.0");
CDAudio_SetVolume (bgmvolume.value);
}
if (playing && realtime > endOfTrack)
{

View file

@ -21,6 +21,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
// cl_main.c -- client main loop
#include "quakedef.h"
#include "bgmusic.h"
// we need to declare some mouse variables here, because the menu system
// references them even when on a unix system.
@ -111,6 +112,8 @@ void CL_Disconnect (void)
{
// stop sounds (especially looping!)
S_StopAllSounds (true);
BGM_Stop();
CDAudio_Stop();
// if running a local server, shut it down
if (cls.demoplayback)

View file

@ -22,6 +22,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
// cl_parse.c -- parse a message received from the server
#include "quakedef.h"
#include "bgmusic.h"
const char *svc_strings[] =
{
@ -1104,10 +1105,12 @@ void CL_ParseServerMessage (void)
if (cl.paused)
{
CDAudio_Pause ();
BGM_Pause ();
}
else
{
CDAudio_Resume ();
BGM_Resume ();
}
}
break;
@ -1151,9 +1154,9 @@ void CL_ParseServerMessage (void)
cl.cdtrack = MSG_ReadByte ();
cl.looptrack = MSG_ReadByte ();
if ( (cls.demoplayback || cls.demorecording) && (cls.forcetrack != -1) )
CDAudio_Play ((byte)cls.forcetrack, true);
BGM_PlayCDtrack ((byte)cls.forcetrack, true);
else
CDAudio_Play ((byte)cl.cdtrack, true);
BGM_PlayCDtrack ((byte)cl.cdtrack, true);
break;
case svc_intermission:

View file

@ -21,6 +21,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
// gl_vidnt.c -- NT GL vid component
#include "quakedef.h"
#include "bgmusic.h"
#include "winquake.h"
#include "resource.h"
#include <commctrl.h>
@ -543,6 +544,7 @@ int VID_SetMode (int modenum)
scr_disabled_for_loading = true;
CDAudio_Pause ();
BGM_Pause ();
if (vid_modenum == NO_MODE)
original_mode = windowed_default;
@ -591,6 +593,7 @@ int VID_SetMode (int modenum)
VID_UpdateWindowStatus ();
CDAudio_Resume ();
BGM_Resume ();
scr_disabled_for_loading = temp;
// now we try to make sure we get the focus on the mode switch, because

View file

@ -23,6 +23,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
// gl_vidnt.c -- NT GL vid component
#include "quakedef.h"
#include "bgmusic.h"
#include "resource.h"
#include "SDL.h"
@ -260,6 +261,7 @@ int VID_SetMode (int modenum)
scr_disabled_for_loading = true;
CDAudio_Pause ();
BGM_Pause ();
// set vertical sync
if (gl_swap_control)
@ -328,6 +330,7 @@ int VID_SetMode (int modenum)
VID_UpdateWindowStatus ();
CDAudio_Resume ();
BGM_Resume ();
scr_disabled_for_loading = temp;
vid_modenum = modenum;

View file

@ -22,6 +22,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
// host.c -- coordinates spawning and killing of local servers
#include "quakedef.h"
#include "bgmusic.h"
#include <setjmp.h>
/*
@ -728,6 +729,7 @@ void _Host_Frame (float time)
time2 = Sys_FloatTime ();
// update audio
BGM_Update(); // adds music raw samples and/or advances midi driver
if (cls.signon == SIGNONS)
{
S_Update (r_origin, vpn, vright, vup);
@ -851,6 +853,7 @@ void Host_Init (quakeparms_t *parms)
R_Init ();
S_Init ();
CDAudio_Init ();
BGM_Init();
Sbar_Init ();
CL_Init ();
@ -917,6 +920,7 @@ void Host_Shutdown(void)
{
if (con_initialized)
History_Shutdown ();
BGM_Shutdown();
CDAudio_Shutdown ();
S_Shutdown ();
IN_Shutdown (); // input is only initialized in Host_Init if we're not dedicated -- kristian

270
Quake/snd_codec.c Normal file
View file

@ -0,0 +1,270 @@
/*
* Audio Codecs: Adapted from ioquake3 with changes.
* For now, only handles streaming music, not sound effects.
*
* Copyright (C) 1999-2005 Id Software, Inc.
* Copyright (C) 2005 Stuart Dalton <badcdev@gmail.com>
* Copyright (C) 2010 O.Sezer <sezero@users.sourceforge.net>
*
* 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "quakedef.h"
#include "snd_codec.h"
#include "snd_codeci.h"
/* headers for individual codecs */
#include "snd_wave.h"
#include "snd_mp3.h"
#include "snd_vorbis.h"
static snd_codec_t *codecs;
/*
=================
S_CodecRegister
=================
*/
static void S_CodecRegister(snd_codec_t *codec)
{
codec->next = codecs;
codecs = codec;
}
/*
=================
S_CodecInit
=================
*/
void S_CodecInit (void)
{
snd_codec_t *codec;
codecs = NULL;
/* Register in the inverse order
* of codec choice preference: */
#ifdef USE_CODEC_WAVE
S_CodecRegister(&wav_codec);
#endif
#ifdef USE_CODEC_MP3
S_CodecRegister(&mp3_codec);
#endif
#ifdef USE_CODEC_VORBIS
S_CodecRegister(&ogg_codec);
#endif
codec = codecs;
while (codec)
{
codec->initialize();
codec = codec->next;
}
}
/*
=================
S_CodecShutdown
=================
*/
void S_CodecShutdown (void)
{
snd_codec_t *codec = codecs;
while (codec)
{
codec->shutdown();
codec = codec->next;
}
codecs = NULL;
}
/*
=================
S_CodecOpenStream
=================
*/
snd_stream_t *S_CodecOpenStreamType (const char *filename, unsigned int type)
{
snd_codec_t *codec;
snd_stream_t *stream;
if (type == CODECTYPE_NONE)
{
Con_Printf("Bad type for %s\n", filename);
return NULL;
}
codec = codecs;
while (codec)
{
if (type == codec->type)
break;
codec = codec->next;
}
if (!codec)
{
Con_Printf("Unknown type for %s\n", filename);
return NULL;
}
stream = codec->codec_open(filename);
if (stream)
stream->status = STREAM_PLAY;
return stream;
}
snd_stream_t *S_CodecOpenStreamExt (const char *filename)
{
snd_codec_t *codec;
snd_stream_t *stream;
const char *ext;
ext = S_FileExtension(filename);
if (!ext)
{
Con_Printf("No extension for %s\n", filename);
return NULL;
}
codec = codecs;
while (codec)
{
if (!Q_strcasecmp(ext, codec->ext))
break;
codec = codec->next;
}
if (!codec)
{
Con_Printf("Unknown extension for %s\n", filename);
return NULL;
}
stream = codec->codec_open(filename);
if (stream)
stream->status = STREAM_PLAY;
return stream;
}
snd_stream_t *S_CodecOpenStreamAny (const char *filename)
{
snd_codec_t *codec;
snd_stream_t *stream;
const char *ext;
ext = S_FileExtension(filename);
if (!ext) /* try all available */
{
char tmp[MAX_QPATH];
codec = codecs;
while (codec)
{
q_snprintf(tmp, sizeof(tmp), "%s%s", filename, codec->ext);
stream = codec->codec_open(tmp);
if (stream)
{
stream->status = STREAM_PLAY;
return stream;
}
codec = codec->next;
}
return NULL;
}
else /* use the name as is */
{
codec = codecs;
while (codec)
{
if (!Q_strcasecmp(ext, codec->ext))
break;
codec = codec->next;
}
if (!codec)
{
Con_Printf("Unknown extension for %s\n", filename);
return NULL;
}
stream = codec->codec_open(filename);
if (stream)
stream->status = STREAM_PLAY;
return stream;
}
}
void S_CodecCloseStream (snd_stream_t *stream)
{
stream->status = STREAM_NONE;
stream->codec->codec_close(stream);
}
int S_CodecRewindStream (snd_stream_t *stream)
{
return stream->codec->codec_rewind(stream);
}
int S_CodecReadStream (snd_stream_t *stream, int bytes, void *buffer)
{
return stream->codec->codec_read(stream, bytes, buffer);
}
/* Util functions (used by codecs) */
snd_stream_t *S_CodecUtilOpen(const char *filename, snd_codec_t *codec)
{
snd_stream_t *stream;
FILE *handle;
qboolean pak;
size_t length;
/* Try to open the file */
length = COM_FOpenFile(filename, &handle, NULL);
pak = file_from_pak;
if (length == (size_t)-1)
{
Con_DPrintf("Couldn't open %s\n", filename);
return NULL;
}
/* Allocate a stream, Z_Malloc zeroes its content */
stream = (snd_stream_t *) Z_Malloc(sizeof(snd_stream_t));
stream->codec = codec;
stream->fh.file = handle;
stream->fh.start = ftell(handle);
stream->fh.pos = 0;
stream->fh.length = (int) length;
stream->fh.pak = stream->pak = pak;
return stream;
}
void S_CodecUtilClose(snd_stream_t **stream)
{
fclose((*stream)->fh.file);
Z_Free(*stream);
*stream = NULL;
}
int S_CodecIsAvailable (unsigned int type)
{
snd_codec_t *codec = codecs;
while (codec)
{
if (type == codec->type)
return codec->initialized;
codec = codec->next;
}
return -1;
}

101
Quake/snd_codec.h Normal file
View file

@ -0,0 +1,101 @@
/*
* Audio Codecs: Adapted from ioquake3 with changes.
* For now, only handles streaming music, not sound effects.
*
* Copyright (C) 1999-2005 Id Software, Inc.
* Copyright (C) 2005 Stuart Dalton <badcdev@gmail.com>
* Copyright (C) 2010 O.Sezer <sezero@users.sourceforge.net>
*
* 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifndef _SND_CODEC_H_
#define _SND_CODEC_H_
typedef struct snd_info_s
{
int rate;
int width;
int channels;
int samples;
int size;
int dataofs;
} snd_info_t;
typedef enum {
STREAM_NONE = -1,
STREAM_INIT,
STREAM_PAUSE,
STREAM_PLAY
} stream_status_t;
typedef struct snd_codec_s snd_codec_t;
typedef struct snd_stream_s
{
fshandle_t fh;
qboolean pak;
snd_info_t info;
stream_status_t status;
snd_codec_t *codec; /* codec handling this stream */
void *priv; /* data private to the codec. */
} snd_stream_t;
void S_CodecInit (void);
void S_CodecShutdown (void);
/* Callers of the following S_CodecOpenStream* functions
* are reponsible for attaching any path to the filename */
snd_stream_t *S_CodecOpenStreamType (const char *filename, unsigned int type);
/* Decides according to the required type. */
snd_stream_t *S_CodecOpenStreamAny (const char *filename);
/* Decides according to file extension. if the
* name has no extension, try all available. */
snd_stream_t *S_CodecOpenStreamExt (const char *filename);
/* Decides according to file extension. the name
* MUST have an extension. */
void S_CodecCloseStream (snd_stream_t *stream);
int S_CodecReadStream (snd_stream_t *stream, int bytes, void *buffer);
int S_CodecRewindStream (snd_stream_t *stream);
snd_stream_t *S_CodecUtilOpen(const char *filename, snd_codec_t *codec);
void S_CodecUtilClose(snd_stream_t **stream);
#define CODECTYPE_NONE 0
#define CODECTYPE_MID (1 << 0)
#define CODECTYPE_MOD (1 << 1)
#define CODECTYPE_FLAC (1 << 2)
#define CODECTYPE_WAV (1 << 3)
#define CODECTYPE_MP3 (1 << 4)
#define CODECTYPE_OGG (1 << 5)
#define CODECTYPE_VORBIS CODECTYPE_OGG
#define CODECTYPE_WAVE CODECTYPE_WAV
#define CODECTYPE_MIDI CODECTYPE_MID
int S_CodecIsAvailable (unsigned int type);
/* return 1 if available, 0 if codec failed init
* or -1 if no such codec is present. */
#endif /* _SND_CODEC_H_ */

52
Quake/snd_codeci.h Normal file
View file

@ -0,0 +1,52 @@
/*
* Audio Codecs: Adapted from ioquake3 with changes.
* For now, only handles streaming music, not sound effects.
*
* Copyright (C) 1999-2005 Id Software, Inc.
* Copyright (C) 2005 Stuart Dalton <badcdev@gmail.com>
* Copyright (C) 2010 O.Sezer <sezero@users.sourceforge.net>
*
* 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifndef _SND_CODECI_H_
#define _SND_CODECI_H_
/* Codec internals */
typedef qboolean (*CODEC_INIT)(void);
typedef void (*CODEC_SHUTDOWN)(void);
typedef snd_stream_t *(*CODEC_OPEN)(const char *filename);
typedef int (*CODEC_READ)(snd_stream_t *stream, int bytes, void *buffer);
typedef int (*CODEC_REWIND)(snd_stream_t *stream);
typedef void (*CODEC_CLOSE)(snd_stream_t *stream);
struct snd_codec_s
{
unsigned int type; /* handled data type. */
qboolean initialized; /* init succeedded */
const char *ext; /* expected extension */
CODEC_INIT initialize;
CODEC_SHUTDOWN shutdown;
CODEC_OPEN codec_open;
CODEC_READ codec_read;
CODEC_REWIND codec_rewind;
CODEC_CLOSE codec_close;
snd_codec_t *next;
};
#endif /* _SND_CODECI_H_ */

View file

@ -22,6 +22,8 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
// snd_dma.c -- main control for any streaming sound output device
#include "quakedef.h"
#include "snd_codec.h"
#include "bgmusic.h"
static void S_Play (void);
static void S_PlayVol (void);
@ -192,6 +194,8 @@ void S_Init (void)
ambient_sfx[AMBIENT_WATER] = S_PrecacheSound ("ambience/water1.wav");
ambient_sfx[AMBIENT_SKY] = S_PrecacheSound ("ambience/wind2.wav");
S_CodecInit ();
S_StopAllSounds (true);
}
@ -207,6 +211,8 @@ void S_Shutdown (void)
sound_started = 0;
snd_blocked = 0;
S_CodecShutdown();
SNDDMA_Shutdown();
shm = NULL;
}
@ -856,6 +862,9 @@ void S_Update (vec3_t origin, vec3_t forward, vec3_t right, vec3_t up)
Con_Printf ("----(%i)----\n", total);
}
// add raw data from streamed samples
// BGM_Update(); // moved to the main loop just before S_Update ()
// mix some sound
S_Update_();
}

466
Quake/snd_mp3.c Normal file
View file

@ -0,0 +1,466 @@
/*
* MP3 decoding support using libmad: Adapted from the SoX library at
* http://sourceforge.net/projects/sox/, LGPLv2, Copyright (c) 2007-2009
* SoX contributors, written by Fabrizio Gennari <fabrizio.ge@tiscali.it>,
* with the decoding part based on the decoder tutorial program madlld
* written by Bertrand Petit <madlld@phoe.fmug.org> (BSD license, see at
* http://www.bsd-dk.dk/~elrond/audio/madlld/). The tag identification
* functions were adapted from the GPL-licensed libid3tag library, see at
* http://www.underbit.com/products/mad/.
*
* 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "quakedef.h"
#if defined(USE_CODEC_MP3)
#include "snd_codec.h"
#include "snd_codeci.h"
#include "snd_mp3.h"
#include <mad.h>
#define ID3_TAG_FLAG_FOOTERPRESENT 0x10
/* Under Windows, importing data from DLLs is a dicey proposition. This is true
* when using dlopen, but also true if linking directly against the DLL if the
* header does not mark the data as __declspec(dllexport), which mad.h does not.
* Sidestep the issue by defining our own mad_timer_zero. This is needed because
* mad_timer_zero is used in some of the mad.h macros.
*/
#define mad_timer_zero mad_timer_zero_stub
static mad_timer_t const mad_timer_zero_stub = {0, 0};
/* MAD returns values with MAD_F_FRACBITS (28) bits of precision, though it's
not certain that all of them are meaningful. Default to 16 bits to
align with most users expectation of output file should be 16 bits. */
#define MP3_MAD_SAMPLEBITS 16
#define MP3_MAD_SAMPLEWIDTH (MP3_MAD_SAMPLEBITS / 8)
#define MP3_BUFFER_SIZE (5 * 8192)
/* Private data */
typedef struct _mp3_priv_t
{
unsigned char mp3_buffer[MP3_BUFFER_SIZE];
struct mad_stream Stream;
struct mad_frame Frame;
struct mad_synth Synth;
mad_timer_t Timer;
ptrdiff_t cursamp;
size_t FrameCount;
} mp3_priv_t;
/* This function merges the functions tagtype() and id3_tag_query()
* from MAD's libid3tag, so we don't have to link to it
* Returns 0 if the frame is not an ID3 tag, tag length if it is */
static qboolean tag_is_id3v1(const unsigned char *data, size_t length)
{
if (length >= 3 &&
data[0] == 'T' && data[1] == 'A' && data[2] == 'G')
{
return true;
}
return false;
}
static qboolean tag_is_id3v2(const unsigned char *data, size_t length)
{
if (length >= 10 &&
(data[0] == 'I' && data[1] == 'D' && data[2] == '3') &&
data[3] < 0xff && data[4] < 0xff &&
data[6] < 0x80 && data[7] < 0x80 && data[8] < 0x80 && data[9] < 0x80)
{
return true;
}
return false;
}
static int mp3_tagsize(const unsigned char *data, size_t length)
{
if (tag_is_id3v1(data, length))
return 128;
if (tag_is_id3v2(data, length))
{
unsigned char flags;
unsigned int size;
flags = data[5];
size = 10 + (data[6]<<21) + (data[7]<<14) + (data[8]<<7) + data[9];
if (flags & ID3_TAG_FLAG_FOOTERPRESENT)
size += 10;
for ( ; size < length && !data[size]; ++size)
; /* Consume padding */
return size;
}
return 0;
}
/* Attempts to read an ID3 tag at the current location in stream and
* consume it all. Returns -1 if no tag is found. Its up to caller
* to recover. */
static int mp3_inputtag(snd_stream_t *stream)
{
mp3_priv_t *p = (mp3_priv_t *) stream->priv;
int rc = -1;
size_t remaining;
size_t tagsize;
/* FIXME: This needs some more work if we are to ever
* look at the ID3 frame. This is because the Stream
* may not be able to hold the complete ID3 frame.
* We should consume the whole frame inside tagtype()
* instead of outside of tagframe(). That would support
* recovering when Stream contains less then 8-bytes (header)
* and also when ID3v2 is bigger then Stream buffer size.
* Need to pass in stream so that buffer can be
* consumed as well as letting additional data to be
* read in.
*/
remaining = p->Stream.bufend - p->Stream.next_frame;
tagsize = mp3_tagsize(p->Stream.this_frame, remaining);
if (tagsize != 0)
{
mad_stream_skip(&p->Stream, tagsize);
rc = 0;
}
/* We know that a valid frame hasn't been found yet
* so help libmad out and go back into frame seek mode.
* This is true whether an ID3 tag was found or not.
*/
mad_stream_sync(&p->Stream);
return rc;
}
/* (Re)fill the stream buffer that is to be decoded. If any data
* still exists in the buffer then they are first shifted to be
* front of the stream buffer. */
static int mp3_inputdata(snd_stream_t *stream)
{
mp3_priv_t *p = (mp3_priv_t *) stream->priv;
size_t bytes_read;
size_t remaining;
remaining = p->Stream.bufend - p->Stream.next_frame;
/* libmad does not consume all the buffer it's given. Some
* data, part of a truncated frame, is left unused at the
* end of the buffer. That data must be put back at the
* beginning of the buffer and taken in account for
* refilling the buffer. This means that the input buffer
* must be large enough to hold a complete frame at the
* highest observable bit-rate (currently 448 kb/s).
* TODO: Is 2016 bytes the size of the largest frame?
* (448000*(1152/32000))/8
*/
memmove(p->mp3_buffer, p->Stream.next_frame, remaining);
bytes_read = FS_fread(p->mp3_buffer + remaining, 1,
MP3_BUFFER_SIZE - remaining, &stream->fh);
if (bytes_read == 0)
{
return -1;
}
mad_stream_buffer(&p->Stream, p->mp3_buffer, bytes_read+remaining);
p->Stream.error = MAD_ERROR_NONE;
return 0;
}
static int mp3_startread(snd_stream_t *stream)
{
mp3_priv_t *p = (mp3_priv_t *) stream->priv;
size_t ReadSize;
mad_stream_init(&p->Stream);
mad_frame_init(&p->Frame);
mad_synth_init(&p->Synth);
mad_timer_reset(&p->Timer);
/* Decode at least one valid frame to find out the input
* format. The decoded frame will be saved off so that it
* can be processed later.
*/
ReadSize = FS_fread(p->mp3_buffer, 1, MP3_BUFFER_SIZE, &stream->fh);
if (ReadSize != MP3_BUFFER_SIZE)
{
if (FS_feof(&stream->fh) || FS_ferror(&stream->fh))
return -1;
}
mad_stream_buffer(&p->Stream, p->mp3_buffer, ReadSize);
/* Find a valid frame before starting up. This makes sure
* that we have a valid MP3 and also skips past ID3v2 tags
* at the beginning of the audio file.
*/
p->Stream.error = MAD_ERROR_NONE;
while (mad_frame_decode(&p->Frame,&p->Stream))
{
/* check whether input buffer needs a refill */
if (p->Stream.error == MAD_ERROR_BUFLEN)
{
if (mp3_inputdata(stream) == -1)
return -1;
continue;
}
/* Consume any ID3 tags */
mp3_inputtag(stream);
/* FIXME: We should probably detect when we've read
* a bunch of non-ID3 data and still haven't found a
* frame. In that case we can abort early without
* scanning the whole file.
*/
p->Stream.error = MAD_ERROR_NONE;
}
if (p->Stream.error)
{
Con_Printf("MP3: No valid MP3 frame found\n");
return -1;
}
switch(p->Frame.header.mode)
{
case MAD_MODE_SINGLE_CHANNEL:
case MAD_MODE_DUAL_CHANNEL:
case MAD_MODE_JOINT_STEREO:
case MAD_MODE_STEREO:
stream->info.channels = MAD_NCHANNELS(&p->Frame.header);
break;
default:
Con_Printf("MP3: Cannot determine number of channels\n");
return -1;
}
p->FrameCount = 1;
mad_timer_add(&p->Timer,p->Frame.header.duration);
mad_synth_frame(&p->Synth,&p->Frame);
stream->info.width = MP3_MAD_SAMPLEWIDTH;
stream->info.rate = p->Synth.pcm.samplerate;
p->cursamp = 0;
return 0;
}
/* Read up to len samples from p->Synth
* If needed, read some more MP3 data, decode them and synth them
* Place in buf[].
* Return number of samples read. */
static int mp3_decode(snd_stream_t *stream, byte *buf, int len)
{
mp3_priv_t *p = (mp3_priv_t *) stream->priv;
int donow, i, done = 0;
mad_fixed_t sample;
int chan, x;
do
{
x = (p->Synth.pcm.length - p->cursamp) * stream->info.channels;
donow = q_min(len, x);
i = 0;
while (i < donow)
{
for (chan = 0; chan < stream->info.channels; chan++)
{
sample = p->Synth.pcm.samples[chan][p->cursamp];
/* convert from fixed to short,
* write in host-endian format. */
if (sample <= -MAD_F_ONE)
sample = -0x7FFF;
else if (sample >= MAD_F_ONE)
sample = 0x7FFF;
else
sample >>= (MAD_F_FRACBITS + 1 - 16);
if (bigendien)
{
*buf++ = (sample >> 8) & 0xFF;
*buf++ = sample & 0xFF;
}
else
{ /* LITTLE_ENDIAN */
*buf++ = sample & 0xFF;
*buf++ = (sample >> 8) & 0xFF;
}
i++;
}
p->cursamp++;
};
len -= donow;
done += donow;
if (len == 0)
break;
/* check whether input buffer needs a refill */
if (p->Stream.error == MAD_ERROR_BUFLEN)
{
if (mp3_inputdata(stream) == -1)
{
/* check feof() ?? */
Con_DPrintf("mp3 EOF\n");
break;
}
}
if (mad_frame_decode(&p->Frame, &p->Stream))
{
if (MAD_RECOVERABLE(p->Stream.error))
{
mp3_inputtag(stream);
continue;
}
else
{
if (p->Stream.error == MAD_ERROR_BUFLEN)
continue;
else
{
Con_Printf("MP3: unrecoverable frame level error (%s)\n",
mad_stream_errorstr(&p->Stream));
break;
}
}
}
p->FrameCount++;
mad_timer_add(&p->Timer, p->Frame.header.duration);
mad_synth_frame(&p->Synth, &p->Frame);
p->cursamp = 0;
} while (1);
return done;
}
static int mp3_stopread(snd_stream_t *stream)
{
mp3_priv_t *p = (mp3_priv_t*) stream->priv;
mad_synth_finish(&p->Synth);
mad_frame_finish(&p->Frame);
mad_stream_finish(&p->Stream);
return 0;
}
static qboolean S_MP3_CodecInitialize (void)
{
return true;
}
static void S_MP3_CodecShutdown (void)
{
}
static int MP3_check_file (snd_stream_t *stream)
{
unsigned char magic[16];
if (FS_fread(magic, 1, 16, &stream->fh) != 16)
return -1;
if ((magic[0] == 0xFF && (magic[1] & 0xF0) == 0xF0) ||
tag_is_id3v2(magic, 16))
return 0;
if (FS_fseek(&stream->fh, -128, SEEK_END) < 0)
return -1;
if (FS_fread(magic, 1, 16, &stream->fh) != 16)
return -1;
if (tag_is_id3v1(magic, 16))
return 0;
return -1;
}
static snd_stream_t *S_MP3_CodecOpenStream (const char *filename)
{
snd_stream_t *stream;
stream = S_CodecUtilOpen(filename, &mp3_codec);
if (!stream)
return NULL;
stream->priv = Z_Malloc(sizeof(mp3_priv_t));
/* check some rudimentary info before feeding it to libmad. */
if (MP3_check_file(stream) < 0)
{
/*
Con_Printf("%s format couldn't be identified.\n", filename);
Z_Free(stream->priv);
S_CodecUtilClose(&stream);
return NULL;
*/
Con_DPrintf("%s has no identifier tags.\n", filename);
}
FS_rewind(&stream->fh);
if (mp3_startread(stream) < 0)
{
Con_Printf("%s is not a valid MP3 file.\n", filename);
Z_Free(stream->priv);
S_CodecUtilClose(&stream);
return NULL;
}
return stream;
}
static int S_MP3_CodecReadStream (snd_stream_t *stream, int bytes, void *buffer)
{
int res = mp3_decode(stream, (byte *)buffer, bytes / stream->info.width);
return res * stream->info.width;
}
static void S_MP3_CodecCloseStream (snd_stream_t *stream)
{
mp3_stopread(stream);
Z_Free(stream->priv);
S_CodecUtilClose(&stream);
}
static int S_MP3_CodecRewindStream (snd_stream_t *stream)
{
/* FIXME: do this better */
mp3_stopread(stream);
FS_rewind(&stream->fh);
return mp3_startread(stream);
}
snd_codec_t mp3_codec =
{
CODECTYPE_MP3,
true, /* always available. */
".mp3",
S_MP3_CodecInitialize,
S_MP3_CodecShutdown,
S_MP3_CodecOpenStream,
S_MP3_CodecReadStream,
S_MP3_CodecRewindStream,
S_MP3_CodecCloseStream,
NULL
};
#endif /* USE_CODEC_MP3 */

13
Quake/snd_mp3.h Normal file
View file

@ -0,0 +1,13 @@
/* MP3 decoding support using libmad. */
#if !defined(_SND_MP3_H_)
#define _SND_MP3_H_
#if defined(USE_CODEC_MP3)
extern snd_codec_t mp3_codec;
#endif /* USE_CODEC_MP3 */
#endif /* ! _SND_MP3_H_ */

169
Quake/snd_vorbis.c Normal file
View file

@ -0,0 +1,169 @@
/*
* Ogg/Vorbis streaming music support, adapted from several open source
* Quake engine based projects with many modifications.
*
* 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "quakedef.h"
#if defined(USE_CODEC_VORBIS)
#include "snd_codec.h"
#include "snd_codeci.h"
#include "snd_vorbis.h"
#define OV_EXCLUDE_STATIC_CALLBACKS
#include <vorbis/vorbisfile.h>
/* The OGG codec can return the samples in a number of different
* formats, we use the standard signed short format. */
#define OGG_SAMPLEWIDTH 2
#define OGG_SIGNED_DATA 1
static int ogg_bigendian = 0;
/* CALLBACK FUNCTIONS: */
static int ovc_fclose (void *f)
{
return 0; /* we fclose() elsewhere. */
}
static int ovc_fseek (void *f, ogg_int64_t off, int whence)
{
if (f == NULL) return (-1);
return FS_fseek((fshandle_t *)f, (long) off, whence);
}
static const ov_callbacks ovc_qfs =
{
(size_t (*)(void *, size_t, size_t, void *)) FS_fread,
(int (*)(void *, ogg_int64_t, int)) ovc_fseek,
(int (*)(void *)) ovc_fclose,
(long (*)(void *)) FS_ftell
};
static qboolean S_OGG_CodecInitialize (void)
{
ogg_bigendian = (bigendien == true) ? 1 : 0;
ogg_codec.initialized = true;
return true;
}
static void S_OGG_CodecShutdown (void)
{
}
static snd_stream_t *S_OGG_CodecOpenStream (const char *filename)
{
snd_stream_t *stream;
OggVorbis_File *ovFile;
vorbis_info *ogg_info;
int res;
stream = S_CodecUtilOpen(filename, &ogg_codec);
if (!stream)
return NULL;
ovFile = (OggVorbis_File *) Z_Malloc(sizeof(OggVorbis_File));
res = ov_open_callbacks(&stream->fh, ovFile, NULL, 0, ovc_qfs);
if (res < 0)
{
Con_Printf("%s is not a valid Ogg Vorbis file (error %i).\n", filename, res);
Z_Free(ovFile);
S_CodecUtilClose(&stream);
return NULL;
}
stream->priv = ovFile;
if (!ov_seekable(ovFile))
{
Con_Printf("OGG_Open: stream %s not seekable.\n", filename);
ov_clear(ovFile);
Z_Free(ovFile);
S_CodecUtilClose(&stream);
return NULL;
}
ogg_info = ov_info(ovFile, 0);
if (!ogg_info)
{
Con_Printf("Unable to get stream information for %s.\n", filename);
ov_clear(ovFile);
Z_Free(ovFile);
S_CodecUtilClose(&stream);
return NULL;
}
stream->info.rate = ogg_info->rate;
stream->info.channels = ogg_info->channels;
stream->info.width = OGG_SAMPLEWIDTH;
return stream;
}
static int S_OGG_CodecReadStream (snd_stream_t *stream, int bytes, void *buffer)
{
int section; /* FIXME: handle section changes */
int cnt, res, rem;
char * ptr;
cnt = 0; rem = bytes;
ptr = (char *) buffer;
while (1)
{
res = ov_read((OggVorbis_File *) stream->priv, ptr, rem, ogg_bigendian,
OGG_SAMPLEWIDTH, OGG_SIGNED_DATA, &section);
if (res <= 0)
break;
rem -= res;
cnt += res;
if (rem <= 0)
break;
ptr += res;
}
return (res >= 0) ? cnt : res;
}
static void S_OGG_CodecCloseStream (snd_stream_t *stream)
{
ov_clear((OggVorbis_File *)stream->priv);
Z_Free(stream->priv);
S_CodecUtilClose(&stream);
}
static int S_OGG_CodecRewindStream (snd_stream_t *stream)
{
return ov_time_seek ((OggVorbis_File *)stream->priv, 0.0);
}
snd_codec_t ogg_codec =
{
CODECTYPE_OGG,
false,
".ogg",
S_OGG_CodecInitialize,
S_OGG_CodecShutdown,
S_OGG_CodecOpenStream,
S_OGG_CodecReadStream,
S_OGG_CodecRewindStream,
S_OGG_CodecCloseStream,
NULL
};
#endif /* USE_CODEC_VORBIS */

13
Quake/snd_vorbis.h Normal file
View file

@ -0,0 +1,13 @@
/* Ogg/Vorbis streaming music support. */
#if !defined(_SND_VORBIS_H_)
#define _SND_VORBIS_H_
#if defined(USE_CODEC_VORBIS)
extern snd_codec_t ogg_codec;
#endif /* USE_CODEC_VORBIS */
#endif /* ! _SND_VORBIS_H_ */

282
Quake/snd_wave.c Normal file
View file

@ -0,0 +1,282 @@
/*
* WAV streaming music support. Adapted from ioquake3 with changes.
*
* Copyright (C) 1999-2005 Id Software, Inc.
* Copyright (C) 2005 Stuart Dalton <badcdev@gmail.com>
* Copyright (C) 2010 O.Sezer <sezero@users.sourceforge.net>
*
* 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "quakedef.h"
#if defined(USE_CODEC_WAVE)
#include "snd_codec.h"
#include "snd_codeci.h"
#include "snd_wave.h"
/*
=================
FGetLittleLong
=================
*/
static int FGetLittleLong (FILE *f)
{
int v;
fread(&v, 1, sizeof(v), f);
return LittleLong(v);
}
/*
=================
FGetLittleShort
=================
*/
static short FGetLittleShort(FILE *f)
{
short v;
fread(&v, 1, sizeof(v), f);
return LittleShort(v);
}
/*
=================
WAV_ReadChunkInfo
=================
*/
static int WAV_ReadChunkInfo(FILE *f, char *name)
{
int len, r;
name[4] = 0;
r = fread(name, 1, 4, f);
if (r != 4)
return -1;
len = FGetLittleLong(f);
if (len < 0)
{
Con_Printf("WAV: Negative chunk length\n");
return -1;
}
return len;
}
/*
=================
WAV_FindRIFFChunk
Returns the length of the data in the chunk, or -1 if not found
=================
*/
static int WAV_FindRIFFChunk(FILE *f, const char *chunk)
{
char name[5];
int len;
while ((len = WAV_ReadChunkInfo(f, name)) >= 0)
{
/* If this is the right chunk, return */
if (!strncmp(name, chunk, 4))
return len;
len = ((len + 1) & ~1); /* pad by 2 . */
/* Not the right chunk - skip it */
fseek(f, len, SEEK_CUR);
}
return -1;
}
/*
=================
WAV_ReadRIFFHeader
=================
*/
static qboolean WAV_ReadRIFFHeader(const char *name, FILE *file, snd_info_t *info)
{
char dump[16];
int wav_format;
int bits;
int fmtlen = 0;
if (fread(dump, 1, 12, file) < 12 ||
strncmp(dump, "RIFF", 4) != 0 ||
strncmp(&dump[8], "WAVE", 4) != 0)
{
Con_Printf("%s is missing RIFF/WAVE chunks\n", name);
return false;
}
/* Scan for the format chunk */
if ((fmtlen = WAV_FindRIFFChunk(file, "fmt ")) < 0)
{
Con_Printf("%s is missing fmt chunk\n", name);
return false;
}
/* Save the parameters */
wav_format = FGetLittleShort(file);
if (wav_format != WAV_FORMAT_PCM)
{
Con_Printf("%s is not Microsoft PCM format\n", name);
return false;
}
info->channels = FGetLittleShort(file);
info->rate = FGetLittleLong(file);
FGetLittleLong(file);
FGetLittleShort(file);
bits = FGetLittleShort(file);
if (bits != 8 && bits != 16)
{
Con_Printf("%s is not 8 or 16 bit\n", name);
return false;
}
info->width = bits / 8;
info->dataofs = 0;
/* Skip the rest of the format chunk if required */
if (fmtlen > 16)
{
fmtlen -= 16;
fseek(file, fmtlen, SEEK_CUR);
}
/* Scan for the data chunk */
if ((info->size = WAV_FindRIFFChunk(file, "data")) < 0)
{
Con_Printf("%s is missing data chunk\n", name);
return false;
}
info->samples = (info->size / info->width) / info->channels;
if (info->samples == 0)
{
Con_Printf("%s has zero samples\n", name);
return false;
}
return true;
}
/*
=================
S_WAV_CodecOpenStream
=================
*/
snd_stream_t *S_WAV_CodecOpenStream(const char *filename)
{
snd_stream_t *stream;
long start;
stream = S_CodecUtilOpen(filename, &wav_codec);
if (!stream)
return NULL;
start = stream->fh.start;
/* Read the RIFF header */
/* The file reads are sequential, therefore no need
* for the FS_*() functions: We will manipulate the
* file by ourselves from now on. */
if (!WAV_ReadRIFFHeader(filename, stream->fh.file, &stream->info))
{
S_CodecUtilClose(&stream);
return NULL;
}
stream->fh.start = ftell(stream->fh.file); /* reset to data position */
if (stream->fh.start - start + stream->info.size > stream->fh.length)
{
Con_Printf("%s data size mismatch\n", filename);
S_CodecUtilClose(&stream);
return NULL;
}
return stream;
}
/*
=================
S_WAV_CodecReadStream
=================
*/
int S_WAV_CodecReadStream(snd_stream_t *stream, int bytes, void *buffer)
{
int remaining = stream->info.size - stream->fh.pos;
int i, samples;
if (remaining <= 0)
return 0;
if (bytes > remaining)
bytes = remaining;
stream->fh.pos += bytes;
fread(buffer, 1, bytes, stream->fh.file);
if (stream->info.width == 2)
{
samples = bytes / 2;
for (i = 0; i < samples; i++)
((short *)buffer)[i] = LittleShort( ((short *)buffer)[i] );
}
return bytes;
}
static void S_WAV_CodecCloseStream (snd_stream_t *stream)
{
S_CodecUtilClose(&stream);
}
static int S_WAV_CodecRewindStream (snd_stream_t *stream)
{
FS_rewind(&stream->fh);
return 0;
}
static qboolean S_WAV_CodecInitialize (void)
{
return true;
}
static void S_WAV_CodecShutdown (void)
{
}
snd_codec_t wav_codec =
{
CODECTYPE_WAVE,
true, /* always available. */
".wav",
S_WAV_CodecInitialize,
S_WAV_CodecShutdown,
S_WAV_CodecOpenStream,
S_WAV_CodecReadStream,
S_WAV_CodecRewindStream,
S_WAV_CodecCloseStream,
NULL
};
#endif /* USE_CODEC_WAVE */

13
Quake/snd_wave.h Normal file
View file

@ -0,0 +1,13 @@
/* WAV streaming music support. */
#if !defined(_SND_WAVE_H_)
#define _SND_WAVE_H_
#if defined(USE_CODEC_WAVE)
extern snd_codec_t wav_codec;
#endif /* USE_CODEC_WAVE */
#endif /* ! _SND_WAVE_H_ */