arduino-audio-tools
Loading...
Searching...
No Matches
AdaptiveResamplingBuffer.h
Go to the documentation of this file.
1#pragma once
2
3#include <atomic>
4
9
10namespace audio_tools {
11
51class AdaptiveResamplingBuffer : public BaseBuffer<uint8_t>,
52 public AudioInfoSupport {
53 public:
59
71
84
85 // resample_stream internally stores a pointer to the sibling
86 // queue_stream member (via setStream()). A copy would leave the copy's
87 // resample_stream pointing at the original's queue_stream, silently
88 // sharing/dangling state - so copying (and, since neither QueueStream
89 // nor ResampleStream defines a real move, moving) is disabled.
92
98 void setBuffer(BaseBuffer<uint8_t>& buffer) { p_buffer = &buffer; }
99
104 void setAudioInfo(AudioInfo info) override {
105 audio_info = info;
106 // if we are already running, apply the change immediately
108 }
109
110 AudioInfo audioInfo() override { return audio_info; }
111
117 bool begin() {
118 if (p_buffer == nullptr) return false;
119 if (resample_range <= 0.0f) {
120 LOGW(
121 "resample_range is 0: call setStepRangePercent() before begin() "
122 "to enable adaptive resampling");
123 }
128 // This is a jitter buffer for an endless/live stream: a momentarily
129 // empty backing buffer is a transient underflow, not end of stream.
130 resample_stream.transformationReader().setEofOnZeroReads(false);
131 last_time_ms = 0;
132 is_active = true;
133 is_primed = false;
135 // pid.calculate() returns a correction in [-resample_range, +resample_range]
136 // which is subtracted from the feedforward center in recalculate().
137 return pid.begin(1.0, resample_range, -resample_range, p, i, d);
138 }
139
143 void end() {
144 is_active = false;
147 recalc_count = 0;
148 last_time_ms = 0;
149 has_peek = false;
150 }
151
153 bool write(uint8_t data) override { return writeArray(&data, 1) == 1; }
154
157 int writeArray(const uint8_t data[], int len) override {
158 if (p_buffer == nullptr) return 0;
159 int result = p_buffer->writeArray(data, len);
160 total_bytes_written += result;
161 recalculate();
162 return result;
163 }
164
166 bool read(uint8_t& result) override { return readArray(&result, 1) == 1; }
167
170 int readArray(uint8_t data[], int len) override {
171 if (p_buffer == nullptr || len <= 0 || !checkPrimed()) return 0;
172 int written = 0;
173 if (has_peek) {
174 data[written++] = peek_byte;
175 has_peek = false;
176 }
177 if (written < len) {
178 written += resample_stream.readBytes(data + written, len - written);
179 }
180 total_bytes_read += written;
181 return written;
182 }
183
186 bool peek(uint8_t& result) override {
187 if (!checkPrimed()) return false;
188 if (!has_peek) {
189 if (resample_stream.readBytes(&peek_byte, 1) != 1) return false;
190 has_peek = true;
191 }
192 result = peek_byte;
193 return true;
194 }
195
200 void reset() override {
201 if (p_buffer != nullptr) p_buffer->reset();
202 has_peek = false;
203 pid.reset();
205 step_size = 1.0f;
207 last_time_ms = 0;
208 is_primed = false;
210 if (is_active) {
214 }
215 }
216
224 int available() override {
225 if (p_buffer == nullptr || !checkPrimed()) return 0;
228 return (has_peek ? 1 : 0) + (int)estimate;
229 }
230
232 int availableForWrite() override {
233 if (p_buffer == nullptr) return 0;
234 return p_buffer->availableForWrite();
235 }
236
238 uint8_t* address() override { return nullptr; }
239
241 size_t size() override { return p_buffer == nullptr ? 0 : p_buffer->size(); }
242
244 float levelPercent() override {
245 if (p_buffer == nullptr) return 0.0f;
246 return p_buffer->levelPercent();
247 }
248
255 float recalculate() {
256 if (p_buffer == nullptr) return step_size;
257
258 // determine the actual elapsed time so the PID's integral/derivative
259 // terms don't depend on how often/how large the caller's writes are
261 float dt = last_time_ms == 0 ? 1.0f : (now_ms - last_time_ms) / 1000.0f;
262 if (dt <= 0.0f) dt = 0.001f;
263 pid.setDt(dt);
265
266 // calculate new resampling step size
269 // A larger step size makes the resampler consume the buffered input
270 // faster, so a low fill level (correction > 0) must reduce the step
271 // size, and a high fill level (correction < 0) must increase it.
273 // Feedforward + feedback: center on the long-run average step size
274 // implied by cumulative bytes written vs. read (a low-noise estimate
275 // of genuine producer/consumer clock drift) and let the PID's
276 // correction only handle short-term jitter around that center. This
277 // is far more stable than centering on a fixed 1.0 and relying solely
278 // on the PID to chase both drift and jitter from a noisy instantaneous
279 // fill level.
281
282 // log step size every 100th recalculation
283 if (recalc_count++ % 100 == 0) {
284 LOGI("step_size: %f", step_size.load());
285 }
286
288 return step_size;
289 }
290
303 // Deliberately NOT netted against the buffer's current occupancy
304 // (e.g. total_written - p_buffer->available()): that quantity is
305 // itself the result of whatever step size the resampler already
306 // applied, so feeding it back as the next step size's center creates
307 // a circular, self-reinforcing loop - any startup transient gets
308 // permanently amplified instead of forgotten. total_bytes_written and
309 // total_bytes_read are each driven purely by the source/sink and
310 // don't depend on our own past control output, so their ratio is the
311 // step size that would have kept the buffer's fill level unchanged
312 // over the window - a genuine, non-circular drift estimate. Residual
313 // absolute fill-level error is exactly what the PID feedback term
314 // already corrects for.
315 float ratio = (float)total_bytes_written / (float)total_bytes_read;
316 // safety net against a pathological/runaway estimate; real clock drift
317 // is normally well under 1%, so this only guards against bugs/edge cases
318 if (ratio < 0.5f) ratio = 0.5f;
319 if (ratio > 2.0f) ratio = 2.0f;
320 return ratio;
321 }
322
332
336
340
349
357
360 float stepSize() { return step_size; }
361
364 bool isPrimed() { return checkPrimed(); }
365
375
383 void setPIDParameters(float p_value, float i_value, float d_value) {
384 p = p_value;
385 i = i_value;
386 d = d_value;
387 }
388
389 protected:
395
398 bool checkPrimed() {
399 if (is_primed) return true;
400 if (start_fill_percent <= 0.0f) {
401 is_primed = true;
402 } else if (p_buffer != nullptr &&
404 is_primed = true;
405 }
406 if (is_primed) {
407 // Priming accumulates writes with no matching reads, so the
408 // resampler's first-ever read would have to work through that
409 // whole backlog at once - permanently skewing the cumulative
410 // written-vs-read ratio used by averageStepSize(). Start counting
411 // only from the moment real (post-priming) traffic begins.
413 }
414 return is_primed;
415 }
416
417 PIDController pid; // PID controller for adaptive resampling step size
418 QueueStream<uint8_t> queue_stream; // Internal queue stream for buffering audio data
419 BaseBuffer<uint8_t>* p_buffer = nullptr; // Pointer to the user-provided raw buffer
420 ResampleStream resample_stream; // Resample stream for adjusting playback rate
421 KalmanFilter kalman_filter{0.01f, 0.1f}; // Kalman filter for smoothing buffer fill level
422 AudioInfo audio_info; // Audio format of the buffered data
423 std::atomic<float> step_size{1.0f}; // Current resampling step size (see class docs on thread-safety)
424 float resample_range = 0; // Allowed resampling range (fraction)
425 float p = 0.005; // PID proportional gain
426 float i = 0.00005; // PID integral gain
427 float d = 0.0001; // PID derivative gain
428 float level_percent_smoothed = 0.0; // Last calculated (Kalman-smoothed) fill level (percent)
429 uint32_t recalc_count = 0; // recalculate() call counter (used to throttle logging)
430 uint32_t last_time_ms = 0; // Timestamp of the last recalculate() call, for PID dt
431 bool is_active = false; // true between begin() and end()
432 uint8_t peek_byte = 0; // one-byte lookahead cache for peek()
433 bool has_peek = false; // true if peek_byte holds an unconsumed byte
434 float start_fill_percent = 50.0f; // required fill level before priming completes (0 = disabled)
435 bool is_primed = false; // true once the start fill level has been reached once
436 uint64_t total_bytes_written = 0; // cumulative raw bytes written since counters were last reset
437 uint64_t total_bytes_read = 0; // cumulative resampled bytes delivered since counters were last reset
438 uint32_t min_bytes_for_drift_estimate = 4096; // warm-up threshold for averageStepSize()
439};
440
441} // namespace audio_tools
#define LOGW(...)
Definition AudioLoggerIDF.h:29
#define LOGI(...)
Definition AudioLoggerIDF.h:28
A BaseBuffer<uint8_t> that wraps a raw (unresampled) backing buffer and transparently resamples on re...
Definition AdaptiveResamplingBuffer.h:52
void setStepRangePercent(float rangePercent)
Set the allowed resampling range as a percent.
Definition AdaptiveResamplingBuffer.h:346
bool has_peek
Definition AdaptiveResamplingBuffer.h:433
uint32_t last_time_ms
Definition AdaptiveResamplingBuffer.h:430
uint8_t * address() override
Not supported: resampled data has no contiguous physical representation.
Definition AdaptiveResamplingBuffer.h:238
size_t size() override
Capacity (in bytes) of the backing buffer.
Definition AdaptiveResamplingBuffer.h:241
void setStartFillPercent(float percent)
Defines the fill level (in percent of the backing buffer's capacity) that must be reached once,...
Definition AdaptiveResamplingBuffer.h:83
AdaptiveResamplingBuffer & operator=(const AdaptiveResamplingBuffer &)=delete
float p
Definition AdaptiveResamplingBuffer.h:425
void setBuffer(BaseBuffer< uint8_t > &buffer)
Set the raw (unresampled) backing buffer.
Definition AdaptiveResamplingBuffer.h:98
void setPIDParameters(float p_value, float i_value, float d_value)
Set the PID controller parameters.
Definition AdaptiveResamplingBuffer.h:383
KalmanFilter kalman_filter
Definition AdaptiveResamplingBuffer.h:421
BaseBuffer< uint8_t > * p_buffer
Definition AdaptiveResamplingBuffer.h:419
AdaptiveResamplingBuffer(const AdaptiveResamplingBuffer &)=delete
int readArray(uint8_t data[], int len) override
Definition AdaptiveResamplingBuffer.h:170
float d
Definition AdaptiveResamplingBuffer.h:427
bool is_active
Definition AdaptiveResamplingBuffer.h:431
bool isPrimed()
Definition AdaptiveResamplingBuffer.h:364
bool write(uint8_t data) override
Writes a single byte to the backing buffer.
Definition AdaptiveResamplingBuffer.h:153
AdaptiveResamplingBuffer(BaseBuffer< uint8_t > &buffer, float stepRangePercent=5.0f)
Construct a new AdaptiveResamplingBuffer object.
Definition AdaptiveResamplingBuffer.h:66
float start_fill_percent
Definition AdaptiveResamplingBuffer.h:434
bool peek(uint8_t &result) override
Definition AdaptiveResamplingBuffer.h:186
float i
Definition AdaptiveResamplingBuffer.h:426
void resetDriftCounters()
Resets the cumulative counters used by averageStepSize().
Definition AdaptiveResamplingBuffer.h:391
bool begin()
Initialize the buffer and internal components.
Definition AdaptiveResamplingBuffer.h:117
AudioInfo audio_info
Definition AdaptiveResamplingBuffer.h:422
uint32_t recalc_count
Definition AdaptiveResamplingBuffer.h:429
int available() override
Definition AdaptiveResamplingBuffer.h:224
uint64_t total_bytes_read
Definition AdaptiveResamplingBuffer.h:437
uint64_t total_bytes_written
Definition AdaptiveResamplingBuffer.h:436
ResampleStream resample_stream
Definition AdaptiveResamplingBuffer.h:420
int availableForWrite() override
Number of raw bytes that can still be written to the backing buffer.
Definition AdaptiveResamplingBuffer.h:232
QueueStream< uint8_t > queue_stream
Definition AdaptiveResamplingBuffer.h:418
float levelPercent() override
Current actual fill level of the backing buffer in percent (0-100).
Definition AdaptiveResamplingBuffer.h:244
AdaptiveResamplingBuffer()=default
Construct a new AdaptiveResamplingBuffer object You need to call setBuffer() and setStepRangePercent(...
uint32_t min_bytes_for_drift_estimate
Definition AdaptiveResamplingBuffer.h:438
uint8_t peek_byte
Definition AdaptiveResamplingBuffer.h:432
float resample_range
Definition AdaptiveResamplingBuffer.h:424
void end()
End the buffer and release resources.
Definition AdaptiveResamplingBuffer.h:143
float stepSize()
Definition AdaptiveResamplingBuffer.h:360
std::atomic< float > step_size
Definition AdaptiveResamplingBuffer.h:423
bool checkPrimed()
Definition AdaptiveResamplingBuffer.h:398
bool read(uint8_t &result) override
Reads a single resampled byte.
Definition AdaptiveResamplingBuffer.h:166
void setKalmanParameters(float process_noise, float measurement_noise)
Set the Kalman filter parameters.
Definition AdaptiveResamplingBuffer.h:372
float averageStepSize()
Long-run average step size implied by cumulative bytes written vs. bytes read, used as the feedforwar...
Definition AdaptiveResamplingBuffer.h:301
void setMinBytesForDriftEstimate(uint32_t bytes)
Minimum cumulative output bytes that must have been read before averageStepSize() is trusted; below t...
Definition AdaptiveResamplingBuffer.h:329
float levelPercentSmoothed()
Get the Kalman-smoothed fill level from the last recalculate() call, in percent.
Definition AdaptiveResamplingBuffer.h:356
void setAudioInfo(AudioInfo info) override
Defines the audio format: needed to correctly interpret and resample the sample frames stored in the ...
Definition AdaptiveResamplingBuffer.h:104
bool is_primed
Definition AdaptiveResamplingBuffer.h:435
float level_percent_smoothed
Definition AdaptiveResamplingBuffer.h:428
uint64_t totalBytesRead()
Definition AdaptiveResamplingBuffer.h:339
void reset() override
Definition AdaptiveResamplingBuffer.h:200
AudioInfo audioInfo() override
provides the actual input AudioInfo
Definition AdaptiveResamplingBuffer.h:110
uint64_t totalBytesWritten()
Definition AdaptiveResamplingBuffer.h:335
float recalculate()
Recalculate the resampling step size based on buffer fill level. Called automatically by writeArray()...
Definition AdaptiveResamplingBuffer.h:255
int writeArray(const uint8_t data[], int len) override
Definition AdaptiveResamplingBuffer.h:157
PIDController pid
Definition AdaptiveResamplingBuffer.h:417
Supports changes to the sampling rate, bits and channels.
Definition AudioTypes.h:131
Shared functionality of all buffers.
Definition Buffers.h:23
virtual void reset()=0
clears the buffer
virtual int writeArray(const T data[], int len)
Fills the buffer data.
Definition Buffers.h:56
virtual size_t size()=0
virtual int availableForWrite()=0
provides the number of entries that are available to write
virtual float levelPercent()
Returns the level of the buffer in %.
Definition Buffers.h:114
virtual int available()=0
provides the number of entries that are available to read
Simple 1D Kalman Filter for smoothing measurements.
Definition KalmanFilter.h:27
float calculate()
Returns the current estimated value.
Definition KalmanFilter.h:102
bool begin(float process_noise, float measurement_noise)
reset the filter with new parameters
Definition KalmanFilter.h:56
void end()
End or clear the filter (sets the estimate to zero).
Definition KalmanFilter.h:78
void addMeasurement(float measurement)
Updates the filter with a new measurement and returns the filtered value.
Definition KalmanFilter.h:87
A simple header only PID Controller.
Definition PIDController.h:15
float calculate(float target, float measured)
Definition PIDController.h:49
void setDt(float dt)
Definition PIDController.h:35
void reset()
Definition PIDController.h:41
bool begin(float dt, float max, float min, float kp, float ki, float kd)
Definition PIDController.h:23
Stream class which stores the data in a temporary queue buffer. The queue can be consumed e....
Definition BaseStream.h:359
virtual bool begin() override
Activates the output.
Definition BaseStream.h:387
void setBuffer(BaseBuffer< T > &buffer)
Definition BaseStream.h:381
virtual void end() override
stops the processing
Definition BaseStream.h:406
virtual void setStream(Stream &stream) override
Defines/Changes the input & output.
Definition AudioIO.h:257
size_t readBytes(uint8_t *data, size_t len) override
Definition AudioIO.h:286
void end() override
Definition AudioIO.h:308
virtual TransformationReader< ReformatBaseStream > & transformationReader()
Provides access to the TransformationReader.
Definition AudioIO.h:325
Dynamic Resampling. We can use a variable factor to speed up or slow down the playback.
Definition ResampleStream.h:33
void setAudioInfo(AudioInfo newInfo) override
Defines the input AudioInfo.
Definition ResampleStream.h:123
void setStepSize(float step)
influence the sample rate
Definition ResampleStream.h:144
bool begin(ResampleConfig cfg)
Definition ResampleStream.h:63
Generic Implementation of sound input and output for desktop environments using portaudio.
Definition AudioCodecsBase.h:10
uint32_t millis()
Returns the milliseconds since the start.
Definition Arduino.h:256
size_t writeData(Print *p_out, T *data, int samples, int maxSamples=512)
Definition AudioTypes.h:508
Basic Audio information which drives e.g. I2S.
Definition AudioTypes.h:51