2 #include "opusfile.hpp"
14 int istream_read(void *user_data
, unsigned char *ptr
, int size
)
16 std::istream
*stream
= static_cast<std::istream
*>(user_data
);
19 if(size
< 0 || !stream
->read(reinterpret_cast<char*>(ptr
), size
))
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
);
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
);
38 return stream
->good() ? 0 : -1;
41 opus_int64
istream_tell(void *user_data
)
43 std::istream
*stream
= static_cast<std::istream
*>(user_data
);
45 return stream
->tellg();
49 template<typename T
> struct OggTypeInfo
{ };
51 struct OggTypeInfo
<ogg_int16_t
> {
52 template<typename
...Args
>
53 static int read(Args
&& ...args
)
54 { return op_read(std::forward
<Args
>(args
)...); }
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
>;
73 class OpusFileDecoder final
: public Decoder
{
74 UniquePtr
<std::istream
> mFile
;
76 OggOpusFilePtr mOggFile
;
79 ChannelConfig mChannelConfig
{ChannelConfig::Mono
};
80 SampleType mSampleType
{SampleType::UInt8
};
82 std::pair
<uint64_t,uint64_t> mLoopPts
{0, 0};
85 ALuint
do_read(T
*ptr
, ALuint count
) noexcept
89 int num_chans
= FramesToBytes(1, mChannelConfig
, SampleType::UInt8
);
92 if(num_chans
!= op_head(mOggFile
.get(), -1)->channel_count
)
94 int len
= (count
-total
) * num_chans
;
96 long got
= OggTypeInfo
<T
>::read(mOggFile
.get(), samples
, len
, &mOggBitstream
);
99 samples
+= got
*num_chans
;
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
106 if(mChannelConfig
== ChannelConfig::X51
)
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
)
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
)
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]);
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
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
);
229 if(key
== "LOOP_END")
231 auto pt
= ParseTimeval(val
, 48000.0);
232 if(pt
.index() == 1) loop_points
.second
= std::get
<1>(pt
);
236 if(key
== "LOOPLENGTH")
238 auto pt
= ParseTimeval(val
, 48000.0);
240 loop_points
.second
= loop_points
.first
+ std::get
<1>(pt
);
246 int num_chans
= op_head(oggfile
.get(), -1)->channel_count
;
247 ChannelConfig channels
= ChannelConfig::Mono
;
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
;
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
);