blob: e62e63c3d7d5ca239f4c091fea891f7184475f7c [file] [log] [blame]
// Copyright 2021 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_DROPOUT_H_
#define SRC_MEDIA_AUDIO_LIB_ANALYSIS_DROPOUT_H_
#include <zircon/types.h>
#include <cmath>
#include <iomanip>
#include <limits>
#include <optional>
#include "lib/syslog/cpp/macros.h"
#include "src/media/audio/lib/format/audio_buffer.h"
namespace media::audio {
// Below are two utility classes that can be used to detect dropouts.
// PowerChecker verifies that a stream contains a signal of expected power.
// It calculates an audio section's power, returning false if this does not meet the expected val.
//
// This checker includes each sample in exactly one calculation (it does not use overlapping RMS
// windows). E.g., for a 512-sample window, the first RMS check is based on samples 0-511, and the
// second check based on samples 512-1023 (modulo any intervening RestartRmsWindow() calls).
//
// For simplicity, this class is currently limited to FLOAT data only.
class PowerChecker {
public:
PowerChecker(int64_t rms_window_in_frames, int32_t channels, double expected_min_power_rms,
const std::string& tag = "")
: rms_window_in_frames_(rms_window_in_frames),
channels_(channels),
expected_power_rms_(expected_min_power_rms),
tag_(tag.empty() ? tag : tag + ": ") {
RestartRmsWindow();
}
bool Check(const float* samples, int64_t frame_position, int64_t num_frames, bool print = false) {
if (frame_position_ != frame_position) {
RestartRmsWindow();
}
frame_position_ = frame_position + num_frames;
bool pass = true;
// Ingest all the provided samples before leaving
while (num_frames) {
// Starting from any previously-retained running totals/counts, incorporate each additional
// sample until we have enough to analyze. Stop earlier if we run out of samples.
while (running_window_frame_count_ < rms_window_in_frames_ && num_frames) {
for (auto chan = 0; chan < channels_; ++chan) {
auto val = *samples;
running_sum_squares_ += (val * val);
samples++;
}
running_window_frame_count_++;
num_frames--;
}
// If we have enough to analyze, do so now and reset our running totals/counts to zero.
// Otherwise, just ingest these values and return true.
if (running_window_frame_count_ == rms_window_in_frames_) {
auto mean_squares =
running_sum_squares_ / static_cast<double>(rms_window_in_frames_ * channels_);
auto current_root_mean_squares = sqrt(mean_squares);
if (current_root_mean_squares + std::numeric_limits<float>::epsilon() <
expected_power_rms_) {
// We might have been provided enough samples for multiple windows (thus more than one
// success/fail calculation). If ANY of them fail, return false.
pass = false;
if (print) {
FX_LOGS(ERROR) << tag_ << "********** Dropout detected -- measured power " << std::fixed
<< std::setprecision(6) << std::setw(8) << current_root_mean_squares
<< " (expected " << std::fixed << std::setprecision(4) << std::setw(6)
<< expected_power_rms_ << ") across window of " << rms_window_in_frames_
<< " frames **********";
}
} else {
if constexpr (kSuccessLogStride) {
if (print) {
if (success_log_count_ == 0) {
FX_LOGS(INFO) << tag_ << "********** Across window of " << rms_window_in_frames_
<< " frames, successfully measured power " << std::fixed
<< std::setprecision(6) << std::setw(8) << current_root_mean_squares
<< " (expected " << std::fixed << std::setprecision(4) << std::setw(6)
<< expected_power_rms_ << ") **********";
}
success_log_count_ = ++success_log_count_ % kSuccessLogStride;
}
}
}
RestartRmsWindow();
}
}
return pass;
}
private:
void RestartRmsWindow() {
running_window_frame_count_ = 0;
running_sum_squares_ = 0.0;
}
const int64_t rms_window_in_frames_;
const int32_t channels_; // only used for display purposes
const double expected_power_rms_;
const std::string tag_;
int64_t frame_position_ = 0;
int64_t running_window_frame_count_;
double running_sum_squares_;
// To calibrate appropriate RMS limits for specific content, display the calculated RMS power even
// on success. A stride reduces log spam; making it PRIME (797 is appropriate) varies sampling
// across periodic signals. To disable this logging altogether, set kSuccessLogStride to 0.
static constexpr int64_t kSuccessLogStride = 0;
int64_t success_log_count_ = 0; // only used for display purposes
};
// SilenceChecker verifies that a stream does not contain a consecutive number of truly silent
// frames, with Check() returning false if this ever occurs. For simplicity, this class is currently
// limited to FLOAT data only.
class SilenceChecker {
public:
explicit SilenceChecker(int64_t max_count_silent_frames_allowed, int32_t channels,
const std::string& tag = "")
: max_silent_frames_allowed_(max_count_silent_frames_allowed),
channels_(channels),
tag_(tag.empty() ? tag : tag + ": ") {}
bool Check(const float* samples, int64_t frame_position, int64_t num_frames, bool print = false) {
if (frame_position_ != frame_position) {
running_silent_frame_count_ = 0;
}
frame_position_ = frame_position + num_frames;
int64_t max_silent_frames_detected = 0;
// Ingest all provided samples before leaving
while (num_frames--) {
// Starting from any previously-retained running count, incorporate additional silent frames.
bool frame_is_silent = true;
for (auto chan = 0; chan < channels_; ++chan) {
if (samples[chan] > std::numeric_limits<float>::epsilon() ||
samples[chan] < -std::numeric_limits<float>::epsilon()) {
frame_is_silent = false;
break;
}
}
running_silent_frame_count_ = frame_is_silent ? running_silent_frame_count_ + 1 : 0;
max_silent_frames_detected =
std::max(max_silent_frames_detected, running_silent_frame_count_);
samples += channels_;
}
if (print && max_silent_frames_detected > max_silent_frames_allowed_) {
FX_LOGS(ERROR) << tag_ << "********* Silence detected -- measured "
<< max_silent_frames_detected
<< " consecutive silent frames (max allowed: " << max_silent_frames_allowed_
<< ") **********";
}
return (max_silent_frames_detected <= max_silent_frames_allowed_);
}
private:
const int64_t max_silent_frames_allowed_;
const int32_t channels_;
const std::string tag_;
int64_t frame_position_ = 0;
int64_t running_silent_frame_count_ = 0;
};
} // namespace media::audio
#endif // SRC_MEDIA_AUDIO_LIB_ANALYSIS_DROPOUT_H_