Get an accurate stream length from minimp3
[alure.git] / src / decoders / mp3.cpp
blob00e3a56adf58074adeaf72bf2a82d77e647a3189
2 #include "mp3.hpp"
4 #include <stdexcept>
5 #include <iostream>
6 #include <cassert>
8 #include "context.h"
10 #define MINIMP3_IMPLEMENTATION
11 #define MINIMP3_FLOAT_OUTPUT
12 #include "minimp3.h"
14 namespace {
16 constexpr size_t MinMp3DataSize = 16384;
17 constexpr size_t MaxMp3DataSize = MinMp3DataSize * 8;
19 size_t append_file_data(std::istream &file, alure::Vector<uint8_t> &data, size_t count)
21 size_t old_size = data.size();
22 if(old_size >= MaxMp3DataSize || count == 0)
23 return 0;
24 count = std::min(count, MaxMp3DataSize - old_size);
25 data.resize(old_size + count);
27 file.clear();
28 file.read(reinterpret_cast<char*>(data.data()+old_size), count);
29 size_t got = file.gcount();
30 data.resize(old_size + got);
32 return got;
35 size_t find_i3dv2(alure::ArrayView<uint8_t> data)
37 if(data.size() > 10 && memcmp(data.data(), "ID3", 3) == 0)
38 return (((data[6]&0x7f) << 21) | ((data[7]&0x7f) << 14) |
39 ((data[8]&0x7f) << 7) | ((data[9]&0x7f) )) + 10;
40 return 0;
43 } // namespace
46 namespace alure {
48 class Mp3Decoder final : public Decoder {
49 UniquePtr<std::istream> mFile;
51 Vector<uint8_t> mFileData;
53 mp3dec_t mMp3;
54 Vector<float> mSampleData;
55 mp3dec_frame_info_t mLastFrame{};
56 mutable std::mutex mMutex;
58 mutable std::streamsize mSampleCount{-1};
59 ChannelConfig mChannels{ChannelConfig::Mono};
60 SampleType mSampleType{SampleType::UInt8};
61 int mSampleRate{0};
63 static int decodeFrame(std::istream &file, mp3dec_t &mp3, Vector<uint8_t> &file_data,
64 float *sample_data, mp3dec_frame_info_t *frame_info)
66 if(file_data.size() < MinMp3DataSize && !file.eof())
68 size_t todo = MinMp3DataSize - file_data.size();
69 append_file_data(file, file_data, todo);
72 int samples_to_get = mp3dec_decode_frame(&mp3, file_data.data(), file_data.size(),
73 sample_data, frame_info);
74 while(samples_to_get == 0 && !file.eof())
76 if(append_file_data(file, file_data, MinMp3DataSize) == 0)
77 break;
78 samples_to_get = mp3dec_decode_frame(&mp3, file_data.data(), file_data.size(),
79 sample_data, frame_info);
81 return samples_to_get;
84 public:
85 Mp3Decoder(UniquePtr<std::istream> file, Vector<uint8_t>&& initial_data,
86 const mp3dec_t &mp3, const mp3dec_frame_info_t &first_frame,
87 ChannelConfig chans, SampleType stype, int srate) noexcept
88 : mFile(std::move(file)), mFileData(std::move(initial_data)), mMp3(mp3)
89 , mLastFrame(first_frame), mChannels(chans), mSampleType(stype), mSampleRate(srate)
90 { }
91 ~Mp3Decoder() override { }
93 ALuint getFrequency() const noexcept override;
94 ChannelConfig getChannelConfig() const noexcept override;
95 SampleType getSampleType() const noexcept override;
97 uint64_t getLength() const noexcept override;
98 bool seek(uint64_t pos) noexcept override;
100 std::pair<uint64_t,uint64_t> getLoopPoints() const noexcept override;
102 ALuint read(ALvoid *ptr, ALuint count) noexcept override;
105 ALuint Mp3Decoder::getFrequency() const noexcept { return mSampleRate; }
106 ChannelConfig Mp3Decoder::getChannelConfig() const noexcept { return mChannels; }
107 SampleType Mp3Decoder::getSampleType() const noexcept { return mSampleType; }
109 uint64_t Mp3Decoder::getLength() const noexcept
111 if(LIKELY(mSampleCount >= 0))
112 return mSampleCount;
114 std::lock_guard<std::mutex> _(mMutex);
116 mFile->clear();
117 std::streamsize oldfpos = mFile->tellg();
118 if(oldfpos < 0 || !mFile->seekg(0))
120 mSampleCount = 0;
121 return mSampleCount;
124 Vector<uint8_t> file_data;
125 mp3dec_t mp3;
127 mp3dec_init(&mp3);
129 append_file_data(*mFile, file_data, MinMp3DataSize);
131 size_t id_size = find_i3dv2(file_data);
132 if(id_size > 0)
134 if(id_size <= file_data.size())
135 file_data.erase(file_data.begin(), file_data.begin()+id_size);
136 else
138 mFile->ignore(id_size - file_data.size());
139 file_data.clear();
143 std::streamsize count = 0;
144 do {
145 // Read the next frame.
146 mp3dec_frame_info_t frame_info{};
147 int samples_to_get = decodeFrame(*mFile, mp3, file_data, nullptr, &frame_info);
148 if(samples_to_get <= 0) break;
150 // Don't continue if the frame changed format
151 if((mChannels == ChannelConfig::Mono && frame_info.channels != 1) ||
152 (mChannels == ChannelConfig::Stereo && frame_info.channels != 2) ||
153 mSampleRate != frame_info.hz)
154 break;
156 // Keep going to the next frame
157 if(file_data.size() >= (size_t)frame_info.frame_bytes)
158 file_data.erase(file_data.begin(), file_data.begin()+frame_info.frame_bytes);
159 else
161 mFile->ignore(frame_info.frame_bytes - file_data.size());
162 file_data.clear();
164 count += samples_to_get;
165 } while(1);
166 mSampleCount = count;
168 mFile->clear();
169 mFile->seekg(oldfpos);
170 return mSampleCount;
173 bool Mp3Decoder::seek(uint64_t pos) noexcept
175 // Use temporary local storage to avoid trashing current data in case of
176 // failure.
177 Vector<uint8_t> file_data;
178 mp3dec_t mp3;
180 mp3dec_init(&mp3);
182 // Seeking to somewhere in the file. Backup the current file position and
183 // reset back to the beginning.
184 // TODO: Obvious optimization: Track the current sample offset and don't
185 // rewind if seeking forward.
186 mFile->clear();
187 std::streamsize oldfpos = mFile->tellg();
188 if(oldfpos < 0 || !mFile->seekg(0))
189 return false;
191 append_file_data(*mFile, file_data, MinMp3DataSize);
193 size_t id_size = find_i3dv2(file_data);
194 if(id_size > 0)
196 if(id_size <= file_data.size())
197 file_data.erase(file_data.begin(), file_data.begin()+id_size);
198 else
200 mFile->ignore(id_size - file_data.size());
201 file_data.clear();
205 uint64_t curpos = 0;
206 do {
207 // Read the next frame.
208 mp3dec_frame_info_t frame_info{};
209 int samples_to_get = decodeFrame(*mFile, mp3, file_data, nullptr, &frame_info);
210 if(samples_to_get <= 0) break;
212 // Don't continue if the frame changed format
213 if((mChannels == ChannelConfig::Mono && frame_info.channels != 1) ||
214 (mChannels == ChannelConfig::Stereo && frame_info.channels != 2) ||
215 mSampleRate != frame_info.hz)
216 break;
218 if((uint64_t)samples_to_get > pos - curpos)
220 // Desired sample is within this frame, decode the samples and go
221 // to the desired offset.
222 Vector<float> sample_data(MINIMP3_MAX_SAMPLES_PER_FRAME);
223 samples_to_get = decodeFrame(*mFile, mp3, file_data, sample_data.data(),
224 &frame_info);
226 if((uint64_t)samples_to_get > pos - curpos)
228 sample_data.resize(samples_to_get * frame_info.channels);
229 sample_data.erase(sample_data.begin(),
230 sample_data.begin() + (pos-curpos)*frame_info.channels);
231 file_data.erase(file_data.begin(), file_data.begin()+frame_info.frame_bytes);
232 mSampleData = std::move(sample_data);
233 mFileData = std::move(file_data);
234 mLastFrame = frame_info;
235 mMp3 = mp3;
236 return true;
240 // Keep going to the next frame
241 if(file_data.size() >= (size_t)frame_info.frame_bytes)
242 file_data.erase(file_data.begin(), file_data.begin()+frame_info.frame_bytes);
243 else
245 mFile->ignore(frame_info.frame_bytes - file_data.size());
246 file_data.clear();
248 curpos += samples_to_get;
249 } while(1);
251 // Seeking failed. Restore original file position.
252 mFile->clear();
253 mFile->seekg(oldfpos);
254 return false;
257 std::pair<uint64_t,uint64_t> Mp3Decoder::getLoopPoints() const noexcept
259 return {0, std::numeric_limits<uint64_t>::max()};
262 ALuint Mp3Decoder::read(ALvoid *ptr, ALuint count) noexcept
264 union {
265 void *v;
266 float *f;
267 short *s;
268 } dst = { ptr };
269 ALuint total = 0;
271 std::lock_guard<std::mutex> _(mMutex);
272 while(total < count)
274 ALuint todo = count-total;
276 if(!mSampleData.empty())
278 // Write out whatever samples we have.
279 todo = std::min<ALuint>(todo, mSampleData.size()/mLastFrame.channels);
281 size_t numspl = todo*mLastFrame.channels;
282 if(mSampleType == SampleType::Float32)
284 std::copy(mSampleData.begin(), mSampleData.begin()+numspl, dst.f);
285 dst.f += numspl;
287 else
289 mp3dec_f32_to_s16(mSampleData.data(), dst.s, numspl);
290 dst.s += numspl;
292 mSampleData.erase(mSampleData.begin(), mSampleData.begin()+numspl);
294 total += todo;
295 continue;
298 // Read directly into the output buffer if it doesn't need conversion
299 // and there's enough guaranteed room.
300 float *samples_ptr;
301 if(mSampleType == SampleType::Float32 &&
302 todo*mLastFrame.channels >= MINIMP3_MAX_SAMPLES_PER_FRAME)
303 samples_ptr = dst.f;
304 else
306 mSampleData.resize(MINIMP3_MAX_SAMPLES_PER_FRAME);
307 samples_ptr = mSampleData.data();
310 mp3dec_frame_info_t frame_info{};
311 int samples_to_get = decodeFrame(*mFile, mMp3, mFileData, samples_ptr, &frame_info);
312 if(samples_to_get <= 0)
314 mSampleData.clear();
315 break;
318 // Format changing not supported. End the stream.
319 if((mChannels == ChannelConfig::Mono && frame_info.channels != 1) ||
320 (mChannels == ChannelConfig::Stereo && frame_info.channels != 2) ||
321 mSampleRate != frame_info.hz)
323 mSampleData.clear();
324 break;
327 // Remove used file data, update sample storage size with what we got
328 mFileData.erase(mFileData.begin(), mFileData.begin()+frame_info.frame_bytes);
329 mLastFrame = frame_info;
330 if(!mSampleData.empty())
331 mSampleData.resize(samples_to_get * frame_info.channels);
332 else
334 dst.f += samples_to_get * frame_info.channels;
335 total += samples_to_get;
339 return total;
343 Mp3DecoderFactory::Mp3DecoderFactory() noexcept
347 Mp3DecoderFactory::~Mp3DecoderFactory()
351 SharedPtr<Decoder> Mp3DecoderFactory::createDecoder(UniquePtr<std::istream> &file) noexcept
353 Vector<uint8_t> initial_data;
354 mp3dec_t mp3{};
356 mp3dec_init(&mp3);
358 // Make sure the file is valid and we get some samples.
359 if(append_file_data(*file, initial_data, MinMp3DataSize) == 0)
360 return nullptr;
362 // If the file contains an ID3v2 tag, skip it.
363 // TODO: Read it? Does it have e.g. sample length or loop points?
364 size_t id_size = find_i3dv2(initial_data);
365 if(id_size > 0)
367 if(id_size <= initial_data.size())
368 initial_data.erase(initial_data.begin(), initial_data.begin()+id_size);
369 else
371 file->ignore(id_size - initial_data.size());
372 initial_data.clear();
374 // Refill initial data buffer after clearing the ID3 chunk.
375 append_file_data(*file, initial_data, MinMp3DataSize - initial_data.size());
378 mp3dec_frame_info_t frame_info{};
379 int samples_to_get = mp3dec_decode_frame(&mp3, initial_data.data(), initial_data.size(),
380 nullptr, &frame_info);
381 while(samples_to_get == 0 && !file->eof())
383 if(append_file_data(*file, initial_data, MinMp3DataSize) == 0)
384 break;
385 samples_to_get = mp3dec_decode_frame(&mp3, initial_data.data(), initial_data.size(),
386 nullptr, &frame_info);
388 if(!samples_to_get)
389 return nullptr;
391 if(frame_info.hz < 1)
392 return nullptr;
394 ChannelConfig chans = ChannelConfig::Mono;
395 if(frame_info.channels == 1)
396 chans = ChannelConfig::Mono;
397 else if(frame_info.channels == 2)
398 chans = ChannelConfig::Stereo;
399 else
400 return nullptr;
402 SampleType stype = SampleType::Int16;
403 if(ContextImpl::GetCurrent()->isSupported(chans, SampleType::Float32))
404 stype = SampleType::Float32;
406 return MakeShared<Mp3Decoder>(std::move(file), std::move(initial_data), mp3,
407 frame_info, chans, stype, frame_info.hz);
410 } // namespace alure