Added: mp3 decoder based dr_mp3 single file library

--HG--
branch : develop
This commit is contained in:
Nimetu 2019-02-18 13:46:39 +02:00
parent 4a31913c60
commit 1e6027c970
5 changed files with 3903 additions and 0 deletions

View file

@ -0,0 +1,96 @@
// NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
// Copyright (C) 2018 Winch Gate Property Limited
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#ifndef NLSOUND_AUDIO_DECODER_MP3_H
#define NLSOUND_AUDIO_DECODER_MP3_H
#include <nel/misc/types_nl.h>
#include <nel/sound/audio_decoder.h>
// disable drmp3_init_file()
#define DR_MP3_NO_STDIO
#include <nel/sound/decoder/dr_mp3.h>
namespace NLSOUND {
/**
* \brief CAudioDecoderMP3
* \date 2019-01-13 12:39GMT
* \author Meelis Mägi (Nimetu)
* CAudioDecoderMP3
* Create trough IAudioDecoder, type "mp3"
*/
class CAudioDecoderMP3 : public IAudioDecoder
{
protected:
NLMISC::IStream *_Stream;
bool _IsSupported;
bool _Loop;
bool _IsMusicEnded;
sint32 _StreamOffset;
sint32 _StreamSize;
drmp3 _Decoder;
// set to total pcm frames after getLength() is called
uint64 _PCMFrameCount;
public:
CAudioDecoderMP3(NLMISC::IStream *stream, bool loop);
virtual ~CAudioDecoderMP3();
inline NLMISC::IStream *getStream() { return _Stream; }
inline sint32 getStreamSize() { return _StreamSize; }
inline sint32 getStreamOffset() { return _StreamOffset; }
// Return true if mp3 is valid
bool isFormatSupported() const;
/// Get information on a music file (only ID3v1 tag is read.
static bool getInfo(NLMISC::IStream *stream, std::string &artist, std::string &title, float &length);
/// Get how many bytes the music buffer requires for output minimum.
virtual uint32 getRequiredBytes();
/// Get an amount of bytes between minimum and maximum (can be lower than minimum if at end).
virtual uint32 getNextBytes(uint8 *buffer, uint32 minimum, uint32 maximum);
/// Get the amount of channels (2 is stereo) in output.
virtual uint8 getChannels();
/// Get the samples per second (often 44100) in output.
virtual uint getSamplesPerSec();
/// Get the bits per sample (often 16) in output.
virtual uint8 getBitsPerSample();
/// Get if the music has ended playing (never true if loop).
virtual bool isMusicEnded();
/// Get the total time in seconds.
virtual float getLength();
/// Set looping
virtual void setLooping(bool loop);
}; /* class CAudioDecoderMP3 */
} /* namespace NLSOUND */
#endif // NLSOUND_AUDIO_DECODER_MP3_H
/* end of file */

File diff suppressed because it is too large Load diff

View file

