Merge pull request #40 from McSinyx/travis
[alure.git] / src / decoders / vorbisfile.cpp
blobba27c35e4623d5b2d8c43c40a573f8e38523ace1
2 #include "vorbisfile.hpp"
4 #include <iostream>
6 #include "context.h"
8 #include "vorbis/vorbisfile.h"
10 namespace {
12 int istream_seek(void *user_data, ogg_int64_t offset, int whence)
14 std::istream *stream = static_cast<std::istream*>(user_data);
15 stream->clear();
17 if(whence == SEEK_CUR)
18 stream->seekg(offset, std::ios_base::cur);
19 else if(whence == SEEK_SET)
20 stream->seekg(offset, std::ios_base::beg);
21 else if(whence == SEEK_END)
22 stream->seekg(offset, std::ios_base::end);
23 else
24 return -1;
26 return stream->tellg();
29 size_t istream_read(void *ptr, size_t size, size_t nmemb, void *user_data)
31 std::istream *stream = static_cast<std::istream*>(user_data);
32 stream->clear();
34 stream->read(static_cast<char*>(ptr), nmemb*size);
35 size_t ret = stream->gcount();
36 return ret/size;
39 long istream_tell(void *user_data)
41 std::istream *stream = static_cast<std::istream*>(user_data);
42 stream->clear();
43 return stream->tellg();
46 int istream_close(void*) { return 0; }
49 struct OggVorbisfileHolder : public OggVorbis_File {
50 OggVorbisfileHolder() { this->datasource = nullptr; }
51 ~OggVorbisfileHolder() { if(this->datasource) ov_clear(this); }
53 using OggVorbisfilePtr = alure::UniquePtr<OggVorbisfileHolder>;
55 } // namespace
57 namespace alure {
59 class VorbisFileDecoder final : public Decoder {
60 UniquePtr<std::istream> mFile;
62 OggVorbisfilePtr mOggFile;
63 vorbis_info *mVorbisInfo{nullptr};
64 int mOggBitstream{0};
66 ChannelConfig mChannelConfig{ChannelConfig::Mono};
68 std::pair<uint64_t,uint64_t> mLoopPoints{0, 0};
70 public:
71 VorbisFileDecoder(UniquePtr<std::istream> file, OggVorbisfilePtr oggfile,
72 vorbis_info *vorbisinfo, ChannelConfig sconfig,
73 std::pair<uint64_t,uint64_t> loop_points) noexcept
74 : mFile(std::move(file)), mOggFile(std::move(oggfile)), mVorbisInfo(vorbisinfo)
75 , mChannelConfig(sconfig), mLoopPoints(loop_points)
76 { }
77 ~VorbisFileDecoder() override { }
79 ALuint getFrequency() const noexcept override;
80 ChannelConfig getChannelConfig() const noexcept override;
81 SampleType getSampleType() const noexcept override;
83 uint64_t getLength() const noexcept override;
84 bool seek(uint64_t pos) noexcept override;
86 std::pair<uint64_t,uint64_t> getLoopPoints() const noexcept override;
88 ALuint read(ALvoid *ptr, ALuint count) noexcept override;
91 ALuint VorbisFileDecoder::getFrequency() const noexcept { return mVorbisInfo->rate; }
92 ChannelConfig VorbisFileDecoder::getChannelConfig() const noexcept { return mChannelConfig; }
93 SampleType VorbisFileDecoder::getSampleType() const noexcept { return SampleType::Int16; }
95 uint64_t VorbisFileDecoder::getLength() const noexcept
97 ogg_int64_t len = ov_pcm_total(mOggFile.get(), -1);
98 return std::max<ogg_int64_t>(len, 0);
101 bool VorbisFileDecoder::seek(uint64_t pos) noexcept
103 return ov_pcm_seek(mOggFile.get(), pos) == 0;
106 std::pair<uint64_t,uint64_t> VorbisFileDecoder::getLoopPoints() const noexcept
108 return mLoopPoints;
111 ALuint VorbisFileDecoder::read(ALvoid *ptr, ALuint count) noexcept
113 ALuint total = 0;
114 ALshort *samples = (ALshort*)ptr;
115 while(total < count)
117 int len = (count-total) * mVorbisInfo->channels * 2;
118 #ifdef __BIG_ENDIAN__
119 long got = ov_read(mOggFile.get(), reinterpret_cast<char*>(samples), len, 1, 2, 1, &mOggBitstream);
120 #else
121 long got = ov_read(mOggFile.get(), reinterpret_cast<char*>(samples), len, 0, 2, 1, &mOggBitstream);
122 #endif
123 if(got <= 0) break;
125 got /= 2;
126 samples += got;
127 got /= mVorbisInfo->channels;
128 total += got;
131 // 1, 2, and 4 channel files decode into the same channel order as
132 // OpenAL, however 6 (5.1), 7 (6.1), and 8 (7.1) channel files need to be
133 // re-ordered.
134 if(mChannelConfig == ChannelConfig::X51)
136 samples = (ALshort*)ptr;
137 for(ALuint i = 0;i < total;++i)
139 // OpenAL : FL, FR, FC, LFE, RL, RR
140 // Vorbis : FL, FC, FR, RL, RR, LFE
141 std::swap(samples[i*6 + 1], samples[i*6 + 2]);
142 std::swap(samples[i*6 + 3], samples[i*6 + 5]);
143 std::swap(samples[i*6 + 4], samples[i*6 + 5]);
146 else if(mChannelConfig == ChannelConfig::X61)
148 samples = (ALshort*)ptr;
149 for(ALuint i = 0;i < total;++i)
151 // OpenAL : FL, FR, FC, LFE, RC, SL, SR
152 // Vorbis : FL, FC, FR, SL, SR, RC, LFE
153 std::swap(samples[i*7 + 1], samples[i*7 + 2]);
154 std::swap(samples[i*7 + 3], samples[i*7 + 6]);
155 std::swap(samples[i*7 + 4], samples[i*7 + 5]);
156 std::swap(samples[i*7 + 5], samples[i*7 + 6]);
159 else if(mChannelConfig == ChannelConfig::X71)
161 samples = (ALshort*)ptr;
162 for(ALuint i = 0;i < total;++i)
164 // OpenAL : FL, FR, FC, LFE, RL, RR, SL, SR
165 // Vorbis : FL, FC, FR, SL, SR, RL, RR, LFE
166 std::swap(samples[i*8 + 1], samples[i*8 + 2]);
167 std::swap(samples[i*8 + 3], samples[i*8 + 7]);
168 std::swap(samples[i*8 + 4], samples[i*8 + 5]);
169 std::swap(samples[i*8 + 5], samples[i*8 + 6]);
170 std::swap(samples[i*8 + 6], samples[i*8 + 7]);
174 return total;
178 SharedPtr<Decoder> VorbisFileDecoderFactory::createDecoder(UniquePtr<std::istream> &file) noexcept
180 static const ov_callbacks streamIO = {
181 istream_read, istream_seek, istream_close, istream_tell
184 auto oggfile = MakeUnique<OggVorbisfilePtr::element_type>();
185 if(ov_open_callbacks(file.get(), oggfile.get(), NULL, 0, streamIO) != 0)
186 return nullptr;
188 vorbis_info *vorbisinfo = ov_info(oggfile.get(), -1);
189 if(!vorbisinfo) return nullptr;
191 std::pair<uint64_t,uint64_t> loop_points = { 0, std::numeric_limits<uint64_t>::max() };
192 if(vorbis_comment *vc = ov_comment(oggfile.get(), -1))
194 for(int i = 0;i < vc->comments;i++)
196 StringView val(vc->user_comments[i], vc->comment_lengths[i]);
197 auto seppos = val.find_first_of('=');
198 if(seppos == StringView::npos) continue;
200 StringView key = val.substr(0, seppos);
201 val = val.substr(seppos+1);
203 // RPG Maker seems to recognize LOOPSTART and LOOPLENGTH for loop
204 // points in a Vorbis comment. ZDoom recognizes LOOP_START and
205 // LOOP_END. We can recognize both.
206 if(key == "LOOP_START" || key == "LOOPSTART")
208 auto pt = ParseTimeval(val, vorbisinfo->rate);
209 if(pt.index() == 1) loop_points.first = std::get<1>(pt);
210 continue;
213 if(key == "LOOP_END")
215 auto pt = ParseTimeval(val, vorbisinfo->rate);
216 if(pt.index() == 1) loop_points.second = std::get<1>(pt);
217 continue;
220 if(key == "LOOPLENGTH")
222 auto pt = ParseTimeval(val, vorbisinfo->rate);
223 if(pt.index() == 1)
224 loop_points.second = loop_points.first + std::get<1>(pt);
225 continue;
230 ChannelConfig channels = ChannelConfig::Mono;
231 if(vorbisinfo->channels == 1)
232 channels = ChannelConfig::Mono;
233 else if(vorbisinfo->channels == 2)
234 channels = ChannelConfig::Stereo;
235 else if(vorbisinfo->channels == 4)
236 channels = ChannelConfig::Quad;
237 else if(vorbisinfo->channels == 6)
238 channels = ChannelConfig::X51;
239 else if(vorbisinfo->channels == 7)
240 channels = ChannelConfig::X61;
241 else if(vorbisinfo->channels == 8)
242 channels = ChannelConfig::X71;
243 else
244 return nullptr;
246 return MakeShared<VorbisFileDecoder>(
247 std::move(file), std::move(oggfile), vorbisinfo, channels, loop_points
251 } // namespace alure