remove flac and mad mp3 support
This commit is contained in:
parent
3fdd493f09
commit
e37cbba505
11 changed files with 10 additions and 929 deletions
|
@ -29,8 +29,8 @@ TOOLLIBS=$(XLDFLAGS) -lpng -lz -lm @EXTRA_LIBS@ @THREAD_LIBS@ -L/usr/local/lib \
|
|||
|
||||
STRATAGUS_LIBS= -lpng -lz -lm \
|
||||
@EXTRA_LIBS@ @VIDEO_LIBS@ @THREAD_LIBS@ $(COMP_LIBS) \
|
||||
@FLAC_LIBS@ @VORBIS_LIBS@ @THEORA_LIBS@ @MNG_LIBS@ \
|
||||
@MAD_LIBS@ @MIKMOD_LIBS@ @LUA_LIBS@ @STATIC_LDFLAGS@ \
|
||||
@VORBIS_LIBS@ @THEORA_LIBS@ @MNG_LIBS@ \
|
||||
@MIKMOD_LIBS@ @LUA_LIBS@ @STATIC_LDFLAGS@ \
|
||||
-lz -lm $(LDFLAGS)
|
||||
|
||||
DISTLIST=$(TOPDIR)/distlist
|
||||
|
@ -52,7 +52,7 @@ CFLAGS=$(CPPFLAGS) $(IFLAGS) @DEFS@ -I. \
|
|||
@PROFILE_CFLAGS@ @STRATAGUS_CFLAGS@ @EXTRA_CFLAGS@ \
|
||||
@VIDEO_CFLAGS@ @BZ2_CFLAGS@ \
|
||||
@VORBIS_CFLAGS@ @THEORA_CFLAGS@ \
|
||||
@MNG_CFLAGS@ @MAD_CFLAGS@ @FLAC_CFLAGS@ \
|
||||
@MNG_CFLAGS@ \
|
||||
@MIKMOD_CFLAGS@ @LUA_CFLAGS@ \
|
||||
$(COMP_CFLAGS) @PLATFORM@ \
|
||||
|
||||
|
|
32
configure.in
32
configure.in
|
@ -262,38 +262,6 @@ AC_SUBST(MNG_CFLAGS)
|
|||
AC_SUBST(MNG_LIBS)
|
||||
dnl ----------------------------
|
||||
|
||||
dnl --- CHECK FOR MAD LIB ------
|
||||
AC_ARG_WITH(mad,
|
||||
[ --with-mad [Use mad mp3 (default: no)]], MAD="$with_mad")
|
||||
MAD_CFLAGS=
|
||||
MAD_LIBS=
|
||||
if test "$MAD" != "yes"; then
|
||||
MAD_CFLAGS=""
|
||||
MAD_LIBS=""
|
||||
else
|
||||
AC_CHECK_LIB(mad, mad_decoder_init,, AC_MSG_ERROR(libmad not found))
|
||||
MAD_CFLAGS="-DUSE_MAD"
|
||||
MAD_LIBS="-lmad"
|
||||
fi
|
||||
AC_SUBST(MAD_CFLAGS)
|
||||
AC_SUBST(MAD_LIBS)
|
||||
dnl ----------------------------
|
||||
|
||||
dnl --- CHECK FOR FLAC LIB -----
|
||||
AC_ARG_WITH(flac,
|
||||
[ --with-flac [Use FLAC (default: no)]], FLAC="$with_flac")
|
||||
if test "$FLAC" != "yes"; then
|
||||
FLAC_CFLAGS=
|
||||
FLAC_LIBS=
|
||||
else
|
||||
AC_CHECK_LIB(FLAC, FLAC__stream_decoder_init,, AC_MSG_ERROR(libFLAC not found))
|
||||
FLAC_CFLAGS="-DUSE_FLAC"
|
||||
FLAC_LIBS="-lFLAC"
|
||||
fi
|
||||
AC_SUBST(FLAC_CFLAGS)
|
||||
AC_SUBST(FLAC_LIBS)
|
||||
dnl ----------------------------
|
||||
|
||||
dnl ---- CHECK FOR LUA ---------
|
||||
AC_ARG_WITH(lua,
|
||||
[ --with-lua=prefix [Prefix where Lua is installed]], LUAPFX="$with_lua")
|
||||
|
|
|
@ -50,8 +50,6 @@
|
|||
<li><a href="http://www.xipg.org/">libogg</a> (recommended).<p>
|
||||
<li><a href="http://mikmod.raphnet.net/">libmikmod</a> (optional).<p>
|
||||
<li><a href="http://sources.redhat.com/bzip2/">libbzip2</a> (optional).<p>
|
||||
<li><a href="http://flac.sourceforge.net/">libFLAC</a> (optional).<p>
|
||||
<li><a href="http://www.underbit.com/products/mad/">libmad</a> (optional).<p>
|
||||
|
||||
<a href="http://stratagus.sourceforge.net/games.shtml">A stratagus game</a>.<br>
|
||||
</ul>
|
||||
|
@ -63,7 +61,7 @@
|
|||
architectures like (big endian) PPC or (64bit) Alpha should also
|
||||
work at least with Linux.</font><p>
|
||||
|
||||
<li><b>Memory:</b> 32 MB of RAM (64 MB for mp3/ogg support).<br><p>
|
||||
<li><b>Memory:</b> 32 MB of RAM (64 MB for ogg support).<br><p>
|
||||
|
||||
<li><b>Video Card:</b> Any graphics card that can handle 16 bpp
|
||||
and 640x480 is supported. (OpenGL is supported, and requires a 32MB Card)<p>
|
||||
|
|
|
@ -83,10 +83,8 @@ extern char *CurrentMusicFile;
|
|||
-- Functions
|
||||
----------------------------------------------------------------------------*/
|
||||
|
||||
extern CSample *LoadFlac(const char *name, int flags); /// Load a flac file
|
||||
extern CSample *LoadWav(const char *name, int flags); /// Load a wav file
|
||||
extern CSample *LoadVorbis(const char *name, int flags); /// Load a vorbis file
|
||||
extern CSample *LoadMp3(const char *name, int flags); /// Load a mp3 file
|
||||
extern CSample *LoadMikMod(const char *name, int flags); /// Load a module file
|
||||
|
||||
/// Set the channel volume
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
MODULE = src/sound
|
||||
MSRC = script_sound.cpp flac.cpp mad.cpp mikmod.cpp music.cpp ogg.cpp \
|
||||
MSRC = script_sound.cpp mikmod.cpp music.cpp ogg.cpp \
|
||||
sound.cpp sound_id.cpp sound_server.cpp unitsound.cpp wav.cpp
|
||||
|
||||
SRC += $(addprefix $(MODULE)/,$(MSRC))
|
||||
|
|
|
@ -1,369 +0,0 @@
|
|||
// _________ __ __
|
||||
// / _____// |_____________ _/ |______ ____ __ __ ______
|
||||
// \_____ \\ __\_ __ \__ \\ __\__ \ / ___\| | \/ ___/
|
||||
// / \| | | | \// __ \| | / __ \_/ /_/ > | /\___ |
|
||||
// /_______ /|__| |__| (____ /__| (____ /\___ /|____//____ >
|
||||
// \/ \/ \//_____/ \/
|
||||
// ______________________ ______________________
|
||||
// T H E W A R B E G I N S
|
||||
// Stratagus - A free fantasy real time strategy game engine
|
||||
//
|
||||
/**@name flac.cpp - flac support */
|
||||
//
|
||||
// (c) Copyright 2002-2005 by Lutz Sammer, Fabrice Rossi and Nehal Mistry
|
||||
//
|
||||
// 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; only version 2 of the License.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// $Id$
|
||||
|
||||
//@{
|
||||
|
||||
/*----------------------------------------------------------------------------
|
||||
-- Includes
|
||||
----------------------------------------------------------------------------*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include "stratagus.h"
|
||||
|
||||
#ifdef USE_FLAC // {
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "FLAC/stream_decoder.h"
|
||||
|
||||
#include "myendian.h"
|
||||
#include "iolib.h"
|
||||
#include "sound_server.h"
|
||||
|
||||
/*----------------------------------------------------------------------------
|
||||
-- Declaration
|
||||
----------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
** Private flac data structure to handle flac streaming.
|
||||
*/
|
||||
struct FlacData {
|
||||
FLAC__StreamDecoder *FlacStream; /// Decoder stream
|
||||
CFile *FlacFile; /// File handle
|
||||
};
|
||||
|
||||
class CSampleFlac : public CSample
|
||||
{
|
||||
public:
|
||||
~CSampleFlac();
|
||||
int Read(void *buf, int len);
|
||||
|
||||
FlacData Data;
|
||||
};
|
||||
|
||||
class CSampleFlacStream : public CSample
|
||||
{
|
||||
public:
|
||||
~CSampleFlacStream();
|
||||
int Read(void *buf, int len);
|
||||
|
||||
FlacData Data;
|
||||
};
|
||||
|
||||
struct FlacUserData {
|
||||
CSample *Sample;
|
||||
FlacData *Data;
|
||||
};
|
||||
|
||||
/*----------------------------------------------------------------------------
|
||||
-- Functions
|
||||
----------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
** Read callback from FLAC stream decoder.
|
||||
**
|
||||
** @param stream Decoder stream.
|
||||
** @param status Error state.
|
||||
** @param user User data.
|
||||
*/
|
||||
static void FLAC_error_callback(const FLAC__StreamDecoder *stream,
|
||||
FLAC__StreamDecoderErrorStatus status, void *user)
|
||||
{
|
||||
DebugPrint(" %s\n" _C_ FLAC__StreamDecoderErrorStatusString[status]);
|
||||
}
|
||||
|
||||
/**
|
||||
** Read callback from FLAC stream decoder.
|
||||
**
|
||||
** @param stream Decoder stream.
|
||||
** @param buffer Buffer to be filled.
|
||||
** @param bytes Number of bytes to be filled.
|
||||
** @param user User data.
|
||||
**
|
||||
** @return Error status.
|
||||
*/
|
||||
static FLAC__StreamDecoderReadStatus FLAC_read_callback(
|
||||
const FLAC__StreamDecoder *stream, FLAC__byte buffer[],
|
||||
unsigned int *bytes, void *user)
|
||||
{
|
||||
CSample *sample;
|
||||
FlacData *data;
|
||||
unsigned int i;
|
||||
|
||||
sample = ((FlacUserData *)user)->Sample;
|
||||
data = ((FlacUserData *)user)->Data;
|
||||
|
||||
if ((i = data->FlacFile->read(buffer, *bytes)) != *bytes) {
|
||||
*bytes = i;
|
||||
if (!i) {
|
||||
return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM;
|
||||
}
|
||||
}
|
||||
return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE;
|
||||
}
|
||||
|
||||
/**
|
||||
** Write callback from FLAC stream decoder.
|
||||
**
|
||||
** @param stream Decoder stream.
|
||||
** @param metadata metadata block
|
||||
** @param user User data.
|
||||
*/
|
||||
static void FLAC_metadata_callback(const FLAC__StreamDecoder *stream,
|
||||
const FLAC__StreamMetadata *metadata, void *user)
|
||||
{
|
||||
CSample *sample;
|
||||
|
||||
if (metadata->type == FLAC__METADATA_TYPE_STREAMINFO) {
|
||||
sample = ((FlacUserData *)user)->Sample;
|
||||
|
||||
sample->Channels = metadata->data.stream_info.channels;
|
||||
sample->Frequency = metadata->data.stream_info.sample_rate;
|
||||
sample->SampleSize = metadata->data.stream_info.bits_per_sample;
|
||||
|
||||
if (!sample->Buffer) {
|
||||
Assert(metadata->data.stream_info.total_samples);
|
||||
sample->Buffer = new unsigned char[metadata->data.stream_info.total_samples * 4];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
** Write callback from FLAC stream decoder.
|
||||
**
|
||||
** @param stream Decoder stream.
|
||||
** @param frame Frame to decode.
|
||||
** @param buffer Buffer to be filled.
|
||||
** @param user User data.
|
||||
**
|
||||
** @return Error status.
|
||||
*/
|
||||
static FLAC__StreamDecoderWriteStatus FLAC_write_callback(
|
||||
const FLAC__StreamDecoder *stream, const FLAC__Frame *frame,
|
||||
const FLAC__int32 *const buffer[], void *user)
|
||||
{
|
||||
CSample *sample;
|
||||
FlacData *data;
|
||||
unsigned int i;
|
||||
int j;
|
||||
char *buf;
|
||||
int ssize;
|
||||
|
||||
sample = ((FlacUserData *)user)->Sample;
|
||||
data = ((FlacUserData *)user)->Data;
|
||||
|
||||
Assert(sample->Buffer);
|
||||
Assert(frame->header.bits_per_sample == sample->SampleSize);
|
||||
|
||||
ssize = (frame->header.bits_per_sample / 8);
|
||||
buf = new char[frame->header.blocksize * sample->Channels * ssize];
|
||||
|
||||
// FIXME: mono flac files don't play correctly
|
||||
|
||||
// FLAC splits it up the channels, we need to sew it back together
|
||||
for (i = 0; i < frame->header.blocksize; ++i) {
|
||||
for (j = 0; j < sample->Channels; ++j) {
|
||||
if (ssize == 1) {
|
||||
buf[i * sample->Channels + j] = ((const char *)buffer[j])[i];
|
||||
} else {
|
||||
buf[i * 2 * sample->Channels + j * 2] = ((const char *)buffer[j])[i * 2];
|
||||
buf[i * 2 * sample->Channels + j * 2 + 1] = ((const char *)buffer[j])[i * 2 + 1];
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
memcpy(sample->Buffer + sample->Pos + sample->Len, buf,
|
||||
frame->header.blocksize * sample->Channels * ssize);
|
||||
sample->Len += frame->header.blocksize * sample->Channels * ssize;
|
||||
|
||||
delete[] buf;
|
||||
|
||||
return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
** Type member function to read from the flac file
|
||||
**
|
||||
** @param buf Buffer to write data to
|
||||
** @param len Length of the buffer
|
||||
**
|
||||
** @return Number of bytes read
|
||||
*/
|
||||
int CSampleFlacStream::Read(void *buf, int len)
|
||||
{
|
||||
if (this->Pos > SOUND_BUFFER_SIZE / 2) {
|
||||
memcpy(this->Buffer, this->Buffer + this->Pos, this->Len);
|
||||
this->Pos = 0;
|
||||
}
|
||||
|
||||
while (this->Len < SOUND_BUFFER_SIZE / 4 &&
|
||||
FLAC__stream_decoder_get_state(this->Data.FlacStream) != FLAC__STREAM_DECODER_END_OF_STREAM) {
|
||||
// need to read new data
|
||||
FLAC__stream_decoder_process_single(this->Data.FlacStream);
|
||||
}
|
||||
|
||||
if (this->Len < len) {
|
||||
len = this->Len;
|
||||
}
|
||||
|
||||
memcpy(buf, this->Buffer + this->Pos, len);
|
||||
this->Pos += len;
|
||||
this->Len -= len;
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
/**
|
||||
** Type member function to free an flac file
|
||||
*/
|
||||
CSampleFlacStream::~CSampleFlacStream()
|
||||
{
|
||||
this->Data.FlacFile->close();
|
||||
delete this->Data.FlacFile;
|
||||
FLAC__stream_decoder_finish(this->Data.FlacStream);
|
||||
FLAC__stream_decoder_delete(this->Data.FlacStream);
|
||||
delete[] this->Buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
** Type member function to read from the flac file
|
||||
**
|
||||
** @param buf Buffer to write data to
|
||||
** @param len Length of the buffer
|
||||
**
|
||||
** @return Number of bytes read
|
||||
*/
|
||||
int CSampleFlac::Read(void *buf, int len)
|
||||
{
|
||||
if (len > this->Len) {
|
||||
len = this->Len;
|
||||
}
|
||||
|
||||
memcpy(buf, this->Buffer + this->Pos, len);
|
||||
this->Pos += len;
|
||||
this->Len -= len;
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
/**
|
||||
** Type member function to free an flac file
|
||||
*/
|
||||
CSampleFlac::~CSampleFlac()
|
||||
{
|
||||
delete[] this->Buffer;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
** Load flac.
|
||||
**
|
||||
** @param name File name.
|
||||
** @param flags Load flags.
|
||||
**
|
||||
** @return Returns the loaded sample.
|
||||
*/
|
||||
CSample *LoadFlac(const char *name, int flags)
|
||||
{
|
||||
CSample *sample;
|
||||
FlacData *data;
|
||||
CFile *f;
|
||||
unsigned int magic[1];
|
||||
FLAC__StreamDecoder *stream;
|
||||
|
||||
f = new CFile;
|
||||
if (f->open(name, CL_OPEN_READ) == -1) {
|
||||
fprintf(stderr, "Can't open file `%s'\n", name);
|
||||
delete f;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
f->read(magic, sizeof(magic));
|
||||
if (AccessLE32(magic) != 0x43614C66) { // "fLaC" in ASCII
|
||||
f->close();
|
||||
delete f;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
f->seek(0, SEEK_SET);
|
||||
|
||||
if (!(stream = FLAC__stream_decoder_new())) {
|
||||
fprintf(stderr, "Can't initialize flac decoder\n");
|
||||
f->close();
|
||||
delete f;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (flags & PlayAudioStream) {
|
||||
sample = new CSampleFlacStream;
|
||||
data = &((CSampleFlacStream *)sample)->Data;
|
||||
} else {
|
||||
sample = new CSampleFlac;
|
||||
data = &((CSampleFlac *)sample)->Data;
|
||||
}
|
||||
data->FlacFile = f;
|
||||
data->FlacStream = stream;
|
||||
sample->Len = 0;
|
||||
sample->Pos = 0;
|
||||
|
||||
FLAC__stream_decoder_set_read_callback(stream, FLAC_read_callback);
|
||||
FLAC__stream_decoder_set_write_callback(stream, FLAC_write_callback);
|
||||
FLAC__stream_decoder_set_metadata_callback(stream, FLAC_metadata_callback);
|
||||
FLAC__stream_decoder_set_error_callback(stream, FLAC_error_callback);
|
||||
FlacUserData d = { sample, data };
|
||||
FLAC__stream_decoder_set_client_data(stream, &d);
|
||||
FLAC__stream_decoder_init(stream);
|
||||
|
||||
if (flags & PlayAudioStream) {
|
||||
sample->Buffer = new unsigned char[SOUND_BUFFER_SIZE];
|
||||
FLAC__stream_decoder_process_until_end_of_metadata(stream);
|
||||
} else {
|
||||
// Buffer will be new'ed from metadata callback
|
||||
sample->Buffer = NULL;
|
||||
|
||||
Assert(FLAC__stream_decoder_get_state(stream) ==
|
||||
FLAC__STREAM_DECODER_SEARCH_FOR_METADATA);
|
||||
FLAC__stream_decoder_process_until_end_of_stream(stream);
|
||||
Assert(FLAC__stream_decoder_get_state(stream) ==
|
||||
FLAC__STREAM_DECODER_END_OF_STREAM);
|
||||
|
||||
FLAC__stream_decoder_finish(stream);
|
||||
FLAC__stream_decoder_delete(stream);
|
||||
f->close();
|
||||
delete f;
|
||||
}
|
||||
return sample;
|
||||
}
|
||||
|
||||
#endif // USE_FLAC
|
||||
|
||||
//@}
|
|
@ -1,479 +0,0 @@
|
|||
// _________ __ __
|
||||
// / _____// |_____________ _/ |______ ____ __ __ ______
|
||||
// \_____ \\ __\_ __ \__ \\ __\__ \ / ___\| | \/ ___/
|
||||
// / \| | | | \// __ \| | / __ \_/ /_/ > | /\___ |
|
||||
// /_______ /|__| |__| (____ /__| (____ /\___ /|____//____ >
|
||||
// \/ \/ \//_____/ \/
|
||||
// ______________________ ______________________
|
||||
// T H E W A R B E G I N S
|
||||
// Stratagus - A free fantasy real time strategy game engine
|
||||
//
|
||||
/**@name mad.cpp - mp3 support with libmad */
|
||||
//
|
||||
// (c) Copyright 2002-2005 by Lutz Sammer
|
||||
//
|
||||
// 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; only version 2 of the License.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// $Id$
|
||||
|
||||
//@{
|
||||
|
||||
/*----------------------------------------------------------------------------
|
||||
-- Includes
|
||||
----------------------------------------------------------------------------*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include "stratagus.h"
|
||||
|
||||
#ifdef USE_MAD // {
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <memory.h>
|
||||
#include "mad.h"
|
||||
|
||||
#include "iolib.h"
|
||||
#include "sound_server.h"
|
||||
|
||||
/*----------------------------------------------------------------------------
|
||||
-- Declaration
|
||||
----------------------------------------------------------------------------*/
|
||||
|
||||
#define MAD_INBUF_SIZE 65536
|
||||
|
||||
/**
|
||||
** Private mp3 data structure to handle mp3 streaming.
|
||||
*/
|
||||
struct MadData {
|
||||
struct mad_decoder MadDecoder; /// Mad decoder handle
|
||||
CFile *MadFile; /// File handle
|
||||
unsigned char Buffer[MAD_INBUF_SIZE]; /// Input buffer
|
||||
int BufferLen; /// Length of filled buffer
|
||||
};
|
||||
|
||||
class CSampleMad : public CSample
|
||||
{
|
||||
public:
|
||||
~CSampleMad();
|
||||
int Read(void *buf, int len);
|
||||
|
||||
MadData Data;
|
||||
};
|
||||
|
||||
class CSampleMadStream : public CSample
|
||||
{
|
||||
public:
|
||||
~CSampleMadStream();
|
||||
int Read(void *buf, int len);
|
||||
|
||||
MadData Data;
|
||||
};
|
||||
|
||||
struct MadUserData {
|
||||
CSample *Sample;
|
||||
MadData *Data;
|
||||
};
|
||||
|
||||
/*----------------------------------------------------------------------------
|
||||
-- Functions
|
||||
----------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
** MAD read callback.
|
||||
**
|
||||
** @param user Our user pointer.
|
||||
** @param stream MP3 stream.
|
||||
**
|
||||
** @return MAP_FLOW_STOP if eof, MAD_FLOW_CONTINUE otherwise.
|
||||
*/
|
||||
static enum mad_flow MAD_read(void *user, struct mad_stream *stream)
|
||||
{
|
||||
CSample *sample;
|
||||
MadData *data;
|
||||
int i;
|
||||
|
||||
sample = ((MadUserData *)user)->Sample;
|
||||
data = ((MadUserData *)user)->Data;
|
||||
|
||||
if (stream->next_frame) {
|
||||
memmove(data->Buffer, stream->next_frame, data->BufferLen =
|
||||
&data->Buffer[data->BufferLen] - stream->next_frame);
|
||||
}
|
||||
|
||||
i = data->MadFile->read(data->Buffer + data->BufferLen, MAD_INBUF_SIZE - data->BufferLen);
|
||||
if (!i) {
|
||||
return MAD_FLOW_STOP;
|
||||
}
|
||||
|
||||
data->BufferLen += i;
|
||||
mad_stream_buffer(stream, data->Buffer, data->BufferLen);
|
||||
|
||||
return MAD_FLOW_CONTINUE;
|
||||
}
|
||||
|
||||
/**
|
||||
** 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 the decoded PCM audio.
|
||||
**
|
||||
** @param user User argument.
|
||||
** @param header MAD header.
|
||||
** @param pcm MAD pcm data struture.
|
||||
*/
|
||||
static enum mad_flow MAD_write(void *user,
|
||||
struct mad_header const *header, struct mad_pcm *pcm)
|
||||
{
|
||||
CSample *sample;
|
||||
int i;
|
||||
int j;
|
||||
int n;
|
||||
short *buf;
|
||||
int s;
|
||||
int comp;
|
||||
|
||||
sample = ((MadUserData *)user)->Sample;
|
||||
|
||||
n = pcm->length;
|
||||
|
||||
if (!sample->SampleSize) {
|
||||
sample->Frequency = pcm->samplerate;
|
||||
sample->Channels = pcm->channels;
|
||||
sample->SampleSize = 16;
|
||||
}
|
||||
|
||||
comp = n * pcm->channels * 2;
|
||||
|
||||
buf = (short *)(sample->Buffer + sample->Pos + sample->Len);
|
||||
|
||||
for (i = 0; i < n; ++i) {
|
||||
for (j = 0; j < sample->Channels; ++j) {
|
||||
s = pcm->samples[j][i];
|
||||
// round
|
||||
s += (1L << (MAD_F_FRACBITS - 16));
|
||||
// clip
|
||||
if (s >= MAD_F_ONE) {
|
||||
s = MAD_F_ONE - 1;
|
||||
} else if (s < -MAD_F_ONE) {
|
||||
s = -MAD_F_ONE;
|
||||
}
|
||||
// quantize
|
||||
s >>= (MAD_F_FRACBITS + 1 - 16);
|
||||
buf[i * sample->Channels + j] = s;
|
||||
}
|
||||
}
|
||||
|
||||
sample->Len += comp;
|
||||
|
||||
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 MAD_error(void *user,
|
||||
struct mad_stream *stream, struct mad_frame *frame)
|
||||
{
|
||||
fprintf(stderr, "decoding error 0x%04x (%s)\n",
|
||||
stream->error, mad_stream_errorstr(stream));
|
||||
|
||||
return MAD_FLOW_BREAK;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
/**
|
||||
** Read one frame from mad decoder.
|
||||
**
|
||||
** @param sample Sample
|
||||
** @param data Mad data
|
||||
** @param buf Buffer to write data to
|
||||
** @param len Length of the buffer
|
||||
**
|
||||
** @return Number of bytes read
|
||||
*/
|
||||
static int MadRead(CSample *sample, MadData *data, unsigned char *buf, int len)
|
||||
{
|
||||
struct mad_decoder *decoder;
|
||||
struct mad_stream *stream;
|
||||
struct mad_frame *frame;
|
||||
struct mad_synth *synth;
|
||||
|
||||
decoder = &data->MadDecoder;
|
||||
|
||||
DebugPrint("%p %p %d\n" _C_ decoder _C_ buf _C_ len);
|
||||
|
||||
stream = &decoder->sync->stream;
|
||||
frame = &decoder->sync->frame;
|
||||
synth = &decoder->sync->synth;
|
||||
DebugPrint("Error: %d\n" _C_ stream->error);
|
||||
|
||||
MadUserData d = { sample, data };
|
||||
MAD_read(&d, stream);
|
||||
|
||||
if (mad_frame_decode(frame, stream) == -1) {
|
||||
Assert(0);
|
||||
}
|
||||
mad_synth_frame(synth, frame);
|
||||
|
||||
decoder->output_func(decoder->cb_data, &frame->header, &synth->pcm);
|
||||
|
||||
|
||||
return 0;
|
||||
|
||||
do {
|
||||
DebugPrint("Read stream\n");
|
||||
switch (MAD_read(decoder->cb_data, stream)) {
|
||||
case MAD_FLOW_STOP:
|
||||
return 0;
|
||||
case MAD_FLOW_BREAK:
|
||||
return -1;
|
||||
case MAD_FLOW_IGNORE:
|
||||
continue;
|
||||
case MAD_FLOW_CONTINUE:
|
||||
break;
|
||||
}
|
||||
|
||||
while (1) {
|
||||
if (mad_frame_decode(frame, stream) == -1) {
|
||||
if (!MAD_RECOVERABLE(stream->error)) {
|
||||
break;
|
||||
}
|
||||
|
||||
switch (MAD_error(decoder->cb_data, stream, frame)) {
|
||||
case MAD_FLOW_STOP:
|
||||
return 0;
|
||||
case MAD_FLOW_BREAK:
|
||||
return -1;
|
||||
case MAD_FLOW_IGNORE:
|
||||
break;
|
||||
case MAD_FLOW_CONTINUE:
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
mad_synth_frame(synth, frame);
|
||||
|
||||
#if 0
|
||||
// FIXME: write out the frame buffer!
|
||||
switch (decoder->output_func(decoder->cb_data, &frame->header,
|
||||
&synth->pcm)) {
|
||||
case MAD_FLOW_STOP:
|
||||
return 0;
|
||||
case MAD_FLOW_BREAK:
|
||||
return -1;
|
||||
case MAD_FLOW_IGNORE:
|
||||
case MAD_FLOW_CONTINUE:
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
// Should stop here!
|
||||
} while (stream->error == MAD_ERROR_BUFLEN);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
** Type member function to read from the mp3 file
|
||||
**
|
||||
** @param buf Buffer to write data to
|
||||
** @param len Length of the buffer
|
||||
**
|
||||
** @return Number of bytes read
|
||||
*/
|
||||
int CSampleMadStream::Read(void *buf, int len)
|
||||
{
|
||||
int i;
|
||||
int n;
|
||||
int divide;
|
||||
unsigned char sndbuf[SOUND_BUFFER_SIZE];
|
||||
|
||||
DebugPrint("%p %d\n" _C_ buf _C_ len);
|
||||
|
||||
if (this->Pos > SOUND_BUFFER_SIZE / 2) {
|
||||
memcpy(this->Buffer, this->Buffer + this->Pos, this->Len);
|
||||
this->Pos = 0;
|
||||
}
|
||||
|
||||
divide = 176400 / (this->Frequency * 2 * this->Channels);
|
||||
|
||||
while (this->Len < SOUND_BUFFER_SIZE / 4) {
|
||||
// not enough in buffer, read more
|
||||
n = (SOUND_BUFFER_SIZE - this->Len) / divide;
|
||||
|
||||
i = MadRead(this, &this->Data, sndbuf, n);
|
||||
if (i <= 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
this->Len += i;
|
||||
}
|
||||
|
||||
if (this->Len < len) {
|
||||
len = this->Len;
|
||||
}
|
||||
|
||||
memcpy(buf, this->Buffer + this->Pos, len);
|
||||
this->Pos += len;
|
||||
this->Len -= len;
|
||||
|
||||
return len;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
** Type member function to free an mp3 file
|
||||
*/
|
||||
CSampleMadStream::~CSampleMadStream()
|
||||
{
|
||||
// release the decoder
|
||||
mad_synth_finish(this->Data.MadDecoder.sync->synth);
|
||||
mad_frame_finish(&this->Data.MadDecoder.sync->frame);
|
||||
mad_stream_finish(&this->Data.MadDecoder.sync->stream);
|
||||
|
||||
// delete this->Data.MadDecoder.sync;
|
||||
mad_decoder_finish(&this->Data.MadDecoder);
|
||||
|
||||
this->Data.MadFile->close();
|
||||
delete this->Data.MadFile;
|
||||
}
|
||||
|
||||
/**
|
||||
** Type member function to read from the mp3 file
|
||||
**
|
||||
** @param buf Buffer to write data to
|
||||
** @param len Length of the buffer
|
||||
**
|
||||
** @return Number of bytes read
|
||||
*/
|
||||
int CSampleMad::Read(void *buf, int len)
|
||||
{
|
||||
if (len > this->Len) {
|
||||
len = this->Len;
|
||||
}
|
||||
|
||||
memcpy(buf, this->Buffer + this->Pos, len);
|
||||
this->Pos += len;
|
||||
this->Len -= len;
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
/**
|
||||
** Type member function to free an mp3 file
|
||||
*/
|
||||
CSampleMad::~CSampleMad()
|
||||
{
|
||||
delete[] this->Buffer;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
** Load mp3.
|
||||
**
|
||||
** @param name File name.
|
||||
** @param flags Load flags.
|
||||
**
|
||||
** @return Returns the loaded sample.
|
||||
*/
|
||||
CSample *LoadMp3(const char *name, int flags)
|
||||
{
|
||||
CFile *f;
|
||||
unsigned char magic[2];
|
||||
CSample *sample;
|
||||
MadData *data;
|
||||
|
||||
f = new CFile;
|
||||
if (f->open(name, CL_OPEN_READ) == -1) {
|
||||
fprintf(stderr, "Can't open file `%s'\n", name);
|
||||
delete f;
|
||||
return NULL;
|
||||
}
|
||||
f->read(magic, sizeof(magic));
|
||||
// 0xFF 0xE? for mp3 stream
|
||||
if (magic[0] != 0xFF || (magic[1]&0xE0) != 0xE0) {
|
||||
f->close();
|
||||
delete f;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
f->seek(0, SEEK_SET);
|
||||
|
||||
if (0 && flags & PlayAudioStream) {
|
||||
sample = new CSampleMadStream;
|
||||
data = &((CSampleMadStream *)sample)->Data;
|
||||
} else {
|
||||
sample = new CSampleMad;
|
||||
data = &((CSampleMad *)sample)->Data;
|
||||
}
|
||||
data->MadFile = f;
|
||||
data->BufferLen = 0;
|
||||
sample->Len = 0;
|
||||
sample->Pos = 0;
|
||||
sample->SampleSize = 0;
|
||||
|
||||
// streaming currently broken
|
||||
if (0 && flags & PlayAudioStream) {
|
||||
#if 0
|
||||
sample->SampleSize = 0;
|
||||
|
||||
// configure input, output, and error functions
|
||||
mad_decoder_init(&data->MadDecoder, sample,
|
||||
MAD_read, NULL /* header */, NULL /* filter */, MAD_write,
|
||||
MAD_error, NULL /* message */);
|
||||
|
||||
data->MadDecoder.sync = malloc(sizeof(*data->MadDecoder.sync));
|
||||
|
||||
mad_stream_init(&data->MadDecoder.sync->stream);
|
||||
mad_frame_init(&data->MadDecoder.sync->frame);
|
||||
mad_synth_init(&data->MadDecoder.sync->synth);
|
||||
mad_stream_options(&data->MadDecoder.sync->stream,
|
||||
data->MadDecoder.options);
|
||||
|
||||
MadRead(sample, &sample->Data, sample->Buffer, SOUND_BUFFER_SIZE);
|
||||
#endif
|
||||
} else {
|
||||
// FIXME: surely there's a better way to do this
|
||||
sample->Buffer = new unsigned char[55000000];
|
||||
Assert(sample->Buffer);
|
||||
|
||||
// configure input, output, and error functions
|
||||
|
||||
MadUserData d = { sample, data };
|
||||
mad_decoder_init(&data->MadDecoder, &d,
|
||||
MAD_read, NULL /* header */, NULL /* filter */, MAD_write,
|
||||
MAD_error, NULL /* message */);
|
||||
|
||||
mad_decoder_run(&data->MadDecoder, MAD_DECODER_MODE_SYNC);
|
||||
|
||||
// release the decoder
|
||||
mad_decoder_finish(&data->MadDecoder);
|
||||
f->close();
|
||||
delete f;
|
||||
|
||||
DebugPrint(" %d\n" _C_ sample->Len);
|
||||
}
|
||||
|
||||
return sample;
|
||||
}
|
||||
|
||||
#endif // USE_MAD
|
||||
|
||||
//@}
|
|
@ -393,7 +393,6 @@ void SetSoundRange(CSound *sound, unsigned char range)
|
|||
** @return the sound unique identifier
|
||||
**
|
||||
** @todo FIXME: Must handle the errors better.
|
||||
** FIXME: Support for more sample files (ogg/flac/mp3).
|
||||
*/
|
||||
CSound *RegisterSound(const char *files[], unsigned number)
|
||||
{
|
||||
|
|
|
@ -517,16 +517,6 @@ CSample *LoadSample(const char *name)
|
|||
return sample;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_FLAC
|
||||
if ((sample = LoadFlac(buf, PlayAudioLoadInMemory))) {
|
||||
return sample;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_MAD
|
||||
if ((sample = LoadMp3(buf, PlayAudioLoadInMemory))) {
|
||||
return sample;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_MIKMOD
|
||||
if ((sample = LoadMikMod(buf, PlayAudioLoadInMemory))) {
|
||||
return sample;
|
||||
|
@ -682,16 +672,6 @@ int PlayMusic(const char *file)
|
|||
sample = LoadVorbis(name, PlayAudioStream);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_MAD
|
||||
if (!sample) {
|
||||
sample = LoadMp3(name, PlayAudioStream);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_FLAC
|
||||
if (!sample) {
|
||||
sample = LoadFlac(name, PlayAudioStream);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_MIKMOD
|
||||
if (!sample) {
|
||||
sample = LoadMikMod(name, PlayAudioStream);
|
||||
|
|
|
@ -95,7 +95,7 @@
|
|||
** @see script_sound.cpp @see sound_id.cpp @see sound_server.cpp
|
||||
** @see unitsound.cpp
|
||||
** @see sdl_audio.cpp
|
||||
** @see mad.cpp @see ogg.cpp @see flac.cpp @see wav.cpp
|
||||
** @see ogg.cpp @see wav.cpp
|
||||
**
|
||||
** @subsection Video Video
|
||||
**
|
||||
|
@ -901,18 +901,12 @@ int main(int argc, char **argv)
|
|||
#ifdef USE_BZ2LIB
|
||||
"BZ2LIB "
|
||||
#endif
|
||||
#ifdef USE_FLAC
|
||||
"FLAC "
|
||||
#endif
|
||||
#ifdef USE_VORBIS
|
||||
"VORBIS "
|
||||
#endif
|
||||
#ifdef USE_THEORA
|
||||
"THEORA "
|
||||
#endif
|
||||
#ifdef USE_MAD
|
||||
"MP3 "
|
||||
#endif
|
||||
#ifdef USE_MIKMOD
|
||||
"MIKMOD "
|
||||
#endif
|
||||
|
|
|
@ -43,7 +43,7 @@ RSC=rc.exe
|
|||
# PROP Ignore_Export_Lib 0
|
||||
# PROP Target_Dir ""
|
||||
# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /c
|
||||
# ADD CPP /nologo /MD /W3 /GR /GX /O2 /I "src\include" /I "include" /I "src\guichan\include" /D "NDEBUG" /D "USE_WIN32" /D "USE_MNG" /D "USE_ZLIB" /D "USE_BZ2LIB" /D "USE_MIKMOD" /D "USE_VORBIS" /D "USE_THEORA" /D "USE_MAD" /D "USE_FLAC" /D "WIN32" /D "_WINDOWS" /D "_MBCS" /YX /FD /c
|
||||
# ADD CPP /nologo /MD /W3 /GR /GX /O2 /I "src\include" /I "include" /I "src\guichan\include" /D "NDEBUG" /D "USE_WIN32" /D "USE_MNG" /D "USE_ZLIB" /D "USE_BZ2LIB" /D "USE_MIKMOD" /D "USE_VORBIS" /D "USE_THEORA" /D "WIN32" /D "_WINDOWS" /D "_MBCS" /YX /FD /c
|
||||
# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32
|
||||
# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
|
||||
# ADD BASE RSC /l 0x409 /d "NDEBUG"
|
||||
|
@ -53,7 +53,7 @@ BSC32=bscmake.exe
|
|||
# ADD BSC32 /nologo
|
||||
LINK32=link.exe
|
||||
# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /machine:I386
|
||||
# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib ws2_32.lib winmm.lib opengl32.lib SDL.lib SDLmain.lib zlib.lib libbz2.lib libpng.lib lua.lib ogg_static.lib vorbis_static.lib mikmod.lib libFLAC_static.lib libmad.lib theora_static.lib libmng.lib /nologo /stack:0x2000000 /subsystem:windows /profile /machine:I386 /libpath:"lib"
|
||||
# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib ws2_32.lib winmm.lib opengl32.lib SDL.lib SDLmain.lib zlib.lib libbz2.lib libpng.lib lua.lib ogg_static.lib vorbis_static.lib mikmod.lib theora_static.lib libmng.lib /nologo /stack:0x2000000 /subsystem:windows /profile /machine:I386 /libpath:"lib"
|
||||
|
||||
!ELSEIF "$(CFG)" == "stratagus - Win32 Debug"
|
||||
|
||||
|
@ -69,7 +69,7 @@ LINK32=link.exe
|
|||
# PROP Ignore_Export_Lib 0
|
||||
# PROP Target_Dir ""
|
||||
# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /GZ /c
|
||||
# ADD CPP /nologo /MDd /W3 /Gm /GR /GX /ZI /Od /I "src\include" /I "include" /I "src\guichan\include" /D "_DEBUG" /D "DEBUG" /D "USE_WIN32" /D "USE_MNG" /D "USE_ZLIB" /D "USE_BZ2LIB" /D "USE_MIKMOD" /D "USE_VORBIS" /D "USE_THEORA" /D "USE_MAD" /D "USE_FLAC" /D "WIN32" /D "_WINDOWS" /D "_MBCS" /FR /YX /FD /GZ /c
|
||||
# ADD CPP /nologo /MDd /W3 /Gm /GR /GX /ZI /Od /I "src\include" /I "include" /I "src\guichan\include" /D "_DEBUG" /D "DEBUG" /D "USE_WIN32" /D "USE_MNG" /D "USE_ZLIB" /D "USE_BZ2LIB" /D "USE_MIKMOD" /D "USE_VORBIS" /D "USE_THEORA" /D "WIN32" /D "_WINDOWS" /D "_MBCS" /FR /YX /FD /GZ /c
|
||||
# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32
|
||||
# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
|
||||
# ADD BASE RSC /l 0x409 /d "_DEBUG"
|
||||
|
@ -79,7 +79,7 @@ BSC32=bscmake.exe
|
|||
# ADD BSC32 /nologo
|
||||
LINK32=link.exe
|
||||
# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /debug /machine:I386 /pdbtype:sept
|
||||
# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib ws2_32.lib winmm.lib opengl32.lib SDL.lib SDLmain.lib zlib.lib libbz2.lib libpng.lib lua.lib ogg_static.lib vorbis_static.lib mikmod.lib libFLAC_static.lib libmad.lib theora_static.lib libmng.lib /nologo /stack:0x2000000 /subsystem:windows /debug /machine:I386 /nodefaultlib:"MSVCRT" /libpath:"lib"
|
||||
# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib ws2_32.lib winmm.lib opengl32.lib SDL.lib SDLmain.lib zlib.lib libbz2.lib libpng.lib lua.lib ogg_static.lib vorbis_static.lib mikmod.lib theora_static.lib libmng.lib /nologo /stack:0x2000000 /subsystem:windows /debug /machine:I386 /nodefaultlib:"MSVCRT" /libpath:"lib"
|
||||
# SUBTRACT LINK32 /profile
|
||||
|
||||
!ENDIF
|
||||
|
@ -487,14 +487,6 @@ SOURCE=.\src\pathfinder\script_pathfinder.cpp
|
|||
# PROP Default_Filter ""
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=.\src\sound\flac.cpp
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=.\src\sound\mad.cpp
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=.\src\sound\mikmod.cpp
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
|
Loading…
Reference in a new issue