Fix compilation with newer versions of DUMB
[alure.git] / src / decoders / mp3.cpp
blob80316a0d68acd1ceeb4c1067d6c7d8950abe01c9
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 int decode_frame(std::istream &file, mp3dec_t &mp3, alure::Vector<uint8_t> &file_data,
44 float *sample_data, mp3dec_frame_info_t *frame_info)
46 if(file_data.size() < MinMp3DataSize && !file.eof())
48 size_t todo = MinMp3DataSize - file_data.size();
49 append_file_data(file, file_data, todo);
52 int samples_to_get = mp3dec_decode_frame(&mp3, file_data.data(), file_data.size(),
53 sample_data, frame_info);
54 while(samples_to_get == 0 && !file.eof())
56 if(append_file_data(file, file_data, MinMp3DataSize) == 0)
57 break;
58 samples_to_get = mp3dec_decode_frame(&mp3, file_data.data(), file_data.size(),
59 sample_data, frame_info);
61 return samples_to_get;
64 } // namespace
67 namespace alure {
69 class Mp3Decoder final : public Decoder {
70 UniquePtr<std::istream> mFile;
72 Vector<uint8_t> mFileData;
74 mp3dec_t mMp3;
75 Vector<float> mSampleData;
76 mp3dec_frame_info_t mLastFrame{};
77 mutable std::mutex mMutex;
79 mutable std::streamsize mSampleCount{-1};
80 ChannelConfig mChannels{ChannelConfig::Mono};
81 SampleType mSampleType{SampleType::UInt8};
82 int mSampleRate{0};
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 = decode_frame(*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 = decode_frame(*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 = decode_frame(*mFile, mp3, file_data, sample_data.data(), &frame_info);
225 if((uint64_t)samples_to_get > pos - curpos)
227 sample_data.resize(samples_to_get * frame_info.channels);
228 sample_data.erase(sample_data.begin(),
229 sample_data.begin() + (pos-curpos)*frame_info.channels);
230 file_data.erase(file_data.begin(), file_data.begin()+frame_info.frame_bytes);
231 mSampleData = std::move(sample_data);
232 mFileData = std::move(file_data);
233 mLastFrame = frame_info;
234 mMp3 = mp3;
235 return true;
239 // Keep going to the next frame
240 if(file_data.size() >= (size_t)frame_info.frame_bytes)
241 file_data.erase(file_data.begin(), file_data.begin()+frame_info.frame_bytes);
242 else
244 mFile->ignore(frame_info.frame_bytes - file_data.size());
245 file_data.clear();
247 curpos += samples_to_get;
248 } while(1);
250 // Seeking failed. Restore original file position.
251 mFile->clear();
252 mFile->seekg(oldfpos);
253 return false;
256 std::pair<uint64_t,uint64_t> Mp3Decoder::getLoopPoints() const noexcept
258 return {0, std::numeric_limits<uint64_t>::max()};
261 ALuint Mp3Decoder::read(ALvoid *ptr, ALuint count) noexcept
263 union {
264 void *v;
265 float *f;
266 short *s;
267 } dst = { ptr };
268 ALuint total = 0;
270 std::lock_guard<std::mutex> _(mMutex);
271 while(total < count)
273 ALuint todo = count-total;
275 if(!mSampleData.empty())
277 // Write out whatever samples we have.
278 todo = std::min<ALuint>(todo, mSampleData.size()/mLastFrame.channels);
280 size_t numspl = todo*mLastFrame.channels;
281 if(mSampleType == SampleType::Float32)
283 std::copy(mSampleData.begin(), mSampleData.begin()+numspl, dst.f);
284 dst.f += numspl;
286 else
288 mp3dec_f32_to_s16(mSampleData.data(), dst.s, numspl);
289 dst.s += numspl;
291 mSampleData.erase(mSampleData.begin(), mSampleData.begin()+numspl);
293 total += todo;
294 continue;
297 // Read directly into the output buffer if it doesn't need conversion
298 // and there's enough guaranteed room.
299 float *samples_ptr;
300 if(mSampleType == SampleType::Float32 &&
301 todo*mLastFrame.channels >= MINIMP3_MAX_SAMPLES_PER_FRAME)
302 samples_ptr = dst.f;
303 else
305 mSampleData.resize(MINIMP3_MAX_SAMPLES_PER_FRAME);
306 samples_ptr = mSampleData.data();
309 mp3dec_frame_info_t frame_info{};
310 int samples_to_get = decode_frame(*mFile, mMp3, mFileData, samples_ptr, &frame_info);
311 if(samples_to_get <= 0)
313 mSampleData.clear();
314 break;
317 // Format changing not supported. End the stream.
318 if((mChannels == ChannelConfig::Mono && frame_info.channels != 1) ||
319 (mChannels == ChannelConfig::Stereo && frame_info.channels != 2) ||
320 mSampleRate != frame_info.hz)
322 mSampleData.clear();
323 break;
326 // Remove used file data, update sample storage size with what we got
327 mFileData.erase(mFileData.begin(), mFileData.begin()+frame_info.frame_bytes);
328 mLastFrame = frame_info;
329 if(!mSampleData.empty())
330 mSampleData.resize(samples_to_get * frame_info.channels);
331 else
333 dst.f += samples_to_get * frame_info.channels;
334 total += samples_to_get;
338 return total;
342 Mp3DecoderFactory::Mp3DecoderFactory() noexcept
346 Mp3DecoderFactory::~Mp3DecoderFactory()
350 SharedPtr<Decoder> Mp3DecoderFactory::createDecoder(UniquePtr<std::istream> &file) noexcept
352 Vector<uint8_t> initial_data;
353 mp3dec_t mp3{};
355 mp3dec_init(&mp3);
357 // Make sure the file is valid and we get some samples.
358 if(append_file_data(*file, initial_data, MinMp3DataSize) == 0)
359 return nullptr;
361 // If the file contains an ID3v2 tag, skip it.
362 // TODO: Read it? Does it have e.g. sample length or loop points?
363 size_t id_size = find_i3dv2(initial_data);
364 if(id_size > 0)
366 if(id_size <= initial_data.size())
367 initial_data.erase(initial_data.begin(), initial_data.begin()+id_size);
368 else
370 file->ignore(id_size - initial_data.size());
371 initial_data.clear();
375 mp3dec_frame_info_t frame_info{};
376 int samples_to_get = decode_frame(*file, mp3, initial_data, nullptr, &frame_info);
377 if(!samples_to_get) return nullptr;
379 if(frame_info.hz < 1)
380 return nullptr;
382 ChannelConfig chans = ChannelConfig::Mono;
383 if(frame_info.channels == 1)
384 chans = ChannelConfig::Mono;
385 else if(frame_info.channels == 2)
386 chans = ChannelConfig::Stereo;
387 else
388 return nullptr;
390 SampleType stype = SampleType::Int16;
391 if(ContextImpl::GetCurrent()->isSupported(chans, SampleType::Float32))
392 stype = SampleType::Float32;
394 return MakeShared<Mp3Decoder>(std::move(file), std::move(initial_data), mp3,
395 frame_info, chans, stype, frame_info.hz);
398 } // namespace alure