blob: c50fdefd2b53e723ef2627734ed4c4c52d37ec2d [file]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef SRC_MEDIA_AUDIO_LIB_ANALYSIS_ANALYSIS_H_
#define SRC_MEDIA_AUDIO_LIB_ANALYSIS_ANALYSIS_H_
#include <zircon/types.h>
#include <unordered_map>
#include <unordered_set>
#include "src/media/audio/lib/format/audio_buffer.h"
namespace media::audio {
namespace internal {
// Perform a Fast Fourier Transform on the provided data arrays.
//
// On input, real[] and imag[] contain 'buf_size' number of double-float values
// in the time domain (such as audio samples); buf_size must be a power-of-two.
//
// On output, real[] and imag[] contain 'buf_size' number of double-float values
// in frequency domain, but generally used only through buf_size/2 (per Nyquist)
void FFT(double* real, double* imag, uint32_t buf_size);
// Calculate phase in radians for the complex pair. Correctly handles negative
// or zero values: range of return value is [-PI,PI], not just [-PI/2,PI/2].
double GetPhase(double real, double imag);
// Convert provided real-imag (cartesian) data into magn-phase (polar) format.
// This is done with 2 in-buffers 2 two out-buffers -- NOT 2 in-out-buffers.
// TODO(mpuryear): will clients (tests) want this transformed in-place?
void RectangularToPolar(const double* real, const double* imag, uint32_t buf_size, double* magn,
double* phase = nullptr);
void RealDFT(const double* reals, uint32_t len, double* r_freq, double* i_freq);
void InverseDFT(double* real, double* imag, uint32_t buf_size, double* real_out);
void InverseFFT(double* real, double* imag, uint32_t buf_size);
} // namespace internal
struct AudioFreqResult {
// Raw list of square magnitudes for all bins up to size/2.
std::vector<double> all_square_magnitudes;
// Mapping from frequency -> magnitude, for each requested frequency.
std::unordered_map<int32_t, double> magnitudes;
// Phase in radians, for each requested frequency.
std::unordered_map<int32_t, double> phases;
// Total magnitude over all requested frequencies.
// Magnitude is the root-sum-of-squares of the magnitude at all requested frequencies.
double total_magn_signal;
// Total magnitude over all other frequencies.
// Magnitude is the root-sum-of-squares of the magnitude at all other frequencies.
double total_magn_other;
};
// For the given audio buffer, analyze contents and return the magnitude (and phase) at the given
// frequency. Also return magnitude of all other content. Useful for frequency response and
// signal-to-noise. Internally uses an FFT, so slice.NumFrames() must be a power-of-two. The format
// must have channels() == 1.
//
// |freq| is the number of **complete sinusoidal periods** that should perfectly fit into the
// buffer.
template <fuchsia::media::AudioSampleFormat SampleFormat>
AudioFreqResult MeasureAudioFreqs(AudioBufferSlice<SampleFormat> slice,
std::unordered_set<int32_t> freqs);
// Shorthand that analyzes a single frequency.
template <fuchsia::media::AudioSampleFormat SampleFormat>
AudioFreqResult MeasureAudioFreq(AudioBufferSlice<SampleFormat> slice, int32_t freq) {
auto result = MeasureAudioFreqs(slice, {freq});
FX_DCHECK(result.total_magn_signal == result.magnitudes[freq]);
return result;
}
// Compute the root-mean-square (RMS) energy of a slice. This is a measure of loudness.
template <fuchsia::media::AudioSampleFormat SampleFormat>
double MeasureAudioRMS(AudioBufferSlice<SampleFormat> slice) {
FX_CHECK(slice.NumFrames() > 0);
double sum = 0;
for (int64_t frame = 0; frame < slice.NumFrames(); frame++) {
for (int32_t chan = 0; chan < slice.format().channels(); chan++) {
double s = SampleFormatTraits<SampleFormat>::ToFloat(slice.SampleAt(frame, chan));
sum += s * s;
}
}
return sqrt(sum / slice.NumSamples());
}
// Locate the left edge of the first impulse in the given slice, ignoring samples quieter
// than the given noise floor. Returns the frame index if found, and std::nullopt otherwise.
// The given slice must have a single channel. We assume the impulse has a positive signal.
template <fuchsia::media::AudioSampleFormat SampleFormat>
std::optional<int64_t> FindImpulseLeadingEdge(
AudioBufferSlice<SampleFormat> slice,
typename SampleFormatTraits<SampleFormat>::SampleT noise_floor) {
FX_CHECK(slice.format().channels() == 1);
auto normalize = [](typename SampleFormatTraits<SampleFormat>::SampleT val) {
float d = val;
if constexpr (SampleFormat == fuchsia::media::AudioSampleFormat::UNSIGNED_8) {
d -= 128;
}
return d;
};
// If our impulse was a single frame, we could simply find the maximum value.
// To support wider impulses, we need to find the left edge of the impulse. We
// do this by finding the first value such that there does not exist a value
// more than 50% larger.
float max_value = 0;
for (int64_t f = 0; f < slice.NumFrames(); f++) {
max_value = std::max(max_value, normalize(slice.SampleAt(f, 0)));
}
for (int64_t f = 0; f < slice.NumFrames(); f++) {
float val = normalize(slice.SampleAt(f, 0));
if (val <= noise_floor) {
continue;
}
if (1.5 * val > max_value) {
return f;
}
}
return std::nullopt;
}
// Multiply the input buffer by a Tukey window, producing a new output buffer. A Tukey window
// contains a ramp up from zero, followed by a flat top of 1.0, followed by a ramp down to zero.
// The total width of the up and down ramps is described by the alpha parameter, which must be <= 1.
template <fuchsia::media::AudioSampleFormat SampleFormat>
AudioBuffer<SampleFormat> MultiplyByTukeyWindow(AudioBufferSlice<SampleFormat> slice, double alpha);
} // namespace media::audio
#endif // SRC_MEDIA_AUDIO_LIB_ANALYSIS_ANALYSIS_H_