FastLED 3.10.6
Loading...
Searching...
No Matches
AutoResearchMp3.h
Go to the documentation of this file.
1
18
19#pragma once
20
21#include <FastLED.h>
22
23#include "fl/stl/int.h"
24#include "fl/stl/cstring.h"
25#include "fl/stl/unique_ptr.h"
26
28
29#include "fl/codec/mp3.h"
31
32namespace autoresearch {
33namespace mp3_check {
34
35struct Result {
36 bool success;
37 fl::u32 streams_run;
39 const char* first_failure;
41 fl::u32 actual_fnv1a;
47 /* Populated on every run, pass or fail. A green result that reports only
48 zeros is not evidence that anything was decoded -- it just means nothing
49 tripped the failure path. These say what the device actually produced. */
53 /* Wall-clock for the first pass only, so it has the same scope as the
54 counters above it. Timing both passes while counting one reads as if the
55 decode were twice as slow as it is. */
57 /* How much audio those samples represent, from the fixture's own sample
58 rate and channel count. Reporting it alongside decode_micros is what
59 makes the pair interpretable: the ratio is the real-time margin, and on a
60 160 MHz part it is not large enough to leave implicit. */
61 fl::u32 audio_micros;
62};
63
64inline fl::u32 fnv1a(fl::u32 hash, const fl::i16* samples, fl::u32 count) {
65 for (fl::u32 i = 0; i < count; ++i) {
66 const fl::u16 v = (fl::u16)samples[i];
67 hash = (hash ^ (fl::u8)(v & 0xFF)) * 16777619u;
68 hash = (hash ^ (fl::u8)(v >> 8)) * 16777619u;
69 }
70 return hash;
71}
72
75inline fl::u32 fnv1a_mix(fl::u32 hash, fl::u32 value) {
76 for (int byte = 0; byte < 4; ++byte) {
77 hash = (hash ^ (fl::u8)((value >> (byte * 8)) & 0xFF)) * 16777619u;
78 }
79 return hash;
80}
81
82struct Decoded {
83 fl::u32 fnv1a;
84 fl::u32 samples;
85 fl::u32 frames;
86 /* The fixture records each stream's sample rate, channel count and layer.
87 Carrying them back is what makes them assertions rather than comments: a
88 decoder that emitted the right sample values at the wrong rate, or fell
89 back to mono, would otherwise match on the checksum path and pass. */
90 fl::u32 hz;
91 fl::u32 channels;
92 fl::u32 layer;
93};
94
98inline Decoded decodeStream(fl::third_party::mp3dec_t* dec,
99 fl::third_party::mp3dec_scratch_t* scratch,
100 fl::i16* pcm, const mp3_fixture::Stream& stream) {
101 Decoded out;
102 out.fnv1a = 2166136261u;
103 out.samples = 0;
104 out.frames = 0;
105 out.hz = 0;
106 out.channels = 0;
107 out.layer = 0;
108
109 fl::u32 offset = 0;
110 while (offset + 4 < stream.size) {
111 fl::third_party::mp3dec_frame_info_t info;
112 fl::memset(&info, 0, sizeof(info));
113 const int samples = fl::third_party::mp3dec_decode_frame_r(
114 dec, scratch, stream.data + offset, (int)(stream.size - offset),
115 pcm, &info);
116 if (info.frame_bytes <= 0) {
117 break;
118 }
119 offset += (fl::u32)info.frame_bytes;
120 if (samples > 0) {
121 const fl::u32 count = (fl::u32)samples * (fl::u32)info.channels;
122 out.fnv1a = fnv1a(out.fnv1a, pcm, count);
123 out.samples += count;
124 ++out.frames;
125 out.hz = (fl::u32)info.hz;
126 out.channels = (fl::u32)info.channels;
127 out.layer = (fl::u32)info.layer;
128 }
129 }
130 return out;
131}
132
133inline void recordFailure(Result& r, const char* name, fl::u32 expected_hash,
134 fl::u32 actual_hash, fl::u32 expected_samples,
135 fl::u32 actual_samples) {
136 ++r.streams_failed;
137 if (r.first_failure == nullptr) {
138 r.first_failure = name;
139 r.expected_fnv1a = expected_hash;
140 r.actual_fnv1a = actual_hash;
141 r.expected_samples = expected_samples;
142 r.actual_samples = actual_samples;
143 }
144}
145
146inline Result run() {
147 Result r;
148 fl::memset(&r, 0, sizeof(r));
149 r.success = true;
150 r.first_failure = nullptr;
151 r.scratch_bytes = (fl::u32)sizeof(fl::third_party::mp3dec_scratch_t);
152 r.shared_decoder_matched = true;
153 r.combined_fnv1a = 2166136261u;
154
155 // The decoder state and scratch arena are far too large for the stack on a
156 // microcontroller -- roughly 11 KB and 8 KB -- so they go on the heap, once.
161 // make_unique<T[]> yields unique_ptr<T[]>, released with delete[].
163 if (!dec || !scratch || !pcm) {
164 r.success = false;
165 r.first_failure = "allocation";
166 return r;
167 }
168
169 const fl::u32 started = (fl::u32)micros();
170 fl::u32 audio_us = 0;
171
172 // Pass 1: a fresh decoder per stream, which is how the host fixture was
173 // produced. This is the bit-exactness check.
174 for (fl::u32 i = 0; i < mp3_fixture::kStreamCount; ++i) {
176 fl::third_party::mp3dec_init(dec.get());
177 const Decoded got = decodeStream(dec.get(), scratch.get(), pcm.get(), s);
178 ++r.streams_run;
179 r.samples_decoded += got.samples;
180 r.frames_decoded += got.frames;
182 if (s.hz > 0u && s.channels > 0u) {
183 const fl::u32 per_channel = got.samples / s.channels;
184 audio_us += (fl::u32)(((fl::u64)per_channel * 1000000ull) / s.hz);
185 }
186 if (got.fnv1a != s.fnv1a || got.samples != s.samples ||
187 got.frames != s.frames || got.hz != s.hz ||
188 got.channels != s.channels || got.layer != s.layer) {
189 recordFailure(r, s.name, s.fnv1a, got.fnv1a, s.samples,
190 got.samples);
191 }
192 }
193
194 r.decode_micros = (fl::u32)micros() - started;
195 r.audio_micros = audio_us;
196 const fl::u32 verify_started = (fl::u32)micros();
197
198 // Pass 2: one decoder driven across every stream in turn, without
199 // re-initialising between them. Layer I/II scale info and Layer III's
200 // main-data window share arena storage (FastLED#4116) on the grounds that a
201 // frame is one layer or the other and never both. If that is wrong -- or if
202 // it is right on x86-64 and wrong here, where the struct is packed
203 // differently -- the second stream decodes differently than it did alone.
204 fl::third_party::mp3dec_init(dec.get());
205 for (fl::u32 i = 0; i < mp3_fixture::kStreamCount; ++i) {
207 const Decoded got = decodeStream(dec.get(), scratch.get(), pcm.get(), s);
208 if (got.fnv1a != s.fnv1a || got.samples != s.samples ||
209 got.hz != s.hz || got.channels != s.channels ||
210 got.layer != s.layer) {
211 r.shared_decoder_matched = false;
212 if (r.first_failure == nullptr) {
213 r.first_failure = s.name;
214 r.expected_fnv1a = s.fnv1a;
215 r.actual_fnv1a = got.fnv1a;
217 r.actual_samples = got.samples;
218 }
219 }
220 }
221
222 r.verify_micros = (fl::u32)micros() - verify_started;
225 return r;
226}
227
228} // namespace mp3_check
229} // namespace autoresearch
Expected MP3 decodes for the on-device codec check.
pointer get() const FL_NO_EXCEPT
Definition unique_ptr.h:97
fl::UISlider offset("Offset", 0.0f, 0.0f, 1.0f, 0.01f)
uint8_t byte
Definition midi_Defs.h:36
void recordFailure(Result &r, const char *name, fl::u32 expected_hash, fl::u32 actual_hash, fl::u32 expected_samples, fl::u32 actual_samples)
fl::u32 fnv1a(fl::u32 hash, const fl::i16 *samples, fl::u32 count)
fl::u32 fnv1a_mix(fl::u32 hash, fl::u32 value)
Fold one stream's checksum into the run-wide one, so a single reported value covers every stream in o...
Decoded decodeStream(fl::third_party::mp3dec_t *dec, fl::third_party::mp3dec_scratch_t *scratch, fl::i16 *pcm, const mp3_fixture::Stream &stream)
Decode one stream.
unsigned char u8
Definition stdint.h:131
fl::enable_if<!fl::is_array< T >::value, unique_ptr< T > >::type make_unique(Args &&... args) FL_NO_EXCEPT
Definition unique_ptr.h:261
void * memset(void *s, int c, size_t n) FL_NO_EXCEPT