blob: 744fec43518f60761997496050809ac48ad5f9ad [file] [log] [blame]
// Copyright 2018 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.
#include <string>
#include "garnet/bin/media/audio_core/mixer/test/audio_performance.h"
#include "garnet/bin/media/audio_core/mixer/test/frequency_set.h"
#include "garnet/bin/media/audio_core/mixer/test/mixer_tests_shared.h"
namespace media::audio::test {
// Convenience abbreviation within this source file to shorten names
using Resampler = ::media::audio::Mixer::Resampler;
// For the given resampler, measure elapsed time over a number of mix jobs.
void AudioPerformance::Profile() {
printf("\n\n Performance Profiling");
AudioPerformance::ProfileMixers();
AudioPerformance::ProfileOutputProducers();
}
void AudioPerformance::ProfileMixers() {
zx_time_t start_time = zx_clock_get(ZX_CLOCK_MONOTONIC);
DisplayMixerConfigLegend();
DisplayMixerColumnHeader();
ProfileSampler(Resampler::SampleAndHold);
ProfileSampler(Resampler::LinearInterpolation);
DisplayMixerColumnHeader();
DisplayMixerConfigLegend();
printf(" Total time to profile Mixers: %lu ms\n --------\n\n",
(zx_clock_get(ZX_CLOCK_MONOTONIC) - start_time) / 1000000);
}
void AudioPerformance::DisplayMixerColumnHeader() {
printf("Configuration\t\t Mean\t First\t Best\t Worst\n");
}
void AudioPerformance::DisplayMixerConfigLegend() {
printf("\n Elapsed time in microsec for Mix() to produce %u frames\n",
kFreqTestBufSize);
printf(
"\n For mixer configuration R-fff.IOGAnnnnn, where:\n"
"\t R: Resampler type - [P]oint, [L]inear\n"
"\t fff: Format - un8, i16, i24, f32,\n"
"\t I: Input channels (one-digit number),\n"
"\t O: Output channels (one-digit number),\n"
"\t G: Gain factor - [M]ute, [U]nity, [S]caled, [R]amping,\n"
"\t A: Accumulate - [-] no or [+] yes,\n"
"\t nnnnn: Sample rate (five-digit number)\n\n");
}
// Profile the samplers in various input and output channel configurations
void AudioPerformance::ProfileSampler(Resampler sampler_type) {
ProfileSamplerIn(1, sampler_type);
ProfileSamplerIn(2, sampler_type);
ProfileSamplerIn(3, sampler_type);
ProfileSamplerIn(4, sampler_type);
}
// Based on our lack of support for arbitrary channelization, only profile the
// following channel configurations: 1-1, 1-2, 2-1, 2-2, 3-3, 4-4
void AudioPerformance::ProfileSamplerIn(uint32_t num_input_chans,
Resampler sampler_type) {
if (num_input_chans > 2) {
ProfileSamplerChans(num_input_chans, num_input_chans, sampler_type);
} else {
ProfileSamplerChans(num_input_chans, 1, sampler_type);
ProfileSamplerChans(num_input_chans, 2, sampler_type);
}
}
// Profile the samplers in scenarios with, and without, frame rate conversion
void AudioPerformance::ProfileSamplerChans(uint32_t num_input_chans,
uint32_t num_output_chans,
Resampler sampler_type) {
ProfileSamplerChansRate(num_input_chans, num_output_chans, sampler_type,
48000);
ProfileSamplerChansRate(num_input_chans, num_output_chans, sampler_type,
44100);
}
// Profile the samplers with gains of: Mute, Unity, Scaling (non-mute non-unity)
void AudioPerformance::ProfileSamplerChansRate(uint32_t num_input_chans,
uint32_t num_output_chans,
Resampler sampler_type,
uint32_t source_rate) {
// Mute scenario
ProfileSamplerChansRateScale(num_input_chans, num_output_chans, sampler_type,
source_rate, GainType::Mute);
// Unity scenario
ProfileSamplerChansRateScale(num_input_chans, num_output_chans, sampler_type,
source_rate, GainType::Unity);
// Scaling (non-mute, non-unity) scenario
ProfileSamplerChansRateScale(num_input_chans, num_output_chans, sampler_type,
source_rate, GainType::Scaled);
// Ramping scenario
ProfileSamplerChansRateScale(num_input_chans, num_output_chans, sampler_type,
source_rate, GainType::Ramped);
}
// Profile the samplers when not accumulating and when accumulating
void AudioPerformance::ProfileSamplerChansRateScale(uint32_t num_input_chans,
uint32_t num_output_chans,
Resampler sampler_type,
uint32_t source_rate,
GainType gain_type) {
ProfileSamplerChansRateScaleMix(num_input_chans, num_output_chans,
sampler_type, source_rate, gain_type, false);
ProfileSamplerChansRateScaleMix(num_input_chans, num_output_chans,
sampler_type, source_rate, gain_type, true);
}
// Profile the samplers when mixing data types: uint8, int16, int24-in-32, float
void AudioPerformance::ProfileSamplerChansRateScaleMix(
uint32_t num_input_chans, uint32_t num_output_chans, Resampler sampler_type,
uint32_t source_rate, GainType gain_type, bool accumulate) {
ProfileMixer<uint8_t>(num_input_chans, num_output_chans, sampler_type,
source_rate, gain_type, accumulate);
ProfileMixer<int16_t>(num_input_chans, num_output_chans, sampler_type,
source_rate, gain_type, accumulate);
ProfileMixer<int32_t>(num_input_chans, num_output_chans, sampler_type,
source_rate, gain_type, accumulate);
ProfileMixer<float>(num_input_chans, num_output_chans, sampler_type,
source_rate, gain_type, accumulate);
}
template <typename SampleType>
void AudioPerformance::ProfileMixer(uint32_t num_input_chans,
uint32_t num_output_chans,
Resampler sampler_type,
uint32_t source_rate, GainType gain_type,
bool accumulate) {
fuchsia::media::AudioSampleFormat sample_format;
double amplitude;
std::string format;
if (std::is_same<SampleType, uint8_t>::value) {
sample_format = fuchsia::media::AudioSampleFormat::UNSIGNED_8;
amplitude = std::numeric_limits<int8_t>::max();
format = "un8";
} else if (std::is_same<SampleType, int16_t>::value) {
sample_format = fuchsia::media::AudioSampleFormat::SIGNED_16;
amplitude = std::numeric_limits<int16_t>::max();
format = "i16";
} else if (std::is_same<SampleType, int32_t>::value) {
sample_format = fuchsia::media::AudioSampleFormat::SIGNED_24_IN_32;
amplitude = std::numeric_limits<int32_t>::max() & ~0x0FF;
format = "i24";
} else if (std::is_same<SampleType, float>::value) {
sample_format = fuchsia::media::AudioSampleFormat::FLOAT;
amplitude = 1.0;
format = "f32";
} else {
ASSERT_TRUE(false) << "Unknown mix sample format for testing";
return;
}
uint32_t dest_rate = 48000;
MixerPtr mixer = SelectMixer(sample_format, num_input_chans, source_rate,
num_output_chans, dest_rate, sampler_type);
uint32_t source_buffer_size = kFreqTestBufSize * dest_rate / source_rate;
uint32_t source_frames = source_buffer_size + 1;
std::unique_ptr<SampleType[]> source =
std::make_unique<SampleType[]>(source_frames * num_input_chans);
std::unique_ptr<float[]> accum =
std::make_unique<float[]>(kFreqTestBufSize * num_output_chans);
uint32_t frac_src_frames = source_frames * Mixer::FRAC_ONE;
int32_t frac_src_offset;
uint32_t dest_offset, previous_dest_offset;
OverwriteCosine(source.get(), source_buffer_size * num_input_chans,
FrequencySet::kReferenceFreqs[FrequencySet::kRefFreqIdx],
amplitude);
zx_duration_t first, worst, best, total_elapsed = 0;
Bookkeeping info;
info.step_size = (source_rate * Mixer::FRAC_ONE) / dest_rate;
info.denominator = dest_rate;
info.rate_modulo =
(source_rate * Mixer::FRAC_ONE) - (info.step_size * dest_rate);
float gain_db;
bool source_mute = false;
char gain_char;
switch (gain_type) {
case GainType::Mute:
// 0dB, Mute
gain_db = Gain::kUnityGainDb;
source_mute = true;
gain_char = 'M';
break;
case GainType::Unity:
// 0dB
gain_db = Gain::kUnityGainDb;
gain_char = 'U';
break;
case GainType::Scaled:
// -42dB
gain_db = -42.0f;
gain_char = 'S';
break;
case GainType::Ramped:
// -1dB => -159dB
gain_db = Gain::kUnityGainDb - 1.0f;
gain_char = 'R';
break;
}
info.gain.SetDestGain(Gain::kUnityGainDb);
info.gain.SetDestMute(false);
for (uint32_t i = 0; i < kNumMixerProfilerRuns; ++i) {
info.gain.SetSourceGain(gain_db);
info.gain.SetSourceMute(source_mute);
if (gain_type == GainType::Ramped) {
// Ramp within the "greater than Mute but less than Unity" range.
// Ramp duration assumes a mix duration of less than two secs.
info.gain.SetSourceGainWithRamp(Gain::kMinGainDb + 1.0f, ZX_SEC(2));
}
zx_duration_t elapsed;
zx_time_t start_time = zx_clock_get(ZX_CLOCK_MONOTONIC);
dest_offset = 0;
frac_src_offset = 0;
info.src_pos_modulo = 0;
while (dest_offset < kFreqTestBufSize) {
previous_dest_offset = dest_offset;
mixer->Mix(accum.get(), kFreqTestBufSize, &dest_offset, source.get(),
frac_src_frames, &frac_src_offset, accumulate, &info);
// Mix() might process less than all of accum, so Advance() after each.
info.gain.Advance(dest_offset - previous_dest_offset,
TimelineRate(source_rate, ZX_SEC(1)));
}
elapsed = zx_clock_get(ZX_CLOCK_MONOTONIC) - start_time;
if (i > 0) {
worst = std::max(worst, elapsed);
best = std::min(best, elapsed);
} else {
first = elapsed;
worst = elapsed;
best = elapsed;
}
total_elapsed += elapsed;
}
auto mean = static_cast<double>(total_elapsed) / kNumMixerProfilerRuns;
printf("%c-%s.%u%u%c%c%u:",
(sampler_type == Resampler::SampleAndHold ? 'P' : 'L'), format.c_str(),
num_input_chans, num_output_chans, gain_char, (accumulate ? '+' : '-'),
source_rate);
printf("\t%9.3lf\t%9.3lf\t%9.3lf\t%9.3lf\n", mean / 1000.0, first / 1000.0,
best / 1000.0, worst / 1000.0);
}
void AudioPerformance::DisplayOutputColumnHeader() {
printf("Config\t Mean\t First\t Best\t Worst\n");
}
void AudioPerformance::DisplayOutputConfigLegend() {
printf("\n Elapsed time in microsec to ProduceOutput() %u frames\n",
kFreqTestBufSize);
printf(
"\n For output configuration FFF-Rn, where:\n"
"\t FFF: Format of source data - Un8, I16, I24, F32,\n"
"\t R: Range of source data - [S]ilence, [O]ut-of-range, [N]ormal,\n"
"\t n: Number of output channels (one-digit number)\n\n");
}
void AudioPerformance::ProfileOutputProducers() {
zx_time_t start_time = zx_clock_get(ZX_CLOCK_MONOTONIC);
DisplayOutputConfigLegend();
DisplayOutputColumnHeader();
ProfileOutputChans(1);
ProfileOutputChans(2);
ProfileOutputChans(4);
ProfileOutputChans(6);
ProfileOutputChans(8);
DisplayOutputColumnHeader();
DisplayOutputConfigLegend();
printf(" Total time to profile OutputProducers: %lu ms\n --------\n\n",
(zx_clock_get(ZX_CLOCK_MONOTONIC) - start_time) / 1000000);
}
void AudioPerformance::ProfileOutputChans(uint32_t num_chans) {
ProfileOutputRange(num_chans, OutputDataRange::Silence);
ProfileOutputRange(num_chans, OutputDataRange::OutOfRange);
ProfileOutputRange(num_chans, OutputDataRange::Normal);
}
void AudioPerformance::ProfileOutputRange(uint32_t num_chans,
OutputDataRange data_range) {
ProfileOutputType<uint8_t>(num_chans, data_range);
ProfileOutputType<int16_t>(num_chans, data_range);
ProfileOutputType<int32_t>(num_chans, data_range);
ProfileOutputType<float>(num_chans, data_range);
}
template <typename SampleType>
void AudioPerformance::ProfileOutputType(uint32_t num_chans,
OutputDataRange data_range) {
fuchsia::media::AudioSampleFormat sample_format;
std::string format;
char range;
if (std::is_same<SampleType, uint8_t>::value) {
sample_format = fuchsia::media::AudioSampleFormat::UNSIGNED_8;
format = "Un8";
} else if (std::is_same<SampleType, int16_t>::value) {
sample_format = fuchsia::media::AudioSampleFormat::SIGNED_16;
format = "I16";
} else if (std::is_same<SampleType, int32_t>::value) {
sample_format = fuchsia::media::AudioSampleFormat::SIGNED_24_IN_32;
format = "I24";
} else if (std::is_same<SampleType, float>::value) {
sample_format = fuchsia::media::AudioSampleFormat::FLOAT;
format = "F32";
} else {
ASSERT_TRUE(false) << "Unknown output sample format for testing";
return;
}
audio::OutputProducerPtr output_producer =
SelectOutputProducer(sample_format, num_chans);
uint32_t num_samples = kFreqTestBufSize * num_chans;
std::unique_ptr<float[]> accum = std::make_unique<float[]>(num_samples);
std::unique_ptr<SampleType[]> dest =
std::make_unique<SampleType[]>(num_samples);
switch (data_range) {
case OutputDataRange::Silence:
range = 'S';
break;
case OutputDataRange::OutOfRange:
range = 'O';
for (size_t idx = 0; idx < num_samples; ++idx) {
accum[idx] = (idx % 2 ? -1.5f : 1.5f);
}
break;
case OutputDataRange::Normal:
range = 'N';
OverwriteCosine(accum.get(), num_samples,
FrequencySet::kReferenceFreqs[FrequencySet::kRefFreqIdx]);
break;
default:
ASSERT_TRUE(false) << "Unknown output sample format for testing";
return;
}
zx_duration_t first, worst, best, total_elapsed = 0;
if (data_range == OutputDataRange::Silence) {
for (uint32_t i = 0; i < kNumOutputProfilerRuns; ++i) {
zx_duration_t elapsed;
zx_time_t start_time = zx_clock_get(ZX_CLOCK_MONOTONIC);
output_producer->FillWithSilence(dest.get(), kFreqTestBufSize);
elapsed = zx_clock_get(ZX_CLOCK_MONOTONIC) - start_time;
if (i > 0) {
worst = std::max(worst, elapsed);
best = std::min(best, elapsed);
} else {
first = elapsed;
worst = elapsed;
best = elapsed;
}
total_elapsed += elapsed;
}
} else {
for (uint32_t i = 0; i < kNumOutputProfilerRuns; ++i) {
zx_duration_t elapsed;
zx_time_t start_time = zx_clock_get(ZX_CLOCK_MONOTONIC);
output_producer->ProduceOutput(accum.get(), dest.get(), kFreqTestBufSize);
elapsed = zx_clock_get(ZX_CLOCK_MONOTONIC) - start_time;
if (i > 0) {
worst = std::max(worst, elapsed);
best = std::min(best, elapsed);
} else {
first = elapsed;
worst = elapsed;
best = elapsed;
}
total_elapsed += elapsed;
}
}
auto mean = static_cast<double>(total_elapsed) / kNumOutputProfilerRuns;
printf("%s-%c%u:\t%9.3lf\t%9.3lf\t%9.3lf\t%9.3lf\n", format.c_str(), range,
num_chans, mean / 1000.0, first / 1000.0, best / 1000.0,
worst / 1000.0);
}
} // namespace media::audio::test