| // 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. |
| |
| #include "src/media/audio/audio_core/mixer/point_sampler.h" |
| |
| #include <fbl/algorithm.h> |
| #include <gmock/gmock.h> |
| #include <gtest/gtest.h> |
| |
| #include "src/media/audio/lib/format2/channel_mapper.h" |
| #include "src/media/audio/lib/format2/sample_converter.h" |
| #include "src/media/audio/lib/processing/gain.h" |
| |
| namespace media::audio::mixer { |
| namespace { |
| |
| using testing::FloatEq; |
| using testing::Pointwise; |
| |
| using Resampler = ::media::audio::Mixer::Resampler; |
| |
| constexpr auto kMaxInt24In32 = media_audio::kMaxInt24In32; |
| constexpr auto kMinInt24In32 = media_audio::kMinInt24In32; |
| |
| // TODO(fxbug.dev/70578): Relocate position-related tests here, from audio_fidelity_tests |
| // TODO(fxbug.dev/70580): Refactor the set of pass-thru, rechannel, accumulate and gain unittests, |
| // so they run on SincSampler as well (perhaps moving them into a mixer_unittest.cc). |
| class PointSamplerTest : public testing::Test { |
| protected: |
| static const std::vector<int32_t> kFrameRates; |
| static const std::vector<int32_t> kUnsupportedFrameRates; |
| |
| static const std::vector<std::pair<int32_t, int32_t>> kChannelConfigs; |
| static const std::vector<std::pair<int32_t, int32_t>> kUnsupportedChannelConfigs; |
| |
| static const std::vector<fuchsia::media::AudioSampleFormat> kFormats; |
| const fuchsia::media::AudioSampleFormat kInvalidFormat = |
| static_cast<fuchsia::media::AudioSampleFormat>( |
| static_cast<int64_t>(kFormats[std::size(kFormats) - 1]) + 1); |
| |
| std::unique_ptr<Mixer> SelectPointSampler( |
| int32_t source_channels, int32_t dest_channels, int32_t source_frame_rate, |
| int32_t dest_frame_rate, fuchsia::media::AudioSampleFormat source_format, |
| fuchsia::media::AudioSampleFormat dest_format = fuchsia::media::AudioSampleFormat::FLOAT) { |
| fuchsia::media::AudioStreamType source_stream_type; |
| source_stream_type.channels = source_channels; |
| source_stream_type.frames_per_second = source_frame_rate; |
| source_stream_type.sample_format = source_format; |
| |
| fuchsia::media::AudioStreamType dest_stream_type; |
| dest_stream_type.channels = dest_channels; |
| dest_stream_type.frames_per_second = dest_frame_rate; |
| dest_stream_type.sample_format = dest_format; |
| |
| return Mixer::Select(source_stream_type, dest_stream_type, Mixer::Resampler::SampleAndHold); |
| } |
| |
| // When we specify source data in uint8/int16/int32 formats, it improves readability to specify |
| // expected values in that format as well. The expected array itself is float[], so we use this |
| // function to shift values expressed as uint8, int16, etc., into the [-1.0, 1.0] float range. |
| // |
| // Note: 'shift_by' values must be 1 less than might seem obvious, to account for the sign bit. |
| // E.g.: to shift int16 values -0x8000 and 0x7FFF into float range, shift_by must be 15 (not 16). |
| void ShiftRightBy(std::vector<float>& floats, uint32_t shift_by) { |
| for (float& val : floats) { |
| for (auto shift_num = 0u; shift_num < shift_by; ++shift_num) { |
| val *= 0.5f; |
| } |
| } |
| } |
| |
| // Use the supplied mixer to mix without SRC. Assumes no accumulation, but can be overridden. |
| // Used by tests that do simple mixing and need not inspect the returned position values. |
| void DoMix(Mixer* mixer, const void* source_buf, float* accum_buf, bool accumulate, |
| int64_t num_frames, float gain_db = media_audio::kUnityGainDb) { |
| ASSERT_NE(mixer, nullptr); |
| |
| int64_t dest_offset = 0; |
| auto source_offset = Fixed(0); |
| |
| auto& bk = mixer->bookkeeping(); |
| bk.gain.SetSourceGain(gain_db); |
| |
| mixer->Mix(accum_buf, num_frames, &dest_offset, source_buf, num_frames, &source_offset, |
| accumulate); |
| |
| EXPECT_EQ(dest_offset, num_frames); |
| EXPECT_EQ(source_offset, Fixed(num_frames)); |
| } |
| }; |
| |
| const std::vector<int32_t> PointSamplerTest::kFrameRates = { |
| 8000, 11025, 16000, 22050, 24000, 32000, |
| 44100, 48000, 88200, 96000, 176400, fuchsia::media::MAX_PCM_FRAMES_PER_SECOND, |
| }; |
| |
| const std::vector<int32_t> PointSamplerTest::kUnsupportedFrameRates = { |
| fuchsia::media::MIN_PCM_FRAMES_PER_SECOND - 1, |
| fuchsia::media::MAX_PCM_FRAMES_PER_SECOND + 1, |
| }; |
| |
| const std::vector<std::pair<int32_t, int32_t>> PointSamplerTest::kChannelConfigs = { |
| {1, 1}, {1, 2}, {1, 3}, {1, 4}, // |
| {2, 1}, {2, 2}, {2, 3}, {2, 4}, // |
| {3, 1}, {3, 2}, {3, 3}, // |
| {4, 1}, {4, 2}, {4, 4}, // |
| {5, 5}, {6, 6}, {7, 7}, {8, 8}, |
| }; |
| |
| const std::vector<std::pair<int32_t, int32_t>> PointSamplerTest::kUnsupportedChannelConfigs = { |
| {1, 5}, {1, 8}, {1, 9}, // Unsupported channel |
| {2, 5}, {2, 8}, {2, 9}, // configurations -- |
| {3, 5}, {3, 8}, {3, 9}, // maximum number of |
| {4, 5}, {4, 7}, {4, 9}, // channels is 8. |
| {5, 1}, {9, 1}, {9, 9}, |
| }; |
| |
| const std::vector<fuchsia::media::AudioSampleFormat> PointSamplerTest::kFormats = { |
| fuchsia::media::AudioSampleFormat::UNSIGNED_8, |
| fuchsia::media::AudioSampleFormat::SIGNED_16, |
| fuchsia::media::AudioSampleFormat::SIGNED_24_IN_32, |
| fuchsia::media::AudioSampleFormat::FLOAT, |
| }; |
| |
| // These formats are supported |
| TEST_F(PointSamplerTest, Construction) { |
| for (auto channel_config : kChannelConfigs) { |
| for (auto rate : kFrameRates) { |
| for (auto format : kFormats) { |
| auto mixer = |
| SelectPointSampler(channel_config.first, channel_config.second, rate, rate, format); |
| |
| SCOPED_TRACE(testing::Message() << "Chans " << channel_config.first << ">" |
| << channel_config.second << ", rates " << rate << ":" |
| << rate << ", format " << static_cast<int64_t>(format)); |
| EXPECT_NE(mixer, nullptr); |
| } |
| } |
| } |
| } |
| |
| // Rate mismatch is unsupported |
| TEST_F(PointSamplerTest, ConstructionDifferingRates) { |
| for (auto source_rate : kFrameRates) { |
| for (auto dest_rate : kFrameRates) { |
| if (source_rate == dest_rate) { |
| continue; |
| } |
| |
| // Use channel configs and formats that are known-good. |
| auto channel_config = kChannelConfigs[0]; |
| auto format = kFormats[0]; |
| |
| SCOPED_TRACE(testing::Message() << "Chans " << channel_config.first << ">" |
| << channel_config.second << ", rates " << source_rate << ":" |
| << dest_rate << ", format " << static_cast<int64_t>(format)); |
| EXPECT_EQ(nullptr, SelectPointSampler(channel_config.first, channel_config.second, |
| source_rate, dest_rate, format)); |
| |
| channel_config = kChannelConfigs[std::size(kChannelConfigs) - 1]; |
| format = kFormats[std::size(kFormats) - 1]; |
| |
| SCOPED_TRACE(testing::Message() << "Chans " << channel_config.first << ">" |
| << channel_config.second << ", rates " << source_rate << ":" |
| << dest_rate << ", format " << static_cast<int64_t>(format)); |
| EXPECT_EQ(nullptr, SelectPointSampler(channel_config.first, channel_config.second, |
| source_rate, dest_rate, format)); |
| } |
| } |
| } |
| |
| // Out-of-range rates are unsupported |
| TEST_F(PointSamplerTest, ConstructionUnsupportedRate) { |
| for (auto bad_rate : kUnsupportedFrameRates) { |
| // Use channel configs and formats that are known-good. |
| auto channel_config = kChannelConfigs[0]; |
| auto format = kFormats[0]; |
| |
| SCOPED_TRACE(testing::Message() |
| << "Chans " << channel_config.first << ">" << channel_config.second << ", rates " |
| << bad_rate << ":" << bad_rate << ", format " << static_cast<int64_t>(format)); |
| EXPECT_EQ(nullptr, SelectPointSampler(channel_config.first, channel_config.second, bad_rate, |
| bad_rate, format)); |
| |
| channel_config = kChannelConfigs[std::size(kChannelConfigs) - 1]; |
| format = kFormats[std::size(kFormats) - 1]; |
| |
| SCOPED_TRACE(testing::Message() |
| << "Chans " << channel_config.first << ">" << channel_config.second << ", rates " |
| << bad_rate << ":" << bad_rate << ", format " << static_cast<int64_t>(format)); |
| EXPECT_EQ(nullptr, SelectPointSampler(channel_config.first, channel_config.second, bad_rate, |
| bad_rate, format)); |
| } |
| } |
| |
| // These channel configs are unsupported |
| TEST_F(PointSamplerTest, ConstructionUnsupportedChannelConfig) { |
| for (auto bad_channel_config : kUnsupportedChannelConfigs) { |
| // Use rates and formats that are known-good. |
| auto rate = kFrameRates[0]; |
| auto format = fuchsia::media::AudioSampleFormat::SIGNED_16; |
| |
| SCOPED_TRACE(testing::Message() << "Chans " << bad_channel_config.first << ">" |
| << bad_channel_config.second << ", rates " << rate << ":" |
| << rate << ", format " << static_cast<int64_t>(format)); |
| EXPECT_EQ(nullptr, SelectPointSampler(bad_channel_config.first, bad_channel_config.second, rate, |
| rate, format)); |
| |
| rate = kFrameRates[std::size(kFrameRates) - 1]; |
| format = fuchsia::media::AudioSampleFormat::FLOAT; |
| |
| SCOPED_TRACE(testing::Message() << "Chans " << bad_channel_config.first << ">" |
| << bad_channel_config.second << ", rates " << rate << ":" |
| << rate << ", format " << static_cast<int64_t>(format)); |
| EXPECT_EQ(nullptr, SelectPointSampler(bad_channel_config.first, bad_channel_config.second, rate, |
| rate, format)); |
| } |
| } |
| |
| // This format is unsupported |
| TEST_F(PointSamplerTest, ConstructionUnsupportedFormat) { |
| // Use channel configs and rates that are known-good. |
| auto channel_config = kChannelConfigs[0]; |
| auto rate = kFrameRates[0]; |
| |
| // bad format: one more than the last enum |
| auto bad_format = kInvalidFormat; |
| |
| SCOPED_TRACE(testing::Message() << "Chans " << channel_config.first << ">" |
| << channel_config.second << ", rates " << rate << ":" << rate |
| << ", format " << static_cast<uint64_t>(bad_format)); |
| EXPECT_EQ(nullptr, SelectPointSampler(channel_config.first, channel_config.second, rate, rate, |
| bad_format)); |
| } |
| |
| // PassThru - can audio data flow through a Mix() call without change, in various configurations? |
| // |
| class PointSamplerPassThruTest : public PointSamplerTest {}; |
| |
| // Can 8-bit values flow unchanged (1-1, N-N) thru the system? With 1:1 frame |
| // conversion, unity scale and no accumulation, we expect bit-equality. |
| TEST_F(PointSamplerPassThruTest, Uint8) { |
| auto source = std::vector<uint8_t>{0x00, 0xFF, 0x27, 0xCD, 0x7F, 0x80, 0xA6, 0x6D}; |
| |
| auto accum = std::vector<float>(source.size()); |
| auto expect = std::vector<float>(source.size()); |
| for (auto idx = 0u; idx < std::size(source); ++idx) { |
| expect[idx] = static_cast<float>(source[idx]) / 0x80 - 1.0f; |
| } |
| |
| // Try in 1-channel mode |
| auto mixer = |
| SelectPointSampler(1, 1, 48000, 48000, fuchsia::media::AudioSampleFormat::UNSIGNED_8); |
| DoMix(mixer.get(), source.data(), accum.data(), false, accum.size()); |
| EXPECT_THAT(accum, Pointwise(FloatEq(), expect)); |
| |
| // Now try in 8-channel mode |
| std::fill(accum.begin(), accum.end(), 0xB4); // fill accum with nonsense (to be overwritten) |
| mixer = SelectPointSampler(8, 8, 32000, 32000, fuchsia::media::AudioSampleFormat::UNSIGNED_8); |
| DoMix(mixer.get(), source.data(), accum.data(), false, accum.size() / 8); |
| EXPECT_THAT(accum, Pointwise(FloatEq(), expect)); |
| } |
| |
| // Can 16-bit values flow unchanged (2-2, N-N) thru the system? With 1:1 frame |
| // conversion, unity scale and no accumulation, we expect bit-equality. |
| TEST_F(PointSamplerPassThruTest, Int16) { |
| auto source = std::vector<int16_t>{-0x8000, 0x7FFF, -0x67A7, 0x4D4D, -0x123, 0, 0x2600, -0x2DCB}; |
| |
| auto accum = std::vector<float>(source.size()); |
| auto expect = std::vector<float>(source.size()); |
| std::copy(source.begin(), source.end(), expect.begin()); |
| ShiftRightBy(expect, 15); |
| |
| // Try in 2-channel mode |
| auto mixer = SelectPointSampler(2, 2, 48000, 48000, fuchsia::media::AudioSampleFormat::SIGNED_16); |
| DoMix(mixer.get(), source.data(), accum.data(), false, accum.size() / 2); |
| EXPECT_THAT(accum, Pointwise(FloatEq(), expect)); |
| |
| std::fill(accum.begin(), accum.end(), 0xF00D); // fill accum with nonsense (to be overwritten) |
| // Now try in 4-channel mode |
| mixer = SelectPointSampler(4, 4, 192000, 192000, fuchsia::media::AudioSampleFormat::SIGNED_16); |
| DoMix(mixer.get(), source.data(), accum.data(), false, accum.size() / 4); |
| EXPECT_THAT(accum, Pointwise(FloatEq(), expect)); |
| } |
| |
| // Can 24-bit values flow unchanged (2-2, N-N) thru the system? With 1:1 frame |
| // conversion, unity scale and no accumulation, we expect bit-equality. |
| TEST_F(PointSamplerPassThruTest, Int24In32) { |
| auto source = |
| std::vector<int32_t>{kMinInt24In32, kMaxInt24In32, -0x67A7E700, 0x4D4D4D00, -0x1234500, 0, |
| 0x26006200, -0x2DCBA900}; |
| |
| auto accum = std::vector<float>(source.size()); |
| auto expect = std::vector<float>(source.size()); |
| std::copy(source.begin(), source.end(), expect.begin()); |
| ShiftRightBy(expect, 31); |
| |
| // Try in 2-channel mode |
| auto mixer = |
| SelectPointSampler(2, 2, 48000, 48000, fuchsia::media::AudioSampleFormat::SIGNED_24_IN_32); |
| DoMix(mixer.get(), source.data(), accum.data(), false, accum.size() / 2); |
| EXPECT_THAT(accum, Pointwise(FloatEq(), expect)); |
| |
| std::fill(accum.begin(), accum.end(), 0xBADF00D); // fill accum with nonsense (to be overwritten) |
| // Now try in 8-channel mode |
| mixer = |
| SelectPointSampler(8, 8, 96000, 96000, fuchsia::media::AudioSampleFormat::SIGNED_24_IN_32); |
| DoMix(mixer.get(), source.data(), accum.data(), false, accum.size() / 8); |
| EXPECT_THAT(accum, Pointwise(FloatEq(), expect)); |
| } |
| |
| // Can float values flow unchanged (1-1, N-N) thru the system? With 1:1 frame |
| // conversion, unity scale and no accumulation, we expect bit-equality. |
| TEST_F(PointSamplerPassThruTest, Float) { |
| auto source = std::vector<float>{-1.0, 1.0f, -0.809783935f, 0.603912353f, -0.00888061523f, |
| 0.0f, 0.296875f, -0.357757568f}; |
| |
| // Try in 1-channel mode |
| auto accum = std::vector<float>(source.size()); |
| auto mixer = SelectPointSampler(1, 1, 48000, 48000, fuchsia::media::AudioSampleFormat::FLOAT); |
| DoMix(mixer.get(), source.data(), accum.data(), false, accum.size()); |
| EXPECT_THAT(accum, Pointwise(FloatEq(), source)); |
| |
| // Now try in 4-channel mode |
| std::fill(accum.begin(), accum.end(), NAN); // fill accum with nonsense (overwritten) |
| mixer = SelectPointSampler(4, 4, 8000, 8000, fuchsia::media::AudioSampleFormat::FLOAT); |
| DoMix(mixer.get(), source.data(), accum.data(), false, accum.size() / 4); |
| EXPECT_THAT(accum, Pointwise(FloatEq(), source)); |
| } |
| |
| // Rechannelization tests |
| // |
| // Do we map source channels to destination channels correctly, in the overall mixer context? |
| class PointSamplerRechannelTest : public PointSamplerTest {}; |
| |
| // Are all valid data values passed correctly to 16-bit outputs for the 1->2 channel mapping. |
| TEST_F(PointSamplerRechannelTest, MonoToStereo) { |
| auto source = std::vector<int16_t>{-0x08000, -0x3FFF, -1, 0, 1, 0x7FFF}; |
| |
| auto accum = std::vector<float>(source.size() * 2); |
| auto expect = std::vector<float>(source.size() * 2); |
| for (auto idx = 0u; idx < source.size(); ++idx) { |
| expect[idx * 2] = source[idx]; |
| expect[idx * 2 + 1] = source[idx]; |
| } |
| ShiftRightBy(expect, 15); |
| |
| auto mixer = SelectPointSampler(1, 2, 48000, 48000, fuchsia::media::AudioSampleFormat::SIGNED_16); |
| DoMix(mixer.get(), source.data(), accum.data(), false, accum.size() / 2); |
| EXPECT_THAT(accum, Pointwise(FloatEq(), expect)); |
| } |
| |
| // Validate that we correctly mix stereo->mono, including precision below the source data format. |
| // The two samples in each input frame should be averaged, for each single-sample output frame. |
| // This includes resolution below what can be expressed with the 16-bit source format. |
| TEST_F(PointSamplerRechannelTest, StereoToMono) { |
| auto source = std::vector<int16_t>{ |
| 0, 0, // Various values ... |
| 0x1, -0x1, // ... that sum ... |
| -0x1357, 0x1357, // ... to zero. |
| -0x1111, 0x3333, // positive even sum |
| -0x5555, 0x1111, // negative even sum |
| -0x0001, 0x0006, // positive odd sum - the ".5" result shouldn't be lost |
| -0x2005, 0x2000, // negative odd sum - the ".5" result shouldn't be lost |
| 0x7FFF, 0x7FFF, // positive limit |
| -0x8000, -0x8000, // negative limit |
| }; |
| |
| auto accum = // overwritten |
| std::vector<float>{-0x1234, 0x4321, -0x13579, 0xC0FF, -0xAAAA, 0x555, 0xABC, 0x42, 0xD00D}; |
| auto expect = std::vector<float>{0, 0, 0, 0x1111, -0x2222, 2.5, -2.5, 0x7FFF, -0x8000}; |
| ShiftRightBy(expect, 15); // right-shift these int16 values into float range |
| |
| auto mixer = SelectPointSampler(2, 1, 48000, 48000, fuchsia::media::AudioSampleFormat::SIGNED_16); |
| DoMix(mixer.get(), source.data(), accum.data(), false, accum.size()); |
| EXPECT_THAT(accum, Pointwise(FloatEq(), expect)); |
| } |
| |
| // Validate that we correctly mix quad->mono, including precision beyond the source format. |
| // The four samples in each input frame should be averaged, for each single-sample output frame. |
| // This includes resolution below what can be expressed with the 24-bit source format. |
| TEST_F(PointSamplerRechannelTest, QuadToMono) { |
| auto source = std::vector<int32_t>{ |
| // clang-format off |
| 0x00000100, 0, 0, 0, // should become 0.25 |
| -0x00000100, 0, 0, 0, // should become -0.25 |
| 0x00000100, 0x00000100, 0x00000100, 0, // should become 0.75 |
| -0x00000100, -0x00000100, -0x00000100, 0, // should become -0.75 |
| kMinInt24In32, kMinInt24In32, kMinInt24In32, kMinInt24In32, // should become kMinInt32In32 |
| kMaxInt24In32, kMaxInt24In32, kMaxInt24In32, kMaxInt24In32, // should become kMaxInt24In32 |
| kMaxInt24In32, kMaxInt24In32, -kMaxInt24In32, -kMaxInt24In32, // should become 0 |
| // clang-format on |
| }; |
| |
| // Express expected values as "int24" (not int32) to clearly show fractional and min/max values. |
| auto accum = std::vector<float>(source.size() / 4); |
| std::vector<float> expect; |
| if constexpr (media_audio::kEnable4ChannelWorkaround) { |
| // For now, 4->1 just ignores channels 2 & 3. |
| // TODO(fxbug.dev/85201): Remove this workaround, once the device properly maps channels. |
| expect = { |
| // clang-format off |
| 0.5, |
| -0.5, |
| 1.0, |
| -1.0, |
| -0x800000, |
| 0x7FFFFF, |
| 0x7FFFFF, |
| // clang-format on |
| }; |
| } else { |
| expect = { |
| // clang-format off |
| 0.25, |
| -0.25, |
| 0.75, |
| -0.75, |
| -0x800000, |
| 0x7FFFFF, |
| 0, |
| // clang-format on |
| }; |
| } |
| ShiftRightBy(expect, 23); // right-shift these "int24" values into float range |
| |
| auto mixer = |
| SelectPointSampler(4, 1, 64000, 64000, fuchsia::media::AudioSampleFormat::SIGNED_24_IN_32); |
| DoMix(mixer.get(), source.data(), accum.data(), false, accum.size()); |
| EXPECT_THAT(accum, Pointwise(FloatEq(), expect)); |
| } |
| |
| // Validate quad->stereo mixing, including sub-format precision. Note: 0|1|2|3 becomes 0+2 | 1+3 |
| TEST_F(PointSamplerRechannelTest, QuadToStereo) { |
| auto source = std::vector<int32_t>{ |
| // clang-format off |
| 0x00000100, -0x00000100, 0, 0, // [0,2]=>0.5, [1,3]=>-0.5 |
| kMinInt24In32, kMaxInt24In32, kMinInt24In32, kMaxInt24In32, // [0,2]=>kMin, [1,3]=>kMax |
| kMaxInt24In32, 0, -kMaxInt24In32, 0, // [0,2]=>0, [1,3]=>0 |
| // clang-format on |
| }; |
| |
| // Will be overwritten |
| auto accum = std::vector<float>{-0x1234, 0x4321, -0x13579, 0xC0FF, -0xAAAA, 0x555}; |
| |
| // Express expected values as "int24" (not int32) to clearly show fractional and min/max values. |
| std::vector<float> expect; |
| if constexpr (media_audio::kEnable4ChannelWorkaround) { |
| // For now, 4->2 just ignores channels 2 & 3. |
| // TODO(fxbug.dev/85201): Remove this workaround, once the device properly maps channels. |
| expect = {1, -1, -0x800000, 0x7FFFFF, 0x7FFFFF, 0}; |
| } else { |
| expect = {0.5, -0.5, -0x800000, 0x7FFFFF, 0, 0}; |
| } |
| |
| ShiftRightBy(expect, 23); // right-shift these "int24" values into float range |
| |
| auto mixer = |
| SelectPointSampler(4, 2, 22050, 22050, fuchsia::media::AudioSampleFormat::SIGNED_24_IN_32); |
| DoMix(mixer.get(), source.data(), accum.data(), false, |
| accum.size() / 2); // dest frames have 2 samples each |
| EXPECT_THAT(accum, Pointwise(FloatEq(), expect)); |
| } |
| |
| // Are all valid data values passed correctly to 16-bit outputs for the 1->4 channel mapping? |
| TEST_F(PointSamplerRechannelTest, MonoToQuad) { |
| auto source = std::vector<int16_t>{-0x8000, -0x3FFF, -1, 0, 1, 0x7FFF}; |
| |
| auto accum = std::vector<float>(source.size() * 4); |
| auto expect = std::vector<float>(source.size() * 4); |
| for (auto idx = 0u; idx < source.size(); ++idx) { |
| expect[idx * 4] = source[idx]; |
| expect[idx * 4 + 1] = source[idx]; |
| expect[idx * 4 + 2] = source[idx]; |
| expect[idx * 4 + 3] = source[idx]; |
| } |
| ShiftRightBy(expect, 15); // right-shift these int16 values into float range |
| |
| auto mixer = SelectPointSampler(1, 4, 48000, 48000, fuchsia::media::AudioSampleFormat::SIGNED_16); |
| DoMix(mixer.get(), source.data(), accum.data(), false, accum.size() / 4); |
| EXPECT_THAT(accum, Pointwise(FloatEq(), expect)); |
| } |
| |
| // Are all valid data values passed correctly to 16-bit outputs for the 2->4 channel mapping? |
| // Here, we split a stereo source frame to quad output as [L, R, L, R]. |
| TEST_F(PointSamplerRechannelTest, StereoToQuad) { |
| // Input data in the [L, R] channelization -- arbitrary values in the 24-in-32 format |
| auto source = std::vector<int32_t>{ |
| // clang-format off |
| kMinInt24In32, -0x3FFFFF00, |
| -0x00000100, 0, |
| 0x00000100, kMaxInt24In32, |
| // clang-format on |
| }; |
| |
| auto accum = std::vector<float>(source.size() * 2); |
| auto expect = std::vector<float>(source.size() * 2); |
| for (auto idx = 0u; idx < source.size(); idx += 2) { |
| expect[idx * 2] = static_cast<float>(source[idx]); // First sample should be L |
| expect[idx * 2 + 1] = static_cast<float>(source[idx + 1]); // Second sample should be R |
| expect[idx * 2 + 2] = static_cast<float>(source[idx]); // Third sample should be L |
| expect[idx * 2 + 3] = static_cast<float>(source[idx + 1]); // Fourth sample should be R |
| } |
| ShiftRightBy(expect, 31); // right-shift these int32 values into float range |
| |
| auto mixer = |
| SelectPointSampler(2, 4, 48000, 48000, fuchsia::media::AudioSampleFormat::SIGNED_24_IN_32); |
| DoMix(mixer.get(), source.data(), accum.data(), false, accum.size() / 4); |
| EXPECT_THAT(accum, Pointwise(FloatEq(), expect)); |
| } |
| |
| // Accumulate tests |
| // |
| // Can values in our multi-stream accumulator temporarily exceed the max or min values for an |
| // individual stream? What is our accumulator's limit; does it clamp or rollover? |
| // |
| class PointSamplerAccumulateTest : public PointSamplerTest {}; |
| |
| // Do we obey the 'accumulate' flag if mixing into existing accumulated data? |
| // The PassThru tests depend on accum FALSE working correctly: just validate TRUE here. |
| TEST_F(PointSamplerAccumulateTest, Basic) { |
| auto source = std::vector<int16_t>{-0x1111, 0x3333, -0x6666, 0x4444}; |
| |
| auto accum = std::vector<float>{0x5432, 0x1234, -0x0123, -0x3210}; |
| auto expect = std::vector<float>{0x4321, 0x4567, -0x6789, 0x1234}; |
| auto expect2 = std::vector<float>{0x3210, 0x789A, -0xCDEF, 0x5678}; |
| ShiftRightBy(accum, 15); |
| ShiftRightBy(expect, 15); // right-shift these int16 values into float range |
| ShiftRightBy(expect2, 15); |
| |
| auto mixer = SelectPointSampler(2, 2, 48000, 48000, fuchsia::media::AudioSampleFormat::SIGNED_16); |
| DoMix(mixer.get(), source.data(), accum.data(), true, accum.size() / 2); |
| EXPECT_THAT(accum, Pointwise(FloatEq(), expect)); |
| |
| DoMix(mixer.get(), source.data(), accum.data(), true, accum.size() / 2); |
| EXPECT_THAT(accum, Pointwise(FloatEq(), expect2)); |
| } |
| |
| // Can accumulator result exceed the max range of individual streams? |
| TEST_F(PointSamplerAccumulateTest, BeyondSourceLimit) { |
| // When mixed 2x and 3x, these full-scale values far exceed any int16 range |
| auto max_source = std::array<int16_t, 2>{0x7FFF, -0x8000}; |
| |
| std::vector<float> accum(2); |
| std::copy(max_source.begin(), max_source.end(), accum.begin()); |
| ShiftRightBy(accum, 15); |
| |
| std::vector<float> expect_double(2); |
| std::vector<float> expect_triple(2); |
| std::copy(accum.begin(), accum.end(), expect_double.begin()); |
| std::copy(accum.begin(), accum.end(), expect_triple.begin()); |
| for (auto idx = 0u; idx < accum.size(); ++idx) { |
| expect_double[idx] *= 2.0f; |
| expect_triple[idx] *= 3.0f; |
| } |
| |
| // These values exceed the per-stream range of int16 |
| auto mixer = SelectPointSampler(1, 1, 48000, 48000, fuchsia::media::AudioSampleFormat::SIGNED_16); |
| DoMix(mixer.get(), max_source.data(), accum.data(), true, accum.size()); |
| EXPECT_THAT(accum, Pointwise(FloatEq(), expect_double)); |
| |
| // These values even exceed uint16 |
| DoMix(mixer.get(), max_source.data(), accum.data(), true, accum.size()); |
| EXPECT_THAT(accum, Pointwise(FloatEq(), expect_triple)); |
| } |
| |
| // As an optimization, mixers skip mixing altogether if the gain is below a certain mute-equivalent |
| // threshold. They do this even when "accumulate" is false (technically they should write silence). |
| // Validate the SampleAndHold interpolator for this behavior. |
| TEST_F(PointSamplerAccumulateTest, NoOpWhenMuted) { |
| auto source = std::array<int16_t, 4>{-32768, 32767, -16384, 16383}; |
| |
| auto accum = std::vector<float>(source.size()); |
| std::copy(source.begin(), source.end(), accum.begin()); |
| ShiftRightBy(accum, 15); |
| |
| auto expect = std::vector<float>(accum.size()); |
| std::copy(accum.begin(), accum.end(), expect.begin()); |
| |
| auto mixer = SelectPointSampler(1, 1, 48000, 48000, fuchsia::media::AudioSampleFormat::SIGNED_16); |
| // Use a gain guaranteed to silence any signal -- media_audio::kMinGainDb. |
| DoMix(mixer.get(), source.data(), accum.data(), true, accum.size(), media_audio::kMinGainDb); |
| EXPECT_THAT(accum, Pointwise(FloatEq(), expect)); |
| |
| // When accumulate = false but gain is sufficiently low, overwriting previous contents is skipped. |
| // This should lead to the same results as above. |
| DoMix(mixer.get(), source.data(), accum.data(), false, accum.size(), media_audio::kMinGainDb); |
| EXPECT_THAT(accum, Pointwise(FloatEq(), expect)); |
| } |
| |
| // Data scaling tests |
| // |
| // These scaling tests involve gain or accumulation, in the context of mixing (as opposed to gain |
| // unittests that directly probe the Gain object in isolation). |
| // |
| class PointSamplerScalingTest : public PointSamplerTest { |
| protected: |
| float DbFromScale(float scale) { return 20.0f * log10(scale); } |
| }; |
| |
| // Validate data-scaling accuracy in PointSampler mixing, for scaling of exactly 10.0x and 0.25x. |
| TEST_F(PointSamplerScalingTest, Linearity) { |
| auto source = std::vector<int16_t>{0x0CE4, 0x0CCC, 0x23, 4, -0x0E, -0x19, -0x0CCC, -0x0CDB}; |
| std::array<float, 8> accum; |
| |
| // Validate that +20.0 dB scales values by 10x. We calculate our own gain value rather than use |
| // media_audio::ScaleToDb, as Mixer+Gain interactions (via APIs like that) are what we're testing. |
| float desired_scale_factor = 10.0f; |
| float stream_gain_db = DbFromScale(desired_scale_factor); // 20.0f; |
| auto mixer = SelectPointSampler(1, 1, 44100, 44100, fuchsia::media::AudioSampleFormat::SIGNED_16); |
| DoMix(mixer.get(), source.data(), accum.data(), false, accum.size(), stream_gain_db); |
| |
| auto expect = std::vector<float>(8); |
| for (auto idx = 0u; idx < expect.size(); ++idx) { |
| expect[idx] = desired_scale_factor * static_cast<float>(source[idx]); |
| } |
| ShiftRightBy(expect, 15); |
| EXPECT_THAT(accum, Pointwise(FloatEq(), expect)); |
| |
| // How precisely linear is a gain stage? -12.0411998dB should cause 0.25x in value. Again, we |
| // directly calculate a db value, since Gain APIs are within the scope that is being tested. |
| desired_scale_factor = 0.25; |
| stream_gain_db = DbFromScale(desired_scale_factor); //-12.0411998f; |
| mixer = SelectPointSampler(1, 1, 44100, 44100, fuchsia::media::AudioSampleFormat::SIGNED_16); |
| |
| DoMix(mixer.get(), source.data(), accum.data(), false, accum.size(), stream_gain_db); |
| |
| for (auto idx = 0u; idx < expect.size(); ++idx) { |
| expect[idx] = desired_scale_factor * static_cast<float>(source[idx]); |
| } |
| ShiftRightBy(expect, 15); |
| EXPECT_THAT(accum, Pointwise(FloatEq(), expect)); |
| } |
| |
| // kMinGainDbUnity is the lowest gain_db with no observable attenuation of a full-scale signal |
| // (i.e. how far away from Unity can we be, and still be indistinguishable from Unity). |
| static constexpr float kMinGainDbUnity = -0.000000258856886667820f; |
| // This is the highest gain_db with an observable effect on a full-scale signal (i.e. the closest |
| // possible value to Unity that produces a different result). |
| static constexpr float kMaxGainDbNonUnity = -0.000000258865572365570f; |
| // Calculated as follows (validated on various devices/calculators/spreadsheets/etc.) |
| // Ratio (2^25-1)/2^25, multiplied by full-scale (1.0) float, produces hex equivalent 0x0.FFFFFF8 |
| // Float lacks precision for the final "8" so the result will be rounded. Above this ratio, we are |
| // indistinguishable from Unity. At less than this ratio -- at least for full-scale signals -- we |
| // differ from Unity. MinGainUnity and MaxGainNonUnity are db values on EITHER side of this ratio. |
| |
| // kMinGainDbNonMute is the lowest (closest-to-zero) gain_db at which audio is not silenced (i.e. |
| // the smallest gain distinguishable from Mute). Although results may be less than our "hex integer, |
| // right-shifted" pattern can represent, results are still non-zero and thus verify our scale limit. |
| static constexpr float kMinGainDbNonMute = -159.999992f; |
| // kMaxGainDbMute is the highest (furthest-from-Mute) gain that silences full scale data (i.e. the |
| // largest value INdistinguishable from Mute). Consider a gain_db ever-so-slightly above -160dB: |
| // if the increment is small enough, float32 treats it as -160dB, our "automatically mute" limit. |
| static constexpr float kMaxGainDbMute = -159.999993f; |
| // What db value is "half a float32 bit" less than 160.0? This "rounding boundary" marks where |
| // values become indistinguishable from 160.0 db itself. |
| // 160 in float is [mantissa: 1.25, binary exponent: 7]. Mantissa 1.25 is 0x1.400000 where the last |
| // hex digit has 3 significant bits. So "half a float32 bit" here is that final digit's least |
| // significant bit. Thus for float32, the dividing line between what IS and IS NOT distinguishable |
| // from -160.0f has a mantissa in hex of -0x1.3FFFFF. |
| // Reduced to formula, kMinGainDbNonMute|kMaxGainDbMute should be just greater|less than this value: |
| // |
| // -1 * (2^24 + (2^22 - 1)) / 2^24 * 2^7 |
| // sign \------- mantissa -------/ exponent |
| |
| // How does our gain scaling respond to scale values close to the limits? |
| // Using 16-bit inputs, verify the behavior of our Gain object when given the |
| // closest-to-Unity and closest-to-Mute scale values. |
| TEST_F(PointSamplerScalingTest, Precision) { |
| auto max_source = std::array<int16_t, 2>{0x7FFF, -0x8000}; // max/min 16-bit signed values. |
| auto accum = std::vector<float>(2); |
| |
| auto mixer = SelectPointSampler(1, 1, 48000, 48000, fuchsia::media::AudioSampleFormat::SIGNED_16); |
| DoMix(mixer.get(), max_source.data(), accum.data(), false, accum.size(), kMinGainDbUnity); |
| |
| // At this gain_scale, resulting audio should be unchanged. |
| auto max_expect1 = std::vector<float>{0x7FFF, -0x8000}; |
| ShiftRightBy(max_expect1, 15); |
| EXPECT_THAT(accum, Pointwise(FloatEq(), max_expect1)); |
| |
| // mixer = SelectPointSampler(1, 1, 48000, 48000, fuchsia::media::AudioSampleFormat::SIGNED_16); |
| DoMix(mixer.get(), max_source.data(), accum.data(), false, accum.size(), kMaxGainDbNonUnity); |
| |
| // Float32 has 25-bit precision (not 28), hence our min delta is 0x8 (not 1). |
| auto max_expect2 = std::vector<float>{0x07FFEFF8, -0x07FFFFF8}; |
| ShiftRightBy(max_expect2, 27); |
| EXPECT_THAT(accum, Pointwise(FloatEq(), max_expect2)); |
| |
| auto min_source = std::array<int16_t, 2>{1, -1}; |
| // mixer = SelectPointSampler(1, 1, 48000, 48000, fuchsia::media::AudioSampleFormat::SIGNED_16); |
| DoMix(mixer.get(), min_source.data(), accum.data(), false, accum.size(), kMinGainDbNonMute); |
| |
| // How we specify expectations for other tests (specify as integral float, shift-right) cannot |
| // precisely express these values. Nonetheless, they are present and non-zero! |
| auto min_expect = std::array<float, 2>{3.051763215e-13f, -3.051763215e-13f}; |
| EXPECT_THAT(accum, Pointwise(FloatEq(), min_expect)); |
| |
| // Per mixer optimization, we skip mixing if gain is Mute-equivalent. This |
| // is equivalent to setting 'accumulate' and adding zeroes, so set that flag here and expect no |
| // change in the accumulator, even with max inputs. |
| // mixer = SelectPointSampler(1, 1, 48000, 48000, fuchsia::media::AudioSampleFormat::SIGNED_16); |
| DoMix(mixer.get(), max_source.data(), accum.data(), true, accum.size(), kMaxGainDbMute); |
| |
| EXPECT_THAT(accum, Pointwise(FloatEq(), min_expect)); |
| } |
| |
| // |
| // Timing (Resampling) tests |
| // |
| // Sync/timing correctness, to the sample level |
| // Verify correct FROM and TO locations, and quantity. |
| // |
| // 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 source_frames, and source_offset (into those frames). |
| // Likewise "demand" is determined by dest_frames and dest_offset into |
| // dest_frames. |
| |
| // Verify that the samplers mix to/from correct buffer locations. Also ensure |
| // that they don't touch other buffer sections, regardless of 'accumulate'. |
| // This first test uses integer lengths/offsets, and a step_size of ONE. |
| class PointSamplerPositionTest : public PointSamplerTest {}; |
| |
| // Check: source supply equals destination demand. |
| TEST_F(PointSamplerPositionTest, BasicEqualSourceDest) { |
| auto mixer = SelectPointSampler(1, 1, 48000, 48000, fuchsia::media::AudioSampleFormat::SIGNED_16); |
| ASSERT_NE(mixer, nullptr); |
| |
| std::array<int16_t, 5> source{-0x00AA, 0x00BB, -0x00CC, 0x00DD, -0x00EE}; |
| int64_t source_frames = source.size(); |
| auto source_offset = Fixed(2); |
| |
| int64_t dest_frames = 4; |
| int64_t dest_offset = 1; |
| |
| // Source (offset 2 of 5) has 3. Destination (offset 1 of 4) wants 3. |
| // Mix will sum source[2,3,4] to accum[1,2,3] |
| std::vector<float> accum{0x1100, -0x2200, 0x3300, -0x4400, 0x5500}; |
| std::vector<float> expect{0x1100, -0x22CC, 0x33DD, -0x44EE, 0x5500}; |
| ShiftRightBy(accum, 15); |
| ShiftRightBy(expect, 15); |
| |
| mixer->Mix(accum.data(), dest_frames, &dest_offset, source.data(), source_frames, &source_offset, |
| true); |
| |
| EXPECT_EQ(dest_offset, dest_frames); |
| EXPECT_EQ(source_offset, Fixed(source_frames)) << std::hex << source_offset.raw_value(); |
| EXPECT_THAT(accum, Pointwise(FloatEq(), expect)); |
| } |
| |
| // Check: source supply exceeds destination demand. |
| TEST_F(PointSamplerPositionTest, BasicSourceExceedsDemand) { |
| auto mixer = SelectPointSampler(1, 1, 48000, 48000, fuchsia::media::AudioSampleFormat::SIGNED_16); |
| ASSERT_NE(mixer, nullptr); |
| |
| std::array<int16_t, 5> source{-0x00AA, 0x00BB, -0x00CC, 0x00DD, -0x00EE}; |
| int64_t source_frames = source.size(); |
| auto source_offset = Fixed(0); |
| |
| int64_t dest_frames = 3; |
| int64_t dest_offset = 1; |
| |
| // Source (offset 0 of 5) has 5. Destination (offset 1 of 3) wants 2. |
| // Mix will sum source[0,1] to accum[1,2] |
| std::vector<float> accum{0x1100, -0x2200, 0x3300, -0x4400, 0x5500}; |
| std::vector<float> expect{0x1100, -0x22AA, 0x33BB, -0x4400, 0x5500}; |
| ShiftRightBy(accum, 15); |
| ShiftRightBy(expect, 15); |
| |
| mixer->Mix(accum.data(), dest_frames, &dest_offset, source.data(), source_frames, &source_offset, |
| true); |
| |
| EXPECT_EQ(dest_offset, dest_frames); |
| EXPECT_EQ(source_offset, Fixed(2)) << std::hex << source_offset.raw_value(); |
| EXPECT_THAT(accum, Pointwise(FloatEq(), expect)); |
| } |
| |
| // Check: destination demand exceeds source supply. |
| TEST_F(PointSamplerPositionTest, BasicDestExceedsSource) { |
| auto mixer = SelectPointSampler(1, 1, 48000, 48000, fuchsia::media::AudioSampleFormat::SIGNED_16); |
| ASSERT_NE(mixer, nullptr); |
| |
| std::array<int16_t, 5> source{-0x00AA, 0x00BB, -0x00CC, 0x00DD, -0x00EE}; |
| int64_t source_frames = 4; |
| auto source_offset = Fixed(3); |
| |
| int64_t dest_frames = 5; |
| int64_t dest_offset = 0; |
| |
| // Source (offset 3 of 4) has 1. Destination (offset 0 of 5) wants 5. |
| // Mix will sum source[3] to accum[0] |
| std::vector<float> accum{0x1100, -0x2200, 0x3300, -0x4400, 0x5500}; |
| std::vector<float> expect{0x11DD, -0x2200, 0x3300, -0x4400, 0x5500}; |
| ShiftRightBy(accum, 15); |
| ShiftRightBy(expect, 15); |
| |
| mixer->Mix(accum.data(), dest_frames, &dest_offset, source.data(), source_frames, &source_offset, |
| true); |
| |
| EXPECT_EQ(dest_offset, 1); |
| EXPECT_EQ(source_offset, Fixed(source_frames)) << std::hex << source_offset.raw_value(); |
| EXPECT_THAT(accum, Pointwise(FloatEq(), expect)); |
| } |
| |
| // Validate basic (frame-level) position for SampleAndHold resampler. |
| |
| // 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). |
| // These tests use fractional lengths/offsets, still with a step_size of ONE. |
| // |
| // Check: after factoring-in positive filter width, source position is exactly at a frame boundary. |
| TEST_F(PointSamplerPositionTest, FractionalPositionAtFrameBoundary) { |
| auto mixer = SelectPointSampler(1, 1, 44100, 44100, fuchsia::media::AudioSampleFormat::SIGNED_16); |
| |
| ASSERT_NE(mixer, nullptr); |
| |
| // To accommodate "sample-and-hold" or "nearest-neighbor" implementations without changing this |
| // test, we expressly factor-in positive width. Our starting position is in the range (1.0, 2.0], |
| // where this guarantees that Source has 3. Destination (offset 1 of 3) wants 2. |
| Fixed source_offset = Fixed(2) - mixer->pos_filter_width(); |
| Fixed expect_source_offset = source_offset + Fixed(2); |
| std::array<int16_t, 5> source{-0x00AA, 0x00BB, -0x00CC, 0x00DD, -0x00EE}; |
| int64_t source_frames = source.size(); |
| |
| int64_t dest_frames = 3; |
| int64_t dest_offset = 1; |
| // We set position so that for fractional source[1:2, 2:3], PointSampler will choose source[2,3]. |
| // Thus Mix will sum source[2,3] into accum[1,2]. |
| std::vector<float> accum{0x1100, -0x2200, 0x3300, -0x4400, 0x5500}; |
| std::vector<float> expect{0x1100, -0x22CC, 0x33DD, -0x4400, 0x5500}; |
| ShiftRightBy(accum, 15); |
| ShiftRightBy(expect, 15); |
| |
| mixer->Mix(accum.data(), dest_frames, &dest_offset, source.data(), source_frames, &source_offset, |
| true); |
| |
| EXPECT_EQ(dest_offset, dest_frames); |
| EXPECT_EQ(source_offset, expect_source_offset) << std::hex << source_offset.raw_value(); |
| EXPECT_THAT(accum, Pointwise(FloatEq(), expect)); |
| } |
| |
| // Check: factoring-in positive filter width, source position is just short of a frame boundary. |
| TEST_F(PointSamplerPositionTest, FractionalPositionJustBeforeFrameBoundary) { |
| auto mixer = SelectPointSampler(1, 1, 44100, 44100, fuchsia::media::AudioSampleFormat::SIGNED_16); |
| |
| ASSERT_NE(mixer, nullptr); |
| |
| // To accommodate "sample-and-hold" or "nearest-neighbor" implementations without changing this |
| // test, we expressly factor-in positive width. Our starting position is in the range [1.0, 2.0), |
| // where this guarantees that Source has 4. Destination (offset 2 of 4) wants 2. |
| Fixed source_offset = Fixed(2) - mixer->pos_filter_width() - Fixed::FromRaw(1); |
| Fixed expect_source_offset = source_offset + Fixed(2); |
| std::array<int16_t, 5> source{-0x00AA, 0x00BB, -0x00CC, 0x00DD, -0x00EE}; |
| int64_t source_frames = source.size(); |
| |
| int64_t dest_frames = 4; |
| int64_t dest_offset = 2; |
| // We set position so that for fractional source[1:2, 2:3], PointSampler will choose source[1,2]. |
| // Thus Mix will sum source[1,2] into accum[2,3]. |
| std::vector<float> accum{0x1100, -0x2200, 0x3300, -0x4400, 0x5500}; |
| std::vector<float> expect{0x1100, -0x2200, 0x33BB, -0x44CC, 0x5500}; |
| ShiftRightBy(accum, 15); |
| ShiftRightBy(expect, 15); |
| |
| mixer->Mix(accum.data(), dest_frames, &dest_offset, source.data(), source_frames, &source_offset, |
| true); |
| |
| EXPECT_EQ(dest_offset, dest_frames); |
| EXPECT_EQ(source_offset, expect_source_offset) << std::hex << source_offset.raw_value(); |
| EXPECT_THAT(accum, Pointwise(FloatEq(), expect)); |
| } |
| |
| // When setting the frac_source_pos to a value that is at the end (or within pos_filter_width) of |
| // the source buffer, the sampler should not mix additional frames (neither dest_offset nor |
| // source_offset should be advanced). |
| TEST_F(PointSamplerPositionTest, SourceOffsetAtEnd) { |
| auto mixer = SelectPointSampler(1, 1, 44100, 44100, fuchsia::media::AudioSampleFormat::FLOAT); |
| ASSERT_NE(mixer, nullptr); |
| |
| std::array<float, 4> source{1.0f, 1.0f, 1.0f, 1.0f}; |
| Fixed source_offset = Fixed(std::size(source)) - mixer->pos_filter_width(); |
| const auto initial_source_offset = source_offset; |
| |
| std::array<float, 4> accum{0.0f}; |
| int64_t dest_offset = 0; |
| |
| auto& bk = mixer->bookkeeping(); |
| bk.step_size = kOneFrame; |
| |
| mixer->Mix(accum.data(), accum.size(), &dest_offset, source.data(), source.size(), &source_offset, |
| false); |
| |
| EXPECT_EQ(dest_offset, 0); |
| EXPECT_EQ(source_offset, initial_source_offset); |
| EXPECT_EQ(accum[0], 0.0f); |
| } |
| |
| // Verify PointSampler filter width. Current implementation is "FORWARD nearest neighbor". |
| // In other words, when exactly midway between two source frames, we sample the NEWER one. |
| TEST_F(PointSamplerPositionTest, FilterWidth) { |
| int64_t expect_pos_width = kHalfFrame.raw_value(); |
| int64_t expect_neg_width = kHalfFrame.raw_value() - 1; |
| |
| auto mixer = |
| SelectPointSampler(1, 1, 48000, 48000, fuchsia::media::AudioSampleFormat::UNSIGNED_8); |
| |
| EXPECT_EQ(mixer->pos_filter_width().raw_value(), expect_pos_width); |
| EXPECT_EQ(mixer->neg_filter_width().raw_value(), expect_neg_width); |
| } |
| |
| } // namespace |
| } // namespace media::audio::mixer |