engine: sound: better vorbis decoding with stb (don't drop last frames, perform less...
[k8vavoom.git] / source / sound / snd_vorbis_stb.cpp
blob63bf1f4ab28b50896e4b1c515d240615906a2714
1 //**************************************************************************
2 //**
3 //** ## ## ## ## ## #### #### ### ###
4 //** ## ## ## ## ## ## ## ## ## ## #### ####
5 //** ## ## ## ## ## ## ## ## ## ## ## ## ## ##
6 //** ## ## ######## ## ## ## ## ## ## ## ### ##
7 //** ### ## ## ### ## ## ## ## ## ##
8 //** # ## ## # #### #### ## ##
9 //**
10 //** Copyright (C) 1999-2006 Jānis Legzdiņš
11 //** Copyright (C) 2018-2020 Ketmar Dark
12 //**
13 //** This program is free software: you can redistribute it and/or modify
14 //** it under the terms of the GNU General Public License as published by
15 //** the Free Software Foundation, version 3 of the License ONLY.
16 //**
17 //** This program is distributed in the hope that it will be useful,
18 //** but WITHOUT ANY WARRANTY; without even the implied warranty of
19 //** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 //** GNU General Public License for more details.
21 //**
22 //** You should have received a copy of the GNU General Public License
23 //** along with this program. If not, see <http://www.gnu.org/licenses/>.
24 //**
25 //**************************************************************************
26 /*#define STB_VORBIS_NO_PUSHDATA_API*/
27 #define STB_VORBIS_NO_PULLDATA_API
28 #define STB_VORBIS_NO_STDIO
29 #define STB_VORBIS_NO_FAST_SCALED_FLOAT
31 #include "stbdr/stb_vorbis.c"
33 #include "gamedefs.h"
34 #include "snd_local.h"
37 class VVorbisAudioCodec : public VAudioCodec {
38 public:
39 VStream *Strm;
40 bool FreeStream;
41 int BytesLeft;
42 stb_vorbis *decoder;
43 bool eos;
44 vuint8 *inbuf;
45 int inbufSize;
46 int inbufUsed;
47 int inbufFilled;
48 vint16 *outbuf; // stereo
49 int outbufSize; // in shorts
50 int outbufUsed; // in shorts
51 int outbufFilled; // in shorts
52 bool inited;
54 VVorbisAudioCodec (VStream *AStrm, bool AFreeStream);
55 virtual ~VVorbisAudioCodec () override;
56 bool Init ();
57 void Cleanup ();
58 virtual int Decode (short *Data, int NumSamples) override;
59 virtual bool Finished () override;
60 virtual void Restart () override;
62 static VAudioCodec *Create (VStream *);
64 protected:
65 void stopFeeding (bool setEOS=false);
66 // returns `false` on error or eof
67 bool fillInBuffer ();
68 // returns `false` on error or eof
69 bool decodeFrame ();
73 class VVorbisSampleLoader : public VSampleLoader {
74 public:
75 virtual void Load (sfxinfo_t &, VStream &) override;
78 IMPLEMENT_AUDIO_CODEC(VVorbisAudioCodec, "Vorbis(stb)");
80 VVorbisSampleLoader VorbisSampleLoader;
83 //==========================================================================
85 // VVorbisAudioCodec::VVorbisAudioCodec
87 //==========================================================================
88 VVorbisAudioCodec::VVorbisAudioCodec (VStream *AStrm, bool AFreeStream)
89 : Strm(AStrm)
90 , FreeStream(AFreeStream)
91 , decoder(nullptr)
92 , eos(false)
93 , inbuf(nullptr)
94 , inbufSize(65536)
95 , inbufUsed(0)
96 , inbufFilled(0)
97 , outbuf(nullptr)
98 , outbufSize(0)
99 , outbufUsed(0)
100 , outbufFilled(0)
101 , inited(false)
103 BytesLeft = Strm->TotalSize();
104 Strm->Seek(0);
105 inbuf = (vuint8 *)Z_Malloc(inbufSize);
109 //==========================================================================
111 // VVorbisAudioCodec::~VVorbisAudioCodec
113 //==========================================================================
114 VVorbisAudioCodec::~VVorbisAudioCodec () {
115 if (inited) {
116 Cleanup();
117 if (FreeStream) {
118 Strm->Close();
119 delete Strm;
122 Strm = nullptr;
123 if (inbuf) Z_Free(inbuf);
124 if (outbuf) Z_Free(outbuf);
128 //==========================================================================
130 // VVorbisAudioCodec::Cleanup
132 //==========================================================================
133 void VVorbisAudioCodec::Cleanup () {
134 if (decoder) { stb_vorbis_close(decoder); decoder = nullptr; }
135 inbufUsed = inbufFilled = 0;
136 outbufUsed = outbufFilled = 0;
140 //==========================================================================
142 // VVorbisAudioCodec::stopFeeding
144 //==========================================================================
145 void VVorbisAudioCodec::stopFeeding (bool setEOS) {
146 if (setEOS) eos = true;
147 BytesLeft = 0;
148 inbufUsed = inbufFilled = 0;
152 //==========================================================================
154 // VVorbisAudioCodec::fillInBuffer
156 // returns `false` on error or eof
158 //==========================================================================
159 bool VVorbisAudioCodec::fillInBuffer () {
160 if (BytesLeft == 0 || eos) {
161 if (inbufUsed >= inbufFilled) stopFeeding();
162 return (inbufUsed < inbufFilled);
164 if (inbufUsed > 0) {
165 if (inbufUsed < inbufFilled) {
166 memmove(inbuf, inbuf+inbufUsed, inbufFilled-inbufUsed);
167 inbufFilled -= inbufUsed;
168 } else {
169 inbufFilled = 0;
171 inbufUsed = 0;
173 vassert(inbufUsed == 0);
174 int rd = min2(BytesLeft, inbufSize-inbufFilled);
175 vassert(rd >= 0);
176 if (rd == 0) {
177 if (inbufFilled > 0) return true;
178 if (BytesLeft > 0) GCon->Logf(NAME_Error, "stb_vorbis decoder glitched at '%s'", *Strm->GetName());
179 stopFeeding(true);
180 return false;
182 Strm->Serialise(inbuf+inbufFilled, rd);
183 if (Strm->IsError()) { stopFeeding(true); return false; }
184 inbufFilled += rd;
185 BytesLeft -= rd;
186 return true;
190 //==========================================================================
192 // f2i
194 //==========================================================================
195 static inline void floatbuf2short (vint16 *dest, const float *src, unsigned length, bool mono2stereo=false) noexcept {
196 if (!mono2stereo) {
197 while (length--) {
198 *dest = (vint16)(clampval(*src, -1.0f, 1.0f)*32767.0f);
199 ++src;
200 dest += 2;
202 } else {
203 while (length--) {
204 dest[0] = dest[1] = (vint16)(clampval(*src, -1.0f, 1.0f)*32767.0f);
205 ++src;
206 dest += 2;
212 //==========================================================================
214 // VVorbisAudioCodec::decodeFrame
216 // returns `false` on error or eof
218 //==========================================================================
219 bool VVorbisAudioCodec::decodeFrame () {
220 if (outbufUsed < outbufFilled) return true;
221 if (eos || !decoder || !Strm || Strm->IsError()) { stopFeeding(true); return false; }
222 //float *fltpcm[STB_VORBIS_MAX_CHANNELS];
223 float **fltpcm;
224 //GCon->Logf(NAME_Debug, "...decodeFrame: '%s' (used=%d; filled=%d; size=%d); oused=%d; ofilled=%d; osize=%d", *Strm->GetName(), inbufUsed, inbufFilled, inbufSize, outbufUsed, outbufFilled, outbufSize);
225 for (;;) {
226 if (inbufUsed >= inbufFilled) {
227 fillInBuffer();
228 //GCon->Logf(NAME_Debug, "...read new buffer from '%s' (used=%d; filled=%d; size=%d)", *Strm->GetName(), inbufUsed, inbufFilled, inbufSize);
230 //GCon->Logf(NAME_Debug, "...decoding frame from '%s' (used=%d; filled=%d; size=%d)", *Strm->GetName(), inbufUsed, inbufFilled, inbufSize);
231 int chans = 0;
232 int samples = 0;
233 int res = stb_vorbis_decode_frame_pushdata(decoder, (const unsigned char *)(inbuf+inbufUsed), inbufFilled-inbufUsed, &chans, &fltpcm, &samples);
234 //GCon->Logf(NAME_Debug, "...decoded frame from '%s' (res=%d; chans=%d; samples=%d)", *Strm->GetName(), res, chans, samples);
235 if (res < 0) { stopFeeding(true); return false; } // something's strange in the neighbourhood
236 inbufUsed += res;
237 // check samples first
238 if (samples > 0) {
239 if (chans < 1 || chans > 2) { stopFeeding(true); return false; } // why is that?
240 // alloc samples for two channels
241 if (outbufSize < samples*2) {
242 outbufSize = samples*2;
243 outbuf = (vint16 *)Z_Realloc(outbuf, outbufSize*2);
245 vassert(outbufSize >= samples*2);
246 outbufUsed = 0;
247 if (chans == 1) {
248 // mono, expand to stereo
249 floatbuf2short(outbuf, fltpcm[0], (unsigned)samples, true);
250 } else {
251 // stereo
252 floatbuf2short(outbuf, fltpcm[0], (unsigned)samples);
253 floatbuf2short(outbuf+1, fltpcm[1], (unsigned)samples);
255 outbufFilled = samples*2;
256 //GCon->Logf(NAME_Debug, "...DECODED: '%s' (used=%d; filled=%d; size=%d); oused=%d; ofilled=%d; osize=%d", *Strm->GetName(), inbufUsed, inbufFilled, inbufSize, outbufUsed, outbufFilled, outbufSize);
257 return true;
259 // ok, no samples, we may need more data
260 if (res == 0) {
261 //GCon->Logf(NAME_Debug, "...needs more data from '%s'", *Strm->GetName());
262 // just need more data
263 int oldInbufAvail = inbufFilled-inbufUsed;
264 fillInBuffer();
265 if (inbufFilled-inbufUsed == oldInbufAvail) {
266 // cannot get more data, set "end of stream" flag
267 //GCon->Logf(NAME_Debug, "cannot get more data from '%s'", *Strm->GetName());
268 stopFeeding(true);
269 return false;
271 //GCon->Logf(NAME_Debug, "...Decoding frame from '%s' (used=%d; filled=%d; size=%d)", *Strm->GetName(), inbufUsed, inbufFilled, inbufSize);
277 //==========================================================================
279 // VVorbisAudioCodec::Init
281 //==========================================================================
282 bool VVorbisAudioCodec::Init () {
283 Cleanup();
284 eos = false;
285 inited = false;
286 if (!fillInBuffer()) {
287 //GCon->Logf(NAME_Debug, "oops (%s)", *Strm->GetName());
288 return false;
291 //GCon->Logf(NAME_Debug, "buffer filled (%s) (%d/%d/%d)", *Strm->GetName(), inbufUsed, inbufFilled, inbufSize);
292 int usedData = 0;
293 int error = 0;
294 decoder = stb_vorbis_open_pushdata((const unsigned char *)inbuf, inbufFilled-inbufUsed, &usedData, &error, nullptr);
295 if (!decoder || error != 0) {
296 //GCon->Logf(NAME_Debug, "stb_vorbis error: %d", error);
297 Cleanup();
298 return false;
300 inbufUsed += usedData;
302 stb_vorbis_info info = stb_vorbis_get_info(decoder);
304 if (info.sample_rate < 64 || info.sample_rate > 96000*2 || info.channels < 1 || info.channels > 2) {
305 //GCon->Logf(NAME_Debug, "stb_vorbis cannot get info (%d : %d)", (int)info.sample_rate, (int)info.channels);
306 Cleanup();
307 return false;
310 SampleRate = info.sample_rate;
311 SampleBits = 16;
312 NumChannels = 2; // always
313 //GCon->Logf(NAME_Debug, "stb_vorbis created (%s); rate=%d; chans=%d", *Strm->GetName(), SampleRate, NumChannels);
314 //GCon->Logf(NAME_Debug, "buffer filled (%s) (%d/%d/%d)", *Strm->GetName(), inbufUsed, inbufFilled, inbufSize);
315 inited = true;
316 return true;
320 //==========================================================================
322 // VVorbisAudioCodec::Decode
324 //==========================================================================
325 int VVorbisAudioCodec::Decode (short *Data, int NumSamples) {
326 int CurSample = 0;
327 short *dest = Data;
328 while (CurSample < NumSamples) {
329 if (outbufUsed >= outbufFilled) {
330 if (!decodeFrame()) break;
332 while (CurSample < NumSamples && outbufUsed+2 <= outbufFilled) {
333 *dest++ = outbuf[outbufUsed++];
334 *dest++ = outbuf[outbufUsed++];
335 ++CurSample;
337 if (outbufUsed+2 > outbufFilled) outbufUsed = outbufFilled; // just in case
339 return CurSample;
343 //==========================================================================
345 // VVorbisAudioCodec::Finished
347 //==========================================================================
348 bool VVorbisAudioCodec::Finished () {
349 return (eos && outbufUsed >= outbufFilled);
353 //==========================================================================
355 // VVorbisAudioCodec::Restart
357 //==========================================================================
358 void VVorbisAudioCodec::Restart () {
359 Cleanup();
360 Strm->Seek(0);
361 BytesLeft = Strm->TotalSize();
362 Init();
366 //==========================================================================
368 // VVorbisAudioCodec::Create
370 //==========================================================================
371 VAudioCodec *VVorbisAudioCodec::Create (VStream *InStrm) {
372 VVorbisAudioCodec *Codec = new VVorbisAudioCodec(InStrm, true);
373 if (!Codec->Init()) {
374 Codec->Cleanup();
375 delete Codec;
376 return nullptr;
378 return Codec;
382 //==========================================================================
384 // VVorbisAudioCodec::Create
386 //==========================================================================
387 void VVorbisSampleLoader::Load (sfxinfo_t &Sfx, VStream &Stream) {
388 VVorbisAudioCodec *Codec = new VVorbisAudioCodec(&Stream, false);
389 //GCon->Logf(NAME_Debug, "trying sfx '%s' (VVorbisSampleLoader)", *Stream.GetName());
390 if (!Codec->Init()) {
391 Codec->Cleanup();
392 } else {
393 LoadFromAudioCodec(Sfx, Codec);
395 delete Codec;