From dfd3535e0250b2f3e47163cb66f16766bc5a9200 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Sat, 1 Apr 2017 19:46:38 +0200 Subject: [PATCH] - 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. --- src/CMakeLists.txt | 1 + src/sound/i_music.cpp | 5 + src/sound/i_musicinterns.h | 1 + src/sound/i_sound.h | 3 +- src/sound/i_soundinternal.h | 2 +- src/sound/mpg123_decoder.cpp | 4 +- src/sound/mpg123_decoder.h | 2 +- src/sound/musicformats/music_libsndfile.cpp | 264 ++++++++++++++++++++ src/sound/oalsound.cpp | 4 +- src/sound/sndfile_decoder.cpp | 4 +- src/sound/sndfile_decoder.h | 2 +- 11 files changed, 281 insertions(+), 11 deletions(-) create mode 100644 src/sound/musicformats/music_libsndfile.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9af325791..dcabeb2a0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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 diff --git a/src/sound/i_music.cpp b/src/sound/i_music.cpp index bf3ca81de..e00fdae4a 100644 --- a/src/sound/i_music.cpp +++ b/src/sound/i_music.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) { diff --git a/src/sound/i_musicinterns.h b/src/sound/i_musicinterns.h index ba69741a5..227464c11 100644 --- a/src/sound/i_musicinterns.h +++ b/src/sound/i_musicinterns.h @@ -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); // -------------------------------------------------------------------------- diff --git a/src/sound/i_sound.h b/src/sound/i_sound.h index cd58c6f64..5f1917d68 100644 --- a/src/sound/i_sound.h +++ b/src/sound/i_sound.h @@ -160,8 +160,7 @@ public: virtual MIDIDevice* CreateMIDIDevice() const = 0; -protected: - virtual SoundDecoder *CreateDecoder(FileReader *reader); + static SoundDecoder *CreateDecoder(FileReader *reader); }; extern SoundRenderer *GSnd; diff --git a/src/sound/i_soundinternal.h b/src/sound/i_soundinternal.h index 786ff5dca..bb154a195 100644 --- a/src/sound/i_soundinternal.h +++ b/src/sound/i_soundinternal.h @@ -132,7 +132,7 @@ struct SoundDecoder virtual size_t read(char *buffer, size_t bytes) = 0; virtual TArray 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; } diff --git a/src/sound/mpg123_decoder.cpp b/src/sound/mpg123_decoder.cpp index 605970bc9..1aa5a0e2f 100644 --- a/src/sound/mpg123_decoder.cpp +++ b/src/sound/mpg123_decoder.cpp @@ -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; diff --git a/src/sound/mpg123_decoder.h b/src/sound/mpg123_decoder.h index 59e1df2ca..e1c786888 100644 --- a/src/sound/mpg123_decoder.h +++ b/src/sound/mpg123_decoder.h @@ -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(); diff --git a/src/sound/musicformats/music_libsndfile.cpp b/src/sound/musicformats/music_libsndfile.cpp new file mode 100644 index 000000000..6676d498f --- /dev/null +++ b/src/sound/musicformats/music_libsndfile.cpp @@ -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(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; +} diff --git a/src/sound/oalsound.cpp b/src/sound/oalsound.cpp index 90639624a..d38435be1 100644 --- a/src/sound/oalsound.cpp +++ b/src/sound/oalsound.cpp @@ -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 lock(Renderer->StreamLock); - if(!Decoder->seek(ms_pos)) + if(!Decoder->seek(ms_pos, true)) return false; if(!Playing.load()) diff --git a/src/sound/sndfile_decoder.cpp b/src/sound/sndfile_decoder.cpp index 5a957eb27..9d0d2331b 100644 --- a/src/sound/sndfile_decoder.cpp +++ b/src/sound/sndfile_decoder.cpp @@ -132,9 +132,9 @@ TArray 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; diff --git a/src/sound/sndfile_decoder.h b/src/sound/sndfile_decoder.h index f53f7e52a..0c4cfe935 100644 --- a/src/sound/sndfile_decoder.h +++ b/src/sound/sndfile_decoder.h @@ -13,7 +13,7 @@ struct SndFileDecoder : public SoundDecoder virtual size_t read(char *buffer, size_t bytes); virtual TArray 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();