src/media/audio/audio_core/mixer/test/mixer_response_tests.cc
// Ideal frequency response measurement is 0.00 dB across the audible spectrum
//
// Ideal SINAD is at least 6 dB per signal-bit (>96 dB, if 16-bit resolution).
//
// Phase measurement is the amount that a certain frequency is delayed -- measured in radians,
// because after a delay of more than its wavelength, a frequency's perceptible delay "wraps around"
// to a value 2_PI less. Zero phase is ideal; constant phase is very good; linear is reasonable.
//
// If UseFullFrequencySet is false, we test at only three summary frequencies.
Ideal frequency response measurement is 0.00 dB across the audible spectrum
Ideal SINAD is at least 6 dB per signal-bit (>96 dB, if 16-bit resolution).

Phase measurement is the amount that a certain frequency is delayed -- measured in radians,
because after a delay of more than its wavelength, a frequency's perceptible delay "wraps around"
to a value 2_PI less. Zero phase is ideal; constant phase is very good; linear is reasonable.

If UseFullFrequencySet is false, we test at only three summary frequencies.
Ideal frequency response measurement is 0 . 00 dB across the audible spectrum Ideal SINAD is at least 6 dB per signal - bit ( > 96 dB if 16 - bit resolution ) . Phase measurement is the amount that a certain frequency is delayed -- measured in radians because after a delay of more than its wavelength a frequency ' s perceptible delay " wraps around " to a value 2_PI less . Zero phase is ideal ; constant phase is very good ; linear is reasonable . If UseFullFrequencySet is false we test at only three summary frequencies .
void MeasureFreqRespSinadPhase(Mixer* mixer, uint32_t num_src_frames, double* level_db,
                               double* sinad_db, double* phase_rad) {
  if (!std::isnan(level_db[0])) {
    return;
  }
  level_db[0] = -INFINITY;
  auto format = Format::Create<ASF::FLOAT>(1, 48000 ).take_value();
  auto num_dest_frames = kFreqTestBufSize;
  AudioBuffer source(format, num_src_frames);
  AudioBuffer accum(format, num_dest_frames);
  auto& info = mixer->bookkeeping();
  info.step_size = (Mixer::FRAC_ONE * num_src_frames) / num_dest_frames;
  info.rate_modulo = (Mixer::FRAC_ONE * num_src_frames) - (info.step_size * num_dest_frames);
  info.denominator = num_dest_frames;
  bool use_full_set = FrequencySet::UseFullFrequencySet;
  const auto first_idx = 0u;
  const auto last_idx =
      use_full_set ? FrequencySet::kNumReferenceFreqs : FrequencySet::kSummaryIdxs.size();
  for (auto idx = first_idx; idx < last_idx; ++idx) {
    uint32_t freq_idx = idx;
    if (!use_full_set) {
      freq_idx = FrequencySet::kSummaryIdxs[idx];
    }
    auto frequency_to_measure = FrequencySet::kReferenceFreqs[freq_idx];
    if (frequency_to_measure * 2 >= num_src_frames) {
      if (freq_idx < FrequencySet::kFirstOutBandRefFreqIdx) {
        level_db[freq_idx] = -INFINITY;
        phase_rad[freq_idx] = -INFINITY;
      }
      sinad_db[freq_idx] = -INFINITY;
      continue;
    }
    source = GenerateCosineAudio(format, num_src_frames, frequency_to_measure);
    info.src_pos_modulo = 0;
    uint32_t dest_frames, dest_offset = 0;
    uint32_t frac_src_frames = source.NumFrames() * Mixer::FRAC_ONE;
    int32_t frac_src_offset = static_cast<int32_t>(frac_src_frames);
    auto source_is_consumed =
        mixer->Mix(&accum.samples()[0], num_dest_frames, &dest_offset, &source.samples()[0],
                   frac_src_frames, &frac_src_offset, false);
    FX_CHECK(source_is_consumed);
    FX_CHECK(dest_offset == 0u);
    FX_CHECK(frac_src_offset == static_cast<int32_t>(frac_src_frames));
    frac_src_offset = 0;
    for (uint32_t packet = 0; packet < kResamplerTestNumPackets; ++packet) {
      dest_frames = num_dest_frames * (packet + 1) / kResamplerTestNumPackets;
      mixer->Mix(&accum.samples()[0], dest_frames, &dest_offset, &source.samples()[0],
                 frac_src_frames, &frac_src_offset, false);
    }
    int32_t expected_frac_src_offset = frac_src_frames;
    if (dest_offset < dest_frames) {
      AUDIO_LOG(TRACE) << "Performing wraparound mix: dest_frames " << dest_frames
                       << ", dest_offset " << dest_offset << ", frac_src_frames " << std::hex
                       << frac_src_frames << ", frac_src_offset " << frac_src_offset;
      ASSERT_GE(frac_src_offset, 0);
      EXPECT_GE(static_cast<uint32_t>(frac_src_offset) + mixer->pos_filter_width().raw_value(),
                frac_src_frames)
          << "src_off " << std::hex << frac_src_offset << ", pos_width "
          << mixer->pos_filter_width().raw_value() << ", src_frames " << frac_src_frames;
      frac_src_offset -= frac_src_frames;
      mixer->Mix(&accum.samples()[0], dest_frames, &dest_offset, &source.samples()[0],
                 frac_src_frames, &frac_src_offset, false);
      expected_frac_src_offset = 0;
    }
    EXPECT_EQ(dest_offset, dest_frames);
    EXPECT_EQ(frac_src_offset, expected_frac_src_offset);
    mixer->Reset();
    const bool out_of_band = (frequency_to_measure * 2 >= num_dest_frames);
    auto result = out_of_band ? MeasureAudioFreqs(AudioBufferSlice(&accum), {})
                              : MeasureAudioFreqs(AudioBufferSlice(&accum), {frequency_to_measure});
    if (out_of_band) {
      sinad_db[freq_idx] = -Gain::DoubleToDb(result.total_magn_other);
    } else {
      auto magn_signal = result.magnitudes[frequency_to_measure];
      auto magn_other = result.total_magn_other;
      level_db[freq_idx] = Gain::DoubleToDb(magn_signal);
      sinad_db[freq_idx] = Gain::DoubleToDb(magn_signal / magn_other);
      phase_rad[freq_idx] = result.phases[frequency_to_measure];
    }
  }
}
void MeasureFreqRespSinadPhase ( Mixer * mixer , uint32_t num_src_frames , double * level_db , double * sinad_db , double * phase_rad ) { if ( ! std :: isnan ( level_db [ 0 ] ) ) { return ; } level_db [ 0 ] = - INFINITY ; auto format = Format :: Create < ASF :: FLOAT > ( 1 , 48000 ) . take_value ( ) ; auto num_dest_frames = kFreqTestBufSize ; AudioBuffer source ( format , num_src_frames ) ; AudioBuffer accum ( format , num_dest_frames ) ; auto & info = mixer -> bookkeeping ( ) ; info . step_size = ( Mixer :: FRAC_ONE * num_src_frames ) / num_dest_frames ; info . rate_modulo = ( Mixer :: FRAC_ONE * num_src_frames ) - ( info . step_size * num_dest_frames ) ; info . denominator = num_dest_frames ; bool use_full_set = FrequencySet :: UseFullFrequencySet ; const auto first_idx = 0u ; const auto last_idx = use_full_set ? FrequencySet :: kNumReferenceFreqs : FrequencySet :: kSummaryIdxs . size ( ) ; for ( auto idx = first_idx ; idx < last_idx ; ++ idx ) { uint32_t freq_idx = idx ; if ( ! use_full_set ) { freq_idx = FrequencySet :: kSummaryIdxs [ idx ] ; } auto frequency_to_measure = FrequencySet :: kReferenceFreqs [ freq_idx ] ; if ( frequency_to_measure * 2 >= num_src_frames ) { if ( freq_idx < FrequencySet :: kFirstOutBandRefFreqIdx ) { level_db [ freq_idx ] = - INFINITY ; phase_rad [ freq_idx ] = - INFINITY ; } sinad_db [ freq_idx ] = - INFINITY ; continue ; } source = GenerateCosineAudio ( format , num_src_frames , frequency_to_measure ) ; info . src_pos_modulo = 0 ; uint32_t dest_frames , dest_offset = 0 ; uint32_t frac_src_frames = source . NumFrames ( ) * Mixer :: FRAC_ONE ; int32_t frac_src_offset = static_cast < int32_t > ( frac_src_frames ) ; auto source_is_consumed = mixer -> Mix ( & accum . samples ( ) [ 0 ] , num_dest_frames , & dest_offset , & source . samples ( ) [ 0 ] , frac_src_frames , & frac_src_offset , false ) ; FX_CHECK ( source_is_consumed ) ; FX_CHECK ( dest_offset == 0u ) ; FX_CHECK ( frac_src_offset == static_cast < int32_t > ( frac_src_frames ) ) ; frac_src_offset = 0 ; for ( uint32_t packet = 0 ; packet < kResamplerTestNumPackets ; ++ packet ) { dest_frames = num_dest_frames * ( packet + 1 ) / kResamplerTestNumPackets ; mixer -> Mix ( & accum . samples ( ) [ 0 ] , dest_frames , & dest_offset , & source . samples ( ) [ 0 ] , frac_src_frames , & frac_src_offset , false ) ; } int32_t expected_frac_src_offset = frac_src_frames ; if ( dest_offset < dest_frames ) { AUDIO_LOG ( TRACE ) << " " << dest_frames << " " << dest_offset << " " << std :: hex << frac_src_frames << " " << frac_src_offset ; ASSERT_GE ( frac_src_offset , 0 ) ; EXPECT_GE ( static_cast < uint32_t > ( frac_src_offset ) + mixer -> pos_filter_width ( ) . raw_value ( ) , frac_src_frames ) << " " << std :: hex << frac_src_offset << " " << mixer -> pos_filter_width ( ) . raw_value ( ) << " " << frac_src_frames ; frac_src_offset -= frac_src_frames ; mixer -> Mix ( & accum . samples ( ) [ 0 ] , dest_frames , & dest_offset , & source . samples ( ) [ 0 ] , frac_src_frames , & frac_src_offset , false ) ; expected_frac_src_offset = 0 ; } EXPECT_EQ ( dest_offset , dest_frames ) ; EXPECT_EQ ( frac_src_offset , expected_frac_src_offset ) ; mixer -> Reset ( ) ; const bool out_of_band = ( frequency_to_measure * 2 >= num_dest_frames ) ; auto result = out_of_band ? MeasureAudioFreqs ( AudioBufferSlice ( & accum ) , { } ) : MeasureAudioFreqs ( AudioBufferSlice ( & accum ) , { frequency_to_measure } ) ; if ( out_of_band ) { sinad_db [ freq_idx ] = - Gain :: DoubleToDb ( result . total_magn_other ) ; } else { auto magn_signal = result . magnitudes [ frequency_to_measure ] ; auto magn_other = result . total_magn_other ; level_db [ freq_idx ] = Gain :: DoubleToDb ( magn_signal ) ; sinad_db [ freq_idx ] = Gain :: DoubleToDb ( magn_signal / magn_other ) ; phase_rad [ freq_idx ] = result . phases [ frequency_to_measure ] ; } } }
Ideal frequency response measurement is 0.00 dB across the audible spectrum
Ideal SINAD is at least 6 dB per signal-bit (>96 dB, if 16-bit resolution).
Ideal frequency response measurement is 0 . 00 dB across the audible spectrum Ideal SINAD is at least 6 dB per signal - bit ( > 96 dB if 16 - bit resolution ) .
// This run already has frequency response/SINAD/phase results for this sampler and resampling // ratio; don't waste time and cycles rerunning it. // Set this to a valid (worst-case) value, so that (for any outcome) another test does not later // rerun this combination of sampler and resample ratio. /* unused */ // Some resamplers need additional data in order to produce the final values, and the amount of // data can change depending on resampling ratio. However, all FFT inputs are considered periodic, // so to generate a periodic output from the resampler, we can provide extra source elements to // resamplers by simply wrapping around to source[0], etc. // We use this to keep ongoing src_pos_modulo across multiple Mix() calls. // kReferenceFreqs[] contains the full set of test frequencies (47). kSummaryIdxs is a subset of // 3 -- each kSummaryIdxs[] value is an index (in kReferenceFreqs[]) to one of those frequencies. // const auto first_idx = use_full_set ? FrequencySet::kFirstInBandRefFreqIdx : 0u; // use_full_set ? FrequencySet::kFirstOutBandRefFreqIdx : FrequencySet::kSummaryIdxs.size(); // Generate signal, rate-convert, and measure level and phase responses -- for each frequency. // If full-spectrum, test at all kReferenceFreqs[] values; else only use those in kSummaryIdxs[] // If frequency is too high to be characterized in this buffer, skip it. Per Nyquist limit, // buffer length must be at least 2x the frequency we want to measure. // Populate the source buffer with a sinusoid at each reference frequency. // Use this to keep ongoing src_pos_modulo across multiple Mix() calls, but then reset it each // time we start testing a new input signal frequency. // First "prime" the resampler by sending a mix command exactly at the end of the source buffer. // This allows it to cache the frames at buffer's end. For our testing, buffers are periodic, so // these frames are exactly what would have immediately preceded the first data in the buffer. // This enables resamplers with significant side width to perform as they would in steady-state. // Now resample source to accum. (Why in pieces? See kResamplerTestNumPackets: frequency_set.h) // Wrap around in the source buffer -- making the offset slightly negative. We can do // this, within the positive filter width of this sampler. // After running each frequency, clear the cached filter state. This is not strictly necessary // today, since each frequency test starts precisely at buffer-start (thus for Point and Linear // resamplers, no previously-cached state is needed). However, this IS a requirement for future // resamplers with larger positive filter widths (they exposed the bug); address this now. // Is this source frequency beyond the Nyquist limit for our destination frame rate? // Convert Frequency Response and Signal-to-Noise-And-Distortion (SINAD) to decibels. // This out-of-band frequency should have been entirely rejected -- capture total magnitude. // This is equivalent to Gain::DoubleToDb(1.0 / result.total_magn_other). // This frequency is in-band -- capture its level/phase and the magnitude of all else.