Fix compilation with newer versions of DUMB
[alure.git] / src / decoders / opusfile.cpp
blob358b54b9532db3998b1fa01952a4a2bcce814580
2 #include "opusfile.hpp"
4 #include <stdexcept>
5 #include <iostream>
6 #include <limits>
8 #include "buffer.h"
10 #include "opusfile.h"
12 namespace {
14 int istream_read(void *user_data, unsigned char *ptr, int size)
16 std::istream *stream = static_cast<std::istream*>(user_data);
17 stream->clear();
19 if(size < 0 || !stream->read(reinterpret_cast<char*>(ptr), size))
20 return -1;
21 return stream->gcount();
24 int istream_seek(void *user_data, opus_int64 offset, int whence)
26 std::istream *stream = static_cast<std::istream*>(user_data);
27 stream->clear();
29 if(whence == SEEK_CUR)
30 stream->seekg(offset, std::ios_base::cur);
31 else if(whence == SEEK_SET)
32 stream->seekg(offset, std::ios_base::beg);
33 else if(whence == SEEK_END)
34 stream->seekg(offset, std::ios_base::end);
35 else
36 return -1;
38 return stream->good() ? 0 : -1;
41 opus_int64 istream_tell(void *user_data)
43 std::istream *stream = static_cast<std::istream*>(user_data);
44 stream->clear();
45 return stream->tellg();
49 template<typename T> struct OggTypeInfo { };
50 template<>
51 struct OggTypeInfo<ogg_int16_t> {
52 template<typename ...Args>
53 static int read(Args&& ...args)
54 { return op_read(std::forward<Args>(args)...); }
56 template<>
57 struct OggTypeInfo<float> {
58 template<typename ...Args>
59 static int read(Args&& ...args)
60 { return op_read_float(std::forward<Args>(args)...); }
64 struct OggOpusFileDeleter {
65 void operator()(OggOpusFile *ptr) const { op_free(ptr); }
67 using OggOpusFilePtr = alure::UniquePtr<OggOpusFile,OggOpusFileDeleter>;
69 } // namespace
71 namespace alure {
73 class OpusFileDecoder final : public Decoder {
74 UniquePtr<std::istream> mFile;
76 OggOpusFilePtr mOggFile;
77 int mOggBitstream{0};
79 ChannelConfig mChannelConfig{ChannelConfig::Mono};
80 SampleType mSampleType{SampleType::UInt8};
82 std::pair<uint64_t,uint64_t> mLoopPts{0, 0};
84 template<typename T>
85 ALuint do_read(T *ptr, ALuint count) noexcept
87 ALuint total = 0;
88 T *samples = ptr;
89 int num_chans = FramesToBytes(1, mChannelConfig, SampleType::UInt8);
90 while(total < count)
92 if(num_chans != op_head(mOggFile.get(), -1)->channel_count)
93 break;
94 int len = (count-total) * num_chans;
96 long got = OggTypeInfo<T>::read(mOggFile.get(), samples, len, &mOggBitstream);
97 if(got <= 0) break;
99 samples += got*num_chans;
100 total += got;
103 // 1, 2, and 4 channel files decode into the same channel order as
104 // OpenAL, however 6 (5.1), 7 (6.1), and 8 (7.1) channel files need to be
105 // re-ordered.
106 if(mChannelConfig == ChannelConfig::X51)
108 samples = ptr;
109 for(ALuint i = 0;i < total;++i)
111 // OpenAL : FL, FR, FC, LFE, RL, RR
112 // Opus : FL, FC, FR, RL, RR, LFE
113 std::swap(samples[i*6 + 1], samples[i*6 + 2]);
114 std::swap(samples[i*6 + 3], samples[i*6 + 5]);
115 std::swap(samples[i*6 + 4], samples[i*6 + 5]);
118 else if(mChannelConfig == ChannelConfig::X61)
120 samples = ptr;
121 for(ALuint i = 0;i < total;++i)
123 // OpenAL : FL, FR, FC, LFE, RC, SL, SR
124 // Opus : FL, FC, FR, SL, SR, RC, LFE
125 std::swap(samples[i*7 + 1], samples[i*7 + 2]);
126 std::swap(samples[i*7 + 3], samples[i*7 + 6]);
127 std::swap(samples[i*7 + 4], samples[i*7 + 5]);
128 std::swap(samples[i*7 + 5], samples[i*7 + 6]);
131 else if(mChannelConfig == ChannelConfig::X71)
133 samples = ptr;
134 for(ALuint i = 0;i < total;++i)
136 // OpenAL : FL, FR, FC, LFE, RL, RR, SL, SR
137 // Opus : FL, FC, FR, SL, SR, RL, RR, LFE
138 std::swap(samples[i*8 + 1], samples[i*8 + 2]);
139 std::swap(samples[i*8 + 3], samples[i*8 + 7]);
140 std::swap(samples[i*8 + 4], samples[i*8 + 5]);
141 std::swap(samples[i*8 + 5], samples[i*8 + 6]);
142 std::swap(samples[i*8 + 6], samples[i*8 + 7]);
146 return total;
149 public:
150 OpusFileDecoder(UniquePtr<std::istream> file, OggOpusFilePtr oggfile, ChannelConfig sconfig,
151 SampleType stype, const std::pair<uint64_t,uint64_t> &loop_points) noexcept
152 : mFile(std::move(file)), mOggFile(std::move(oggfile)), mChannelConfig(sconfig)
153 , mSampleType(stype), mLoopPts(loop_points)
155 ~OpusFileDecoder() override { }
157 ALuint getFrequency() const noexcept override;
158 ChannelConfig getChannelConfig() const noexcept override;
159 SampleType getSampleType() const noexcept override;
161 uint64_t getLength() const noexcept override;
162 bool seek(uint64_t pos) noexcept override;
164 std::pair<uint64_t,uint64_t> getLoopPoints() const noexcept override;
166 ALuint read(ALvoid *ptr, ALuint count) noexcept override;
169 // libopusfile always decodes to 48khz.
170 ALuint OpusFileDecoder::getFrequency() const noexcept { return 48000; }
171 ChannelConfig OpusFileDecoder::getChannelConfig() const noexcept { return mChannelConfig; }
172 SampleType OpusFileDecoder::getSampleType() const noexcept { return mSampleType; }
174 uint64_t OpusFileDecoder::getLength() const noexcept
176 ogg_int64_t len = op_pcm_total(mOggFile.get(), -1);
177 return std::max<ogg_int64_t>(len, 0);
180 bool OpusFileDecoder::seek(uint64_t pos) noexcept
182 return op_pcm_seek(mOggFile.get(), pos) == 0;
185 std::pair<uint64_t,uint64_t> OpusFileDecoder::getLoopPoints() const noexcept
187 return mLoopPts;
190 ALuint OpusFileDecoder::read(ALvoid *ptr, ALuint count) noexcept
192 if(mSampleType == SampleType::Float32)
193 return do_read(reinterpret_cast<float*>(ptr), count);
194 return do_read(reinterpret_cast<ogg_int16_t*>(ptr), count);
198 SharedPtr<Decoder> OpusFileDecoderFactory::createDecoder(UniquePtr<std::istream> &file) noexcept
200 static const OpusFileCallbacks streamIO = {
201 istream_read, istream_seek, istream_tell, nullptr
204 OggOpusFilePtr oggfile(op_open_callbacks(file.get(), &streamIO, nullptr, 0, nullptr));
205 if(!oggfile) return nullptr;
207 std::pair<uint64_t,uint64_t> loop_points = { 0, std::numeric_limits<uint64_t>::max() };
208 if(const OpusTags *tags = op_tags(oggfile.get(), -1))
210 for(int i = 0;i < tags->comments;i++)
212 StringView val(tags->user_comments[i], tags->comment_lengths[i]);
213 auto seppos = val.find_first_of('=');
214 if(seppos == StringView::npos) continue;
216 StringView key = val.substr(0, seppos);
217 val = val.substr(seppos+1);
219 // RPG Maker seems to recognize LOOPSTART and LOOPLENGTH for loop
220 // points in a Vorbis comment. ZDoom recognizes LOOP_START and
221 // LOOP_END. We can recognize both.
222 if(key == "LOOP_START" || key == "LOOPSTART")
224 auto pt = ParseTimeval(val, 48000.0);
225 if(pt.index() == 1) loop_points.first = std::get<1>(pt);
226 continue;
229 if(key == "LOOP_END")
231 auto pt = ParseTimeval(val, 48000.0);
232 if(pt.index() == 1) loop_points.second = std::get<1>(pt);
233 continue;
236 if(key == "LOOPLENGTH")
238 auto pt = ParseTimeval(val, 48000.0);
239 if(pt.index() == 1)
240 loop_points.second = loop_points.first + std::get<1>(pt);
241 continue;
246 int num_chans = op_head(oggfile.get(), -1)->channel_count;
247 ChannelConfig channels = ChannelConfig::Mono;
248 if(num_chans == 1)
249 channels = ChannelConfig::Mono;
250 else if(num_chans == 2)
251 channels = ChannelConfig::Stereo;
252 else if(num_chans == 4)
253 channels = ChannelConfig::Quad;
254 else if(num_chans == 6)
255 channels = ChannelConfig::X51;
256 else if(num_chans == 7)
257 channels = ChannelConfig::X61;
258 else if(num_chans == 8)
259 channels = ChannelConfig::X71;
260 else
261 return nullptr;
263 if(Context::GetCurrent().isSupported(channels, SampleType::Float32))
264 return MakeShared<OpusFileDecoder>(std::move(file), std::move(oggfile), channels,
265 SampleType::Float32, loop_points);
266 return MakeShared<OpusFileDecoder>(std::move(file), std::move(oggfile), channels,
267 SampleType::Int16, loop_points);
270 } // namespace alure