- added a dedicated player class for streamed music formats (i.e. MP3, Ogg and Flac)

The idea is to have more control on the game side instead of dealing with these formats in the backend, which was done for FMod because it already had the decoders implemented.
However, with OpenAL this setup makes no sense and only complicates future extensions that can be better handled at a higher level.
This commit is contained in:
Christoph Oelckers 2017-04-01 19:46:38 +02:00
parent 4a0b3c3bab
commit dfd3535e02
11 changed files with 281 additions and 11 deletions

View file

@ -1245,6 +1245,7 @@ set (PCH_SOURCES
sound/musicformats/music_cd.cpp sound/musicformats/music_cd.cpp
sound/musicformats/music_dumb.cpp sound/musicformats/music_dumb.cpp
sound/musicformats/music_gme.cpp sound/musicformats/music_gme.cpp
sound/musicformats/music_libsndfile.cpp
sound/musicformats/music_mus_midiout.cpp sound/musicformats/music_mus_midiout.cpp
sound/musicformats/music_smf_midiout.cpp sound/musicformats/music_smf_midiout.cpp
sound/musicformats/music_hmi_midiout.cpp sound/musicformats/music_hmi_midiout.cpp

View file

@ -488,6 +488,11 @@ retry_as_sndsys:
{ {
info = MOD_OpenSong(*reader); info = MOD_OpenSong(*reader);
} }
if (info == nullptr)
{
info = SndFile_OpenSong(*reader);
if (info != nullptr) reader = nullptr;
}
if (info == NULL) if (info == NULL)
{ {

View file

@ -672,6 +672,7 @@ MusInfo *MOD_OpenSong(FileReader &reader);
const char *GME_CheckFormat(uint32_t header); const char *GME_CheckFormat(uint32_t header);
MusInfo *GME_OpenSong(FileReader &reader, const char *fmt); MusInfo *GME_OpenSong(FileReader &reader, const char *fmt);
MusInfo *SndFile_OpenSong(FileReader &fr);
// -------------------------------------------------------------------------- // --------------------------------------------------------------------------

View file

@ -160,8 +160,7 @@ public:
virtual MIDIDevice* CreateMIDIDevice() const = 0; virtual MIDIDevice* CreateMIDIDevice() const = 0;
protected: static SoundDecoder *CreateDecoder(FileReader *reader);
virtual SoundDecoder *CreateDecoder(FileReader *reader);
}; };
extern SoundRenderer *GSnd; extern SoundRenderer *GSnd;

View file

@ -132,7 +132,7 @@ struct SoundDecoder
virtual size_t read(char *buffer, size_t bytes) = 0; virtual size_t read(char *buffer, size_t bytes) = 0;
virtual TArray<char> readAll(); virtual TArray<char> readAll();
virtual bool seek(size_t ms_offset) = 0; virtual bool seek(size_t ms_offset, bool ms) = 0;
virtual size_t getSampleOffset() = 0; virtual size_t getSampleOffset() = 0;
virtual size_t getSampleLength() { return 0; } virtual size_t getSampleLength() { return 0; }

View file

@ -134,14 +134,14 @@ size_t MPG123Decoder::read(char *buffer, size_t bytes)
return amt; return amt;
} }
bool MPG123Decoder::seek(size_t ms_offset) bool MPG123Decoder::seek(size_t ms_offset, bool ms)
{ {
int enc, channels; int enc, channels;
long srate; long srate;
if(mpg123_getformat(MPG123, &srate, &channels, &enc) == MPG123_OK) if(mpg123_getformat(MPG123, &srate, &channels, &enc) == MPG123_OK)
{ {
size_t smp_offset = (size_t)((double)ms_offset / 1000. * srate); size_t smp_offset = ms? (size_t)((double)ms_offset / 1000. * srate) : ms_offset;
if(mpg123_seek(MPG123, (off_t)smp_offset, SEEK_SET) >= 0) if(mpg123_seek(MPG123, (off_t)smp_offset, SEEK_SET) >= 0)
{ {
Done = false; Done = false;

View file

@ -16,7 +16,7 @@ struct MPG123Decoder : public SoundDecoder
virtual void getInfo(int *samplerate, ChannelConfig *chans, SampleType *type); virtual void getInfo(int *samplerate, ChannelConfig *chans, SampleType *type);
virtual size_t read(char *buffer, size_t bytes); virtual size_t read(char *buffer, size_t bytes);
virtual bool seek(size_t ms_offset); virtual bool seek(size_t ms_offset, bool ms);
virtual size_t getSampleOffset(); virtual size_t getSampleOffset();
virtual size_t getSampleLength(); virtual size_t getSampleLength();

View file

@ -0,0 +1,264 @@
/*
** music_libsndfile.cpp
** Uses libsndfile for streaming music formats
**
**---------------------------------------------------------------------------
** Copyright 2017 Christoph Oelckers
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
** derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**---------------------------------------------------------------------------
**
*/
// HEADER FILES ------------------------------------------------------------
#include "i_musicinterns.h"
#include "c_cvars.h"
#include "critsec.h"
#include "v_text.h"
#include "files.h"
#include "templates.h"
#include "sndfile_decoder.h"
#include "mpg123_decoder.h"
// MACROS ------------------------------------------------------------------
// TYPES -------------------------------------------------------------------
class SndFileSong : public StreamSong
{
public:
SndFileSong(FileReader *reader, SoundDecoder *decoder, uint32_t loop_start, uint32_t loop_end);
~SndFileSong();
bool SetSubsong(int subsong);
void Play(bool looping, int subsong);
FString GetStats();
protected:
FCriticalSection CritSec;
FileReader *Reader;
SoundDecoder *Decoder;
int Channels;
int SampleRate;
uint32_t Loop_Start;
uint32_t Loop_End;
int CalcSongLength();
static bool Read(SoundStream *stream, void *buff, int len, void *userdata);
};
// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
// EXTERNAL DATA DECLARATIONS ----------------------------------------------
// PUBLIC DATA DEFINITIONS -------------------------------------------------
// PRIVATE DATA DEFINITIONS ------------------------------------------------
// CODE --------------------------------------------------------------------
//==========================================================================
//
// GME_OpenSong
//
//==========================================================================
MusInfo *SndFile_OpenSong(FileReader &fr)
{
uint8_t signature[4];
fr.Seek(0, SEEK_SET);
fr.Read(signature, 4);
uint32_t loop_start = 0, loop_end = ~0u;
if (!memcmp(signature, "OggS", 4) || !memcmp(signature, "fLaC", 4))
{
// Todo: Read loop points from metadata
// ms to samples.
//size_t smp_offset = ms? (size_t)((double)ms_offset / 1000. * SndInfo.samplerate) : ms_offset;
}
fr.Seek(0, SEEK_SET);
auto decoder = SoundRenderer::CreateDecoder(&fr);
if (decoder == nullptr) return nullptr;
return new SndFileSong(&fr, decoder, loop_start, loop_end);
}
//==========================================================================
//
// SndFileSong - Constructor
//
//==========================================================================
SndFileSong::SndFileSong(FileReader *reader, SoundDecoder *decoder, uint32_t loop_start, uint32_t loop_end)
{
ChannelConfig iChannels;
SampleType Type;
decoder->getInfo(&SampleRate, &iChannels, &Type);
Loop_Start = loop_start;
Loop_End = clamp<uint32_t>(loop_end, 0, (uint32_t)decoder->getSampleLength());
Reader = reader;
Decoder = decoder;
Channels = iChannels == ChannelConfig_Stereo? 2:1;
m_Stream = GSnd->CreateStream(Read, 32*1024, iChannels == ChannelConfig_Stereo? 0 : SoundStream::Mono, SampleRate, this);
}
//==========================================================================
//
// SndFileSong - Destructor
//
//==========================================================================
SndFileSong::~SndFileSong()
{
Stop();
if (m_Stream != nullptr)
{
delete m_Stream;
m_Stream = nullptr;
}
if (Decoder != nullptr)
{
delete Decoder;
}
if (Reader != nullptr)
{
delete Reader;
}
}
//==========================================================================
//
// SndFileSong :: Play
//
//==========================================================================
void SndFileSong::Play(bool looping, int track)
{
m_Status = STATE_Stopped;
m_Looping = looping;
if (m_Stream->Play(looping, 1))
{
m_Status = STATE_Playing;
}
}
//==========================================================================
//
// SndFileSong :: SetSubsong
//
//==========================================================================
bool SndFileSong::SetSubsong(int track)
{
return false;
}
//==========================================================================
//
// SndFileSong :: GetStats
//
//==========================================================================
FString SndFileSong::GetStats()
{
FString out;
size_t SamplePos;
SamplePos = Decoder->getSampleOffset();
int time = int (SamplePos / SampleRate);
out.Format(
"Track: " TEXTCOLOR_YELLOW "%s, %dHz" TEXTCOLOR_NORMAL
" Time:" TEXTCOLOR_YELLOW "%02d:%02d" TEXTCOLOR_NORMAL,
Channels == 2? "Stereo" : "Mono", SampleRate,
time/60,
time % 60);
return out;
}
//==========================================================================
//
// SndFileSong :: Read STATIC
//
//==========================================================================
bool SndFileSong::Read(SoundStream *stream, void *vbuff, int ilen, void *userdata)
{
char *buff = (char*)vbuff;
SndFileSong *song = (SndFileSong *)userdata;
song->CritSec.Enter();
size_t len = size_t(ilen);
size_t currentpos = song->Decoder->getSampleOffset();
size_t framestoread = len / (song->Channels*2);
bool err = false;
if (!song->m_Looping)
{
size_t maxpos = song->Decoder->getSampleLength();
if (currentpos == maxpos)
{
memset(buff, 0, len);
song->CritSec.Leave();
return false;
}
if (currentpos + framestoread > maxpos)
{
size_t got = song->Decoder->read(buff, (maxpos - currentpos) * song->Channels * 2);
memset(buff + got, 0, len - got);
}
else
{
size_t got = song->Decoder->read(buff, len);
err = (got != len);
}
}
else
{
if (currentpos + framestoread > song->Loop_End)
{
size_t endblock = (song->Loop_End - currentpos) * song->Channels * 2;
err = (song->Decoder->read(buff, endblock) != endblock);
buff = buff + endblock;
len -= endblock;
song->Decoder->seek(song->Loop_Start, false);
}
err |= song->Decoder->read(buff, len) != len;
}
song->CritSec.Leave();
return !err;
}

View file

@ -212,7 +212,7 @@ class OpenALSoundStream : public SoundStream
size_t got = self->Decoder->read((char*)ptr, length); size_t got = self->Decoder->read((char*)ptr, length);
if(got < (unsigned int)length) if(got < (unsigned int)length)
{ {
if(!self->Looping || !self->Decoder->seek(0)) if(!self->Looping || !self->Decoder->seek(0, false))
return false; return false;
got += self->Decoder->read((char*)ptr+got, length-got); got += self->Decoder->read((char*)ptr+got, length-got);
} }
@ -361,7 +361,7 @@ public:
virtual bool SetPosition(unsigned int ms_pos) virtual bool SetPosition(unsigned int ms_pos)
{ {
std::unique_lock<std::mutex> lock(Renderer->StreamLock); std::unique_lock<std::mutex> lock(Renderer->StreamLock);
if(!Decoder->seek(ms_pos)) if(!Decoder->seek(ms_pos, true))
return false; return false;
if(!Playing.load()) if(!Playing.load())

View file

@ -132,9 +132,9 @@ TArray<char> SndFileDecoder::readAll()
return output; return output;
} }
bool SndFileDecoder::seek(size_t ms_offset) bool SndFileDecoder::seek(size_t ms_offset, bool ms)
{ {
size_t smp_offset = (size_t)((double)ms_offset / 1000. * SndInfo.samplerate); size_t smp_offset = ms? (size_t)((double)ms_offset / 1000. * SndInfo.samplerate) : ms_offset;
if(sf_seek(SndFile, smp_offset, SEEK_SET) < 0) if(sf_seek(SndFile, smp_offset, SEEK_SET) < 0)
return false; return false;
return true; return true;

View file

@ -13,7 +13,7 @@ struct SndFileDecoder : public SoundDecoder
virtual size_t read(char *buffer, size_t bytes); virtual size_t read(char *buffer, size_t bytes);
virtual TArray<char> readAll(); virtual TArray<char> readAll();
virtual bool seek(size_t ms_offset); virtual bool seek(size_t ms_offset, bool ms);
virtual size_t getSampleOffset(); virtual size_t getSampleOffset();
virtual size_t getSampleLength(); virtual size_t getSampleLength();