mirror of
https://github.com/ZDoom/gzdoom.git
synced 2024-11-10 23:01:50 +00:00
- 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:
parent
4a0b3c3bab
commit
dfd3535e02
11 changed files with 281 additions and 11 deletions
|
@ -1245,6 +1245,7 @@ set (PCH_SOURCES
|
|||
sound/musicformats/music_cd.cpp
|
||||
sound/musicformats/music_dumb.cpp
|
||||
sound/musicformats/music_gme.cpp
|
||||
sound/musicformats/music_libsndfile.cpp
|
||||
sound/musicformats/music_mus_midiout.cpp
|
||||
sound/musicformats/music_smf_midiout.cpp
|
||||
sound/musicformats/music_hmi_midiout.cpp
|
||||
|
|
|
@ -488,6 +488,11 @@ retry_as_sndsys:
|
|||
{
|
||||
info = MOD_OpenSong(*reader);
|
||||
}
|
||||
if (info == nullptr)
|
||||
{
|
||||
info = SndFile_OpenSong(*reader);
|
||||
if (info != nullptr) reader = nullptr;
|
||||
}
|
||||
|
||||
if (info == NULL)
|
||||
{
|
||||
|
|
|
@ -672,6 +672,7 @@ MusInfo *MOD_OpenSong(FileReader &reader);
|
|||
|
||||
const char *GME_CheckFormat(uint32_t header);
|
||||
MusInfo *GME_OpenSong(FileReader &reader, const char *fmt);
|
||||
MusInfo *SndFile_OpenSong(FileReader &fr);
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
|
|
|
@ -160,8 +160,7 @@ public:
|
|||
|
||||
virtual MIDIDevice* CreateMIDIDevice() const = 0;
|
||||
|
||||
protected:
|
||||
virtual SoundDecoder *CreateDecoder(FileReader *reader);
|
||||
static SoundDecoder *CreateDecoder(FileReader *reader);
|
||||
};
|
||||
|
||||
extern SoundRenderer *GSnd;
|
||||
|
|
|
@ -132,7 +132,7 @@ struct SoundDecoder
|
|||
|
||||
virtual size_t read(char *buffer, size_t bytes) = 0;
|
||||
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 getSampleLength() { return 0; }
|
||||
|
||||
|
|
|
@ -134,14 +134,14 @@ size_t MPG123Decoder::read(char *buffer, size_t bytes)
|
|||
return amt;
|
||||
}
|
||||
|
||||
bool MPG123Decoder::seek(size_t ms_offset)
|
||||
bool MPG123Decoder::seek(size_t ms_offset, bool ms)
|
||||
{
|
||||
int enc, channels;
|
||||
long srate;
|
||||
|
||||
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)
|
||||
{
|
||||
Done = false;
|
||||
|
|
|
@ -16,7 +16,7 @@ struct MPG123Decoder : public SoundDecoder
|
|||
virtual void getInfo(int *samplerate, ChannelConfig *chans, SampleType *type);
|
||||
|
||||
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 getSampleLength();
|
||||
|
||||
|
|
264
src/sound/musicformats/music_libsndfile.cpp
Normal file
264
src/sound/musicformats/music_libsndfile.cpp
Normal 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;
|
||||
}
|
|
@ -212,7 +212,7 @@ class OpenALSoundStream : public SoundStream
|
|||
size_t got = self->Decoder->read((char*)ptr, length);
|
||||
if(got < (unsigned int)length)
|
||||
{
|
||||
if(!self->Looping || !self->Decoder->seek(0))
|
||||
if(!self->Looping || !self->Decoder->seek(0, false))
|
||||
return false;
|
||||
got += self->Decoder->read((char*)ptr+got, length-got);
|
||||
}
|
||||
|
@ -361,7 +361,7 @@ public:
|
|||
virtual bool SetPosition(unsigned int ms_pos)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(Renderer->StreamLock);
|
||||
if(!Decoder->seek(ms_pos))
|
||||
if(!Decoder->seek(ms_pos, true))
|
||||
return false;
|
||||
|
||||
if(!Playing.load())
|
||||
|
|
|
@ -132,9 +132,9 @@ TArray<char> SndFileDecoder::readAll()
|
|||
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)
|
||||
return false;
|
||||
return true;
|
||||
|
|
|
@ -13,7 +13,7 @@ struct SndFileDecoder : public SoundDecoder
|
|||
|
||||
virtual size_t read(char *buffer, size_t bytes);
|
||||
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 getSampleLength();
|
||||
|
||||
|
|
Loading…
Reference in a new issue