1 // NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
2 // Copyright (C) 2018-2019 Winch Gate Property Limited
4 // This source file has been modified by the following contributors:
5 // Copyright (C) 2019 Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
7 // This program is free software: you can redistribute it and/or modify
8 // it under the terms of the GNU Affero General Public License as
9 // published by the Free Software Foundation, either version 3 of the
10 // License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU Affero General Public License for more details.
17 // You should have received a copy of the GNU Affero General Public License
18 // along with this program. If not, see <http://www.gnu.org/licenses/>.
23 #if !defined(NL_OS_WINDOWS) || (NL_COMP_VC_VERSION > 90) /* VS2008 does not have stdint.h */
25 #include <nel/sound/audio_decoder_mp3.h>
27 #define DR_MP3_IMPLEMENTATION
28 #include <nel/sound/decoder/dr_mp3.h>
31 using namespace NLMISC
;
32 using namespace NLSOUND
;
37 static size_t drmp3_read(void* pUserData
, void* pBufferOut
, size_t bytesToRead
)
39 NLSOUND::CAudioDecoderMP3
*decoder
= static_cast<NLSOUND::CAudioDecoderMP3
*>(pUserData
);
40 NLMISC::IStream
*stream
= decoder
->getStream();
41 nlassert(stream
->isReading());
43 uint32 available
= decoder
->getStreamSize() - stream
->getPos();
47 if (bytesToRead
> available
)
48 bytesToRead
= available
;
50 stream
->serialBuffer((uint8
*)pBufferOut
, bytesToRead
);
55 static drmp3_bool32
drmp3_seek(void* pUserData
, int offset
, drmp3_seek_origin origin
)
57 NLSOUND::CAudioDecoderMP3
*decoder
= static_cast<NLSOUND::CAudioDecoderMP3
*>(pUserData
);
58 NLMISC::IStream
*stream
= decoder
->getStream();
59 nlassert(stream
->isReading());
61 NLMISC::IStream::TSeekOrigin seekOrigin
;
62 if (origin
== drmp3_seek_origin_start
)
63 seekOrigin
= NLMISC::IStream::begin
;
64 else if (origin
== drmp3_seek_origin_current
)
65 seekOrigin
= NLMISC::IStream::current
;
69 stream
->seek((sint32
) offset
, seekOrigin
);
73 // these should always be 44100Hz/16bit/2ch
74 #define MP3_SAMPLE_RATE 44100
75 #define MP3_BITS_PER_SAMPLE 16
76 #define MP3_CHANNELS 2
78 CAudioDecoderMP3::CAudioDecoderMP3(NLMISC::IStream
*stream
, bool loop
)
80 _Stream(stream
), _Loop(loop
), _IsMusicEnded(false), _StreamSize(0), _IsSupported(false), _PCMFrameCount(0)
82 _StreamOffset
= stream
->getPos();
83 stream
->seek(0, NLMISC::IStream::end
);
84 _StreamSize
= stream
->getPos();
85 stream
->seek(_StreamOffset
, NLMISC::IStream::begin
);
88 config
.outputChannels
= MP3_CHANNELS
;
89 config
.outputSampleRate
= MP3_SAMPLE_RATE
;
91 _IsSupported
= drmp3_init(&_Decoder
, &drmp3_read
, &drmp3_seek
, this, &config
);
94 nlwarning("MP3: Decoder failed to read stream");
98 CAudioDecoderMP3::~CAudioDecoderMP3()
102 drmp3_uninit(&_Decoder
);
106 bool CAudioDecoderMP3::isFormatSupported() const
111 /// Get information on a music file.
112 bool CAudioDecoderMP3::getInfo(NLMISC::IStream
*stream
, std::string
&artist
, std::string
&title
, float &length
)
114 CAudioDecoderMP3
mp3(stream
, false);
115 if (!mp3
.isFormatSupported())
123 length
= mp3
.getLength();
126 stream
->seek(-128, NLMISC::IStream::end
);
129 stream
->serialBuffer(buf
, 128);
131 if(buf
[0] == 'T' && buf
[1] == 'A' && buf
[2] == 'G')
134 for(i
= 0; i
< 30; ++i
) if (buf
[3+i
] == '\0') break;
135 artist
.assign((char *)&buf
[3], i
);
137 for(i
= 0; i
< 30; ++i
) if (buf
[33+i
] == '\0') break;
138 title
.assign((char *)&buf
[33], i
);
145 uint32
CAudioDecoderMP3::getRequiredBytes()
147 return 0; // no minimum requirement of bytes to buffer out
150 uint32
CAudioDecoderMP3::getNextBytes(uint8
*buffer
, uint32 minimum
, uint32 maximum
)
152 if (_IsMusicEnded
) return 0;
153 nlassert(minimum
<= maximum
); // can't have this..
155 // TODO: CStreamFileSource::play() will stall when there is no frames on warmup
156 // supported can be set false if there is an issue creating converter
159 _IsMusicEnded
= true;
163 sint16
*pFrameBufferOut
= (sint16
*)buffer
;
164 uint32 bytesPerFrame
= MP3_BITS_PER_SAMPLE
/ 8 * _Decoder
.channels
;
166 uint32 totalFramesRead
= 0;
167 uint32 framesToRead
= minimum
/ bytesPerFrame
;
168 while(framesToRead
> 0)
170 float tempBuffer
[4096];
171 uint64 tempFrames
= drmp3_countof(tempBuffer
) / _Decoder
.channels
;
173 if (tempFrames
> framesToRead
)
174 tempFrames
= framesToRead
;
176 tempFrames
= drmp3_read_pcm_frames_f32(&_Decoder
, tempFrames
, tempBuffer
);
180 drmp3dec_f32_to_s16(tempBuffer
, pFrameBufferOut
, tempFrames
* _Decoder
.channels
);
181 pFrameBufferOut
+= tempFrames
* _Decoder
.channels
;
183 framesToRead
-= tempFrames
;
184 totalFramesRead
+= tempFrames
;
187 _IsMusicEnded
= (framesToRead
> 0);
188 return totalFramesRead
* bytesPerFrame
;
191 uint8
CAudioDecoderMP3::getChannels()
193 return _Decoder
.channels
;
196 uint
CAudioDecoderMP3::getSamplesPerSec()
198 return _Decoder
.sampleRate
;
201 uint8
CAudioDecoderMP3::getBitsPerSample()
203 return MP3_BITS_PER_SAMPLE
;
206 bool CAudioDecoderMP3::isMusicEnded()
208 return _IsMusicEnded
;
211 float CAudioDecoderMP3::getLength()
213 // cached because drmp3_get_pcm_frame_count is reading full file
214 if (_PCMFrameCount
== 0)
216 _PCMFrameCount
= drmp3_get_pcm_frame_count(&_Decoder
);
219 return _PCMFrameCount
/ (float) _Decoder
.sampleRate
;
222 void CAudioDecoderMP3::setLooping(bool loop
)
227 } /* namespace NLSOUND */
229 #endif /* (NL_COMP_VC_VERSION > 90) */