41 using ReadCallback = std::function<int(
float* dest,
int num_frames,
int num_channels)>;
58 void start(
int num_channels,
int buffer_frames = 44100) {
61 if (num_channels <= 0 || buffer_frames <= 0 ||
62 num_channels > std::numeric_limits<int>::max() / buffer_frames ||
63 num_channels > std::numeric_limits<int>::max() / kChunkFrames) {
69 finished_.store(
false);
70 running_.store(
false);
74 const auto sample_capacity =
75 static_cast<int64_t
>(buffer_frames) *
static_cast<int64_t
>(num_channels);
76 num_channels_ = num_channels;
77 buffer_size_ =
static_cast<int>(sample_capacity);
78 buffer_.resize(
static_cast<size_t>(buffer_size_), 0.0f);
82 finished_.store(
false);
85 thread_ = std::thread([
this] { background_loop(); });
90 running_.store(
false);
92 if (thread_.joinable()) thread_.join();
101 int read(
float* dest,
int num_frames,
int num_channels) {
102 if (dest ==
nullptr || num_frames <= 0)
return 0;
103 if (num_channels != num_channels_ || buffer_.empty())
return 0;
104 if (num_channels <= 0 ||
105 num_frames > std::numeric_limits<int>::max() / num_channels)
return 0;
107 int samples_requested = num_frames * num_channels;
108 int avail = available_.load(std::memory_order_acquire);
109 int samples_to_read = std::min(samples_requested, avail);
111 if (samples_to_read <= 0) {
112 std::memset(dest, 0,
static_cast<size_t>(samples_requested) *
sizeof(
float));
117 int first_chunk = std::min(samples_to_read, buffer_size_ - read_pos_);
118 std::memcpy(dest, buffer_.data() + read_pos_,
119 static_cast<size_t>(first_chunk) *
sizeof(
float));
121 int second_chunk = samples_to_read - first_chunk;
122 if (second_chunk > 0) {
123 std::memcpy(dest + first_chunk, buffer_.data(),
124 static_cast<size_t>(second_chunk) *
sizeof(
float));
127 read_pos_ = (read_pos_ + samples_to_read) % buffer_size_;
128 available_.fetch_sub(samples_to_read, std::memory_order_release);
135 int frames_read = samples_to_read / num_channels;
136 if (frames_read < num_frames) {
137 std::memset(dest + samples_to_read, 0,
138 static_cast<size_t>((num_frames - frames_read) * num_channels) *
sizeof(
float));
152 return num_channels_ > 0 ? available_.load() / num_channels_ : 0;
157 std::vector<float> buffer_;
158 int buffer_size_ = 0;
159 int num_channels_ = 0;
162 std::atomic<int> available_{0};
163 std::atomic<bool> running_{
false};
164 std::atomic<bool> finished_{
false};
167 runtime::WakeSignal wake_;
168 static constexpr int kChunkFrames = 1024;
170 static constexpr auto kIdleBackstop = std::chrono::seconds(1);
172 void background_loop() {
173 std::vector<float>
temp(
static_cast<size_t>(kChunkFrames * num_channels_));
175 while (running_.load()) {
181 return !running_.load() ||
182 available_.load(std::memory_order_acquire) < buffer_size_;
186 if (!running_.load())
break;
188 int space = buffer_size_ - available_.load(std::memory_order_acquire);
189 int frames_to_read = std::min(kChunkFrames, space / std::max(1, num_channels_));
191 if (frames_to_read <= 0)
continue;
194 if (read_callback_) {
195 frames_got = read_callback_(
temp.data(), frames_to_read, num_channels_);
198 if (frames_got <= 0) {
199 finished_.store(
true);
200 running_.store(
false);
203 if (frames_got > frames_to_read) frames_got = frames_to_read;
205 int samples = frames_got * num_channels_;
206 int first_chunk = std::min(samples, buffer_size_ - write_pos_);
207 std::memcpy(buffer_.data() + write_pos_,
temp.data(),
208 static_cast<size_t>(first_chunk) *
sizeof(
float));
210 int second_chunk = samples - first_chunk;
211 if (second_chunk > 0) {
212 std::memcpy(buffer_.data(),
temp.data() + first_chunk,
213 static_cast<size_t>(second_chunk) *
sizeof(
float));
216 write_pos_ = (write_pos_ + samples) % buffer_size_;
217 available_.fetch_add(samples, std::memory_order_release);
bool is_finished() const
Check if the source has been fully read.
Definition buffering_reader.hpp:145
void stop()
Stop the background thread and release resources.
Definition buffering_reader.hpp:89
void set_read_callback(ReadCallback cb)
Set the callback that provides audio data.
Definition buffering_reader.hpp:53
BufferingReader(const BufferingReader &)=delete
~BufferingReader()
Definition buffering_reader.hpp:45
BufferingReader()=default
int frames_available() const
Number of frames currently available in the buffer.
Definition buffering_reader.hpp:151
BufferingReader & operator=(const BufferingReader &)=delete
std::function< int(float *dest, int num_frames, int num_channels)> ReadCallback
Callback that reads audio data from a source (file, network, etc.).
Definition buffering_reader.hpp:41
bool is_running() const
Check if the reader is running.
Definition buffering_reader.hpp:148
void start(int num_channels, int buffer_frames=44100)
Start the background reader thread.
Definition buffering_reader.hpp:58
int read(float *dest, int num_frames, int num_channels)
Read frames from the buffer (audio thread, lock-free on the hot path).
Definition buffering_reader.hpp:101
Definition analyzer_provider.hpp:12
Wakes a sleeping consumer thread from a realtime producer without a lock, and without a system call w...