blob: 92cf4baed68e1f610e3e7ba0e0937c9106bf986d [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 <fbl/algorithm.h>
#include "garnet/bin/media/audio_core/mixer/no_op.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;
TEST(Bookkeeping, Defaults) {
Bookkeeping info;
EXPECT_EQ(info.step_size, Mixer::FRAC_ONE);
EXPECT_EQ(info.rate_modulo, 0u);
EXPECT_EQ(info.denominator, 0u);
EXPECT_EQ(info.src_pos_modulo, 0u);
EXPECT_EQ(info.SnapshotDenominatorFromDestTrans(), 1u);
EXPECT_EQ(info.dest_frames_to_frac_source_frames.subject_time(), 0);
EXPECT_EQ(info.dest_frames_to_frac_source_frames.reference_time(), 0);
EXPECT_EQ(info.dest_frames_to_frac_source_frames.subject_delta(), 0u);
EXPECT_EQ(info.dest_frames_to_frac_source_frames.reference_delta(), 1u);
EXPECT_EQ(info.dest_trans_gen_id, kInvalidGenerationId);
EXPECT_EQ(info.clock_mono_to_frac_source_frames.subject_time(), 0);
EXPECT_EQ(info.clock_mono_to_frac_source_frames.reference_time(), 0);
EXPECT_EQ(info.clock_mono_to_frac_source_frames.subject_delta(), 0u);
EXPECT_EQ(info.clock_mono_to_frac_source_frames.reference_delta(), 1u);
EXPECT_EQ(info.source_trans_gen_id, kInvalidGenerationId);
}
TEST(Bookkeeping, Clocks) {
Bookkeeping info;
info.dest_frames_to_frac_source_frames =
TimelineFunction(0, 1, 44100u, 48000u);
EXPECT_EQ(info.SnapshotDenominatorFromDestTrans(), 160u);
}
TEST(Bookkeeping, Reset) {
Bookkeeping info;
info.mixer = MixerPtr(new mixer::NoOp());
info.src_pos_modulo = 4321u;
info.Reset();
EXPECT_EQ(info.src_pos_modulo, 0u);
// A later test verifies the clearing of the Mixer's filter cache upon Reset.
}
//
// Timing (Resampling) tests
//
// Sync/timing correctness, to the sample level
// Verify correct FROM and TO locations, and quantity. frac_src_frames &
// src_offset are specified in fractional values (fixed 19.13 format).
//
// When doing direct bit-for-bit comparisons in these tests, we must factor in
// the left-shift biasing that is done while converting input data into the
// internal format of our accumulator. For this reason, all "expect" values are
// specified at a higher-than-needed precision of 24-bit, and then normalized
// down to the actual pipeline width.
//
// Each test contains cases that exercise different code paths within the
// samplers. A mix job's length is limited by the quantities of source data and
// output needed -- whichever is smaller. For this reason, we explicitly note
// places where we check "supply > demand", vs. "demand > supply", vs. "supply
// == demand". We used the PointSampler in earlier tests, so we already know
// "Supply == Demand" works there. When setting up each case, the so-called
// "supply" is determined by src_frames, and src_offset (into those frames).
// Likewise "demand" is determined by dest_frames and dest_offset into
// dest_frames.
// Verify that PointSampler mixes from/to correct buffer locations. Also ensure
// that it doesn't touch other buffer sections, regardless of 'accumulate'.
// This first test uses integer lengths/offsets, and a step_size of ONE.
TEST(Resampling, Position_Basic_Point) {
MixerPtr mixer = SelectMixer(fuchsia::media::AudioSampleFormat::SIGNED_16, 1,
24000, 1, 24000, Resampler::SampleAndHold);
//
// Check: source supply exceeds destination demand.
// Source (offset 2 of 5) can supply 3. Destination (offset 1 of 3) wants 2.
int32_t frac_src_offset = 2 << kPtsFractionalBits;
uint32_t dest_offset = 1;
int16_t source[] = {1, 0x17, 0x7B, 0x4D2, 0x3039};
// Mix will accumulate src[2,3] into accum[1,2]
float accum[] = {-0x00002000, -0x00017000, -0x000EA000, -0x00929000,
-0x05BA0000};
float expect[] = {-0x00002000, 0x00064000, 0x003E8000, -0x00929000,
-0x05BA0000};
NormalizeInt28ToPipelineBitwidth(accum, fbl::count_of(accum));
NormalizeInt28ToPipelineBitwidth(expect, fbl::count_of(expect));
Bookkeeping info;
bool mix_result =
mixer->Mix(accum, 3, &dest_offset, source, 5 << kPtsFractionalBits,
&frac_src_offset, true, &info);
EXPECT_FALSE(mix_result); // False: Mix did not complete all of src_frames
EXPECT_EQ(3u, dest_offset);
EXPECT_EQ(4 << kPtsFractionalBits, frac_src_offset);
EXPECT_TRUE(CompareBuffers(accum, expect, fbl::count_of(accum)));
//
// Check: destination demand exceeds source supply.
// Source (offset 3 of 4) has 1. Destination (offset 1 of 4) wants 3.
frac_src_offset = 3 << kPtsFractionalBits;
dest_offset = 1;
// Mix will move source[3] into accum[1] (accum==false)
expect[1] = 0x004D2000;
NormalizeInt28ToPipelineBitwidth(&expect[1], 1);
mix_result =
mixer->Mix(accum, 4, &dest_offset, source, 4 << kPtsFractionalBits,
&frac_src_offset, false, &info);
EXPECT_TRUE(mix_result); // True: Mix completed all of src_frames
EXPECT_EQ(2u, dest_offset);
EXPECT_EQ(4 << kPtsFractionalBits, frac_src_offset);
EXPECT_TRUE(CompareBuffers(accum, expect, fbl::count_of(accum)));
}
// Verify that LinearSampler mixes from and to correct buffer locations.
// Ensure it doesn't touch other buffer sections, regardless of 'accumulate'
// flag. Check scenarios when supply > demand, and vice versa, and ==.
// This first test uses integer lengths/offsets, and a step_size of ONE.
TEST(Resampling, Position_Basic_Linear) {
MixerPtr mixer = SelectMixer(fuchsia::media::AudioSampleFormat::SIGNED_16, 1,
48000, 1, 48000, Resampler::LinearInterpolation);
//
// Check: source supply equals destination demand.
// Source (offset 2 of 5) has 3. Destination (offset 1 of 4) wants 3.
int32_t frac_src_offset = 2 << kPtsFractionalBits;
uint32_t dest_offset = 1;
int16_t source[] = {1, 0xC, 0x7B, 0x4D2, 0x3039};
// Mix will add source[2,3,4] to accum[1,2,3]
float accum[] = {-0x00002000, -0x00017000, -0x000EA000, -0x00929000,
-0x05BA0000};
float expect[] = {-0x00002000, 0x00064000, 0x003E8000, 0x02710000,
-0x05BA0000};
NormalizeInt28ToPipelineBitwidth(accum, fbl::count_of(accum));
NormalizeInt28ToPipelineBitwidth(expect, fbl::count_of(expect));
Bookkeeping info;
bool mix_result =
mixer->Mix(accum, 4, &dest_offset, source, 5 << kPtsFractionalBits,
&frac_src_offset, true, &info);
EXPECT_TRUE(mix_result);
EXPECT_EQ(4u, dest_offset);
EXPECT_EQ(5 << kPtsFractionalBits, frac_src_offset);
EXPECT_TRUE(CompareBuffers(accum, expect, fbl::count_of(accum)));
//
// Check: source supply exceeds destination demand.
// Source (offset 0 of 4) has 4. Destination (offset 2 of 4) wants 2.
frac_src_offset = 0;
dest_offset = 2;
// Mix will add source[0,1] to accum2[2,3]
float accum2[] = {-0x00002000, -0x00017000, -0x000EA000, -0x00929000,
-0x05BA0000};
float expect2[] = {-0x00002000, -0x00017000, -0x000E9000, -0x0091D000,
-0x05BA0000};
NormalizeInt28ToPipelineBitwidth(accum2, fbl::count_of(accum2));
NormalizeInt28ToPipelineBitwidth(expect2, fbl::count_of(expect2));
mix_result =
mixer->Mix(accum2, 4, &dest_offset, source, 4 << kPtsFractionalBits,
&frac_src_offset, true, &info);
EXPECT_FALSE(mix_result);
EXPECT_EQ(4u, dest_offset);
EXPECT_EQ(2 << kPtsFractionalBits, frac_src_offset);
EXPECT_TRUE(CompareBuffers(accum2, expect2, fbl::count_of(accum2)));
//
// Check: destination demand exceeds source supply.
// Source (offset 2 of 3) has 1. Destination (offset 0 of 4) wants 4.
frac_src_offset = 2 << kPtsFractionalBits;
dest_offset = 0;
// Mix will move source[2] to accum[0]
float expect3[] = {0x0007B000, -0x00017000, -0x000E9000, -0x0091D000,
-0x05BA0000};
NormalizeInt28ToPipelineBitwidth(expect3, fbl::count_of(expect3));
mix_result =
mixer->Mix(accum2, 4, &dest_offset, source, 3 << kPtsFractionalBits,
&frac_src_offset, false, &info);
EXPECT_TRUE(mix_result);
EXPECT_EQ(1u, dest_offset);
EXPECT_EQ(3 << kPtsFractionalBits, frac_src_offset);
EXPECT_TRUE(CompareBuffers(accum2, expect3, fbl::count_of(accum2)));
}
// For PointSampler, test sample placement when given fractional position.
// Ensure it doesn't touch other buffer sections, regardless of 'accumulate'
// flag. Check when supply > demand and vice versa (we already know = works).
// This test uses fractional lengths/offsets, still with a step_size of ONE.
// TODO(mpuryear): Change frac_src_frames parameter to be (integer) src_frames,
// as number of frames was never intended to be fractional.
TEST(Resampling, Position_Fractional_Point) {
MixerPtr mixer = SelectMixer(fuchsia::media::AudioSampleFormat::SIGNED_16, 1,
44100, 1, 44100, Resampler::SampleAndHold);
//
// Check: source supply exceeds destination demand
// Source (offset 1.5 of 5) has 3.5. Destination (offset 1 of 3) wants 2.
int32_t frac_src_offset = 3 << (kPtsFractionalBits - 1);
uint32_t dest_offset = 1;
int16_t source[] = {1, 0xC, 0x7B, 0x4D2, 0x3039};
// Mix will accumulate source[1:2,2:3] into accum[1,2]
float accum[] = {-0x00002000, -0x00017000, -0x000EA000, -0x00929000,
-0x05BA0000};
float expect[] = {-0x00002000, -0x0000B000, -0x0006F000, -0x00929000,
-0x05BA0000};
NormalizeInt28ToPipelineBitwidth(accum, fbl::count_of(accum));
NormalizeInt28ToPipelineBitwidth(expect, fbl::count_of(expect));
Bookkeeping info;
bool mix_result =
mixer->Mix(accum, 3, &dest_offset, source, 5 << kPtsFractionalBits,
&frac_src_offset, true, &info);
EXPECT_FALSE(mix_result);
EXPECT_EQ(3u, dest_offset);
EXPECT_EQ(7 << (kPtsFractionalBits - 1), frac_src_offset);
EXPECT_TRUE(CompareBuffers(accum, expect, fbl::count_of(accum)));
//
// Check: Destination demand exceeds source supply
// Source (offset 2.5 of 4) has 1.5. Destination (offset 1 of 4) wants 3.
frac_src_offset = 5 << (kPtsFractionalBits - 1);
dest_offset = 1;
// Mix will move source[2:3,3:4] to accum[1,2]
float expect2[] = {-0x00002000, 0x0007B000, 0x004D2000, -0x00929000,
-0x05BA0000};
NormalizeInt28ToPipelineBitwidth(expect2, fbl::count_of(expect2));
mix_result =
mixer->Mix(accum, 4, &dest_offset, source, 4 << kPtsFractionalBits,
&frac_src_offset, false, &info);
EXPECT_TRUE(mix_result);
EXPECT_EQ(3u, dest_offset);
EXPECT_EQ(9 << (kPtsFractionalBits - 1), frac_src_offset);
EXPECT_TRUE(CompareBuffers(accum, expect2, fbl::count_of(accum)));
}
// Verify LinearSampler mixes from/to correct locations, given fractional src
// locations. Ensure it doesn't touch other buffer sections, regardless of
// 'accumulate' flag. Check cases when supply > demand and vice versa. (Cases
// where supply equals demand are well-covered elsewhere.) This test uses
// fractional offsets, still with a step_size of ONE.
TEST(Resampling, Position_Fractional_Linear) {
MixerPtr mixer = SelectMixer(fuchsia::media::AudioSampleFormat::SIGNED_16, 1,
48000, 1, 48000, Resampler::LinearInterpolation);
//
// Check: Source supply exceeds destination demand
// Source (offset 0.5 of 3) has 2.5. Destination (offset 2 of 4) wants 2.
int32_t frac_src_offset = 1 << (kPtsFractionalBits - 1); // 0.5
uint32_t dest_offset = 2;
int16_t source[] = {-1, -0xB, -0x7C, 0x4D2, 0x3039};
// Mix (accumulate) source[0:1,1:2] into accum[2,3].
float accum[] = {-0x000DEFA0, -0x0014D840, -0x00017920, 0x0007BFF0,
-0x0022BB00};
float expect[] = {-0x000DEFA0, -0x0014D840, -0x0001D920, 0x000387F0,
-0x0022BB00};
NormalizeInt28ToPipelineBitwidth(accum, fbl::count_of(accum));
NormalizeInt28ToPipelineBitwidth(expect, fbl::count_of(expect));
// TODO(mpuryear): round correctly if accumulating fractional result with
// previous opposite-polarity result. Ideally round -67.5+123 (55.5) to 56.
Bookkeeping info;
bool mix_result =
mixer->Mix(accum, 4, &dest_offset, source, 3 << kPtsFractionalBits,
&frac_src_offset, true, &info);
// Less than one frame of the source buffer remains, and we cached the final
// sample, so mix_result should be TRUE.
EXPECT_TRUE(mix_result);
EXPECT_EQ(4u, dest_offset);
EXPECT_EQ(5 << (kPtsFractionalBits - 1), frac_src_offset);
EXPECT_TRUE(CompareBuffers(accum, expect, fbl::count_of(accum)));
// src_offset ended less than 1 from end: src[2] will be cached for next mix.
//
// Check: destination demand exceeds source supply
// Source (offset -0.5 of 2) has 2.5. Destination (offset 1 of 4) wants 3.
frac_src_offset = -(1 << (kPtsFractionalBits - 1));
dest_offset = 1;
// Mix src[2:0,0:1] into accum[1,2]. [1] = (-124:-1), [2] = (-1:-11)
float expect2[] = {-0x000DEFA0, -0x0003E800, -0x00006000, 0x000387F0,
-0x0022BB00};
NormalizeInt28ToPipelineBitwidth(expect2, fbl::count_of(expect2));
mix_result =
mixer->Mix(accum, 4, &dest_offset, source, 2 << kPtsFractionalBits,
&frac_src_offset, false, &info);
EXPECT_TRUE(mix_result);
EXPECT_EQ(3u, dest_offset);
EXPECT_EQ(3 << (kPtsFractionalBits - 1), frac_src_offset);
EXPECT_TRUE(CompareBuffers(accum, expect2, fbl::count_of(accum)));
}
void TestRateModulo(Resampler sampler_type) {
MixerPtr mixer = SelectMixer(fuchsia::media::AudioSampleFormat::FLOAT, 1,
32000, 1, 48000, sampler_type);
float source[] = {0.0f, 0.1f, 0.2f};
float accum[3];
int32_t expected_frac_src_offset = 2 << kPtsFractionalBits;
// Without rate_modulo, we expect frac_src_offset to be less than [2/3 * 3].
int32_t frac_src_offset = 0;
uint32_t dest_offset = 0;
Bookkeeping info;
info.step_size = (Mixer::FRAC_ONE * 2) / 3;
mixer->Mix(accum, fbl::count_of(accum), &dest_offset, source,
fbl::count_of(source) << kPtsFractionalBits, &frac_src_offset,
false, &info);
EXPECT_EQ(fbl::count_of(accum), dest_offset);
EXPECT_LT(frac_src_offset, expected_frac_src_offset);
// With rate_modulo, frac_src_offset should be exactly 2 (i.e. 2/3 * 3).
frac_src_offset = 0;
dest_offset = 0;
info.rate_modulo = (2 << kPtsFractionalBits) - (info.step_size * 3);
info.denominator = 3;
info.src_pos_modulo = 0;
mixer->Mix(accum, fbl::count_of(accum), &dest_offset, source,
fbl::count_of(source) << kPtsFractionalBits, &frac_src_offset,
false, &info);
EXPECT_EQ(fbl::count_of(accum), dest_offset);
EXPECT_EQ(frac_src_offset, expected_frac_src_offset);
}
// Verify PointSampler correctly incorporates rate_modulo & denominator
// parameters into position and interpolation results.
TEST(Resampling, Rate_Modulo_Point) {
TestRateModulo(Resampler::SampleAndHold);
}
// Verify LinearSampler correctly incorporates rate_modulo & denominator
// parameters into position and interpolation results.
TEST(Resampling, Rate_Modulo_Linear) {
TestRateModulo(Resampler::LinearInterpolation);
}
// For the provided sampler, validate src_pos_modulo for default, 0, non-zero.
// For these three input conditions, verify rollover and non-rollover cases.
void TestPositionModulo(Resampler sampler_type) {
MixerPtr mixer = SelectMixer(fuchsia::media::AudioSampleFormat::FLOAT, 1,
44100, 1, 44100, sampler_type);
float accum[3];
uint32_t dest_offset;
int32_t frac_src_offset;
float source[4] = {0.0f};
// For these three "almost-but-not-rollover" cases, we generate 3 output
// samples, leaving source and dest at pos 3 and src_pos_modulo at 9999/10000.
//
// Case: Zero src_pos_modulo, almost-but-not-rollover.
dest_offset = 0;
frac_src_offset = 0;
// For clarity, explicitly setting step_size and denominator, even though
// step_size is auto-initialized to FRAC_ONE and denominator's 10000 persists.
Bookkeeping info;
info.step_size = Mixer::FRAC_ONE;
info.rate_modulo = 3333;
info.denominator = 10000;
info.src_pos_modulo = 0;
mixer->Mix(accum, fbl::count_of(accum), &dest_offset, source,
fbl::count_of(source) << kPtsFractionalBits, &frac_src_offset,
false, &info);
EXPECT_EQ(fbl::count_of(accum), dest_offset);
EXPECT_TRUE(3 * Mixer::FRAC_ONE == frac_src_offset);
EXPECT_EQ(9999u, info.src_pos_modulo);
// Non-zero src_pos_modulo (but rate_modulo is reduced, so same outcome).
dest_offset = 0;
frac_src_offset = 0;
info.step_size = Mixer::FRAC_ONE;
info.rate_modulo = 3332;
info.denominator = 10000;
info.src_pos_modulo = 3;
mixer->Mix(accum, fbl::count_of(accum), &dest_offset, source,
fbl::count_of(source) << kPtsFractionalBits, &frac_src_offset,
false, &info);
EXPECT_EQ(fbl::count_of(accum), dest_offset);
EXPECT_TRUE(3 * Mixer::FRAC_ONE == frac_src_offset);
EXPECT_EQ(9999u, info.src_pos_modulo);
// For these three "just-barely-rollover" cases, we generate 2 output
// samples, leaving source and dest pos at 3 but src_pos_modulo at 0/10000.
//
// Case: Zero src_pos_modulo, just-barely-rollover.
dest_offset = 1;
frac_src_offset = Mixer::FRAC_ONE - 1;
info.step_size = Mixer::FRAC_ONE;
info.rate_modulo = 5000;
info.denominator = 10000;
info.src_pos_modulo = 0;
mixer->Mix(accum, fbl::count_of(accum), &dest_offset, source,
fbl::count_of(source) << kPtsFractionalBits, &frac_src_offset,
false, &info);
EXPECT_EQ(fbl::count_of(accum), dest_offset);
EXPECT_TRUE(3 * Mixer::FRAC_ONE == frac_src_offset);
EXPECT_EQ(0u, info.src_pos_modulo);
// Non-zero src_pos_modulo, just-barely-rollover case.
dest_offset = 1;
frac_src_offset = Mixer::FRAC_ONE - 1;
info.step_size = Mixer::FRAC_ONE;
info.rate_modulo = 3332;
info.denominator = 10000;
info.src_pos_modulo = 3336;
mixer->Mix(accum, fbl::count_of(accum), &dest_offset, source,
fbl::count_of(source) << kPtsFractionalBits, &frac_src_offset,
false, &info);
EXPECT_EQ(fbl::count_of(accum), dest_offset);
EXPECT_TRUE(3 * Mixer::FRAC_ONE == frac_src_offset);
EXPECT_EQ(0u, info.src_pos_modulo);
}
// Verify PointSampler correctly incorporates src_pos_modulo (along with
// rate_modulo and denominator) into position and interpolation results.
TEST(Resampling, Position_Modulo_Point) {
TestPositionModulo(Resampler::SampleAndHold);
}
// Verify LinearSampler correctly incorporates src_pos_modulo (along with
// rate_modulo and denominator) into position and interpolation results.
TEST(Resampling, Position_Modulo_Linear) {
TestPositionModulo(Resampler::LinearInterpolation);
}
// Test LinearSampler interpolation accuracy, given fractional position.
// Inputs trigger various +/- values that should be rounded each direction.
//
// With these six precise spot checks, we verify interpolation accuracy to the
// fullest extent possible with 32-bit float and 13-bit subframe timestamps.
void TestInterpolation(uint32_t source_frames_per_second,
uint32_t dest_frames_per_second) {
MixerPtr mixer = SelectMixer(
fuchsia::media::AudioSampleFormat::FLOAT, 1, source_frames_per_second, 1,
dest_frames_per_second, Resampler::LinearInterpolation);
//
// Base check: interpolated value is exactly calculated, no rounding.
// src offset 0.5, should mix 50/50
float source1[] = {-1.0f, -0.999999880790710f}; // BF800000, BF7FFFFE
float expect1 = -0.999999940395355f; // BF7FFFFF
uint32_t frac_src_frames = (fbl::count_of(source1)) << kPtsFractionalBits;
int32_t frac_src_offset = 1 << (kPtsFractionalBits - 1); // 0x1000 (2000==1)
uint32_t dest_offset = 0;
float accum_result = 0xCAFE; // value will be overwritten
Bookkeeping info;
info.step_size =
(static_cast<uint64_t>(source_frames_per_second) << kPtsFractionalBits) /
dest_frames_per_second;
int32_t expected_src_offset = frac_src_offset + info.step_size;
mixer->Mix(&accum_result, 1, &dest_offset, source1, frac_src_frames,
&frac_src_offset, false, &info);
EXPECT_EQ(1u, dest_offset);
EXPECT_EQ(expected_src_offset, frac_src_offset);
EXPECT_EQ(expect1, accum_result);
//
// Additional check: interpolated result is negative and should round out.
// src offset of 0.25 should lead us to mix the two src samples 75/25, which
// results in a value -0.999999970197678 that in IEEE-754 format is exactly
// halfway between the least-significant bit of floating-point precision
// BF7FFFFF.8). Here, we should round "out" so that this last bit is 0 (the
// 'round even' convention), so we expect BF800000, which is -1.0.
expect1 = -1.0f;
frac_src_offset = 1 << (kPtsFractionalBits - 2); // 0x0800 (2000==1.0)
expected_src_offset = frac_src_offset + info.step_size;
dest_offset = 0;
accum_result = 0xCAFE; // Value will be overwritten.
mixer->Mix(&accum_result, 1, &dest_offset, source1, frac_src_frames,
&frac_src_offset, false, &info);
EXPECT_EQ(1u, dest_offset);
EXPECT_EQ(expected_src_offset, frac_src_offset);
EXPECT_EQ(expect1, accum_result);
//
// Base check: interpolated value is exactly calculated, no rounding.
// src offset 0.5, should mix 50/50
float source2[] = {0.999999880790710f, 1.0f}; // 3F7FFFFE, 3F800000
float expect2 = 0.999999940395355f; // 3F7FFFFF
frac_src_frames = (fbl::count_of(source2)) << kPtsFractionalBits;
frac_src_offset = 1 << (kPtsFractionalBits - 1); // 0x1000 (2000==1.0)
expected_src_offset = frac_src_offset + info.step_size;
dest_offset = 0;
accum_result = 0xCAFE; // Value will be overwritten.
mixer->Mix(&accum_result, 1, &dest_offset, source2, frac_src_frames,
&frac_src_offset, false, &info);
EXPECT_EQ(1u, dest_offset);
EXPECT_EQ(expected_src_offset, frac_src_offset);
EXPECT_EQ(expect2, accum_result);
//
// Additional check: interpolated result is positive and should round out.
// src offset of 0x1800 should lead us to mix the two src samples 25/75, which
// results in a value 0.999999970197678 that in IEEE-754 format is exactly
// halfway between the least-significant bit of floating-point precision
// 3F7FFFFF.8). Here, we should round "out" so that this last bit is 0 (the
// 'round even' convention), so we expect 3F800000, which is +1.0.
expect2 = 1.0f;
frac_src_offset = 3 << (kPtsFractionalBits - 2); // 0x1800 (0x2000==1.0)
expected_src_offset = frac_src_offset + info.step_size;
dest_offset = 0;
accum_result = 0xCAFE; // Value will be overwritten.
mixer->Mix(&accum_result, 1, &dest_offset, source2, frac_src_frames,
&frac_src_offset, false, &info);
EXPECT_EQ(1u, dest_offset);
EXPECT_EQ(expected_src_offset, frac_src_offset);
EXPECT_EQ(expect2, accum_result);
//
// Check: interpolated result is positive and should round in.
// src offset 0x17FF (0x2000 is 1.0) should mix just less than 25/75, which
// results in an interpolated value 0.749694854021072 that in IEEE-754 format
// is exactly halfway between the least-significant bit of floating-point
// precision 3F3FEC00.8). Here, we should round "in" so that the LSB is 0 (the
// 'round even' convention), so we expect 3F3FEC00, which is 0.74969482421875.
float source3[] = {0.0f, 0.999755859375f};
float expect3 = 0.74969482421875f;
frac_src_frames = (fbl::count_of(source3)) << kPtsFractionalBits;
frac_src_offset = (3 << (kPtsFractionalBits - 2)) - 1; // 0x17FF (2000==1.0)
expected_src_offset = frac_src_offset + info.step_size;
dest_offset = 0;
accum_result = 0xCAFE; // Value will be overwritten.
mixer->Mix(&accum_result, 1, &dest_offset, source3, frac_src_frames,
&frac_src_offset, false, &info);
EXPECT_EQ(1u, dest_offset);
EXPECT_EQ(expected_src_offset, frac_src_offset);
EXPECT_EQ(expect3, accum_result);
//
// Check: interpolated result is negative and should round in.
// src offset of 0x0801, which should mix just less than 75/25, resulting in
// an interpolated value of -0.749694854021072 that in IEEE-754 format is
// precisely halfway between the least-significant bit of floating-point
// precision BF3FEC00.8). Here, we should round "in" so that the LSB is 0 (the
// 'round even' convention), so we expect BF3FEC00: -0.74969482421875.
float source4[] = {-0.999755859375f, 0.0f};
float expect4 = -0.74969482421875f;
frac_src_frames = (fbl::count_of(source4)) << kPtsFractionalBits;
frac_src_offset = (1 << (kPtsFractionalBits - 2)) + 1; // 0x0801 (2000==1.0)
expected_src_offset = frac_src_offset + info.step_size;
dest_offset = 0;
accum_result = 0xCAFE; // Value will be overwritten.
mixer->Mix(&accum_result, 1, &dest_offset, source4, frac_src_frames,
&frac_src_offset, false, &info);
EXPECT_EQ(1u, dest_offset);
EXPECT_EQ(expected_src_offset, frac_src_offset);
EXPECT_EQ(expect4, accum_result);
}
// This test varies the fractional starting offsets, still with rate ratio ONE.
TEST(Resampling, Interpolation_Values) { TestInterpolation(48000, 48000); }
// Various checks similar to above, while varying rate ratio. Interp results
// should not change: they depend only on frac_src_pos, not the rate ratio.
// dest_offset and frac_src_offset should continue to advance accurately.
//
// Ratios related to the very-common 147:160 conversion.
TEST(Resampling, Interpolation_Rate_441_48) {
TestInterpolation(88200, 48000);
TestInterpolation(44100, 48000);
}
// Ratios related to the very-common 160:147 conversion.
TEST(Resampling, Interpolation_Rate_48_441) {
TestInterpolation(48000, 44100);
TestInterpolation(48000, 88200);
}
// Power-of-3 rate ratio 1:3 is guaranteed to have fractional rate error, since
// 1/3 cannot be perfectly represented by a single binary value.
TEST(Resampling, Interpolation_Rate_16_48) { TestInterpolation(16000, 48000); }
// Rate change by the smallest-possible increment will be used as micro-SRC, to
// synchronize multiple physically-distinct output devices. This rate ratio also
// has the maximum fractional error when converting to the standard 48000 rate.
TEST(Resampling, Interpolation_Rate_MicroSRC) {
TestInterpolation(47999, 48000);
}
// This rate ratio, when translated into a step_size based on 4096 subframes,
// equates to 3568.999909, generating a maximal fractional value [0.999909].
// Because the callers of Mix() [standard_output_base and audio_renderer_impl]
// truncate, a maximal fractional value represents maximal fractional error.
TEST(Resampling, Interpolation_Rate_Max_Error) {
TestInterpolation(38426, 44100);
}
// Verify PointSampler filter widths.
TEST(Resampling, FilterWidth_Point) {
MixerPtr mixer = SelectMixer(fuchsia::media::AudioSampleFormat::UNSIGNED_8, 1,
48000, 1, 48000, Resampler::SampleAndHold);
EXPECT_EQ(mixer->pos_filter_width(), 0u);
EXPECT_EQ(mixer->neg_filter_width(), Mixer::FRAC_ONE - 1);
mixer->Reset();
EXPECT_EQ(mixer->pos_filter_width(), 0u);
EXPECT_EQ(mixer->neg_filter_width(), Mixer::FRAC_ONE - 1);
}
// Verify LinearSampler filter widths.
TEST(Resampling, FilterWidth_Linear) {
MixerPtr mixer = SelectMixer(fuchsia::media::AudioSampleFormat::FLOAT, 1,
44100, 1, 48000, Resampler::LinearInterpolation);
EXPECT_EQ(mixer->pos_filter_width(), Mixer::FRAC_ONE - 1);
EXPECT_EQ(mixer->neg_filter_width(), Mixer::FRAC_ONE - 1);
mixer->Reset();
EXPECT_EQ(mixer->pos_filter_width(), Mixer::FRAC_ONE - 1);
EXPECT_EQ(mixer->neg_filter_width(), Mixer::FRAC_ONE - 1);
}
// Verify LinearSampler::Reset clears out any cached "previous edge" values.
// Earlier test (Position_Fractional_Linear) already validates that
// LinearSampler correctly caches edge values, so just validate Reset.
TEST(Resampling, Reset_Linear) {
Bookkeeping info;
info.mixer = SelectMixer(fuchsia::media::AudioSampleFormat::SIGNED_16, 1,
48000, 1, 48000, Resampler::LinearInterpolation);
// When src_offset ends on fractional val, it caches that sample for next mix
// Source (offset 0.5 of 3) has 2.5. Destination (offset 2 of 4) wants 2.
int32_t frac_src_offset = 1 << (kPtsFractionalBits - 1); // 0.5
int16_t source[] = {0x1B0, 0xEA, 0x28E, 0x4D2, 0x3039};
uint32_t dest_offset = 2;
// Mix (accumulate) source[0:1,1:2] into accum[2,3].
float accum[] = {-0x0006F000, -0x000DE000, -0x0014D000, -0x001BC000,
-0x0022B000};
float expect[] = {-0x0006F000, -0x000DE000, 0, 0, -0x0022B000};
NormalizeInt28ToPipelineBitwidth(accum, fbl::count_of(accum));
NormalizeInt28ToPipelineBitwidth(expect, fbl::count_of(expect));
EXPECT_TRUE(info.mixer->Mix(accum, 4, &dest_offset, source,
3 << kPtsFractionalBits, &frac_src_offset, true,
&info));
EXPECT_EQ(4u, dest_offset);
EXPECT_EQ(5 << (kPtsFractionalBits - 1), frac_src_offset);
EXPECT_TRUE(CompareBuffers(accum, expect, fbl::count_of(accum)));
// src_offset ended less than 1 from end: src[2] will be cached for next mix.
// Mixes with a frac_src_offset < 0 rely on a cached val. This one, post-
// reset, has no cached vals and hence uses 0 for "left" vals during interp.
info.Reset();
// Start the src at offset -0.5.
frac_src_offset = -(1 << (kPtsFractionalBits - 1));
// Dest wants only one sample, at dest[0].
dest_offset = 0;
expect[0] = 0x000D8000; // Mix(:1B0)=D8 to [0]. W/out Reset, = (28E:1B0)=21F.
NormalizeInt28ToPipelineBitwidth(&expect[0], 1);
EXPECT_FALSE(info.mixer->Mix(accum, 1, &dest_offset, source,
2 << kPtsFractionalBits, &frac_src_offset, false,
&info));
EXPECT_EQ(1u, dest_offset);
EXPECT_EQ(1 << (kPtsFractionalBits - 1), frac_src_offset);
EXPECT_TRUE(CompareBuffers(accum, expect, fbl::count_of(accum)));
}
} // namespace media::audio::test