2022-10-30 22:04:19 -05:00
|
|
|
/**
|
|
|
|
|
* @file
|
|
|
|
|
* @brief Source file for AudioWaveformer class
|
|
|
|
|
* @author Jonathan Thomas <jonathan@openshot.org>
|
|
|
|
|
*
|
|
|
|
|
* @ref License
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
// Copyright (c) 2008-2022 OpenShot Studios, LLC
|
|
|
|
|
//
|
|
|
|
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
|
|
|
|
|
|
|
|
|
#include "AudioWaveformer.h"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
using namespace std;
|
|
|
|
|
using namespace openshot;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Default constructor
|
|
|
|
|
AudioWaveformer::AudioWaveformer(ReaderBase* new_reader) : reader(new_reader)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Destructor
|
|
|
|
|
AudioWaveformer::~AudioWaveformer()
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Extract audio samples from any ReaderBase class
|
2022-11-01 15:17:03 -05:00
|
|
|
AudioWaveformData AudioWaveformer::ExtractSamples(int channel, int num_per_second, bool normalize) {
|
|
|
|
|
AudioWaveformData data;
|
2022-10-30 22:04:19 -05:00
|
|
|
|
|
|
|
|
if (reader) {
|
|
|
|
|
// Open reader (if needed)
|
|
|
|
|
bool does_reader_have_video = reader->info.has_video;
|
|
|
|
|
if (!reader->IsOpen()) {
|
|
|
|
|
reader->Open();
|
|
|
|
|
}
|
|
|
|
|
// Disable video for faster processing
|
|
|
|
|
reader->info.has_video = false;
|
|
|
|
|
|
|
|
|
|
int sample_rate = reader->info.sample_rate;
|
|
|
|
|
int sample_divisor = sample_rate / num_per_second;
|
|
|
|
|
int total_samples = num_per_second * (reader->info.duration + 1.0);
|
|
|
|
|
int extracted_index = 0;
|
|
|
|
|
|
2022-11-01 15:17:03 -05:00
|
|
|
// Resize and clear audio buffers
|
|
|
|
|
data.resize(total_samples);
|
|
|
|
|
data.zero(total_samples);
|
2022-10-30 22:04:19 -05:00
|
|
|
|
|
|
|
|
// Loop through all frames
|
|
|
|
|
int sample_index = 0;
|
|
|
|
|
float samples_max = 0.0;
|
2022-11-01 15:17:03 -05:00
|
|
|
float chunk_max = 0.0;
|
|
|
|
|
float chunk_squared_sum = 0.0;
|
|
|
|
|
|
|
|
|
|
// How many channels are we using
|
|
|
|
|
int channel_count = 1;
|
|
|
|
|
if (channel == -1) {
|
|
|
|
|
channel_count = reader->info.channels;
|
|
|
|
|
}
|
2022-10-30 22:04:19 -05:00
|
|
|
|
|
|
|
|
for (auto f = 1; f <= reader->info.video_length; f++) {
|
|
|
|
|
// Get next frame
|
|
|
|
|
shared_ptr<openshot::Frame> frame = reader->GetFrame(f);
|
|
|
|
|
|
2022-11-01 16:48:37 -05:00
|
|
|
// Cache channels for this frame, to reduce # of calls to frame->GetAudioSamples
|
|
|
|
|
float* channels[channel_count];
|
|
|
|
|
for (auto channel_index = 0; channel_index < reader->info.channels; channel_index++) {
|
|
|
|
|
if (channel == channel_index || channel == -1) {
|
|
|
|
|
channels[channel_index] = frame->GetAudioSamples(channel_index);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-01 15:17:03 -05:00
|
|
|
// Get sample value from a specific channel (or all channels)
|
2022-10-30 22:04:19 -05:00
|
|
|
for (auto s = 0; s < frame->GetAudioSamplesCount(); s++) {
|
2022-11-01 15:17:03 -05:00
|
|
|
for (auto channel_index = 0; channel_index < reader->info.channels; channel_index++) {
|
|
|
|
|
if (channel == channel_index || channel == -1) {
|
2022-11-01 16:48:37 -05:00
|
|
|
float *samples = channels[channel_index];
|
2022-11-01 15:17:03 -05:00
|
|
|
float rms_sample_value = std::sqrt(samples[s] * samples[s]);
|
|
|
|
|
|
|
|
|
|
// Accumulate sample averages
|
|
|
|
|
chunk_squared_sum += rms_sample_value;
|
|
|
|
|
chunk_max = std::max(chunk_max, rms_sample_value);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-30 22:04:19 -05:00
|
|
|
sample_index += 1;
|
|
|
|
|
|
|
|
|
|
// Cut-off reached
|
|
|
|
|
if (sample_index % sample_divisor == 0) {
|
2022-11-01 15:17:03 -05:00
|
|
|
float avg_squared_sum = chunk_squared_sum / (sample_divisor * channel_count);
|
|
|
|
|
data.max_samples[extracted_index] = chunk_max;
|
|
|
|
|
data.rms_samples[extracted_index] = avg_squared_sum;
|
2022-10-30 22:04:19 -05:00
|
|
|
extracted_index++;
|
|
|
|
|
|
|
|
|
|
// Track max/min values
|
2022-11-01 15:17:03 -05:00
|
|
|
samples_max = std::max(samples_max, chunk_max);
|
2022-10-30 22:04:19 -05:00
|
|
|
|
|
|
|
|
// reset sample total and index
|
|
|
|
|
sample_index = 0;
|
2022-11-01 15:17:03 -05:00
|
|
|
chunk_max = 0.0;
|
|
|
|
|
chunk_squared_sum = 0.0;
|
2022-10-30 22:04:19 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Scale all values to the -1 to +1 range (regardless of how small or how large the
|
|
|
|
|
// original audio sample values are)
|
|
|
|
|
if (normalize) {
|
2022-11-01 15:17:03 -05:00
|
|
|
float scale = 1.0f / samples_max;
|
|
|
|
|
data.scale(total_samples, scale);
|
2022-10-30 22:04:19 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Resume previous has_video value
|
|
|
|
|
reader->info.has_video = does_reader_have_video;
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-01 15:17:03 -05:00
|
|
|
|
|
|
|
|
return data;
|
2022-10-30 22:04:19 -05:00
|
|
|
}
|