|  | // Copyright 2016 The Chromium Authors | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #ifdef UNSAFE_BUFFERS_BUILD | 
|  | // TODO(crbug.com/40285824): Remove this and convert code to safer constructs. | 
|  | #pragma allow_unsafe_buffers | 
|  | #endif | 
|  |  | 
|  | #include "chromecast/media/base/slew_volume.h" | 
|  |  | 
|  | #include <algorithm> | 
|  | #include <cmath> | 
|  | #include <cstring> | 
|  |  | 
|  | #include "base/check_op.h" | 
|  | #include "base/containers/span.h" | 
|  | #include "media/base/vector_math.h" | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | // The time to slew from current volume to target volume. | 
|  | const int kMaxSlewTimeMs = 100; | 
|  | const int kDefaultSampleRate = 44100; | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | struct FMACTraits { | 
|  | static void ProcessBulkData(const float* src, | 
|  | float volume, | 
|  | int frames, | 
|  | float* 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) { | 
|  | (*dest) += (*src) * volume; | 
|  | } | 
|  |  | 
|  | static void ProcessZeroVolume(const float* src, int frames, float* dest) {} | 
|  |  | 
|  | static void ProcessUnityVolume(const float* src, int frames, float* dest) { | 
|  | ProcessBulkData(src, 1.0, frames, dest); | 
|  | } | 
|  | }; | 
|  |  | 
|  | struct FMULTraits { | 
|  | static void ProcessBulkData(const float* src, | 
|  | float volume, | 
|  | int frames, | 
|  | float* 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) { | 
|  | (*dest) = (*src) * volume; | 
|  | } | 
|  |  | 
|  | static void ProcessZeroVolume(const float* src, int frames, float* dest) { | 
|  | std::memset(dest, 0, frames * sizeof(*dest)); | 
|  | } | 
|  |  | 
|  | static void ProcessUnityVolume(const float* src, int frames, float* dest) { | 
|  | if (src == dest) { | 
|  | return; | 
|  | } | 
|  | std::memcpy(dest, src, frames * sizeof(*dest)); | 
|  | } | 
|  | }; | 
|  |  | 
|  | namespace chromecast { | 
|  | namespace media { | 
|  |  | 
|  | SlewVolume::SlewVolume() : SlewVolume(kMaxSlewTimeMs) {} | 
|  |  | 
|  | SlewVolume::SlewVolume(int max_slew_time_ms) | 
|  | : SlewVolume(max_slew_time_ms, false) {} | 
|  |  | 
|  | SlewVolume::SlewVolume(int max_slew_time_ms, bool use_cosine_slew) | 
|  | : sample_rate_(kDefaultSampleRate), | 
|  | max_slew_time_ms_(max_slew_time_ms), | 
|  | max_slew_per_sample_(1000.0 / (max_slew_time_ms_ * sample_rate_)), | 
|  | use_cosine_slew_(use_cosine_slew) {} | 
|  |  | 
|  | void SlewVolume::SetSampleRate(int sample_rate) { | 
|  | CHECK_GT(sample_rate, 0); | 
|  |  | 
|  | sample_rate_ = sample_rate; | 
|  | SetVolume(volume_scale_); | 
|  | } | 
|  |  | 
|  | void SlewVolume::SetVolume(double volume_scale) { | 
|  | volume_scale_ = volume_scale; | 
|  | if (interrupted_) { | 
|  | current_volume_ = volume_scale_; | 
|  | last_starting_volume_ = current_volume_; | 
|  | } | 
|  |  | 
|  | // Slew rate should be volume_to_slew / slew_time / sample_rate, but use a | 
|  | // minimum volume_to_slew of 0.1 to avoid very small slew per sample. | 
|  | double volume_diff = | 
|  | std::max(0.1, std::fabs(volume_scale_ - current_volume_)); | 
|  | max_slew_per_sample_ = | 
|  | volume_diff * 1000.0 / (max_slew_time_ms_ * sample_rate_); | 
|  |  | 
|  | if (use_cosine_slew_) { | 
|  | // Set initial state for cosine slew. Cosine fading always lasts | 
|  | // max_slew_time_ms_. | 
|  | slew_counter_ = max_slew_time_ms_ * 0.001 * sample_rate_; | 
|  | slew_angle_ = sin(M_PI / slew_counter_); | 
|  | slew_offset_ = (current_volume_ + volume_scale_) * 0.5; | 
|  | slew_cos_ = (current_volume_ - volume_scale_) * 0.5; | 
|  | slew_sin_ = 0.0; | 
|  | } | 
|  | } | 
|  |  | 
|  | float SlewVolume::LastBufferMaxMultiplier() { | 
|  | return std::max(current_volume_, last_starting_volume_); | 
|  | } | 
|  |  | 
|  | void SlewVolume::SetMaxSlewTimeMs(int max_slew_time_ms) { | 
|  | CHECK_GE(max_slew_time_ms, 0); | 
|  |  | 
|  | max_slew_time_ms_ = max_slew_time_ms; | 
|  | } | 
|  |  | 
|  | void SlewVolume::Interrupted() { | 
|  | interrupted_ = true; | 
|  | current_volume_ = volume_scale_; | 
|  | } | 
|  |  | 
|  | void SlewVolume::ProcessFMAC(bool repeat_transition, | 
|  | const float* src, | 
|  | int frames, | 
|  | int channels, | 
|  | float* dest) { | 
|  | ProcessData<FMACTraits>(repeat_transition, src, frames, channels, dest); | 
|  | } | 
|  |  | 
|  | void SlewVolume::ProcessFMUL(bool repeat_transition, | 
|  | const float* src, | 
|  | int frames, | 
|  | int channels, | 
|  | float* dest) { | 
|  | ProcessData<FMULTraits>(repeat_transition, src, frames, channels, dest); | 
|  | } | 
|  |  | 
|  | template <typename Traits> | 
|  | void SlewVolume::ProcessData(bool repeat_transition, | 
|  | const float* src, | 
|  | int frames, | 
|  | int channels, | 
|  | float* dest) { | 
|  | DCHECK(src); | 
|  | DCHECK(dest); | 
|  | // Ensure |src| and |dest| are 16-byte aligned. | 
|  | DCHECK_EQ(0u, reinterpret_cast<uintptr_t>(src) & | 
|  | (::media::vector_math::kRequiredAlignment - 1)); | 
|  | DCHECK_EQ(0u, reinterpret_cast<uintptr_t>(dest) & | 
|  | (::media::vector_math::kRequiredAlignment - 1)); | 
|  |  | 
|  | if (!frames) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | interrupted_ = false; | 
|  | if (repeat_transition) { | 
|  | current_volume_ = last_starting_volume_; | 
|  | } else { | 
|  | last_starting_volume_ = current_volume_; | 
|  | } | 
|  |  | 
|  | if (current_volume_ == volume_scale_) { | 
|  | if (current_volume_ == 0.0) { | 
|  | Traits::ProcessZeroVolume(src, frames * channels, dest); | 
|  | return; | 
|  | } | 
|  | if (current_volume_ == 1.0) { | 
|  | Traits::ProcessUnityVolume(src, frames * channels, dest); | 
|  | return; | 
|  | } | 
|  | Traits::ProcessBulkData(src, current_volume_, frames * channels, dest); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (use_cosine_slew_) { | 
|  | int slew_frames = std::min(slew_counter_, frames); | 
|  | frames -= slew_frames; | 
|  | slew_counter_ -= slew_frames; | 
|  | for (; slew_frames > 0; --slew_frames) { | 
|  | slew_cos_ -= slew_sin_ * slew_angle_; | 
|  | slew_sin_ += slew_cos_ * slew_angle_; | 
|  | current_volume_ = std::clamp(slew_offset_ + slew_cos_, 0.0, 1.0); | 
|  | for (int i = 0; i < channels; ++i) { | 
|  | Traits::ProcessSingleDatum(src, current_volume_, dest); | 
|  | ++src; | 
|  | ++dest; | 
|  | } | 
|  | } | 
|  | if (!slew_counter_) { | 
|  | current_volume_ = volume_scale_; | 
|  | } | 
|  | } else if (current_volume_ < volume_scale_) { | 
|  | do { | 
|  | for (int i = 0; i < channels; ++i) { | 
|  | Traits::ProcessSingleDatum(src, current_volume_, dest); | 
|  | ++src; | 
|  | ++dest; | 
|  | } | 
|  | --frames; | 
|  | current_volume_ += max_slew_per_sample_; | 
|  | } while (current_volume_ < volume_scale_ && frames); | 
|  | current_volume_ = std::min(current_volume_, volume_scale_); | 
|  | } else {  // current_volume_ > volume_scale_ | 
|  | do { | 
|  | for (int i = 0; i < channels; ++i) { | 
|  | Traits::ProcessSingleDatum(src, current_volume_, dest); | 
|  | ++src; | 
|  | ++dest; | 
|  | } | 
|  | --frames; | 
|  | current_volume_ -= max_slew_per_sample_; | 
|  | } while (current_volume_ > volume_scale_ && frames); | 
|  | current_volume_ = std::max(current_volume_, volume_scale_); | 
|  | } | 
|  | while (frames && (reinterpret_cast<uintptr_t>(src) & | 
|  | (::media::vector_math::kRequiredAlignment - 1))) { | 
|  | for (int i = 0; i < channels; ++i) { | 
|  | Traits::ProcessSingleDatum(src, current_volume_, dest); | 
|  | ++src; | 
|  | ++dest; | 
|  | } | 
|  | --frames; | 
|  | } | 
|  | if (!frames) { | 
|  | return; | 
|  | } | 
|  | Traits::ProcessBulkData(src, current_volume_, frames * channels, dest); | 
|  | } | 
|  |  | 
|  | }  // namespace media | 
|  | }  // namespace chromecast |