Support vorbis comment loop points in FLAC files
[alure.git] / src / decoders / opusfile.cpp
blob1bb67b3d7f4c315bda0ca447f2b6ea8184a71640
2 #include "opusfile.hpp"
4 #include <stdexcept>
5 #include <iostream>
6 #include <limits>
8 #include "buffer.h"
10 #include "opusfile.h"
12 namespace alure
15 static int read(void *user_data, unsigned char *ptr, int size)
17 std::istream *stream = static_cast<std::istream*>(user_data);
18 stream->clear();
20 if(size < 0 || !stream->read(reinterpret_cast<char*>(ptr), size))
21 return -1;
22 return stream->gcount();
25 static int seek(void *user_data, opus_int64 offset, int whence)
27 std::istream *stream = static_cast<std::istream*>(user_data);
28 stream->clear();
30 if(whence == SEEK_CUR)
31 stream->seekg(offset, std::ios_base::cur);
32 else if(whence == SEEK_SET)
33 stream->seekg(offset, std::ios_base::beg);
34 else if(whence == SEEK_END)
35 stream->seekg(offset, std::ios_base::end);
36 else
37 return -1;
39 return stream->good() ? 0 : -1;
42 static opus_int64 tell(void *user_data)
44 std::istream *stream = static_cast<std::istream*>(user_data);
45 stream->clear();
46 return stream->tellg();
50 template<typename T> struct OggTypeInfo { };
51 template<>
52 struct OggTypeInfo<ogg_int16_t>
54 template<typename ...Args>
55 static int read(Args&& ...args)
56 { return op_read(std::forward<Args>(args)...); }
58 template<>
59 struct OggTypeInfo<float>
61 template<typename ...Args>
62 static int read(Args&& ...args)
63 { return op_read_float(std::forward<Args>(args)...); }
66 class OpusFileDecoder final : public Decoder {
67 UniquePtr<std::istream> mFile;
69 OggOpusFile *mOggFile;
70 int mOggBitstream;
72 ChannelConfig mChannelConfig;
73 SampleType mSampleType;
75 template<typename T>
76 ALuint do_read(T *ptr, ALuint count) noexcept
78 ALuint total = 0;
79 T *samples = ptr;
80 int num_chans = FramesToBytes(1, mChannelConfig, SampleType::UInt8);
81 while(total < count)
83 if(num_chans != op_head(mOggFile, -1)->channel_count)
84 break;
85 int len = (count-total) * num_chans;
87 long got = OggTypeInfo<T>::read(mOggFile, samples, len, &mOggBitstream);
88 if(got <= 0) break;
90 samples += got*num_chans;
91 total += got;
94 // 1, 2, and 4 channel files decode into the same channel order as
95 // OpenAL, however 6 (5.1), 7 (6.1), and 8 (7.1) channel files need to be
96 // re-ordered.
97 if(mChannelConfig == ChannelConfig::X51)
99 samples = ptr;
100 for(ALuint i = 0;i < total;++i)
102 // OpenAL : FL, FR, FC, LFE, RL, RR
103 // Opus : FL, FC, FR, RL, RR, LFE
104 std::swap(samples[i*6 + 1], samples[i*6 + 2]);
105 std::swap(samples[i*6 + 3], samples[i*6 + 5]);
106 std::swap(samples[i*6 + 4], samples[i*6 + 5]);
109 else if(mChannelConfig == ChannelConfig::X61)
111 samples = ptr;
112 for(ALuint i = 0;i < total;++i)
114 // OpenAL : FL, FR, FC, LFE, RC, SL, SR
115 // Opus : FL, FC, FR, SL, SR, RC, LFE
116 std::swap(samples[i*7 + 1], samples[i*7 + 2]);
117 std::swap(samples[i*7 + 3], samples[i*7 + 6]);
118 std::swap(samples[i*7 + 4], samples[i*7 + 5]);
119 std::swap(samples[i*7 + 5], samples[i*7 + 6]);
122 else if(mChannelConfig == ChannelConfig::X71)
124 samples = ptr;
125 for(ALuint i = 0;i < total;++i)
127 // OpenAL : FL, FR, FC, LFE, RL, RR, SL, SR
128 // Opus : FL, FC, FR, SL, SR, RL, RR, LFE
129 std::swap(samples[i*8 + 1], samples[i*8 + 2]);
130 std::swap(samples[i*8 + 3], samples[i*8 + 7]);
131 std::swap(samples[i*8 + 4], samples[i*8 + 5]);
132 std::swap(samples[i*8 + 5], samples[i*8 + 6]);
133 std::swap(samples[i*8 + 6], samples[i*8 + 7]);
137 return total;
140 public:
141 OpusFileDecoder(UniquePtr<std::istream> file, OggOpusFile *oggfile, ChannelConfig sconfig, SampleType stype) noexcept
142 : mFile(std::move(file)), mOggFile(oggfile), mOggBitstream(0), mChannelConfig(sconfig), mSampleType(stype)
144 ~OpusFileDecoder() override;
146 ALuint getFrequency() const noexcept override;
147 ChannelConfig getChannelConfig() const noexcept override;
148 SampleType getSampleType() const noexcept override;
150 uint64_t getLength() const noexcept override;
151 bool seek(uint64_t pos) noexcept override;
153 std::pair<uint64_t,uint64_t> getLoopPoints() const noexcept override;
155 ALuint read(ALvoid *ptr, ALuint count) noexcept override;
158 OpusFileDecoder::~OpusFileDecoder()
160 op_free(mOggFile);
164 ALuint OpusFileDecoder::getFrequency() const noexcept
166 // libopusfile always decodes to 48khz.
167 return 48000;
170 ChannelConfig OpusFileDecoder::getChannelConfig() const noexcept
172 return mChannelConfig;
175 SampleType OpusFileDecoder::getSampleType() const noexcept
177 return mSampleType;
181 uint64_t OpusFileDecoder::getLength() const noexcept
183 ogg_int64_t len = op_pcm_total(mOggFile, -1);
184 return std::max<ogg_int64_t>(len, 0);
187 bool OpusFileDecoder::seek(uint64_t pos) noexcept
189 return op_pcm_seek(mOggFile, pos) == 0;
192 std::pair<uint64_t,uint64_t> OpusFileDecoder::getLoopPoints() const noexcept
194 return std::make_pair(0, std::numeric_limits<uint64_t>::max());
197 ALuint OpusFileDecoder::read(ALvoid *ptr, ALuint count) noexcept
199 if(mSampleType == SampleType::Float32)
200 return do_read<float>(reinterpret_cast<float*>(ptr), count);
201 return do_read<ogg_int16_t>(reinterpret_cast<ogg_int16_t*>(ptr), count);
205 SharedPtr<Decoder> OpusFileDecoderFactory::createDecoder(UniquePtr<std::istream> &file) noexcept
207 static const OpusFileCallbacks streamIO = {
208 read, seek, tell, nullptr
211 OggOpusFile *oggfile = op_open_callbacks(file.get(), &streamIO, nullptr, 0, nullptr);
212 if(!oggfile) return nullptr;
214 std::pair<uint64_t,uint64_t> loop_points = { 0, std::numeric_limits<uint64_t>::max() };
215 if(const OpusTags *tags = op_tags(oggfile, -1))
217 for(int i = 0;i < tags->comments;i++)
219 auto seppos = StringView(
220 tags->user_comments[i], tags->comment_lengths[i]
221 ).find_first_of('=');
222 if(seppos == StringView::npos) continue;
224 StringView key(tags->user_comments[i], seppos);
225 StringView val(tags->user_comments[i]+seppos+1, tags->comment_lengths[i]-(seppos+1));
227 // RPG Maker seems to recognize LOOPSTART and LOOPLENGTH for loop
228 // points in a Vorbis comment. ZDoom recognizes LOOP_START and
229 // LOOP_END. We can recognize both.
230 if(key == "LOOP_START" || key == "LOOPSTART")
232 auto pt = parse_timeval(val, 48000.0);
233 if(pt.index() == 1) loop_points.first = std::get<1>(pt);
234 continue;
237 if(key == "LOOP_END")
239 auto pt = parse_timeval(val, 48000.0);
240 if(pt.index() == 1) loop_points.second = std::get<1>(pt);
241 continue;
244 if(key == "LOOPLENGTH")
246 auto pt = parse_timeval(val, 48000.0);
247 if(pt.index() == 1)
248 loop_points.second = loop_points.first + std::get<1>(pt);
249 continue;
254 int num_chans = op_head(oggfile, -1)->channel_count;
255 ChannelConfig channels = ChannelConfig::Mono;
256 if(num_chans == 1)
257 channels = ChannelConfig::Mono;
258 else if(num_chans == 2)
259 channels = ChannelConfig::Stereo;
260 else if(num_chans == 4)
261 channels = ChannelConfig::Quad;
262 else if(num_chans == 6)
263 channels = ChannelConfig::X51;
264 else if(num_chans == 7)
265 channels = ChannelConfig::X61;
266 else if(num_chans == 8)
267 channels = ChannelConfig::X71;
268 else
270 op_free(oggfile);
271 return nullptr;
274 if(Context::GetCurrent().isSupported(channels, SampleType::Float32))
275 return MakeShared<OpusFileDecoder>(std::move(file), oggfile, channels, SampleType::Float32);
276 return MakeShared<OpusFileDecoder>(std::move(file), oggfile, channels, SampleType::Int16);