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
|
||||
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
|
||||
|
|
|
@ -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");
|
||||
|
|
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