Added: mp3 decoder based dr_mp3 single file library
--HG-- branch : develop
This commit is contained in:
parent
4a31913c60
commit
1e6027c970
5 changed files with 3903 additions and 0 deletions
96
code/nel/include/nel/sound/audio_decoder_mp3.h
Normal file
96
code/nel/include/nel/sound/audio_decoder_mp3.h
Normal 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 */
|
3566
code/nel/include/nel/sound/decoder/dr_mp3.h
Normal file
3566
code/nel/include/nel/sound/decoder/dr_mp3.h
Normal file
File diff suppressed because it is too large
Load diff
|
@ -62,6 +62,7 @@ FILE(GLOB STREAM
|
||||||
FILE(GLOB STREAM_FILE
|
FILE(GLOB STREAM_FILE
|
||||||
audio_decoder.cpp ../../include/nel/sound/audio_decoder.h
|
audio_decoder.cpp ../../include/nel/sound/audio_decoder.h
|
||||||
audio_decoder_vorbis.cpp ../../include/nel/sound/audio_decoder_vorbis.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
|
audio_decoder_ffmpeg.cpp ../../include/nel/sound/audio_decoder_ffmpeg.h
|
||||||
stream_file_sound.cpp ../../include/nel/sound/stream_file_sound.h
|
stream_file_sound.cpp ../../include/nel/sound/stream_file_sound.h
|
||||||
stream_file_source.cpp ../../include/nel/sound/stream_file_source.h
|
stream_file_source.cpp ../../include/nel/sound/stream_file_source.h
|
||||||
|
|
|
@ -36,6 +36,7 @@
|
||||||
|
|
||||||
// Project includes
|
// Project includes
|
||||||
#include <nel/sound/audio_decoder_vorbis.h>
|
#include <nel/sound/audio_decoder_vorbis.h>
|
||||||
|
#include <nel/sound/audio_decoder_mp3.h>
|
||||||
|
|
||||||
#ifdef FFMPEG_ENABLED
|
#ifdef FFMPEG_ENABLED
|
||||||
#include <nel/sound/audio_decoder_ffmpeg.h>
|
#include <nel/sound/audio_decoder_ffmpeg.h>
|
||||||
|
@ -102,6 +103,10 @@ IAudioDecoder *IAudioDecoder::createAudioDecoder(const std::string &type, NLMISC
|
||||||
{
|
{
|
||||||
return new CAudioDecoderVorbis(stream, loop);
|
return new CAudioDecoderVorbis(stream, loop);
|
||||||
}
|
}
|
||||||
|
else if (type_lower == "mp3")
|
||||||
|
{
|
||||||
|
return new CAudioDecoderMP3(stream, loop);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
nlwarning("Music file type unknown: '%s'", type_lower.c_str());
|
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());
|
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
|
else
|
||||||
{
|
{
|
||||||
nlwarning("Music file type unknown: '%s'", type_lower.c_str());
|
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");
|
extensions.push_back("ogg");
|
||||||
}
|
}
|
||||||
|
if (std::find(extensions.begin(), extensions.end(), "mp3") == extensions.end())
|
||||||
|
{
|
||||||
|
extensions.push_back("mp3");
|
||||||
|
}
|
||||||
#ifdef FFMPEG_ENABLED
|
#ifdef FFMPEG_ENABLED
|
||||||
extensions.push_back("mp3");
|
extensions.push_back("mp3");
|
||||||
extensions.push_back("flac");
|
extensions.push_back("flac");
|
||||||
|
|
221
code/nel/src/sound/audio_decoder_mp3.cpp
Normal file
221
code/nel/src/sound/audio_decoder_mp3.cpp
Normal 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 */
|
Loading…
Reference in a new issue