[CodeHealth] Spanify vector_math top level APIs
This CL converts top level vector_math APIs to use spans instead of array+length. The actual implementation of the algorithms is untouched, and not spanified. Spanifying the algorithms themselves had a large performance impact, and not attempted. There was a ~2 orders of magnitude slow down from using std::ranges::transform or SpanReaders. Updating these APIs still provides some benefits: the source and destinations are guaranteed to have the same length now. Bug: 373960632 Change-Id: I2fcd44c5aa748d35819c77c314ba6bbc7d106add Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6238738 Auto-Submit: Thomas Guilbert <tguilbert@chromium.org> Reviewed-by: Kenneth MacKay <kmackay@chromium.org> Reviewed-by: Dale Curtis <dalecurtis@chromium.org> Reviewed-by: Ahmed Fakhry <afakhry@chromium.org> Commit-Queue: Thomas Guilbert <tguilbert@chromium.org> Cr-Commit-Position: refs/heads/main@{#1419023}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
634339b600
commit
cbc9f60e71
chromecast/media/base
chromeos/ash/services/recording
media/base
audio_bus.ccaudio_converter.ccaudio_power_monitor.ccchannel_mixer.ccvector_math.ccvector_math.hvector_math_unittest.cc
services/audio
@ -9,6 +9,7 @@
|
|||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
#include "base/check_op.h"
|
#include "base/check_op.h"
|
||||||
|
#include "base/containers/span.h"
|
||||||
#include "media/base/vector_math.h"
|
#include "media/base/vector_math.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@ -24,7 +25,9 @@ struct FMACTraits {
|
|||||||
float volume,
|
float volume,
|
||||||
int frames,
|
int frames,
|
||||||
float* dest) {
|
float* dest) {
|
||||||
::media::vector_math::FMAC(src, volume, frames, dest);
|
const size_t size = static_cast<size_t>(frames);
|
||||||
|
::media::vector_math::FMAC(base::span(src, size), volume,
|
||||||
|
base::span(dest, size));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void ProcessSingleDatum(const float* src, float volume, float* dest) {
|
static void ProcessSingleDatum(const float* src, float volume, float* dest) {
|
||||||
@ -43,7 +46,9 @@ struct FMULTraits {
|
|||||||
float volume,
|
float volume,
|
||||||
int frames,
|
int frames,
|
||||||
float* dest) {
|
float* dest) {
|
||||||
::media::vector_math::FMUL(src, volume, frames, dest);
|
const size_t size = static_cast<size_t>(frames);
|
||||||
|
::media::vector_math::FMUL(base::span(src, size), volume,
|
||||||
|
base::span(dest, size));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void ProcessSingleDatum(const float* src, float volume, float* dest) {
|
static void ProcessSingleDatum(const float* src, float volume, float* dest) {
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
#include "chromeos/ash/services/recording/audio_capture_util.h"
|
#include "chromeos/ash/services/recording/audio_capture_util.h"
|
||||||
|
|
||||||
#include "base/memory/aligned_memory.h"
|
#include "base/memory/aligned_memory.h"
|
||||||
|
#include "base/numerics/safe_conversions.h"
|
||||||
#include "chromeos/ash/services/recording/recording_service_constants.h"
|
#include "chromeos/ash/services/recording/recording_service_constants.h"
|
||||||
#include "media/base/audio_bus.h"
|
#include "media/base/audio_bus.h"
|
||||||
#include "media/base/audio_parameters.h"
|
#include "media/base/audio_parameters.h"
|
||||||
@ -26,16 +27,17 @@ static_assert(kAudioSampleRate % 100 == 0,
|
|||||||
// Using `media::vector_math::FMAC()` works only if the addresses of `src` and
|
// Using `media::vector_math::FMAC()` works only if the addresses of `src` and
|
||||||
// `dest` are `kRequiredAlignment` bit aligned.
|
// `dest` are `kRequiredAlignment` bit aligned.
|
||||||
// This returns true if that's the case.
|
// This returns true if that's the case.
|
||||||
bool CanUseVectorMath(const float* src, const float* dest) {
|
bool CanUseVectorMath(base::span<const float> src,
|
||||||
return base::IsAligned(src, media::vector_math::kRequiredAlignment) &&
|
base::span<const float> dest) {
|
||||||
base::IsAligned(dest, media::vector_math::kRequiredAlignment);
|
return base::IsAligned(src.data(), media::vector_math::kRequiredAlignment) &&
|
||||||
|
base::IsAligned(dest.data(), media::vector_math::kRequiredAlignment);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If `media::vector_math::FMAC()` cannot be used due to lack of required
|
// If `media::vector_math::FMAC()` cannot be used due to lack of required
|
||||||
// alignment, this version can be used to accumulate the `length` number of
|
// alignment, this version can be used to accumulate the `length` number of
|
||||||
// items from `src` on top of the values existing in `dest`.
|
// items from `src` on top of the values existing in `dest`.
|
||||||
void Accumulate(const float* src, float* dest, int length) {
|
void Accumulate(base::span<const float> src, base::span<float> dest) {
|
||||||
for (int i = 0; i < length; ++i) {
|
for (size_t i = 0; i < src.size(); ++i) {
|
||||||
dest[i] += src[i];
|
dest[i] += src[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -68,20 +70,23 @@ std::unique_ptr<media::AudioBus> CreateStereoZeroInitializedAudioBusForDuration(
|
|||||||
|
|
||||||
void AccumulateBusTo(const media::AudioBus& source,
|
void AccumulateBusTo(const media::AudioBus& source,
|
||||||
media::AudioBus* destination,
|
media::AudioBus* destination,
|
||||||
int source_start_frame,
|
|
||||||
int destination_start_frame,
|
int destination_start_frame,
|
||||||
int length) {
|
int length) {
|
||||||
CHECK_EQ(source.channels(), source.channels());
|
CHECK_EQ(source.channels(), source.channels());
|
||||||
CHECK_LE(source_start_frame + length, source.frames());
|
CHECK_LE(length, source.frames());
|
||||||
CHECK_LE(destination_start_frame + length, destination->frames());
|
CHECK_LE(destination_start_frame + length, destination->frames());
|
||||||
|
|
||||||
|
const size_t dest_offset =
|
||||||
|
base::checked_cast<size_t>(destination_start_frame);
|
||||||
|
const size_t count = base::checked_cast<size_t>(length);
|
||||||
|
|
||||||
for (int i = 0; i < source.channels(); ++i) {
|
for (int i = 0; i < source.channels(); ++i) {
|
||||||
const float* src = &source.channel(i)[source_start_frame];
|
auto src = source.channel_span(i).first(count);
|
||||||
float* dest = &destination->channel(i)[destination_start_frame];
|
auto dest = destination->channel_span(i).subspan(dest_offset, count);
|
||||||
if (CanUseVectorMath(src, dest)) {
|
if (CanUseVectorMath(src, dest)) {
|
||||||
media::vector_math::FMAC(src, /*scale=*/1, length, dest);
|
media::vector_math::FMAC(src, /*scale=*/1, dest);
|
||||||
} else {
|
} else {
|
||||||
Accumulate(src, dest, length);
|
Accumulate(src, dest);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -47,16 +47,14 @@ std::unique_ptr<media::AudioBus> CreateStereoZeroInitializedAudioBusForFrames(
|
|||||||
std::unique_ptr<media::AudioBus> CreateStereoZeroInitializedAudioBusForDuration(
|
std::unique_ptr<media::AudioBus> CreateStereoZeroInitializedAudioBusForDuration(
|
||||||
base::TimeDelta duration);
|
base::TimeDelta duration);
|
||||||
|
|
||||||
// Accumulates the `length` number of audio frames in the `source` audio bus
|
// Accumulates the first `length` number of audio frames in the `source` audio
|
||||||
// starting at `source_start_frame`, to the already existing frames in the
|
// bus to the already existing frames in the `destination` bus starting at
|
||||||
// `destination` bus starting at `destination_start_frame`.
|
// `destination_start_frame`. Both `source` and `destination` buses must have
|
||||||
// Both `source` and `destination` buses must have the same number of channels.
|
// the same number of channels. `length` must not exceed the number of frames in
|
||||||
// `source_start_frame` + `length` must be within the bounds of the `source`
|
// `source`, and `destination_start_frame` + `length` also must be within the
|
||||||
// bus, and `destination_start_frame` + `length` also must be within the bounds
|
// bounds of the `destination` bus.
|
||||||
// of the `destination` bus.
|
|
||||||
void AccumulateBusTo(const media::AudioBus& source,
|
void AccumulateBusTo(const media::AudioBus& source,
|
||||||
media::AudioBus* destination,
|
media::AudioBus* destination,
|
||||||
int source_start_frame,
|
|
||||||
int destination_start_frame,
|
int destination_start_frame,
|
||||||
int length);
|
int length);
|
||||||
|
|
||||||
|
@ -77,7 +77,6 @@ void AudioStream::ConsumeAndAccumulateTo(media::AudioBus* destination,
|
|||||||
const int consumed = std::min(front->frames(), remaining_frames_to_consume);
|
const int consumed = std::min(front->frames(), remaining_frames_to_consume);
|
||||||
audio_capture_util::AccumulateBusTo(
|
audio_capture_util::AccumulateBusTo(
|
||||||
/*source=*/*front, /*destination=*/destination,
|
/*source=*/*front, /*destination=*/destination,
|
||||||
/*source_start_frame=*/0,
|
|
||||||
/*destination_start_frame=*/destination_start_frame,
|
/*destination_start_frame=*/destination_start_frame,
|
||||||
/*length=*/consumed);
|
/*length=*/consumed);
|
||||||
remaining_frames_to_consume -= consumed;
|
remaining_frames_to_consume -= consumed;
|
||||||
|
@ -236,7 +236,6 @@ TEST_F(AudioStreamMixerTest, StreamWithLaterTimestampsArrivesFirst) {
|
|||||||
audio_capture_util::AccumulateBusTo(
|
audio_capture_util::AccumulateBusTo(
|
||||||
/*source=*/*stream2_bus2,
|
/*source=*/*stream2_bus2,
|
||||||
/*destination=*/expected_bus.get(),
|
/*destination=*/expected_bus.get(),
|
||||||
/*source_start_frame=*/0,
|
|
||||||
/*destination_start_frame=*/
|
/*destination_start_frame=*/
|
||||||
audio_capture_util::NumberOfAudioFramesInDuration(
|
audio_capture_util::NumberOfAudioFramesInDuration(
|
||||||
base::Milliseconds(45 - 40)),
|
base::Milliseconds(45 - 40)),
|
||||||
@ -331,7 +330,6 @@ TEST_F(AudioStreamMixerTest, FlushingTheMixer) {
|
|||||||
audio_capture_util::AccumulateBusTo(
|
audio_capture_util::AccumulateBusTo(
|
||||||
/*source=*/*stream2_bus,
|
/*source=*/*stream2_bus,
|
||||||
/*destination=*/expected_bus.get(),
|
/*destination=*/expected_bus.get(),
|
||||||
/*source_start_frame=*/0,
|
|
||||||
/*destination_start_frame=*/
|
/*destination_start_frame=*/
|
||||||
audio_capture_util::NumberOfAudioFramesInDuration(
|
audio_capture_util::NumberOfAudioFramesInDuration(
|
||||||
base::Milliseconds(40 - 10)),
|
base::Milliseconds(40 - 10)),
|
||||||
|
@ -381,7 +381,7 @@ void AudioBus::Scale(float volume) {
|
|||||||
DCHECK(!is_bitstream_format_);
|
DCHECK(!is_bitstream_format_);
|
||||||
if (volume > 0 && volume != 1) {
|
if (volume > 0 && volume != 1) {
|
||||||
for (auto channel : channel_data_) {
|
for (auto channel : channel_data_) {
|
||||||
vector_math::FMUL(channel.data(), volume, frames(), channel.data());
|
vector_math::FMUL(channel, volume, channel);
|
||||||
}
|
}
|
||||||
} else if (volume == 0) {
|
} else if (volume == 0) {
|
||||||
Zero();
|
Zero();
|
||||||
|
@ -230,9 +230,8 @@ void AudioConverter::SourceCallback(int fifo_frame_delay, AudioBus* dest) {
|
|||||||
provide_input_dest->CopyTo(temp_dest);
|
provide_input_dest->CopyTo(temp_dest);
|
||||||
} else if (volume > 0) {
|
} else if (volume > 0) {
|
||||||
for (int i = 0; i < provide_input_dest->channels(); ++i) {
|
for (int i = 0; i < provide_input_dest->channels(); ++i) {
|
||||||
vector_math::FMUL(provide_input_dest->channel_span(i).data(), volume,
|
vector_math::FMUL(provide_input_dest->channel_span(i), volume,
|
||||||
provide_input_dest->frames(),
|
temp_dest->channel_span(i));
|
||||||
temp_dest->channel_span(i).data());
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Zero |temp_dest| otherwise, so we're mixing into a clean buffer.
|
// Zero |temp_dest| otherwise, so we're mixing into a clean buffer.
|
||||||
@ -245,9 +244,8 @@ void AudioConverter::SourceCallback(int fifo_frame_delay, AudioBus* dest) {
|
|||||||
// Volume adjust and mix each mixer input into |temp_dest| after rendering.
|
// Volume adjust and mix each mixer input into |temp_dest| after rendering.
|
||||||
if (volume > 0) {
|
if (volume > 0) {
|
||||||
for (int i = 0; i < mixer_input_audio_bus_->channels(); ++i) {
|
for (int i = 0; i < mixer_input_audio_bus_->channels(); ++i) {
|
||||||
vector_math::FMAC(mixer_input_audio_bus_->channel_span(i).data(),
|
vector_math::FMAC(mixer_input_audio_bus_->channel_span(i), volume,
|
||||||
volume, mixer_input_audio_bus_->frames(),
|
temp_dest->channel_span(i));
|
||||||
temp_dest->channel_span(i).data());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -50,7 +50,8 @@ void AudioPowerMonitor::Scan(const AudioBus& buffer, int num_frames) {
|
|||||||
float sum_power = 0.0f;
|
float sum_power = 0.0f;
|
||||||
for (auto channel : buffer.AllChannels()) {
|
for (auto channel : buffer.AllChannels()) {
|
||||||
const std::pair<float, float> ewma_and_max = vector_math::EWMAAndMaxPower(
|
const std::pair<float, float> ewma_and_max = vector_math::EWMAAndMaxPower(
|
||||||
average_power_, channel.data(), num_frames, sample_weight_);
|
average_power_, channel.first(static_cast<size_t>(num_frames)),
|
||||||
|
sample_weight_);
|
||||||
// If data in audio buffer is garbage, ignore its effect on the result.
|
// If data in audio buffer is garbage, ignore its effect on the result.
|
||||||
if (!std::isfinite(ewma_and_max.first)) {
|
if (!std::isfinite(ewma_and_max.first)) {
|
||||||
sum_power += average_power_;
|
sum_power += average_power_;
|
||||||
|
@ -90,9 +90,10 @@ void ChannelMixer::TransformPartial(const AudioBus* input,
|
|||||||
float scale = matrix_[output_ch][input_ch];
|
float scale = matrix_[output_ch][input_ch];
|
||||||
// Scale should always be positive. Don't bother scaling by zero.
|
// Scale should always be positive. Don't bother scaling by zero.
|
||||||
DCHECK_GE(scale, 0);
|
DCHECK_GE(scale, 0);
|
||||||
|
const size_t frames = static_cast<size_t>(frame_count);
|
||||||
if (scale > 0) {
|
if (scale > 0) {
|
||||||
vector_math::FMAC(input->channel_span(input_ch).data(), scale,
|
vector_math::FMAC(input->channel_span(input_ch).first(frames), scale,
|
||||||
frame_count, output_channel.data());
|
output_channel.first(frames));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,6 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "media/base/vector_math.h"
|
#include "media/base/vector_math.h"
|
||||||
#include "media/base/vector_math_testing.h"
|
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
@ -17,6 +16,7 @@
|
|||||||
#include "base/cpu.h"
|
#include "base/cpu.h"
|
||||||
#include "base/memory/aligned_memory.h"
|
#include "base/memory/aligned_memory.h"
|
||||||
#include "build/build_config.h"
|
#include "build/build_config.h"
|
||||||
|
#include "media/base/vector_math_testing.h"
|
||||||
|
|
||||||
// NaCl does not allow intrinsics.
|
// NaCl does not allow intrinsics.
|
||||||
#if defined(ARCH_CPU_X86_FAMILY) && !BUILDFLAG(IS_NACL)
|
#if defined(ARCH_CPU_X86_FAMILY) && !BUILDFLAG(IS_NACL)
|
||||||
@ -33,12 +33,15 @@
|
|||||||
#include <arm_neon.h>
|
#include <arm_neon.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace media {
|
namespace media::vector_math {
|
||||||
namespace vector_math {
|
|
||||||
|
|
||||||
void FMAC(const float src[], float scale, int len, float dest[]) {
|
void FMAC(base::span<const float> src, float scale, base::span<float> dest) {
|
||||||
DCHECK(base::IsAligned(src, kRequiredAlignment));
|
if (src.empty()) {
|
||||||
DCHECK(base::IsAligned(dest, kRequiredAlignment));
|
return;
|
||||||
|
}
|
||||||
|
CHECK_LE(src.size(), dest.size());
|
||||||
|
DCHECK(base::IsAligned(src.data(), kRequiredAlignment));
|
||||||
|
DCHECK(base::IsAligned(dest.data(), kRequiredAlignment));
|
||||||
static const auto fmac_func = [] {
|
static const auto fmac_func = [] {
|
||||||
#if defined(ARCH_CPU_X86_FAMILY) && !BUILDFLAG(IS_NACL)
|
#if defined(ARCH_CPU_X86_FAMILY) && !BUILDFLAG(IS_NACL)
|
||||||
base::CPU cpu;
|
base::CPU cpu;
|
||||||
@ -52,17 +55,22 @@ void FMAC(const float src[], float scale, int len, float dest[]) {
|
|||||||
#endif
|
#endif
|
||||||
}();
|
}();
|
||||||
|
|
||||||
return fmac_func(src, scale, len, dest);
|
return fmac_func(src.data(), scale, src.size(), dest.data());
|
||||||
}
|
}
|
||||||
|
|
||||||
void FMAC_C(const float src[], float scale, int len, float dest[]) {
|
void FMAC_C(const float src[], float scale, int len, float dest[]) {
|
||||||
for (int i = 0; i < len; ++i)
|
for (int i = 0; i < len; ++i) {
|
||||||
dest[i] += src[i] * scale;
|
dest[i] += src[i] * scale;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void FMUL(const float src[], float scale, int len, float dest[]) {
|
void FMUL(base::span<const float> src, float scale, base::span<float> dest) {
|
||||||
DCHECK(base::IsAligned(src, kRequiredAlignment));
|
if (src.empty()) {
|
||||||
DCHECK(base::IsAligned(dest, kRequiredAlignment));
|
return;
|
||||||
|
}
|
||||||
|
CHECK_LE(src.size(), dest.size());
|
||||||
|
DCHECK(base::IsAligned(src.data(), kRequiredAlignment));
|
||||||
|
DCHECK(base::IsAligned(dest.data(), kRequiredAlignment));
|
||||||
static const auto fmul_func = [] {
|
static const auto fmul_func = [] {
|
||||||
#if defined(ARCH_CPU_X86_FAMILY) && !BUILDFLAG(IS_NACL)
|
#if defined(ARCH_CPU_X86_FAMILY) && !BUILDFLAG(IS_NACL)
|
||||||
base::CPU cpu;
|
base::CPU cpu;
|
||||||
@ -76,17 +84,19 @@ void FMUL(const float src[], float scale, int len, float dest[]) {
|
|||||||
#endif
|
#endif
|
||||||
}();
|
}();
|
||||||
|
|
||||||
return fmul_func(src, scale, len, dest);
|
return fmul_func(src.data(), scale, src.size(), dest.data());
|
||||||
}
|
}
|
||||||
|
|
||||||
void FMUL_C(const float src[], float scale, int len, float dest[]) {
|
void FMUL_C(const float src[], float scale, int len, float dest[]) {
|
||||||
for (int i = 0; i < len; ++i)
|
for (int i = 0; i < len; ++i) {
|
||||||
dest[i] = src[i] * scale;
|
dest[i] = src[i] * scale;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::pair<float, float> EWMAAndMaxPower(
|
std::pair<float, float> EWMAAndMaxPower(float initial_value,
|
||||||
float initial_value, const float src[], int len, float smoothing_factor) {
|
base::span<const float> src,
|
||||||
DCHECK(base::IsAligned(src, kRequiredAlignment));
|
float smoothing_factor) {
|
||||||
|
DCHECK(base::IsAligned(src.data(), kRequiredAlignment));
|
||||||
static const auto ewma_and_max_power_func = [] {
|
static const auto ewma_and_max_power_func = [] {
|
||||||
#if defined(ARCH_CPU_X86_FAMILY) && !BUILDFLAG(IS_NACL)
|
#if defined(ARCH_CPU_X86_FAMILY) && !BUILDFLAG(IS_NACL)
|
||||||
base::CPU cpu;
|
base::CPU cpu;
|
||||||
@ -100,11 +110,14 @@ std::pair<float, float> EWMAAndMaxPower(
|
|||||||
#endif
|
#endif
|
||||||
}();
|
}();
|
||||||
|
|
||||||
return ewma_and_max_power_func(initial_value, src, len, smoothing_factor);
|
return ewma_and_max_power_func(initial_value, src.data(), src.size(),
|
||||||
|
smoothing_factor);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::pair<float, float> EWMAAndMaxPower_C(
|
std::pair<float, float> EWMAAndMaxPower_C(float initial_value,
|
||||||
float initial_value, const float src[], int len, float smoothing_factor) {
|
const float src[],
|
||||||
|
int len,
|
||||||
|
float smoothing_factor) {
|
||||||
std::pair<float, float> result(initial_value, 0.0f);
|
std::pair<float, float> result(initial_value, 0.0f);
|
||||||
const float weight_prev = 1.0f - smoothing_factor;
|
const float weight_prev = 1.0f - smoothing_factor;
|
||||||
for (int i = 0; i < len; ++i) {
|
for (int i = 0; i < len; ++i) {
|
||||||
@ -122,12 +135,14 @@ void FMUL_SSE(const float src[], float scale, int len, float dest[]) {
|
|||||||
const int rem = len % 4;
|
const int rem = len % 4;
|
||||||
const int last_index = len - rem;
|
const int last_index = len - rem;
|
||||||
__m128 m_scale = _mm_set_ps1(scale);
|
__m128 m_scale = _mm_set_ps1(scale);
|
||||||
for (int i = 0; i < last_index; i += 4)
|
for (int i = 0; i < last_index; i += 4) {
|
||||||
_mm_store_ps(dest + i, _mm_mul_ps(_mm_load_ps(src + i), m_scale));
|
_mm_store_ps(dest + i, _mm_mul_ps(_mm_load_ps(src + i), m_scale));
|
||||||
|
}
|
||||||
|
|
||||||
// Handle any remaining values that wouldn't fit in an SSE pass.
|
// Handle any remaining values that wouldn't fit in an SSE pass.
|
||||||
for (int i = last_index; i < len; ++i)
|
for (int i = last_index; i < len; ++i) {
|
||||||
dest[i] = src[i] * scale;
|
dest[i] = src[i] * scale;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
__attribute__((target("avx2"))) void FMUL_AVX2(const float src[],
|
__attribute__((target("avx2"))) void FMUL_AVX2(const float src[],
|
||||||
@ -143,29 +158,34 @@ __attribute__((target("avx2"))) void FMUL_AVX2(const float src[],
|
|||||||
bool aligned_dest = (reinterpret_cast<uintptr_t>(dest) & 0x1F) == 0;
|
bool aligned_dest = (reinterpret_cast<uintptr_t>(dest) & 0x1F) == 0;
|
||||||
if (aligned_src) {
|
if (aligned_src) {
|
||||||
if (aligned_dest) {
|
if (aligned_dest) {
|
||||||
for (int i = 0; i < last_index; i += 8)
|
for (int i = 0; i < last_index; i += 8) {
|
||||||
_mm256_store_ps(dest + i,
|
_mm256_store_ps(dest + i,
|
||||||
_mm256_mul_ps(_mm256_load_ps(src + i), m_scale));
|
_mm256_mul_ps(_mm256_load_ps(src + i), m_scale));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
for (int i = 0; i < last_index; i += 8)
|
for (int i = 0; i < last_index; i += 8) {
|
||||||
_mm256_storeu_ps(dest + i,
|
_mm256_storeu_ps(dest + i,
|
||||||
_mm256_mul_ps(_mm256_load_ps(src + i), m_scale));
|
_mm256_mul_ps(_mm256_load_ps(src + i), m_scale));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (aligned_dest) {
|
if (aligned_dest) {
|
||||||
for (int i = 0; i < last_index; i += 8)
|
for (int i = 0; i < last_index; i += 8) {
|
||||||
_mm256_store_ps(dest + i,
|
_mm256_store_ps(dest + i,
|
||||||
_mm256_mul_ps(_mm256_loadu_ps(src + i), m_scale));
|
_mm256_mul_ps(_mm256_loadu_ps(src + i), m_scale));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
for (int i = 0; i < last_index; i += 8)
|
for (int i = 0; i < last_index; i += 8) {
|
||||||
_mm256_storeu_ps(dest + i,
|
_mm256_storeu_ps(dest + i,
|
||||||
_mm256_mul_ps(_mm256_loadu_ps(src + i), m_scale));
|
_mm256_mul_ps(_mm256_loadu_ps(src + i), m_scale));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle any remaining values that wouldn't fit in an SSE pass.
|
// Handle any remaining values that wouldn't fit in an SSE pass.
|
||||||
for (int i = last_index; i < len; ++i)
|
for (int i = last_index; i < len; ++i) {
|
||||||
dest[i] = src[i] * scale;
|
dest[i] = src[i] * scale;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void FMAC_SSE(const float src[], float scale, int len, float dest[]) {
|
void FMAC_SSE(const float src[], float scale, int len, float dest[]) {
|
||||||
@ -173,13 +193,15 @@ void FMAC_SSE(const float src[], float scale, int len, float dest[]) {
|
|||||||
const int last_index = len - rem;
|
const int last_index = len - rem;
|
||||||
__m128 m_scale = _mm_set_ps1(scale);
|
__m128 m_scale = _mm_set_ps1(scale);
|
||||||
for (int i = 0; i < last_index; i += 4) {
|
for (int i = 0; i < last_index; i += 4) {
|
||||||
_mm_store_ps(dest + i, _mm_add_ps(_mm_load_ps(dest + i),
|
_mm_store_ps(dest + i,
|
||||||
_mm_mul_ps(_mm_load_ps(src + i), m_scale)));
|
_mm_add_ps(_mm_load_ps(dest + i),
|
||||||
|
_mm_mul_ps(_mm_load_ps(src + i), m_scale)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle any remaining values that wouldn't fit in an SSE pass.
|
// Handle any remaining values that wouldn't fit in an SSE pass.
|
||||||
for (int i = last_index; i < len; ++i)
|
for (int i = last_index; i < len; ++i) {
|
||||||
dest[i] += src[i] * scale;
|
dest[i] += src[i] * scale;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
__attribute__((target("avx2,fma"))) void FMAC_AVX2(const float src[],
|
__attribute__((target("avx2,fma"))) void FMAC_AVX2(const float src[],
|
||||||
@ -195,45 +217,50 @@ __attribute__((target("avx2,fma"))) void FMAC_AVX2(const float src[],
|
|||||||
bool aligned_dest = (reinterpret_cast<uintptr_t>(dest) & 0x1F) == 0;
|
bool aligned_dest = (reinterpret_cast<uintptr_t>(dest) & 0x1F) == 0;
|
||||||
if (aligned_src) {
|
if (aligned_src) {
|
||||||
if (aligned_dest) {
|
if (aligned_dest) {
|
||||||
for (int i = 0; i < last_index; i += 8)
|
for (int i = 0; i < last_index; i += 8) {
|
||||||
_mm256_store_ps(dest + i,
|
_mm256_store_ps(dest + i,
|
||||||
_mm256_fmadd_ps(_mm256_load_ps(src + i), m_scale,
|
_mm256_fmadd_ps(_mm256_load_ps(src + i), m_scale,
|
||||||
_mm256_load_ps(dest + i)));
|
_mm256_load_ps(dest + i)));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
for (int i = 0; i < last_index; i += 8)
|
for (int i = 0; i < last_index; i += 8) {
|
||||||
_mm256_storeu_ps(dest + i,
|
_mm256_storeu_ps(dest + i,
|
||||||
_mm256_fmadd_ps(_mm256_load_ps(src + i), m_scale,
|
_mm256_fmadd_ps(_mm256_load_ps(src + i), m_scale,
|
||||||
_mm256_loadu_ps(dest + i)));
|
_mm256_loadu_ps(dest + i)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (aligned_dest) {
|
if (aligned_dest) {
|
||||||
for (int i = 0; i < last_index; i += 8)
|
for (int i = 0; i < last_index; i += 8) {
|
||||||
_mm256_store_ps(dest + i,
|
_mm256_store_ps(dest + i,
|
||||||
_mm256_fmadd_ps(_mm256_loadu_ps(src + i), m_scale,
|
_mm256_fmadd_ps(_mm256_loadu_ps(src + i), m_scale,
|
||||||
_mm256_load_ps(dest + i)));
|
_mm256_load_ps(dest + i)));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
for (int i = 0; i < last_index; i += 8)
|
for (int i = 0; i < last_index; i += 8) {
|
||||||
_mm256_storeu_ps(dest + i,
|
_mm256_storeu_ps(dest + i,
|
||||||
_mm256_fmadd_ps(_mm256_loadu_ps(src + i), m_scale,
|
_mm256_fmadd_ps(_mm256_loadu_ps(src + i), m_scale,
|
||||||
_mm256_loadu_ps(dest + i)));
|
_mm256_loadu_ps(dest + i)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle any remaining values that wouldn't fit in an SSE pass.
|
// Handle any remaining values that wouldn't fit in an SSE pass.
|
||||||
for (int i = last_index; i < len; ++i)
|
for (int i = last_index; i < len; ++i) {
|
||||||
dest[i] += src[i] * scale;
|
dest[i] += src[i] * scale;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convenience macro to extract float 0 through 3 from the vector |a|. This is
|
// Convenience macro to extract float 0 through 3 from the vector |a|. This is
|
||||||
// needed because compilers other than clang don't support access via
|
// needed because compilers other than clang don't support access via
|
||||||
// operator[]().
|
// operator[]().
|
||||||
#define EXTRACT_FLOAT(a, i) \
|
#define EXTRACT_FLOAT(a, i) \
|
||||||
(i == 0 ? \
|
(i == 0 ? _mm_cvtss_f32(a) : _mm_cvtss_f32(_mm_shuffle_ps(a, a, i)))
|
||||||
_mm_cvtss_f32(a) : \
|
|
||||||
_mm_cvtss_f32(_mm_shuffle_ps(a, a, i)))
|
|
||||||
|
|
||||||
std::pair<float, float> EWMAAndMaxPower_SSE(
|
std::pair<float, float> EWMAAndMaxPower_SSE(float initial_value,
|
||||||
float initial_value, const float src[], int len, float smoothing_factor) {
|
const float src[],
|
||||||
|
int len,
|
||||||
|
float smoothing_factor) {
|
||||||
// When the recurrence is unrolled, we see that we can split it into 4
|
// When the recurrence is unrolled, we see that we can split it into 4
|
||||||
// separate lanes of evaluation:
|
// separate lanes of evaluation:
|
||||||
//
|
//
|
||||||
@ -269,8 +296,8 @@ std::pair<float, float> EWMAAndMaxPower_SSE(
|
|||||||
max_x4 = _mm_max_ps(max_x4, sample_squared_x4);
|
max_x4 = _mm_max_ps(max_x4, sample_squared_x4);
|
||||||
// Note: The compiler optimizes this to a single multiply-and-accumulate
|
// Note: The compiler optimizes this to a single multiply-and-accumulate
|
||||||
// instruction:
|
// instruction:
|
||||||
ewma_x4 = _mm_add_ps(ewma_x4,
|
ewma_x4 =
|
||||||
_mm_mul_ps(sample_squared_x4, smoothing_factor_x4));
|
_mm_add_ps(ewma_x4, _mm_mul_ps(sample_squared_x4, smoothing_factor_x4));
|
||||||
}
|
}
|
||||||
|
|
||||||
// y[n] = z[n] + (1-a)^1(z[n-1]) + (1-a)^2(z[n-2]) + (1-a)^3(z[n-3])
|
// y[n] = z[n] + (1-a)^1(z[n-1]) + (1-a)^2(z[n-2]) + (1-a)^3(z[n-3])
|
||||||
@ -381,29 +408,34 @@ void FMAC_NEON(const float src[], float scale, int len, float dest[]) {
|
|||||||
const int last_index = len - rem;
|
const int last_index = len - rem;
|
||||||
float32x4_t m_scale = vmovq_n_f32(scale);
|
float32x4_t m_scale = vmovq_n_f32(scale);
|
||||||
for (int i = 0; i < last_index; i += 4) {
|
for (int i = 0; i < last_index; i += 4) {
|
||||||
vst1q_f32(dest + i, vmlaq_f32(
|
vst1q_f32(dest + i,
|
||||||
vld1q_f32(dest + i), vld1q_f32(src + i), m_scale));
|
vmlaq_f32(vld1q_f32(dest + i), vld1q_f32(src + i), m_scale));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle any remaining values that wouldn't fit in an NEON pass.
|
// Handle any remaining values that wouldn't fit in an NEON pass.
|
||||||
for (int i = last_index; i < len; ++i)
|
for (int i = last_index; i < len; ++i) {
|
||||||
dest[i] += src[i] * scale;
|
dest[i] += src[i] * scale;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void FMUL_NEON(const float src[], float scale, int len, float dest[]) {
|
void FMUL_NEON(const float src[], float scale, int len, float dest[]) {
|
||||||
const int rem = len % 4;
|
const int rem = len % 4;
|
||||||
const int last_index = len - rem;
|
const int last_index = len - rem;
|
||||||
float32x4_t m_scale = vmovq_n_f32(scale);
|
float32x4_t m_scale = vmovq_n_f32(scale);
|
||||||
for (int i = 0; i < last_index; i += 4)
|
for (int i = 0; i < last_index; i += 4) {
|
||||||
vst1q_f32(dest + i, vmulq_f32(vld1q_f32(src + i), m_scale));
|
vst1q_f32(dest + i, vmulq_f32(vld1q_f32(src + i), m_scale));
|
||||||
|
}
|
||||||
|
|
||||||
// Handle any remaining values that wouldn't fit in an NEON pass.
|
// Handle any remaining values that wouldn't fit in an NEON pass.
|
||||||
for (int i = last_index; i < len; ++i)
|
for (int i = last_index; i < len; ++i) {
|
||||||
dest[i] = src[i] * scale;
|
dest[i] = src[i] * scale;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::pair<float, float> EWMAAndMaxPower_NEON(
|
std::pair<float, float> EWMAAndMaxPower_NEON(float initial_value,
|
||||||
float initial_value, const float src[], int len, float smoothing_factor) {
|
const float src[],
|
||||||
|
int len,
|
||||||
|
float smoothing_factor) {
|
||||||
// When the recurrence is unrolled, we see that we can split it into 4
|
// When the recurrence is unrolled, we see that we can split it into 4
|
||||||
// separate lanes of evaluation:
|
// separate lanes of evaluation:
|
||||||
//
|
//
|
||||||
@ -468,5 +500,4 @@ std::pair<float, float> EWMAAndMaxPower_NEON(
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
} // namespace vector_math
|
} // namespace media::vector_math
|
||||||
} // namespace media
|
|
||||||
|
@ -7,27 +7,25 @@
|
|||||||
|
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
|
#include "base/containers/span.h"
|
||||||
#include "media/base/media_shmem_export.h"
|
#include "media/base/media_shmem_export.h"
|
||||||
|
|
||||||
namespace media {
|
namespace media::vector_math {
|
||||||
namespace vector_math {
|
|
||||||
|
|
||||||
// Required alignment for inputs and outputs to all vector math functions
|
// Required alignment for inputs and outputs to all vector math functions
|
||||||
enum { kRequiredAlignment = 16 };
|
enum { kRequiredAlignment = 16 };
|
||||||
|
|
||||||
// Multiply each element of |src| (up to |len|) by |scale| and add to |dest|.
|
// Multiply each element of `src` by `scale` and add to `dest`.
|
||||||
// |src| and |dest| must be aligned by kRequiredAlignment.
|
// `src` and `dest` must be aligned by `kRequiredAlignment`.
|
||||||
MEDIA_SHMEM_EXPORT void FMAC(const float src[],
|
MEDIA_SHMEM_EXPORT void FMAC(base::span<const float> src,
|
||||||
float scale,
|
float scale,
|
||||||
int len,
|
base::span<float> dest);
|
||||||
float dest[]);
|
|
||||||
|
|
||||||
// Multiply each element of |src| by |scale| and store in |dest|. |src| and
|
// Multiply each element of `src` by `scale` and store in `dest`.
|
||||||
// |dest| must be aligned by kRequiredAlignment.
|
// `src` and `dest` must be aligned by `kRequiredAlignment`.
|
||||||
MEDIA_SHMEM_EXPORT void FMUL(const float src[],
|
MEDIA_SHMEM_EXPORT void FMUL(base::span<const float> src,
|
||||||
float scale,
|
float scale,
|
||||||
int len,
|
base::span<float> dest);
|
||||||
float dest[]);
|
|
||||||
|
|
||||||
// Computes the exponentially-weighted moving average power of a signal by
|
// Computes the exponentially-weighted moving average power of a signal by
|
||||||
// iterating the recurrence:
|
// iterating the recurrence:
|
||||||
@ -38,11 +36,9 @@ MEDIA_SHMEM_EXPORT void FMUL(const float src[],
|
|||||||
// Returns the final average power and the maximum squared element value.
|
// Returns the final average power and the maximum squared element value.
|
||||||
MEDIA_SHMEM_EXPORT std::pair<float, float> EWMAAndMaxPower(
|
MEDIA_SHMEM_EXPORT std::pair<float, float> EWMAAndMaxPower(
|
||||||
float initial_value,
|
float initial_value,
|
||||||
const float src[],
|
base::span<const float> src,
|
||||||
int len,
|
|
||||||
float smoothing_factor);
|
float smoothing_factor);
|
||||||
|
|
||||||
} // namespace vector_math
|
} // namespace media::vector_math
|
||||||
} // namespace media
|
|
||||||
|
|
||||||
#endif // MEDIA_BASE_VECTOR_MATH_H_
|
#endif // MEDIA_BASE_VECTOR_MATH_H_
|
||||||
|
@ -7,6 +7,9 @@
|
|||||||
#pragma allow_unsafe_buffers
|
#pragma allow_unsafe_buffers
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "media/base/vector_math.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
@ -15,28 +18,25 @@
|
|||||||
#include "base/strings/string_number_conversions.h"
|
#include "base/strings/string_number_conversions.h"
|
||||||
#include "base/strings/stringize_macros.h"
|
#include "base/strings/stringize_macros.h"
|
||||||
#include "build/build_config.h"
|
#include "build/build_config.h"
|
||||||
#include "media/base/vector_math.h"
|
|
||||||
#include "media/base/vector_math_testing.h"
|
#include "media/base/vector_math_testing.h"
|
||||||
#include "testing/gtest/include/gtest/gtest.h"
|
#include "testing/gtest/include/gtest/gtest.h"
|
||||||
|
|
||||||
using std::fill;
|
|
||||||
|
|
||||||
namespace media {
|
namespace media {
|
||||||
|
|
||||||
// Default test values.
|
// Default test values.
|
||||||
static const float kScale = 0.5;
|
static constexpr float kScale = 0.5;
|
||||||
static const float kInputFillValue = 1.0;
|
static constexpr float kInputFillValue = 1.0;
|
||||||
static const float kOutputFillValue = 3.0;
|
static constexpr float kOutputFillValue = 3.0;
|
||||||
static const int kVectorSize = 8192;
|
static constexpr int kVectorSize = 8192;
|
||||||
|
|
||||||
class VectorMathTest : public testing::Test {
|
class VectorMathTest : public testing::Test {
|
||||||
public:
|
public:
|
||||||
VectorMathTest() {
|
VectorMathTest() {
|
||||||
// Initialize input and output vectors.
|
// Initialize input and output vectors.
|
||||||
input_vector_.reset(static_cast<float*>(base::AlignedAlloc(
|
input_array_ = base::AlignedUninit<float>(kVectorSize,
|
||||||
sizeof(float) * kVectorSize, vector_math::kRequiredAlignment)));
|
vector_math::kRequiredAlignment);
|
||||||
output_vector_.reset(static_cast<float*>(base::AlignedAlloc(
|
output_array_ = base::AlignedUninit<float>(kVectorSize,
|
||||||
sizeof(float) * kVectorSize, vector_math::kRequiredAlignment)));
|
vector_math::kRequiredAlignment);
|
||||||
}
|
}
|
||||||
|
|
||||||
VectorMathTest(const VectorMathTest&) = delete;
|
VectorMathTest(const VectorMathTest&) = delete;
|
||||||
@ -44,18 +44,18 @@ class VectorMathTest : public testing::Test {
|
|||||||
|
|
||||||
void FillTestVectors(float input, float output) {
|
void FillTestVectors(float input, float output) {
|
||||||
// Setup input and output vectors.
|
// Setup input and output vectors.
|
||||||
fill(input_vector_.get(), input_vector_.get() + kVectorSize, input);
|
std::ranges::fill(input_array_, input);
|
||||||
fill(output_vector_.get(), output_vector_.get() + kVectorSize, output);
|
std::ranges::fill(output_array_, output);
|
||||||
}
|
}
|
||||||
|
|
||||||
void VerifyOutput(float value) {
|
void VerifyOutput(float value) {
|
||||||
for (int i = 0; i < kVectorSize; ++i)
|
EXPECT_TRUE(std::ranges::all_of(
|
||||||
ASSERT_FLOAT_EQ(output_vector_[i], value);
|
output_array_, [value](float datum) { return datum == value; }));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
std::unique_ptr<float[], base::AlignedFreeDeleter> input_vector_;
|
base::AlignedHeapArray<float> input_array_;
|
||||||
std::unique_ptr<float[], base::AlignedFreeDeleter> output_vector_;
|
base::AlignedHeapArray<float> output_array_;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Ensure each optimized vector_math::FMAC() method returns the same value.
|
// Ensure each optimized vector_math::FMAC() method returns the same value.
|
||||||
@ -65,16 +65,15 @@ TEST_F(VectorMathTest, FMAC) {
|
|||||||
{
|
{
|
||||||
SCOPED_TRACE("FMAC");
|
SCOPED_TRACE("FMAC");
|
||||||
FillTestVectors(kInputFillValue, kOutputFillValue);
|
FillTestVectors(kInputFillValue, kOutputFillValue);
|
||||||
vector_math::FMAC(
|
vector_math::FMAC(input_array_, kScale, output_array_);
|
||||||
input_vector_.get(), kScale, kVectorSize, output_vector_.get());
|
|
||||||
VerifyOutput(kResult);
|
VerifyOutput(kResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
SCOPED_TRACE("FMAC_C");
|
SCOPED_TRACE("FMAC_C");
|
||||||
FillTestVectors(kInputFillValue, kOutputFillValue);
|
FillTestVectors(kInputFillValue, kOutputFillValue);
|
||||||
vector_math::FMAC_C(
|
vector_math::FMAC_C(input_array_.data(), kScale, kVectorSize,
|
||||||
input_vector_.get(), kScale, kVectorSize, output_vector_.get());
|
output_array_.data());
|
||||||
VerifyOutput(kResult);
|
VerifyOutput(kResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,8 +81,8 @@ TEST_F(VectorMathTest, FMAC) {
|
|||||||
{
|
{
|
||||||
SCOPED_TRACE("FMAC_SSE");
|
SCOPED_TRACE("FMAC_SSE");
|
||||||
FillTestVectors(kInputFillValue, kOutputFillValue);
|
FillTestVectors(kInputFillValue, kOutputFillValue);
|
||||||
vector_math::FMAC_SSE(
|
vector_math::FMAC_SSE(input_array_.data(), kScale, kVectorSize,
|
||||||
input_vector_.get(), kScale, kVectorSize, output_vector_.get());
|
output_array_.data());
|
||||||
VerifyOutput(kResult);
|
VerifyOutput(kResult);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
@ -91,8 +90,8 @@ TEST_F(VectorMathTest, FMAC) {
|
|||||||
if (cpu.has_avx2() && cpu.has_fma3()) {
|
if (cpu.has_avx2() && cpu.has_fma3()) {
|
||||||
SCOPED_TRACE("FMAC_AVX2");
|
SCOPED_TRACE("FMAC_AVX2");
|
||||||
FillTestVectors(kInputFillValue, kOutputFillValue);
|
FillTestVectors(kInputFillValue, kOutputFillValue);
|
||||||
vector_math::FMAC_AVX2(input_vector_.get(), kScale, kVectorSize,
|
vector_math::FMAC_AVX2(input_array_.data(), kScale, kVectorSize,
|
||||||
output_vector_.get());
|
output_array_.data());
|
||||||
VerifyOutput(kResult);
|
VerifyOutput(kResult);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -102,8 +101,8 @@ TEST_F(VectorMathTest, FMAC) {
|
|||||||
{
|
{
|
||||||
SCOPED_TRACE("FMAC_NEON");
|
SCOPED_TRACE("FMAC_NEON");
|
||||||
FillTestVectors(kInputFillValue, kOutputFillValue);
|
FillTestVectors(kInputFillValue, kOutputFillValue);
|
||||||
vector_math::FMAC_NEON(
|
vector_math::FMAC_NEON(input_array_.data(), kScale, kVectorSize,
|
||||||
input_vector_.get(), kScale, kVectorSize, output_vector_.get());
|
output_array_.data());
|
||||||
VerifyOutput(kResult);
|
VerifyOutput(kResult);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@ -116,16 +115,15 @@ TEST_F(VectorMathTest, FMUL) {
|
|||||||
{
|
{
|
||||||
SCOPED_TRACE("FMUL");
|
SCOPED_TRACE("FMUL");
|
||||||
FillTestVectors(kInputFillValue, kOutputFillValue);
|
FillTestVectors(kInputFillValue, kOutputFillValue);
|
||||||
vector_math::FMUL(
|
vector_math::FMUL(input_array_, kScale, output_array_);
|
||||||
input_vector_.get(), kScale, kVectorSize, output_vector_.get());
|
|
||||||
VerifyOutput(kResult);
|
VerifyOutput(kResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
SCOPED_TRACE("FMUL_C");
|
SCOPED_TRACE("FMUL_C");
|
||||||
FillTestVectors(kInputFillValue, kOutputFillValue);
|
FillTestVectors(kInputFillValue, kOutputFillValue);
|
||||||
vector_math::FMUL_C(
|
vector_math::FMUL_C(input_array_.data(), kScale, kVectorSize,
|
||||||
input_vector_.get(), kScale, kVectorSize, output_vector_.get());
|
output_array_.data());
|
||||||
VerifyOutput(kResult);
|
VerifyOutput(kResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,8 +131,8 @@ TEST_F(VectorMathTest, FMUL) {
|
|||||||
{
|
{
|
||||||
SCOPED_TRACE("FMUL_SSE");
|
SCOPED_TRACE("FMUL_SSE");
|
||||||
FillTestVectors(kInputFillValue, kOutputFillValue);
|
FillTestVectors(kInputFillValue, kOutputFillValue);
|
||||||
vector_math::FMUL_SSE(
|
vector_math::FMUL_SSE(input_array_.data(), kScale, kVectorSize,
|
||||||
input_vector_.get(), kScale, kVectorSize, output_vector_.get());
|
output_array_.data());
|
||||||
VerifyOutput(kResult);
|
VerifyOutput(kResult);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
@ -142,8 +140,8 @@ TEST_F(VectorMathTest, FMUL) {
|
|||||||
if (cpu.has_avx2()) {
|
if (cpu.has_avx2()) {
|
||||||
SCOPED_TRACE("FMUL_AVX2");
|
SCOPED_TRACE("FMUL_AVX2");
|
||||||
FillTestVectors(kInputFillValue, kOutputFillValue);
|
FillTestVectors(kInputFillValue, kOutputFillValue);
|
||||||
vector_math::FMUL_AVX2(input_vector_.get(), kScale, kVectorSize,
|
vector_math::FMUL_AVX2(input_array_.data(), kScale, kVectorSize,
|
||||||
output_vector_.get());
|
output_array_.data());
|
||||||
VerifyOutput(kResult);
|
VerifyOutput(kResult);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -153,28 +151,38 @@ TEST_F(VectorMathTest, FMUL) {
|
|||||||
{
|
{
|
||||||
SCOPED_TRACE("FMUL_NEON");
|
SCOPED_TRACE("FMUL_NEON");
|
||||||
FillTestVectors(kInputFillValue, kOutputFillValue);
|
FillTestVectors(kInputFillValue, kOutputFillValue);
|
||||||
vector_math::FMUL_NEON(
|
vector_math::FMUL_NEON(input_array_.data(), kScale, kVectorSize,
|
||||||
input_vector_.get(), kScale, kVectorSize, output_vector_.get());
|
output_array_.data());
|
||||||
VerifyOutput(kResult);
|
VerifyOutput(kResult);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(VectorMathTest, EmptyInputs) {
|
||||||
|
{
|
||||||
|
SCOPED_TRACE("FMUL");
|
||||||
|
FillTestVectors(kInputFillValue, kOutputFillValue);
|
||||||
|
vector_math::FMUL(base::span<float>(), kScale, output_array_);
|
||||||
|
VerifyOutput(kOutputFillValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
SCOPED_TRACE("FMAC");
|
||||||
|
FillTestVectors(kInputFillValue, kOutputFillValue);
|
||||||
|
vector_math::FMAC(base::span<float>(), kScale, output_array_);
|
||||||
|
VerifyOutput(kOutputFillValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class EWMATestScenario {
|
class EWMATestScenario {
|
||||||
public:
|
public:
|
||||||
EWMATestScenario(float initial_value, const float src[], int len,
|
EWMATestScenario(float initial_value,
|
||||||
|
base::span<const float> src,
|
||||||
float smoothing_factor)
|
float smoothing_factor)
|
||||||
: initial_value_(initial_value),
|
: initial_value_(initial_value),
|
||||||
data_(static_cast<float*>(
|
|
||||||
len == 0 ? NULL :
|
|
||||||
base::AlignedAlloc(len * sizeof(float),
|
|
||||||
vector_math::kRequiredAlignment))),
|
|
||||||
data_len_(len),
|
|
||||||
smoothing_factor_(smoothing_factor),
|
smoothing_factor_(smoothing_factor),
|
||||||
expected_final_avg_(initial_value),
|
expected_final_avg_(initial_value) {
|
||||||
expected_max_(0.0f) {
|
CopyDataAligned(src);
|
||||||
if (data_len_ > 0)
|
|
||||||
memcpy(data_.get(), src, len * sizeof(float));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy constructor and assignment operator for ::testing::Values(...).
|
// Copy constructor and assignment operator for ::testing::Values(...).
|
||||||
@ -182,16 +190,7 @@ class EWMATestScenario {
|
|||||||
EWMATestScenario& operator=(const EWMATestScenario& other) {
|
EWMATestScenario& operator=(const EWMATestScenario& other) {
|
||||||
this->initial_value_ = other.initial_value_;
|
this->initial_value_ = other.initial_value_;
|
||||||
this->smoothing_factor_ = other.smoothing_factor_;
|
this->smoothing_factor_ = other.smoothing_factor_;
|
||||||
if (other.data_len_ == 0) {
|
this->CopyDataAligned(other.data_);
|
||||||
this->data_.reset();
|
|
||||||
} else {
|
|
||||||
this->data_.reset(static_cast<float*>(
|
|
||||||
base::AlignedAlloc(other.data_len_ * sizeof(float),
|
|
||||||
vector_math::kRequiredAlignment)));
|
|
||||||
memcpy(this->data_.get(), other.data_.get(),
|
|
||||||
other.data_len_ * sizeof(float));
|
|
||||||
}
|
|
||||||
this->data_len_ = other.data_len_;
|
|
||||||
this->expected_final_avg_ = other.expected_final_avg_;
|
this->expected_final_avg_ = other.expected_final_avg_;
|
||||||
this->expected_max_ = other.expected_max_;
|
this->expected_max_ = other.expected_max_;
|
||||||
return *this;
|
return *this;
|
||||||
@ -199,16 +198,14 @@ class EWMATestScenario {
|
|||||||
|
|
||||||
EWMATestScenario ScaledBy(float scale) const {
|
EWMATestScenario ScaledBy(float scale) const {
|
||||||
EWMATestScenario result(*this);
|
EWMATestScenario result(*this);
|
||||||
float* p = result.data_.get();
|
std::ranges::for_each(result.data_,
|
||||||
float* const p_end = p + result.data_len_;
|
[scale](float& datum) { datum *= scale; });
|
||||||
for (; p < p_end; ++p)
|
|
||||||
*p *= scale;
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
EWMATestScenario WithImpulse(float value, int offset) const {
|
EWMATestScenario WithImpulse(float value, int offset) const {
|
||||||
EWMATestScenario result(*this);
|
EWMATestScenario result(*this);
|
||||||
result.data_.get()[offset] = value;
|
result.data_[offset] = value;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -224,7 +221,7 @@ class EWMATestScenario {
|
|||||||
{
|
{
|
||||||
SCOPED_TRACE("EWMAAndMaxPower");
|
SCOPED_TRACE("EWMAAndMaxPower");
|
||||||
const std::pair<float, float>& result = vector_math::EWMAAndMaxPower(
|
const std::pair<float, float>& result = vector_math::EWMAAndMaxPower(
|
||||||
initial_value_, data_.get(), data_len_, smoothing_factor_);
|
initial_value_, data_, smoothing_factor_);
|
||||||
EXPECT_NEAR(expected_final_avg_, result.first, 0.0000001f);
|
EXPECT_NEAR(expected_final_avg_, result.first, 0.0000001f);
|
||||||
EXPECT_NEAR(expected_max_, result.second, 0.0000001f);
|
EXPECT_NEAR(expected_max_, result.second, 0.0000001f);
|
||||||
}
|
}
|
||||||
@ -232,7 +229,7 @@ class EWMATestScenario {
|
|||||||
{
|
{
|
||||||
SCOPED_TRACE("EWMAAndMaxPower_C");
|
SCOPED_TRACE("EWMAAndMaxPower_C");
|
||||||
const std::pair<float, float>& result = vector_math::EWMAAndMaxPower_C(
|
const std::pair<float, float>& result = vector_math::EWMAAndMaxPower_C(
|
||||||
initial_value_, data_.get(), data_len_, smoothing_factor_);
|
initial_value_, data_.data(), data_.size(), smoothing_factor_);
|
||||||
EXPECT_NEAR(expected_final_avg_, result.first, 0.0000001f);
|
EXPECT_NEAR(expected_final_avg_, result.first, 0.0000001f);
|
||||||
EXPECT_NEAR(expected_max_, result.second, 0.0000001f);
|
EXPECT_NEAR(expected_max_, result.second, 0.0000001f);
|
||||||
}
|
}
|
||||||
@ -241,7 +238,7 @@ class EWMATestScenario {
|
|||||||
{
|
{
|
||||||
SCOPED_TRACE("EWMAAndMaxPower_SSE");
|
SCOPED_TRACE("EWMAAndMaxPower_SSE");
|
||||||
const std::pair<float, float>& result = vector_math::EWMAAndMaxPower_SSE(
|
const std::pair<float, float>& result = vector_math::EWMAAndMaxPower_SSE(
|
||||||
initial_value_, data_.get(), data_len_, smoothing_factor_);
|
initial_value_, data_.data(), data_.size(), smoothing_factor_);
|
||||||
EXPECT_NEAR(expected_final_avg_, result.first, 0.0000001f);
|
EXPECT_NEAR(expected_final_avg_, result.first, 0.0000001f);
|
||||||
EXPECT_NEAR(expected_max_, result.second, 0.0000001f);
|
EXPECT_NEAR(expected_max_, result.second, 0.0000001f);
|
||||||
}
|
}
|
||||||
@ -250,8 +247,8 @@ class EWMATestScenario {
|
|||||||
if (cpu.has_avx2() && cpu.has_fma3()) {
|
if (cpu.has_avx2() && cpu.has_fma3()) {
|
||||||
SCOPED_TRACE("EWMAAndMaxPower_AVX2");
|
SCOPED_TRACE("EWMAAndMaxPower_AVX2");
|
||||||
const std::pair<float, float>& result =
|
const std::pair<float, float>& result =
|
||||||
vector_math::EWMAAndMaxPower_AVX2(initial_value_, data_.get(),
|
vector_math::EWMAAndMaxPower_AVX2(initial_value_, data_.data(),
|
||||||
data_len_, smoothing_factor_);
|
data_.size(), smoothing_factor_);
|
||||||
EXPECT_NEAR(expected_final_avg_, result.first, 0.0000001f);
|
EXPECT_NEAR(expected_final_avg_, result.first, 0.0000001f);
|
||||||
EXPECT_NEAR(expected_max_, result.second, 0.0000001f);
|
EXPECT_NEAR(expected_max_, result.second, 0.0000001f);
|
||||||
}
|
}
|
||||||
@ -262,7 +259,7 @@ class EWMATestScenario {
|
|||||||
{
|
{
|
||||||
SCOPED_TRACE("EWMAAndMaxPower_NEON");
|
SCOPED_TRACE("EWMAAndMaxPower_NEON");
|
||||||
const std::pair<float, float>& result = vector_math::EWMAAndMaxPower_NEON(
|
const std::pair<float, float>& result = vector_math::EWMAAndMaxPower_NEON(
|
||||||
initial_value_, data_.get(), data_len_, smoothing_factor_);
|
initial_value_, data_.data(), data_.size(), smoothing_factor_);
|
||||||
EXPECT_NEAR(expected_final_avg_, result.first, 0.0000001f);
|
EXPECT_NEAR(expected_final_avg_, result.first, 0.0000001f);
|
||||||
EXPECT_NEAR(expected_max_, result.second, 0.0000001f);
|
EXPECT_NEAR(expected_max_, result.second, 0.0000001f);
|
||||||
}
|
}
|
||||||
@ -270,12 +267,22 @@ class EWMATestScenario {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void CopyDataAligned(base::span<const float> src) {
|
||||||
|
if (src.empty()) {
|
||||||
|
data_ = base::AlignedHeapArray<float>();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
data_ =
|
||||||
|
base::AlignedUninit<float>(src.size(), vector_math::kRequiredAlignment);
|
||||||
|
data_.copy_from(src);
|
||||||
|
}
|
||||||
|
|
||||||
float initial_value_;
|
float initial_value_;
|
||||||
std::unique_ptr<float, base::AlignedFreeDeleter> data_;
|
base::AlignedHeapArray<float> data_;
|
||||||
int data_len_;
|
|
||||||
float smoothing_factor_;
|
float smoothing_factor_;
|
||||||
float expected_final_avg_;
|
float expected_final_avg_;
|
||||||
float expected_max_;
|
float expected_max_ = 0.0f;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef testing::TestWithParam<EWMATestScenario> VectorMathEWMAAndMaxPowerTest;
|
typedef testing::TestWithParam<EWMATestScenario> VectorMathEWMAAndMaxPowerTest;
|
||||||
@ -309,104 +316,121 @@ INSTANTIATE_TEST_SUITE_P(
|
|||||||
VectorMathEWMAAndMaxPowerTest,
|
VectorMathEWMAAndMaxPowerTest,
|
||||||
::testing::Values(
|
::testing::Values(
|
||||||
// Zero-length input: Result should equal initial value.
|
// Zero-length input: Result should equal initial value.
|
||||||
EWMATestScenario(0.0f, NULL, 0, 0.0f).HasExpectedResult(0.0f, 0.0f),
|
EWMATestScenario(0.0f, base::span<float>(), 0.0f)
|
||||||
EWMATestScenario(1.0f, NULL, 0, 0.0f).HasExpectedResult(1.0f, 0.0f),
|
.HasExpectedResult(0.0f, 0.0f),
|
||||||
|
EWMATestScenario(1.0f, base::span<float>(), 0.0f)
|
||||||
|
.HasExpectedResult(1.0f, 0.0f),
|
||||||
|
|
||||||
// Smoothing factor of zero: Samples have no effect on result.
|
// Smoothing factor of zero: Samples have no effect on result.
|
||||||
EWMATestScenario(0.0f, kOnes, 32, 0.0f).HasExpectedResult(0.0f, 1.0f),
|
EWMATestScenario(0.0f, kOnes, 0.0f).HasExpectedResult(0.0f, 1.0f),
|
||||||
EWMATestScenario(1.0f, kZeros, 32, 0.0f).HasExpectedResult(1.0f, 0.0f),
|
EWMATestScenario(1.0f, kZeros, 0.0f).HasExpectedResult(1.0f, 0.0f),
|
||||||
|
|
||||||
// Smothing factor of one: Result = last sample squared.
|
// Smothing factor of one: Result = last sample squared.
|
||||||
EWMATestScenario(0.0f, kCheckerboard, 32, 1.0f)
|
EWMATestScenario(0.0f, kCheckerboard, 1.0f)
|
||||||
.ScaledBy(2.0f)
|
.ScaledBy(2.0f)
|
||||||
.HasExpectedResult(4.0f, 4.0f),
|
.HasExpectedResult(4.0f, 4.0f),
|
||||||
EWMATestScenario(1.0f, kInverseCheckerboard, 32, 1.0f)
|
EWMATestScenario(1.0f, kInverseCheckerboard, 1.0f)
|
||||||
.ScaledBy(2.0f)
|
.ScaledBy(2.0f)
|
||||||
.HasExpectedResult(0.0f, 4.0f),
|
.HasExpectedResult(0.0f, 4.0f),
|
||||||
|
|
||||||
// Smoothing factor of 1/4, muted signal.
|
// Smoothing factor of 1/4, muted signal.
|
||||||
EWMATestScenario(1.0f, kZeros, 1, 0.25f)
|
EWMATestScenario(1.0f, base::span(kZeros).first(1u), 0.25f)
|
||||||
.HasExpectedResult(std::pow(0.75f, 1.0f), 0.0f),
|
.HasExpectedResult(std::pow(0.75f, 1.0f), 0.0f),
|
||||||
EWMATestScenario(1.0f, kZeros, 2, 0.25f)
|
EWMATestScenario(1.0f, base::span(kZeros).first(2u), 0.25f)
|
||||||
.HasExpectedResult(std::pow(0.75f, 2.0f), 0.0f),
|
.HasExpectedResult(std::pow(0.75f, 2.0f), 0.0f),
|
||||||
EWMATestScenario(1.0f, kZeros, 3, 0.25f)
|
EWMATestScenario(1.0f, base::span(kZeros).first(3u), 0.25f)
|
||||||
.HasExpectedResult(std::pow(0.75f, 3.0f), 0.0f),
|
.HasExpectedResult(std::pow(0.75f, 3.0f), 0.0f),
|
||||||
EWMATestScenario(1.0f, kZeros, 12, 0.25f)
|
EWMATestScenario(1.0f, base::span(kZeros).first(12u), 0.25f)
|
||||||
.HasExpectedResult(std::pow(0.75f, 12.0f), 0.0f),
|
.HasExpectedResult(std::pow(0.75f, 12.0f), 0.0f),
|
||||||
EWMATestScenario(1.0f, kZeros, 13, 0.25f)
|
EWMATestScenario(1.0f, base::span(kZeros).first(13u), 0.25f)
|
||||||
.HasExpectedResult(std::pow(0.75f, 13.0f), 0.0f),
|
.HasExpectedResult(std::pow(0.75f, 13.0f), 0.0f),
|
||||||
EWMATestScenario(1.0f, kZeros, 14, 0.25f)
|
EWMATestScenario(1.0f, base::span(kZeros).first(14u), 0.25f)
|
||||||
.HasExpectedResult(std::pow(0.75f, 14.0f), 0.0f),
|
.HasExpectedResult(std::pow(0.75f, 14.0f), 0.0f),
|
||||||
EWMATestScenario(1.0f, kZeros, 15, 0.25f)
|
EWMATestScenario(1.0f, base::span(kZeros).first(15u), 0.25f)
|
||||||
.HasExpectedResult(std::pow(0.75f, 15.0f), 0.0f),
|
.HasExpectedResult(std::pow(0.75f, 15.0f), 0.0f),
|
||||||
|
|
||||||
// Smoothing factor of 1/4, constant full-amplitude signal.
|
// Smoothing factor of 1/4, constant full-amplitude signal.
|
||||||
EWMATestScenario(0.0f, kOnes, 1, 0.25f).HasExpectedResult(0.25f, 1.0f),
|
EWMATestScenario(0.0f, base::span(kOnes).first(1u), 0.25f)
|
||||||
EWMATestScenario(0.0f, kOnes, 2, 0.25f)
|
.HasExpectedResult(0.25f, 1.0f),
|
||||||
|
EWMATestScenario(0.0f, base::span(kOnes).first(2u), 0.25f)
|
||||||
.HasExpectedResult(0.4375f, 1.0f),
|
.HasExpectedResult(0.4375f, 1.0f),
|
||||||
EWMATestScenario(0.0f, kOnes, 3, 0.25f)
|
EWMATestScenario(0.0f, base::span(kOnes).first(3u), 0.25f)
|
||||||
.HasExpectedResult(0.578125f, 1.0f),
|
.HasExpectedResult(0.578125f, 1.0f),
|
||||||
EWMATestScenario(0.0f, kOnes, 12, 0.25f)
|
EWMATestScenario(0.0f, base::span(kOnes).first(12u), 0.25f)
|
||||||
.HasExpectedResult(0.96832365f, 1.0f),
|
.HasExpectedResult(0.96832365f, 1.0f),
|
||||||
EWMATestScenario(0.0f, kOnes, 13, 0.25f)
|
EWMATestScenario(0.0f, base::span(kOnes).first(13u), 0.25f)
|
||||||
.HasExpectedResult(0.97624274f, 1.0f),
|
.HasExpectedResult(0.97624274f, 1.0f),
|
||||||
EWMATestScenario(0.0f, kOnes, 14, 0.25f)
|
EWMATestScenario(0.0f, base::span(kOnes).first(14u), 0.25f)
|
||||||
.HasExpectedResult(0.98218205f, 1.0f),
|
.HasExpectedResult(0.98218205f, 1.0f),
|
||||||
EWMATestScenario(0.0f, kOnes, 15, 0.25f)
|
EWMATestScenario(0.0f, base::span(kOnes).first(15u), 0.25f)
|
||||||
.HasExpectedResult(0.98663654f, 1.0f),
|
.HasExpectedResult(0.98663654f, 1.0f),
|
||||||
|
|
||||||
// Smoothing factor of 1/4, checkerboard signal.
|
// Smoothing factor of 1/4, checkerboard signal.
|
||||||
EWMATestScenario(0.0f, kCheckerboard, 1, 0.25f)
|
EWMATestScenario(0.0f, base::span(kCheckerboard).first(1u), 0.25f)
|
||||||
.HasExpectedResult(0.0f, 0.0f),
|
.HasExpectedResult(0.0f, 0.0f),
|
||||||
EWMATestScenario(0.0f, kCheckerboard, 2, 0.25f)
|
EWMATestScenario(0.0f, base::span(kCheckerboard).first(2u), 0.25f)
|
||||||
.HasExpectedResult(0.25f, 1.0f),
|
.HasExpectedResult(0.25f, 1.0f),
|
||||||
EWMATestScenario(0.0f, kCheckerboard, 3, 0.25f)
|
EWMATestScenario(0.0f, base::span(kCheckerboard).first(3u), 0.25f)
|
||||||
.HasExpectedResult(0.1875f, 1.0f),
|
.HasExpectedResult(0.1875f, 1.0f),
|
||||||
EWMATestScenario(0.0f, kCheckerboard, 12, 0.25f)
|
EWMATestScenario(0.0f, base::span(kCheckerboard).first(12u), 0.25f)
|
||||||
.HasExpectedResult(0.55332780f, 1.0f),
|
.HasExpectedResult(0.55332780f, 1.0f),
|
||||||
EWMATestScenario(0.0f, kCheckerboard, 13, 0.25f)
|
EWMATestScenario(0.0f, base::span(kCheckerboard).first(13u), 0.25f)
|
||||||
.HasExpectedResult(0.41499585f, 1.0f),
|
.HasExpectedResult(0.41499585f, 1.0f),
|
||||||
EWMATestScenario(0.0f, kCheckerboard, 14, 0.25f)
|
EWMATestScenario(0.0f, base::span(kCheckerboard).first(14u), 0.25f)
|
||||||
.HasExpectedResult(0.56124689f, 1.0f),
|
.HasExpectedResult(0.56124689f, 1.0f),
|
||||||
EWMATestScenario(0.0f, kCheckerboard, 15, 0.25f)
|
EWMATestScenario(0.0f, base::span(kCheckerboard).first(15u), 0.25f)
|
||||||
.HasExpectedResult(0.42093517f, 1.0f),
|
.HasExpectedResult(0.42093517f, 1.0f),
|
||||||
|
|
||||||
// Smoothing factor of 1/4, inverse checkerboard signal.
|
// Smoothing factor of 1/4, inverse checkerboard signal.
|
||||||
EWMATestScenario(0.0f, kInverseCheckerboard, 1, 0.25f)
|
EWMATestScenario(0.0f,
|
||||||
|
base::span(kInverseCheckerboard).first(1u),
|
||||||
|
0.25f)
|
||||||
.HasExpectedResult(0.25f, 1.0f),
|
.HasExpectedResult(0.25f, 1.0f),
|
||||||
EWMATestScenario(0.0f, kInverseCheckerboard, 2, 0.25f)
|
EWMATestScenario(0.0f,
|
||||||
|
base::span(kInverseCheckerboard).first(2u),
|
||||||
|
0.25f)
|
||||||
.HasExpectedResult(0.1875f, 1.0f),
|
.HasExpectedResult(0.1875f, 1.0f),
|
||||||
EWMATestScenario(0.0f, kInverseCheckerboard, 3, 0.25f)
|
EWMATestScenario(0.0f,
|
||||||
|
base::span(kInverseCheckerboard).first(3u),
|
||||||
|
0.25f)
|
||||||
.HasExpectedResult(0.390625f, 1.0f),
|
.HasExpectedResult(0.390625f, 1.0f),
|
||||||
EWMATestScenario(0.0f, kInverseCheckerboard, 12, 0.25f)
|
EWMATestScenario(0.0f,
|
||||||
|
base::span(kInverseCheckerboard).first(12u),
|
||||||
|
0.25f)
|
||||||
.HasExpectedResult(0.41499585f, 1.0f),
|
.HasExpectedResult(0.41499585f, 1.0f),
|
||||||
EWMATestScenario(0.0f, kInverseCheckerboard, 13, 0.25f)
|
EWMATestScenario(0.0f,
|
||||||
|
base::span(kInverseCheckerboard).first(13u),
|
||||||
|
0.25f)
|
||||||
.HasExpectedResult(0.56124689f, 1.0f),
|
.HasExpectedResult(0.56124689f, 1.0f),
|
||||||
EWMATestScenario(0.0f, kInverseCheckerboard, 14, 0.25f)
|
EWMATestScenario(0.0f,
|
||||||
|
base::span(kInverseCheckerboard).first(14u),
|
||||||
|
0.25f)
|
||||||
.HasExpectedResult(0.42093517f, 1.0f),
|
.HasExpectedResult(0.42093517f, 1.0f),
|
||||||
EWMATestScenario(0.0f, kInverseCheckerboard, 15, 0.25f)
|
EWMATestScenario(0.0f,
|
||||||
|
base::span(kInverseCheckerboard).first(15u),
|
||||||
|
0.25f)
|
||||||
.HasExpectedResult(0.56570137f, 1.0f),
|
.HasExpectedResult(0.56570137f, 1.0f),
|
||||||
|
|
||||||
// Smoothing factor of 1/4, impluse signal.
|
// Smoothing factor of 1/4, impluse signal.
|
||||||
EWMATestScenario(0.0f, kZeros, 3, 0.25f)
|
EWMATestScenario(0.0f, base::span(kZeros).first(3u), 0.25f)
|
||||||
.WithImpulse(2.0f, 0)
|
.WithImpulse(2.0f, 0)
|
||||||
.HasExpectedResult(0.562500f, 4.0f),
|
.HasExpectedResult(0.562500f, 4.0f),
|
||||||
EWMATestScenario(0.0f, kZeros, 3, 0.25f)
|
EWMATestScenario(0.0f, base::span(kZeros).first(3u), 0.25f)
|
||||||
.WithImpulse(2.0f, 1)
|
.WithImpulse(2.0f, 1)
|
||||||
.HasExpectedResult(0.75f, 4.0f),
|
.HasExpectedResult(0.75f, 4.0f),
|
||||||
EWMATestScenario(0.0f, kZeros, 3, 0.25f)
|
EWMATestScenario(0.0f, base::span(kZeros).first(3u), 0.25f)
|
||||||
.WithImpulse(2.0f, 2)
|
.WithImpulse(2.0f, 2)
|
||||||
.HasExpectedResult(1.0f, 4.0f),
|
.HasExpectedResult(1.0f, 4.0f),
|
||||||
EWMATestScenario(0.0f, kZeros, 32, 0.25f)
|
EWMATestScenario(0.0f, kZeros, 0.25f)
|
||||||
.WithImpulse(2.0f, 0)
|
.WithImpulse(2.0f, 0)
|
||||||
.HasExpectedResult(0.00013394f, 4.0f),
|
.HasExpectedResult(0.00013394f, 4.0f),
|
||||||
EWMATestScenario(0.0f, kZeros, 32, 0.25f)
|
EWMATestScenario(0.0f, kZeros, 0.25f)
|
||||||
.WithImpulse(2.0f, 1)
|
.WithImpulse(2.0f, 1)
|
||||||
.HasExpectedResult(0.00017858f, 4.0f),
|
.HasExpectedResult(0.00017858f, 4.0f),
|
||||||
EWMATestScenario(0.0f, kZeros, 32, 0.25f)
|
EWMATestScenario(0.0f, kZeros, 0.25f)
|
||||||
.WithImpulse(2.0f, 2)
|
.WithImpulse(2.0f, 2)
|
||||||
.HasExpectedResult(0.00023811f, 4.0f),
|
.HasExpectedResult(0.00023811f, 4.0f),
|
||||||
EWMATestScenario(0.0f, kZeros, 32, 0.25f)
|
EWMATestScenario(0.0f, kZeros, 0.25f)
|
||||||
.WithImpulse(2.0f, 3)
|
.WithImpulse(2.0f, 3)
|
||||||
.HasExpectedResult(0.00031748f, 4.0f)));
|
.HasExpectedResult(0.00031748f, 4.0f)));
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
#include "base/numerics/safe_conversions.h"
|
#include "base/numerics/safe_conversions.h"
|
||||||
#include "base/trace_event/trace_event.h"
|
#include "base/trace_event/trace_event.h"
|
||||||
|
#include "base/types/zip.h"
|
||||||
#include "media/base/audio_bus.h"
|
#include "media/base/audio_bus.h"
|
||||||
#include "media/base/vector_math.h"
|
#include "media/base/vector_math.h"
|
||||||
|
|
||||||
@ -36,9 +37,9 @@ void DelayBuffer::Write(FrameTicks position,
|
|||||||
// Make a copy of the AudioBus for later consumption. Apply the volume setting
|
// Make a copy of the AudioBus for later consumption. Apply the volume setting
|
||||||
// by scaling the audio signal during the copy.
|
// by scaling the audio signal during the copy.
|
||||||
auto copy = media::AudioBus::Create(input_bus.channels(), input_bus.frames());
|
auto copy = media::AudioBus::Create(input_bus.channels(), input_bus.frames());
|
||||||
for (int ch = 0; ch < input_bus.channels(); ++ch) {
|
for (auto [src_ch, dest_ch] :
|
||||||
media::vector_math::FMUL(input_bus.channel(ch), volume, input_bus.frames(),
|
base::zip(input_bus.AllChannels(), copy->AllChannels())) {
|
||||||
copy->channel(ch));
|
media::vector_math::FMUL(src_ch, volume, dest_ch);
|
||||||
}
|
}
|
||||||
|
|
||||||
chunks_.emplace_back(position, std::move(copy));
|
chunks_.emplace_back(position, std::move(copy));
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
#include "base/task/sequenced_task_runner.h"
|
#include "base/task/sequenced_task_runner.h"
|
||||||
#include "base/time/default_tick_clock.h"
|
#include "base/time/default_tick_clock.h"
|
||||||
#include "base/trace_event/trace_event.h"
|
#include "base/trace_event/trace_event.h"
|
||||||
|
#include "base/types/zip.h"
|
||||||
#include "media/base/audio_bus.h"
|
#include "media/base/audio_bus.h"
|
||||||
#include "media/base/vector_math.h"
|
#include "media/base/vector_math.h"
|
||||||
#include "mojo/public/cpp/system/buffer.h"
|
#include "mojo/public/cpp/system/buffer.h"
|
||||||
@ -353,10 +354,9 @@ void LoopbackStream::FlowNetwork::GenerateMoreAudio() {
|
|||||||
}
|
}
|
||||||
do {
|
do {
|
||||||
(*it)->Render(delayed_capture_time, transfer_bus_.get());
|
(*it)->Render(delayed_capture_time, transfer_bus_.get());
|
||||||
for (int ch = 0; ch < transfer_bus_->channels(); ++ch) {
|
for (auto [src_ch, dest_ch] : base::zip(transfer_bus_->AllChannels(),
|
||||||
media::vector_math::FMAC(transfer_bus_->channel(ch), volume_,
|
mix_bus_->AllChannels())) {
|
||||||
transfer_bus_->frames(),
|
media::vector_math::FMAC(src_ch, volume_, dest_ch);
|
||||||
mix_bus_->channel(ch));
|
|
||||||
}
|
}
|
||||||
++it;
|
++it;
|
||||||
} while (it != inputs_.end());
|
} while (it != inputs_.end());
|
||||||
|
Reference in New Issue
Block a user