Rename parse_timeval for consistency
[alure.git] / src / decoders / vorbisfile.cpp
blob016d5ac98519624c61e8117973b54ba7233736a4
2 #include "vorbisfile.hpp"
4 #include <iostream>
6 #include "context.h"
8 #include "vorbis/vorbisfile.h"
10 namespace alure
13 static int seek(void *user_data, ogg_int64_t offset, int whence)
15 std::istream *stream = static_cast<std::istream*>(user_data);
16 stream->clear();
18 if(whence == SEEK_CUR)
19 stream->seekg(offset, std::ios_base::cur);
20 else if(whence == SEEK_SET)
21 stream->seekg(offset, std::ios_base::beg);
22 else if(whence == SEEK_END)
23 stream->seekg(offset, std::ios_base::end);
24 else
25 return -1;
27 return stream->tellg();
30 static size_t read(void *ptr, size_t size, size_t nmemb, void *user_data)
32 std::istream *stream = static_cast<std::istream*>(user_data);
33 stream->clear();
35 stream->read(static_cast<char*>(ptr), nmemb*size);
36 size_t ret = stream->gcount();
37 return ret/size;
40 static long tell(void *user_data)
42 std::istream *stream = static_cast<std::istream*>(user_data);
43 stream->clear();
44 return stream->tellg();
47 static int close(void*)
49 return 0;
53 class VorbisFileDecoder final : public Decoder {
54 UniquePtr<std::istream> mFile;
56 UniquePtr<OggVorbis_File> mOggFile;
57 vorbis_info *mVorbisInfo;
58 int mOggBitstream;
60 ChannelConfig mChannelConfig;
62 std::pair<uint64_t,uint64_t> mLoopPoints;
64 public:
65 VorbisFileDecoder(UniquePtr<std::istream> file, UniquePtr<OggVorbis_File> oggfile,
66 vorbis_info *vorbisinfo, ChannelConfig sconfig,
67 std::pair<uint64_t,uint64_t> loop_points) noexcept
68 : mFile(std::move(file)), mOggFile(std::move(oggfile)), mVorbisInfo(vorbisinfo)
69 , mOggBitstream(0), mChannelConfig(sconfig), mLoopPoints(loop_points)
70 { }
71 ~VorbisFileDecoder() override;
73 ALuint getFrequency() const noexcept override;
74 ChannelConfig getChannelConfig() const noexcept override;
75 SampleType getSampleType() const noexcept override;
77 uint64_t getLength() const noexcept override;
78 bool seek(uint64_t pos) noexcept override;
80 std::pair<uint64_t,uint64_t> getLoopPoints() const noexcept override;
82 ALuint read(ALvoid *ptr, ALuint count) noexcept override;
85 VorbisFileDecoder::~VorbisFileDecoder()
87 ov_clear(mOggFile.get());
91 ALuint VorbisFileDecoder::getFrequency() const noexcept
93 return mVorbisInfo->rate;
96 ChannelConfig VorbisFileDecoder::getChannelConfig() const noexcept
98 return mChannelConfig;
101 SampleType VorbisFileDecoder::getSampleType() const noexcept
103 return SampleType::Int16;
107 uint64_t VorbisFileDecoder::getLength() const noexcept
109 ogg_int64_t len = ov_pcm_total(mOggFile.get(), -1);
110 return std::max<ogg_int64_t>(len, 0);
113 bool VorbisFileDecoder::seek(uint64_t pos) noexcept
115 return ov_pcm_seek(mOggFile.get(), pos) == 0;
118 std::pair<uint64_t,uint64_t> VorbisFileDecoder::getLoopPoints() const noexcept
120 return mLoopPoints;
123 ALuint VorbisFileDecoder::read(ALvoid *ptr, ALuint count) noexcept
125 ALuint total = 0;
126 ALshort *samples = (ALshort*)ptr;
127 while(total < count)
129 int len = (count-total) * mVorbisInfo->channels * 2;
130 #ifdef __BIG_ENDIAN__
131 long got = ov_read(mOggFile.get(), reinterpret_cast<char*>(samples), len, 1, 2, 1, &mOggBitstream);
132 #else
133 long got = ov_read(mOggFile.get(), reinterpret_cast<char*>(samples), len, 0, 2, 1, &mOggBitstream);
134 #endif
135 if(got <= 0) break;
137 got /= 2;
138 samples += got;
139 got /= mVorbisInfo->channels;
140 total += got;
143 // 1, 2, and 4 channel files decode into the same channel order as
144 // OpenAL, however 6 (5.1), 7 (6.1), and 8 (7.1) channel files need to be
145 // re-ordered.
146 if(mChannelConfig == ChannelConfig::X51)
148 samples = (ALshort*)ptr;
149 for(ALuint i = 0;i < total;++i)
151 // OpenAL : FL, FR, FC, LFE, RL, RR
152 // Vorbis : FL, FC, FR, RL, RR, LFE
153 std::swap(samples[i*6 + 1], samples[i*6 + 2]);
154 std::swap(samples[i*6 + 3], samples[i*6 + 5]);
155 std::swap(samples[i*6 + 4], samples[i*6 + 5]);
158 else if(mChannelConfig == ChannelConfig::X61)
160 samples = (ALshort*)ptr;
161 for(ALuint i = 0;i < total;++i)
163 // OpenAL : FL, FR, FC, LFE, RC, SL, SR
164 // Vorbis : FL, FC, FR, SL, SR, RC, LFE
165 std::swap(samples[i*7 + 1], samples[i*7 + 2]);
166 std::swap(samples[i*7 + 3], samples[i*7 + 6]);
167 std::swap(samples[i*7 + 4], samples[i*7 + 5]);
168 std::swap(samples[i*7 + 5], samples[i*7 + 6]);
171 else if(mChannelConfig == ChannelConfig::X71)
173 samples = (ALshort*)ptr;
174 for(ALuint i = 0;i < total;++i)
176 // OpenAL : FL, FR, FC, LFE, RL, RR, SL, SR
177 // Vorbis : FL, FC, FR, SL, SR, RL, RR, LFE
178 std::swap(samples[i*8 + 1], samples[i*8 + 2]);
179 std::swap(samples[i*8 + 3], samples[i*8 + 7]);
180 std::swap(samples[i*8 + 4], samples[i*8 + 5]);
181 std::swap(samples[i*8 + 5], samples[i*8 + 6]);
182 std::swap(samples[i*8 + 6], samples[i*8 + 7]);
186 return total;
190 SharedPtr<Decoder> VorbisFileDecoderFactory::createDecoder(UniquePtr<std::istream> &file) noexcept
192 static const ov_callbacks streamIO = {
193 read, seek, close, tell
196 vorbis_info *vorbisinfo = nullptr;
197 auto oggfile = MakeUnique<OggVorbis_File>();
198 if(ov_open_callbacks(file.get(), oggfile.get(), NULL, 0, streamIO) != 0)
199 return nullptr;
201 vorbisinfo = ov_info(oggfile.get(), -1);
202 if(!vorbisinfo)
204 ov_clear(oggfile.get());
205 return nullptr;
208 std::pair<uint64_t,uint64_t> loop_points = { 0, std::numeric_limits<uint64_t>::max() };
209 if(vorbis_comment *vc = ov_comment(oggfile.get(), -1))
211 for(int i = 0;i < vc->comments;i++)
213 auto seppos = StringView(
214 vc->user_comments[i], vc->comment_lengths[i]
215 ).find_first_of('=');
216 if(seppos == StringView::npos) continue;
218 StringView key(vc->user_comments[i], seppos);
219 StringView val(vc->user_comments[i]+seppos+1, vc->comment_lengths[i]-(seppos+1));
221 // RPG Maker seems to recognize LOOPSTART and LOOPLENGTH for loop
222 // points in a Vorbis comment. ZDoom recognizes LOOP_START and
223 // LOOP_END. We can recognize both.
224 if(key == "LOOP_START" || key == "LOOPSTART")
226 auto pt = ParseTimeval(val, vorbisinfo->rate);
227 if(pt.index() == 1) loop_points.first = std::get<1>(pt);
228 continue;
231 if(key == "LOOP_END")
233 auto pt = ParseTimeval(val, vorbisinfo->rate);
234 if(pt.index() == 1) loop_points.second = std::get<1>(pt);
235 continue;
238 if(key == "LOOPLENGTH")
240 auto pt = ParseTimeval(val, vorbisinfo->rate);
241 if(pt.index() == 1)
242 loop_points.second = loop_points.first + std::get<1>(pt);
243 continue;
248 ChannelConfig channels = ChannelConfig::Mono;
249 if(vorbisinfo->channels == 1)
250 channels = ChannelConfig::Mono;
251 else if(vorbisinfo->channels == 2)
252 channels = ChannelConfig::Stereo;
253 else if(vorbisinfo->channels == 4)
254 channels = ChannelConfig::Quad;
255 else if(vorbisinfo->channels == 6)
256 channels = ChannelConfig::X51;
257 else if(vorbisinfo->channels == 7)
258 channels = ChannelConfig::X61;
259 else if(vorbisinfo->channels == 8)
260 channels = ChannelConfig::X71;
261 else
263 ov_clear(oggfile.get());
264 return nullptr;
267 return MakeShared<VorbisFileDecoder>(
268 std::move(file), std::move(oggfile), vorbisinfo, channels, loop_points