FastLED 3.9.15
Loading...
Searching...
No Matches
vorbis.cpp.hpp
Go to the documentation of this file.
1// ============================================================================
2// stb_vorbis configuration for FastLED embedded targets
3// Use FL_STB_* macros (translated to STB_* in third_party header)
4// These MUST be defined BEFORE including the third_party header
5// ============================================================================
6
7
8
9
10// ============================================================================
11
12#include "fl/codec/vorbis.h"
13#include "fl/stl/cstring.h"
14
15// Include stb_vorbis raw API (FL_STB_* macros translated to STB_* internally)
16// IWYU pragma: begin_keep
18#include "fl/stl/noexcept.h"
19// IWYU pragma: end_keep
20
21namespace fl {
22
23// Short alias for third_party vorbis namespace to avoid verbose qualification
24// without polluting fl:: namespace in unity builds.
25namespace vb = third_party::vorbis;
26
27// NOTE: All 'int' types from stb_vorbis API must be cast to/from fl::i32
28// for portability across platforms where int size may vary.
29
30// StbVorbisDecoder implementation
32
36
38 close(); // Close any existing stream
39
40 fl::i32 error = 0;
41 mVorbis = vb::stb_vorbis_open_memory(data.data(), static_cast<fl::i32>(data.size()), &error, nullptr);
42 return mVorbis != nullptr;
43}
44
46 if (mVorbis) {
47 vb::stb_vorbis_close(static_cast<vb::stb_vorbis*>(mVorbis));
48 mVorbis = nullptr;
49 }
50}
51
53 return mVorbis != nullptr;
54}
55
57 VorbisInfo info;
58 if (mVorbis) {
59 vb::stb_vorbis_info vi = vb::stb_vorbis_get_info(static_cast<vb::stb_vorbis*>(mVorbis));
60 info.sampleRate = vi.sample_rate;
61 info.channels = static_cast<fl::u8>(vi.channels);
62 info.maxFrameSize = static_cast<fl::u32>(vi.max_frame_size);
63 info.totalSamples = vb::stb_vorbis_stream_length_in_samples(static_cast<vb::stb_vorbis*>(mVorbis));
64 info.isValid = true;
65 }
66 return info;
67}
68
69fl::i32 StbVorbisDecoder::getSamplesShortInterleaved(fl::i32 channels, fl::i16* buffer, fl::i32 numShorts) {
70 if (!mVorbis) return 0;
71 // Cast to short* for AVR compatibility where fl::i16 is int but stb_vorbis expects short
72 return static_cast<fl::i32>(vb::stb_vorbis_get_samples_short_interleaved(
73 static_cast<vb::stb_vorbis*>(mVorbis),
74 static_cast<fl::i32>(channels),
75 buffer,
76 static_cast<fl::i32>(numShorts)
77 ));
78}
79
80fl::i32 StbVorbisDecoder::getSamplesFloat(fl::i32 channels, float** buffer, fl::i32 numSamples) {
81 if (!mVorbis) return 0;
82 return static_cast<fl::i32>(vb::stb_vorbis_get_samples_float(
83 static_cast<vb::stb_vorbis*>(mVorbis),
84 static_cast<fl::i32>(channels),
85 buffer,
86 static_cast<fl::i32>(numSamples)
87 ));
88}
89
90bool StbVorbisDecoder::seek(fl::u32 sampleNumber) {
91 if (!mVorbis) return false;
92 return vb::stb_vorbis_seek(static_cast<vb::stb_vorbis*>(mVorbis), sampleNumber) != 0;
93}
94
96 if (!mVorbis) return 0;
97 return static_cast<fl::u32>(vb::stb_vorbis_get_sample_offset(static_cast<vb::stb_vorbis*>(mVorbis)));
98}
99
101 if (!mVorbis) return 0;
102 return vb::stb_vorbis_stream_length_in_samples(static_cast<vb::stb_vorbis*>(mVorbis));
103}
104
105// VorbisDecoderImpl - internal implementation
107public:
110
111 bool begin(fl::filebuf_ptr stream);
112 void end();
113 bool isReady() const { return mDecoder.isOpen(); }
114 bool hasError(fl::string* msg = nullptr) const;
115 bool decodeNextFrame(audio::Sample* outSample);
116 fl::size getPosition() const { return mPosition; }
117 void reset();
118 VorbisInfo getInfo() const { return mDecoder.getInfo(); }
119
120private:
121 static constexpr fl::size FRAME_SIZE = 1024; // Samples per frame
122
124 fl::vector<fl::u8> mFileData; // Entire file in memory (stb_vorbis requirement)
127 fl::size mPosition; // Current stream position in bytes
129};
130
132 mPcmBuffer.resize(FRAME_SIZE * 2); // Stereo
133}
134
138
140 end(); // Clean up any previous state
141
142 if (!stream) {
143 mError = "Null stream";
144 return false;
145 }
146
147 // stb_vorbis pulldata API requires entire file in memory
148 // Read entire stream into buffer
149 mFileData.clear();
150 fl::u8 buffer[1024];
151 while (stream->available(1)) {
152 fl::size bytesRead = stream->read(buffer, sizeof(buffer));
153 if (bytesRead == 0) break;
154 for (fl::size i = 0; i < bytesRead; ++i) {
155 mFileData.push_back(buffer[i]);
156 }
157 }
158
159 if (mFileData.empty()) {
160 mError = "Empty stream";
161 return false;
162 }
163
164 // Open decoder
165 if (!mDecoder.openMemory(mFileData)) {
166 mError = "Failed to decode Vorbis stream";
167 return false;
168 }
169
170 mEndOfStream = false;
171 mError.clear();
172 return true;
173}
174
176 mDecoder.close();
177 mFileData.clear();
178 mPosition = 0;
179 mEndOfStream = false;
180 mError.clear();
181}
182
184 if (!mError.empty()) {
185 if (msg) *msg = mError;
186 return true;
187 }
188 return false;
189}
190
192 if (!mDecoder.isOpen() || mEndOfStream) {
193 return false;
194 }
195
196 VorbisInfo info = mDecoder.getInfo();
197 fl::i32 channels = info.channels > 0 ? info.channels : 1;
198
199 // Decode to i16 interleaved
200 fl::i32 samplesDecoded = mDecoder.getSamplesShortInterleaved(
201 channels,
202 mPcmBuffer.data(),
203 static_cast<fl::i32>(mPcmBuffer.size())
204 );
205
206 if (samplesDecoded == 0) {
207 mEndOfStream = true;
208 mPosition = mFileData.size(); // At end of stream
209 return false;
210 }
211
212 // Update position estimate based on sample offset ratio
213 fl::u32 totalSamples = mDecoder.getTotalSamples();
214 if (totalSamples > 0) {
215 fl::u32 currentSample = mDecoder.getSampleOffset();
216 mPosition = (mFileData.size() * currentSample) / totalSamples;
217 }
218
219 // Convert stereo to mono by averaging if needed
220 if (channels == 2) {
222 mono.reserve(samplesDecoded);
223 for (fl::i32 i = 0; i < samplesDecoded; ++i) {
224 fl::i32 left = mPcmBuffer[i * 2];
225 fl::i32 right = mPcmBuffer[i * 2 + 1];
226 mono.push_back(static_cast<fl::i16>((left + right) / 2));
227 }
228 *outSample = audio::Sample(mono);
229 } else {
230 *outSample = audio::Sample(fl::span<const fl::i16>(mPcmBuffer.data(), samplesDecoded));
231 }
232
233 return true;
234}
235
237 if (mDecoder.isOpen()) {
238 mDecoder.seek(0);
239 mPosition = 0;
240 mEndOfStream = false;
241 }
242}
243
244// VorbisDecoder public implementation
247
248bool VorbisDecoder::begin(fl::filebuf_ptr stream) { return mImpl->begin(stream); }
249void VorbisDecoder::end() { mImpl->end(); }
250bool VorbisDecoder::isReady() const { return mImpl->isReady(); }
251bool VorbisDecoder::hasError(fl::string* msg) const { return mImpl->hasError(msg); }
252bool VorbisDecoder::decodeNextFrame(audio::Sample* outSample) { return mImpl->decodeNextFrame(outSample); }
253fl::size VorbisDecoder::getPosition() const { return mImpl->getPosition(); }
254void VorbisDecoder::reset() { mImpl->reset(); }
255VorbisInfo VorbisDecoder::getInfo() const { return mImpl->getInfo(); }
256
257// Vorbis factory implementation
258VorbisDecoderPtr Vorbis::createDecoder(fl::string* errorMessage) {
259 FL_UNUSED(errorMessage);
261}
262
264 return true; // stb_vorbis is always available
265}
266
268 StbVorbisDecoder decoder;
269 if (!decoder.openMemory(data)) {
270 if (errorMessage) *errorMessage = "Failed to parse Vorbis header";
271 return VorbisInfo();
272 }
273 return decoder.getInfo();
274}
275
278
279 StbVorbisDecoder decoder;
280 if (!decoder.openMemory(data)) {
281 if (errorMessage) *errorMessage = "Failed to open Vorbis stream";
282 return samples;
283 }
284
285 VorbisInfo info = decoder.getInfo();
286 const fl::i32 channels = info.channels > 0 ? info.channels : 1;
287 constexpr fl::i32 FRAME_SIZE = 1024;
288 fl::vector<fl::i16> buffer(FRAME_SIZE * 2);
289
290 while (true) {
291 fl::i32 samplesDecoded = decoder.getSamplesShortInterleaved(
292 channels, buffer.data(), static_cast<fl::i32>(buffer.size())
293 );
294
295 if (samplesDecoded == 0) break;
296
297 // Convert stereo to mono
298 if (channels == 2) {
300 mono.reserve(samplesDecoded);
301 for (fl::i32 i = 0; i < samplesDecoded; ++i) {
302 fl::i32 left = buffer[i * 2];
303 fl::i32 right = buffer[i * 2 + 1];
304 mono.push_back(static_cast<fl::i16>((left + right) / 2));
305 }
306 samples.push_back(audio::Sample(mono));
307 } else {
308 samples.push_back(audio::Sample(fl::span<const fl::i16>(buffer.data(), samplesDecoded)));
309 }
310 }
311
312 return samples;
313}
314
315} // namespace fl
fl::i32 getSamplesFloat(fl::i32 channels, float **buffer, fl::i32 numSamples)
fl::u32 getTotalSamples() const
bool isOpen() const
StbVorbisDecoder() FL_NOEXCEPT
~StbVorbisDecoder() FL_NOEXCEPT
bool openMemory(fl::span< const fl::u8 > data)
fl::u32 getSampleOffset() const
VorbisInfo getInfo() const
bool seek(fl::u32 sampleNumber)
fl::i32 getSamplesShortInterleaved(fl::i32 channels, fl::i16 *buffer, fl::i32 numShorts)
static VorbisInfo parseVorbisInfo(fl::span< const fl::u8 > data, fl::string *errorMessage=nullptr)
static bool isSupported()
static fl::vector< audio::Sample > decodeAll(fl::span< const fl::u8 > data, fl::string *errorMessage=nullptr)
static VorbisDecoderPtr createDecoder(fl::string *errorMessage=nullptr)
~VorbisDecoder() FL_NOEXCEPT
bool hasError(fl::string *msg=nullptr) const
bool isReady() const
VorbisDecoder() FL_NOEXCEPT
bool begin(fl::filebuf_ptr stream)
VorbisInfo getInfo() const
fl::unique_ptr< VorbisDecoderImpl > mImpl
Definition vorbis.h:112
fl::size getPosition() const
bool decodeNextFrame(audio::Sample *outSample)
bool hasError(fl::string *msg=nullptr) const
VorbisInfo getInfo() const
fl::vector< fl::u8 > mFileData
fl::vector< fl::i16 > mPcmBuffer
bool begin(fl::filebuf_ptr stream)
StbVorbisDecoder mDecoder
fl::size getPosition() const
VorbisDecoderImpl() FL_NOEXCEPT
static constexpr fl::size FRAME_SIZE
~VorbisDecoderImpl() FL_NOEXCEPT
bool decodeNextFrame(audio::Sample *outSample)
virtual bool available() const
Definition file_handle.h:53
virtual fl::size_t read(char *buffer, fl::size_t count)=0
const T * data() const FL_NOEXCEPT
Definition span.h:461
constexpr fl::size size() const FL_NOEXCEPT
Definition span.h:458
fl::size size() const FL_NOEXCEPT
T * data() FL_NOEXCEPT
Definition vector.h:619
void reserve(fl::size n) FL_NOEXCEPT
Definition vector.h:591
void push_back(const T &value) FL_NOEXCEPT
Definition vector.h:624
unsigned char u8
Definition s16x16x4.h:132
fl::enable_if<!fl::is_array< T >::value, unique_ptr< T > >::type make_unique(Args &&... args) FL_NOEXCEPT
Definition unique_ptr.h:261
shared_ptr< T > make_shared(Args &&... args) FL_NOEXCEPT
Definition shared_ptr.h:414
fl::shared_ptr< filebuf > filebuf_ptr
Definition idecoder.h:15
Base definition for an LED controller.
Definition crgb.hpp:179
#define FL_UNUSED(x)
#define FL_NOEXCEPT
bool isValid
Definition vorbis.h:21
fl::u32 maxFrameSize
Definition vorbis.h:20
fl::u8 channels
Definition vorbis.h:18
fl::u32 totalSamples
Definition vorbis.h:19
fl::u32 sampleRate
Definition vorbis.h:17