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