OpenShot Library | libopenshot  0.7.0
AudioRecorder.cpp
Go to the documentation of this file.
1 
9 // Copyright (c) 2008-2026 OpenShot Studios, LLC
10 //
11 // SPDX-License-Identifier: LGPL-3.0-or-later
12 
13 #include "AudioRecorder.h"
14 
15 #include <algorithm>
16 #include <cmath>
17 #include <utility>
18 
19 #include "Exceptions.h"
20 #include "FFmpegWriter.h"
21 #include "Frame.h"
22 
23 using namespace openshot;
24 
26 {
27  AudioLevelData result;
28  result.timestamp = block.sample_rate > 0
29  ? static_cast<double>(block.first_sample) / static_cast<double>(block.sample_rate)
30  : 0.0;
31 
32  const int channels = static_cast<int>(block.channels.size());
33  result.peak.assign(channels, 0.0f);
34  result.rms.assign(channels, 0.0f);
35 
36  for (int channel = 0; channel < channels; ++channel) {
37  const auto& samples = block.channels[channel];
38  double squared_sum = 0.0;
39  float peak = 0.0f;
40 
41  for (float sample : samples) {
42  const float abs_sample = std::abs(sample);
43  peak = std::max(peak, abs_sample);
44  squared_sum += static_cast<double>(sample) * static_cast<double>(sample);
45  if (abs_sample >= 1.0f) {
46  result.clipped = true;
47  }
48  }
49 
50  result.peak[channel] = peak;
51  if (!samples.empty()) {
52  result.rms[channel] = static_cast<float>(std::sqrt(squared_sum / static_cast<double>(samples.size())));
53  }
54  }
55 
56  return result;
57 }
58 
59 AudioRecorderWaveformAccumulator::AudioRecorderWaveformAccumulator(int new_sample_rate, int new_samples_per_second)
60  : sample_rate(new_sample_rate)
61  , samples_per_second(new_samples_per_second)
62  , sample_divisor(1)
63  , pending_samples(0)
64  , pending_max(0.0f)
65  , pending_squared_sum(0.0)
66  , emitted_visual_samples(0)
67 {
68  if (sample_rate <= 0 || samples_per_second <= 0) {
69  throw InvalidOptions("Audio waveform settings require a valid sample rate and samples-per-second value.");
70  }
71 
72  sample_divisor = std::max(1, sample_rate / samples_per_second);
73 }
74 
75 std::vector<AudioWaveformChunk> AudioRecorderWaveformAccumulator::ProcessBlock(const AudioRecorderBlock& block)
76 {
77  std::vector<AudioWaveformChunk> chunks;
78  if (block.channels.empty() || block.Samples() <= 0) {
79  return chunks;
80  }
81 
82  AudioWaveformChunk chunk;
83  chunk.samples_per_second = samples_per_second;
84  chunk.start_time = static_cast<double>(emitted_visual_samples) / static_cast<double>(samples_per_second);
85 
86  const int channels = static_cast<int>(block.channels.size());
87  const int samples = block.Samples();
88  for (int sample_index = 0; sample_index < samples; ++sample_index) {
89  for (int channel = 0; channel < channels; ++channel) {
90  if (sample_index >= static_cast<int>(block.channels[channel].size())) {
91  continue;
92  }
93  const float sample = block.channels[channel][sample_index];
94  pending_max = std::max(pending_max, std::abs(sample));
95  pending_squared_sum += static_cast<double>(sample) * static_cast<double>(sample);
96  }
97 
98  pending_samples++;
99  if (pending_samples >= sample_divisor) {
100  const double denominator = static_cast<double>(pending_samples * channels);
101  const float rms = denominator > 0.0
102  ? static_cast<float>(std::sqrt(pending_squared_sum / denominator))
103  : 0.0f;
104 
105  max_samples.push_back(pending_max);
106  rms_samples.push_back(rms);
107  chunk.max_samples.push_back(pending_max);
108  chunk.rms_samples.push_back(rms);
109  emitted_visual_samples++;
110 
111  pending_samples = 0;
112  pending_max = 0.0f;
113  pending_squared_sum = 0.0;
114  }
115  }
116 
117  if (!chunk.max_samples.empty()) {
118  chunk.duration = static_cast<double>(chunk.max_samples.size()) / static_cast<double>(samples_per_second);
119  chunks.push_back(std::move(chunk));
120  }
121 
122  return chunks;
123 }
124 
126 {
127  AudioWaveformData result;
128  result.max_samples = max_samples;
129  result.rms_samples = rms_samples;
130  return result;
131 }
132 
134 {
135  pending_samples = 0;
136  pending_max = 0.0f;
137  pending_squared_sum = 0.0;
138  emitted_visual_samples = 0;
139  max_samples.clear();
140  rms_samples.clear();
141 }
142 
144  const AudioRecorderBlock& block,
145  ChannelLayout channel_layout,
146  int64_t frame_number)
147 {
148  const int channels = static_cast<int>(block.channels.size());
149  const int samples = block.Samples();
150  auto frame = std::make_shared<Frame>(frame_number, samples, channels);
151  frame->SampleRate(block.sample_rate);
152  frame->ChannelsLayout(channel_layout);
153 
154  for (int channel = 0; channel < channels; ++channel) {
155  frame->AddAudio(true, channel, 0, block.channels[channel].data(), samples, 1.0f);
156  }
157 
158  return frame;
159 }
160 
162  : settings(new_settings)
163  , writer(nullptr)
164  , waveform_accumulator(nullptr)
165  , is_open(false)
166  , is_recording(false)
167  , is_monitoring(false)
168  , writer_should_stop(false)
169  , samples_recorded(0)
170  , dropped_blocks(0)
171  , next_frame_number(1)
172 {
173  ValidateSettings();
174 }
175 
177 {
178  Close();
179 }
180 
181 void AudioRecorder::ValidateSettings() const
182 {
183  if (settings.path.empty()) {
184  throw InvalidOptions("Audio recorder requires an output path.");
185  }
186  if (settings.codec.empty()) {
187  throw InvalidOptions("Audio recorder requires an audio codec.");
188  }
189  if (settings.sample_rate < 8000) {
190  throw InvalidSampleRate("Audio recorder requires a sample rate of at least 8000 Hz.", settings.path);
191  }
192  if (settings.channels <= 0) {
193  throw InvalidChannels("Audio recorder requires at least one input channel.", settings.path);
194  }
195  if (settings.buffer_size <= 0) {
196  throw InvalidOptions("Audio recorder requires a positive audio buffer size.", settings.path);
197  }
198  if (settings.waveform_samples_per_second <= 0) {
199  throw InvalidOptions("Audio recorder requires a positive waveform sample rate.", settings.path);
200  }
201  if (settings.max_queue_seconds <= 0) {
202  throw InvalidOptions("Audio recorder requires a positive maximum queue duration.", settings.path);
203  }
204 }
205 
207 {
208  if (is_open) {
209  return;
210  }
211 
212  if (!settings.device_type.empty()) {
213  device_manager.setCurrentAudioDeviceType(settings.device_type, true);
214  }
215 
216  juce::AudioDeviceManager::AudioDeviceSetup setup;
217  setup.inputChannels.clear();
218  for (int channel = 0; channel < settings.channels; ++channel) {
219  setup.inputChannels.setBit(channel);
220  }
221  setup.outputChannels.clear();
222  setup.sampleRate = settings.sample_rate;
223  setup.bufferSize = settings.buffer_size;
224  setup.inputDeviceName = settings.device_name;
225 
226  const juce::String error = device_manager.initialise(
227  settings.channels,
228  0,
229  nullptr,
230  true,
231  settings.device_name,
232  &setup);
233  if (error.isNotEmpty()) {
234  throw InvalidOptions(error.toStdString(), settings.path);
235  }
236 
237  if (auto* device = device_manager.getCurrentAudioDevice()) {
238  const double actual_rate = device->getCurrentSampleRate();
239  if (actual_rate > 0.0) {
240  settings.sample_rate = static_cast<int>(std::llround(actual_rate));
241  }
242  }
243 
244  waveform_accumulator = std::make_unique<AudioRecorderWaveformAccumulator>(
245  settings.sample_rate,
246  settings.waveform_samples_per_second);
247 
248  is_open = true;
249 }
250 
251 void AudioRecorder::OpenWriter()
252 {
253  if (writer) {
254  return;
255  }
256 
257  writer = std::make_unique<FFmpegWriter>(settings.path);
258  writer->SetAudioOptions(true, settings.codec, settings.sample_rate, settings.channels, settings.channel_layout, settings.bit_rate);
259  writer->Open();
260 }
261 
263 {
264  if (is_recording) {
265  return;
266  }
267 
269 
270  writer_should_stop = false;
271  is_recording = true;
272  device_manager.addAudioCallback(this);
273  writer_thread = std::thread(&AudioRecorder::WriterLoop, this);
274 }
275 
277 {
278  if (!is_open) {
279  Open();
280  }
281  StopMonitoring();
282  OpenWriter();
283  if (waveform_accumulator) {
284  waveform_accumulator->Reset();
285  }
286  samples_recorded = 0;
287  dropped_blocks = 0;
288  next_frame_number = 1;
289 }
290 
292 {
293  if (!is_recording && !writer_thread.joinable()) {
294  if (writer) {
295  writer->Close();
296  writer.reset();
297  }
298  return;
299  }
300 
301  is_recording = false;
302  device_manager.removeAudioCallback(this);
303  writer_should_stop = true;
304  queue_condition.notify_all();
305 
306  if (writer_thread.joinable()) {
307  writer_thread.join();
308  }
309 
310  if (writer) {
311  writer->Close();
312  writer.reset();
313  }
314 }
315 
317 {
318  if (is_recording || is_monitoring) {
319  return;
320  }
321 
322  if (!is_open) {
323  Open();
324  }
325 
326  is_monitoring = true;
327  device_manager.addAudioCallback(this);
328 }
329 
331 {
332  if (!is_monitoring) {
333  return;
334  }
335 
336  is_monitoring = false;
337  device_manager.removeAudioCallback(this);
338 }
339 
341 {
342  StopMonitoring();
343  Stop();
344 
345  if (is_open) {
346  device_manager.closeAudioDevice();
347  if (writer) {
348  writer->Close();
349  writer.reset();
350  }
351  is_open = false;
352  }
353 }
354 
356 {
357  return is_open;
358 }
359 
361 {
362  return is_recording;
363 }
364 
366 {
367  return is_monitoring;
368 }
369 
371 {
372  AudioRecorderStats stats;
373  stats.is_open = is_open;
374  stats.is_recording = is_recording;
375  stats.sample_rate = settings.sample_rate;
376  stats.channels = settings.channels;
377  stats.samples_recorded = samples_recorded;
378  stats.dropped_blocks = dropped_blocks;
379  stats.duration = settings.sample_rate > 0
380  ? static_cast<double>(stats.samples_recorded) / static_cast<double>(settings.sample_rate)
381  : 0.0;
382 
383  std::lock_guard<std::mutex> lock(queue_mutex);
384  stats.queued_blocks = static_cast<int64_t>(queue.size());
385  return stats;
386 }
387 
389 {
390  std::lock_guard<std::mutex> lock(state_mutex);
391  return waveform_accumulator ? waveform_accumulator->Snapshot() : AudioWaveformData();
392 }
393 
395 {
396  std::lock_guard<std::mutex> lock(state_mutex);
397  return last_level;
398 }
399 
400 void AudioRecorder::SetLevelCallback(std::function<void(const AudioLevelData&)> callback)
401 {
402  std::lock_guard<std::mutex> lock(state_mutex);
403  level_callback = std::move(callback);
404 }
405 
406 void AudioRecorder::SetWaveformCallback(std::function<void(const AudioWaveformChunk&)> callback)
407 {
408  std::lock_guard<std::mutex> lock(state_mutex);
409  waveform_callback = std::move(callback);
410 }
411 
413  const float* const* inputChannelData,
414  int numInputChannels,
415  float* const* outputChannelData,
416  int numOutputChannels,
417  int numSamples,
418  const juce::AudioIODeviceCallbackContext&)
419 {
420  for (int channel = 0; channel < numOutputChannels; ++channel) {
421  if (outputChannelData[channel]) {
422  std::fill(outputChannelData[channel], outputChannelData[channel] + numSamples, 0.0f);
423  }
424  }
425 
426  if ((!is_recording && !is_monitoring) || numSamples <= 0) {
427  return;
428  }
429 
430  AudioRecorderBlock block;
431  block.sample_rate = settings.sample_rate;
432  block.first_sample = samples_recorded.load();
433  block.channels.resize(settings.channels);
434 
435  for (int channel = 0; channel < settings.channels; ++channel) {
436  block.channels[channel].assign(numSamples, 0.0f);
437  if (channel < numInputChannels && inputChannelData[channel]) {
438  std::copy(inputChannelData[channel], inputChannelData[channel] + numSamples, block.channels[channel].begin());
439  }
440  }
441 
442  AudioLevelData level = level_meter.ProcessBlock(block);
443  std::function<void(const AudioLevelData&)> level_cb;
444  {
445  std::lock_guard<std::mutex> lock(state_mutex);
446  last_level = level;
447  level_cb = level_callback;
448  }
449  if (level_cb) {
450  level_cb(level);
451  }
452 
453  if (!is_recording) {
454  samples_recorded += numSamples;
455  return;
456  }
457 
458  const int64_t max_queue_blocks = static_cast<int64_t>(
459  std::max(1, (settings.max_queue_seconds * settings.sample_rate) / std::max(1, numSamples)));
460  {
461  std::lock_guard<std::mutex> lock(queue_mutex);
462  if (static_cast<int64_t>(queue.size()) >= max_queue_blocks) {
463  dropped_blocks++;
464  } else {
465  queue.push_back(std::move(block));
466  queue_condition.notify_one();
467  }
468  }
469 
470  samples_recorded += numSamples;
471 }
472 
473 void AudioRecorder::audioDeviceAboutToStart(juce::AudioIODevice* device)
474 {
475  (void) device;
476 }
477 
479 {
480 }
481 
482 bool AudioRecorder::PopBlock(AudioRecorderBlock& block)
483 {
484  std::unique_lock<std::mutex> lock(queue_mutex);
485  queue_condition.wait(lock, [this]() {
486  return writer_should_stop || !queue.empty();
487  });
488 
489  if (queue.empty()) {
490  return false;
491  }
492 
493  block = std::move(queue.front());
494  queue.pop_front();
495  return true;
496 }
497 
498 void AudioRecorder::WriterLoop()
499 {
500  int64_t expected_next_sample = -1;
501  while (true) {
502  AudioRecorderBlock block;
503  if (!PopBlock(block)) {
504  if (writer_should_stop) {
505  break;
506  }
507  continue;
508  }
509 
510  std::vector<AudioWaveformChunk> waveform_chunks;
511  std::function<void(const AudioWaveformChunk&)> waveform_cb;
512  {
513  std::lock_guard<std::mutex> lock(state_mutex);
514  if (waveform_accumulator) {
515  waveform_chunks = waveform_accumulator->ProcessBlock(block);
516  }
517  waveform_cb = waveform_callback;
518  }
519 
520  if (waveform_cb) {
521  for (const auto& chunk : waveform_chunks) {
522  waveform_cb(chunk);
523  }
524  }
525 
526  if (writer) {
527  while (expected_next_sample >= 0 && block.first_sample > expected_next_sample) {
528  const int64_t missing_samples = block.first_sample - expected_next_sample;
529  const int silence_samples = static_cast<int>(std::min<int64_t>(
530  missing_samples,
531  std::max(1, settings.buffer_size)));
532  AudioRecorderBlock silence_block;
533  silence_block.sample_rate = block.sample_rate;
534  silence_block.first_sample = expected_next_sample;
535  silence_block.channels.assign(
536  settings.channels,
537  std::vector<float>(silence_samples, 0.0f));
538  writer->WriteFrame(AudioRecordingFrameFactory::CreateFrame(
539  silence_block,
540  settings.channel_layout,
541  next_frame_number++));
542  expected_next_sample += silence_samples;
543  }
544 
545  writer->WriteFrame(AudioRecordingFrameFactory::CreateFrame(
546  block,
547  settings.channel_layout,
548  next_frame_number++));
549  }
550  expected_next_sample = block.first_sample + block.Samples();
551  }
552 }
openshot::AudioRecorder::Close
void Close()
Definition: AudioRecorder.cpp:340
openshot::AudioRecorderSettings::device_type
std::string device_type
Definition: AudioRecorder.h:44
openshot::AudioRecorder::IsOpen
bool IsOpen() const
Definition: AudioRecorder.cpp:355
openshot::AudioRecorder::audioDeviceStopped
void audioDeviceStopped() override
Definition: AudioRecorder.cpp:478
openshot::InvalidSampleRate
Exception when invalid sample rate is detected during encoding.
Definition: Exceptions.h:253
FFmpegWriter.h
Header file for FFmpegWriter class.
openshot::AudioWaveformData::rms_samples
std::vector< float > rms_samples
Definition: AudioWaveformer.h:36
openshot::AudioWaveformChunk::max_samples
std::vector< float > max_samples
Definition: AudioRecorder.h:77
openshot::AudioRecorder::IsRecording
bool IsRecording() const
Definition: AudioRecorder.cpp:360
openshot::AudioRecorder::IsMonitoring
bool IsMonitoring() const
Definition: AudioRecorder.cpp:365
openshot::AudioRecorder::GetWaveformSnapshot
openshot::AudioWaveformData GetWaveformSnapshot() const
Definition: AudioRecorder.cpp:388
openshot::AudioRecorderStats
Definition: AudioRecorder.h:89
openshot::AudioRecorderSettings::channels
int channels
Definition: AudioRecorder.h:47
openshot::AudioRecorder::SetWaveformCallback
void SetWaveformCallback(std::function< void(const AudioWaveformChunk &)> callback)
Definition: AudioRecorder.cpp:406
openshot::AudioRecorder::Start
void Start()
Definition: AudioRecorder.cpp:262
openshot::AudioRecorder::Open
void Open()
Definition: AudioRecorder.cpp:206
openshot
This namespace is the default namespace for all code in the openshot library.
Definition: AnimatedCurve.h:24
openshot::AudioWaveformChunk
Definition: AudioRecorder.h:72
openshot::AudioRecorder::SetLevelCallback
void SetLevelCallback(std::function< void(const AudioLevelData &)> callback)
Definition: AudioRecorder.cpp:400
openshot::AudioRecorder::audioDeviceAboutToStart
void audioDeviceAboutToStart(juce::AudioIODevice *device) override
Definition: AudioRecorder.cpp:473
openshot::AudioRecorderBlock
Definition: AudioRecorder.h:101
openshot::AudioRecorderSettings::buffer_size
int buffer_size
Definition: AudioRecorder.h:50
openshot::AudioRecorder::GetLevelSnapshot
AudioLevelData GetLevelSnapshot() const
Definition: AudioRecorder.cpp:394
openshot::AudioRecorderSettings::bit_rate
int bit_rate
Definition: AudioRecorder.h:49
openshot::AudioRecordingFrameFactory::CreateFrame
static std::shared_ptr< openshot::Frame > CreateFrame(const AudioRecorderBlock &block, openshot::ChannelLayout channel_layout, int64_t frame_number)
Definition: AudioRecorder.cpp:143
openshot::AudioRecorderWaveformAccumulator::Snapshot
openshot::AudioWaveformData Snapshot() const
Definition: AudioRecorder.cpp:125
openshot::AudioRecorder::~AudioRecorder
~AudioRecorder() override
Definition: AudioRecorder.cpp:176
openshot::AudioRecorder::GetStats
AudioRecorderStats GetStats() const
Definition: AudioRecorder.cpp:370
openshot::AudioRecorderBlock::channels
std::vector< std::vector< float > > channels
Definition: AudioRecorder.h:105
openshot::AudioRecorder::StartMonitoring
void StartMonitoring()
Definition: AudioRecorder.cpp:316
openshot::AudioWaveformChunk::start_time
double start_time
Definition: AudioRecorder.h:74
openshot::AudioWaveformChunk::samples_per_second
int samples_per_second
Definition: AudioRecorder.h:76
openshot::AudioRecorderSettings::sample_rate
int sample_rate
Definition: AudioRecorder.h:46
openshot::AudioRecorderWaveformAccumulator::AudioRecorderWaveformAccumulator
AudioRecorderWaveformAccumulator(int sample_rate, int samples_per_second)
Definition: AudioRecorder.cpp:59
openshot::AudioRecorderStats::queued_blocks
int64_t queued_blocks
Definition: AudioRecorder.h:97
openshot::AudioRecorderStats::is_open
bool is_open
Definition: AudioRecorder.h:91
openshot::AudioRecorderSettings::device_name
std::string device_name
Definition: AudioRecorder.h:43
openshot::AudioRecorderLevelMeter::ProcessBlock
AudioLevelData ProcessBlock(const AudioRecorderBlock &block) const
Definition: AudioRecorder.cpp:25
openshot::AudioRecorderBlock::first_sample
int64_t first_sample
Definition: AudioRecorder.h:104
openshot::AudioRecorder::PrepareRecording
void PrepareRecording()
Definition: AudioRecorder.cpp:276
openshot::AudioRecorder::Stop
void Stop()
Definition: AudioRecorder.cpp:291
openshot::AudioRecorderBlock::Samples
int Samples() const
Definition: AudioRecorder.h:107
openshot::AudioLevelData::timestamp
double timestamp
Definition: AudioRecorder.h:58
openshot::AudioRecorder::AudioRecorder
AudioRecorder(const AudioRecorderSettings &settings)
Definition: AudioRecorder.cpp:161
openshot::AudioRecorderStats::duration
double duration
Definition: AudioRecorder.h:98
openshot::AudioRecorderStats::samples_recorded
int64_t samples_recorded
Definition: AudioRecorder.h:95
openshot::AudioRecorderStats::sample_rate
int sample_rate
Definition: AudioRecorder.h:93
openshot::AudioRecorderBlock::sample_rate
int sample_rate
Definition: AudioRecorder.h:103
openshot::AudioLevelData::rms
std::vector< float > rms
Definition: AudioRecorder.h:60
Frame.h
Header file for Frame class.
AudioRecorder.h
Header file for audio recording classes.
openshot::AudioLevelData::clipped
bool clipped
Definition: AudioRecorder.h:61
openshot::AudioWaveformData
This struct holds the extracted waveform data (both the RMS root-mean-squared average,...
Definition: AudioWaveformer.h:33
openshot::AudioRecorder::audioDeviceIOCallbackWithContext
void audioDeviceIOCallbackWithContext(const float *const *inputChannelData, int numInputChannels, float *const *outputChannelData, int numOutputChannels, int numSamples, const juce::AudioIODeviceCallbackContext &context) override
Definition: AudioRecorder.cpp:412
openshot::AudioRecorder::StopMonitoring
void StopMonitoring()
Definition: AudioRecorder.cpp:330
openshot::AudioWaveformChunk::rms_samples
std::vector< float > rms_samples
Definition: AudioRecorder.h:78
openshot::AudioRecorderStats::is_recording
bool is_recording
Definition: AudioRecorder.h:92
openshot::AudioWaveformData::max_samples
std::vector< float > max_samples
Definition: AudioWaveformer.h:35
openshot::AudioLevelData
Definition: AudioRecorder.h:56
openshot::AudioRecorderSettings::path
std::string path
Definition: AudioRecorder.h:42
openshot::AudioRecorderSettings::channel_layout
openshot::ChannelLayout channel_layout
Definition: AudioRecorder.h:48
openshot::AudioRecorderWaveformAccumulator::Reset
void Reset()
Definition: AudioRecorder.cpp:133
openshot::InvalidChannels
Exception when an invalid # of audio channels are detected.
Definition: Exceptions.h:163
openshot::AudioRecorderSettings
Definition: AudioRecorder.h:40
openshot::ChannelLayout
ChannelLayout
This enumeration determines the audio channel layout (such as stereo, mono, 5 point surround,...
Definition: ChannelLayouts.h:28
openshot::AudioRecorderSettings::max_queue_seconds
int max_queue_seconds
Definition: AudioRecorder.h:52
openshot::AudioRecorderSettings::codec
std::string codec
Definition: AudioRecorder.h:45
openshot::AudioRecorderStats::channels
int channels
Definition: AudioRecorder.h:94
openshot::AudioRecorderWaveformAccumulator::ProcessBlock
std::vector< AudioWaveformChunk > ProcessBlock(const AudioRecorderBlock &block)
Definition: AudioRecorder.cpp:75
openshot::InvalidOptions
Exception when invalid encoding options are used.
Definition: Exceptions.h:238
openshot::AudioWaveformChunk::duration
double duration
Definition: AudioRecorder.h:75
openshot::AudioLevelData::peak
std::vector< float > peak
Definition: AudioRecorder.h:59
Exceptions.h
Header file for all Exception classes.
openshot::AudioRecorderSettings::waveform_samples_per_second
int waveform_samples_per_second
Definition: AudioRecorder.h:51
openshot::AudioRecorderStats::dropped_blocks
int64_t dropped_blocks
Definition: AudioRecorder.h:96