@ -62,6 +62,7 @@ FILE(GLOB STREAM
FILE(GLOB STREAM_FILE
audio_decoder.cpp ../../include/nel/sound/audio_decoder.h
audio_decoder_vorbis.cpp ../../include/nel/sound/audio_decoder_vorbis.h
audio_decoder_mp3.cpp ../../include/nel/sound/audio_decoder_mp3.h
audio_decoder_ffmpeg.cpp ../../include/nel/sound/audio_decoder_ffmpeg.h
stream_file_sound.cpp ../../include/nel/sound/stream_file_sound.h
stream_file_source.cpp ../../include/nel/sound/stream_file_source.h

View file

@ -36,6 +36,7 @@
// Project includes
#include <nel/sound/audio_decoder_vorbis.h>
#include <nel/sound/audio_decoder_mp3.h>
#ifdef FFMPEG_ENABLED
#include <nel/sound/audio_decoder_ffmpeg.h>
@ -102,6 +103,10 @@ IAudioDecoder *IAudioDecoder::createAudioDecoder(const std::string &type, NLMISC
{
return new CAudioDecoderVorbis(stream, loop);
}
else if (type_lower == "mp3")
{
return new CAudioDecoderMP3(stream, loop);
}
else
{
nlwarning("Music file type unknown: '%s'", type_lower.c_str());
@ -139,6 +144,16 @@ bool IAudioDecoder::getInfo(const std::string &filepath, std::string &artist, st
nlwarning("Unable to open: '%s'", filepath.c_str());
}
else if (type_lower == "mp3")
{
CIFile ifile;
ifile.setCacheFileOnOpen(false);
ifile.allowBNPCacheFileOnOpen(false);
if (ifile.open(lookup))
return CAudioDecoderMP3::getInfo(&ifile, artist, title, length);
nlwarning("Unable to open: '%s'", filepath.c_str());
}
else
{
nlwarning("Music file type unknown: '%s'", type_lower.c_str());
@ -157,6 +172,10 @@ void IAudioDecoder::getMusicExtensions(std::vector<std::string> &extensions)
{
extensions.push_back("ogg");
}
if (std::find(extensions.begin(), extensions.end(), "mp3") == extensions.end())
{
extensions.push_back("mp3");
}
#ifdef FFMPEG_ENABLED
extensions.push_back("mp3");
extensions.push_back("flac");

View file

@ -0,0 +1,221 @@
// NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
// Copyright (C) 2018 Winch Gate Property Limited
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "stdsound.h"
#include <nel/sound/audio_decoder_mp3.h>
#define DR_MP3_IMPLEMENTATION
#include <nel/sound/decoder/dr_mp3.h>
using namespace std;
using namespace NLMISC;
using namespace NLSOUND;
namespace NLSOUND {
// callback for drmp3
static size_t drmp3_read(void* pUserData, void* pBufferOut, size_t bytesToRead)
{
NLSOUND::CAudioDecoderMP3 *decoder = static_cast<NLSOUND::CAudioDecoderMP3 *>(pUserData);
NLMISC::IStream *stream = decoder->getStream();
nlassert(stream->isReading());
uint32 available = decoder->getStreamSize() - stream->getPos();
if (available == 0)
return 0;
if (bytesToRead > available)
bytesToRead = available;
stream->serialBuffer((uint8 *)pBufferOut, bytesToRead);
return bytesToRead;
}
// callback for drmp3
static drmp3_bool32 drmp3_seek(void* pUserData, int offset, drmp3_seek_origin origin)
{
NLSOUND::CAudioDecoderMP3 *decoder = static_cast<NLSOUND::CAudioDecoderMP3 *>(pUserData);
NLMISC::IStream *stream = decoder->getStream();
nlassert(stream->isReading());
NLMISC::IStream::TSeekOrigin seekOrigin;
if (origin == drmp3_seek_origin_start)
seekOrigin = NLMISC::IStream::begin;
else if (origin == drmp3_seek_origin_current)
seekOrigin = NLMISC::IStream::current;
else
return false;
stream->seek((sint32) offset, seekOrigin);
return true;
}
// these should always be 44100Hz/16bit/2ch
#define MP3_SAMPLE_RATE 44100
#define MP3_BITS_PER_SAMPLE 16
#define MP3_CHANNELS 2
CAudioDecoderMP3::CAudioDecoderMP3(NLMISC::IStream *stream, bool loop)
: IAudioDecoder(),
_Stream(stream), _Loop(loop), _IsMusicEnded(false), _StreamSize(0), _IsSupported(false), _PCMFrameCount(0)
{
_StreamOffset = stream->getPos();
stream->seek(0, NLMISC::IStream::end);
_StreamSize = stream->getPos();
stream->seek(_StreamOffset, NLMISC::IStream::begin);
drmp3_config config;
config.outputChannels = MP3_CHANNELS;
config.outputSampleRate = MP3_SAMPLE_RATE;
_IsSupported = drmp3_init(&_Decoder, &drmp3_read, &drmp3_seek, this, &config);
if (!_IsSupported)
{
nlwarning("MP3: Decoder failed to read stream");
}
}
CAudioDecoderMP3::~CAudioDecoderMP3()
{
drmp3_uninit(&_Decoder);
}
bool CAudioDecoderMP3::isFormatSupported() const
{
return _IsSupported;
}
/// Get information on a music file.
bool CAudioDecoderMP3::getInfo(NLMISC::IStream *stream, std::string &artist, std::string &title, float &length)
{
CAudioDecoderMP3 mp3(stream, false);
if (!mp3.isFormatSupported())
{
title.clear();
artist.clear();
length = 0.f;
return false;
}
length = mp3.getLength();
// ID3v1
stream->seek(-128, NLMISC::IStream::end);
{
uint8 buf[128];
stream->serialBuffer(buf, 128);
if(buf[0] == 'T' && buf[1] == 'A' && buf[2] == 'G')
{
uint i;
for(i = 0; i < 30; ++i) if (buf[3+i] == '\0') break;
artist.assign((char *)&buf[3], i);
for(i = 0; i < 30; ++i) if (buf[33+i] == '\0') break;
title.assign((char *)&buf[33], i);
}
}
return true;
}
uint32 CAudioDecoderMP3::getRequiredBytes()
{
return 0; // no minimum requirement of bytes to buffer out
}
uint32 CAudioDecoderMP3::getNextBytes(uint8 *buffer, uint32 minimum, uint32 maximum)
{
if (_IsMusicEnded) return 0;
nlassert(minimum <= maximum); // can't have this..
// TODO: CStreamFileSource::play() will stall when there is no frames on warmup
// supported can be set false if there is an issue creating converter
if (!_IsSupported)
{
_IsMusicEnded = true;
return 1;
}
sint16 *pFrameBufferOut = (sint16 *)buffer;
uint32 bytesPerFrame = MP3_BITS_PER_SAMPLE / 8 * _Decoder.channels;
uint32 totalFramesRead = 0;
uint32 framesToRead = minimum / bytesPerFrame;
while(framesToRead > 0)
{
float tempBuffer[4096];
uint64 tempFrames = drmp3_countof(tempBuffer) / _Decoder.channels;
if (tempFrames > framesToRead)
tempFrames = framesToRead;
tempFrames = drmp3_read_pcm_frames_f32(&_Decoder, tempFrames, tempBuffer);
if (tempFrames == 0)
break;
drmp3dec_f32_to_s16(tempBuffer, pFrameBufferOut, tempFrames * _Decoder.channels);
pFrameBufferOut += tempFrames * _Decoder.channels;
framesToRead -= tempFrames;
totalFramesRead += tempFrames;
}
_IsMusicEnded = (framesToRead > 0);
return totalFramesRead * bytesPerFrame;
}
uint8 CAudioDecoderMP3::getChannels()
{
return _Decoder.channels;
}
uint CAudioDecoderMP3::getSamplesPerSec()
{
return _Decoder.sampleRate;
}
uint8 CAudioDecoderMP3::getBitsPerSample()
{
return MP3_BITS_PER_SAMPLE;
}
bool CAudioDecoderMP3::isMusicEnded()
{
return _IsMusicEnded;
}
float CAudioDecoderMP3::getLength()
{
// cached because drmp3_get_pcm_frame_count is reading full file
if (_PCMFrameCount == 0)
{
_PCMFrameCount = drmp3_get_pcm_frame_count(&_Decoder);
}
return _PCMFrameCount / (float) _Decoder.sampleRate;
}
void CAudioDecoderMP3::setLooping(bool loop)
{
_Loop = loop;
}
} /* namespace NLSOUND */
/* end of file */