| // Copyright 2016 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 "garnet/bin/media/audio_core/mixer/linear_sampler.h" |
| |
| #include <algorithm> |
| #include <limits> |
| |
| #include "garnet/bin/media/audio_core/mixer/constants.h" |
| #include "garnet/bin/media/audio_core/mixer/mixer_utils.h" |
| #include "lib/fxl/logging.h" |
| |
| namespace media::audio::mixer { |
| |
| // We specify alpha in fixed-point 19.13: a max val of "1.0" is 0x00002000. |
| constexpr float kFramesPerPtsSubframe = 1.0f / (1 << kPtsFractionalBits); |
| |
| inline float Interpolate(float A, float B, uint32_t alpha) { |
| return ((B - A) * kFramesPerPtsSubframe * alpha) + A; |
| } |
| |
| template <size_t DestChanCount, typename SrcSampleType, size_t SrcChanCount> |
| class LinearSamplerImpl : public LinearSampler { |
| public: |
| LinearSamplerImpl() : LinearSampler(FRAC_ONE - 1, FRAC_ONE - 1) {} |
| |
| bool Mix(float* dest, uint32_t dest_frames, uint32_t* dest_offset, |
| const void* src, uint32_t frac_src_frames, int32_t* frac_src_offset, |
| bool accumulate, Bookkeeping* info) override; |
| |
| // If/when Bookkeeping is included in this class, clear src_pos_modulo here. |
| void Reset() override { ::memset(filter_data_, 0, sizeof(filter_data_)); } |
| |
| private: |
| template <ScalerType ScaleType, bool DoAccumulate, bool HasModulo> |
| inline bool Mix(float* dest, uint32_t dest_frames, uint32_t* dest_offset, |
| const void* src, uint32_t frac_src_frames, |
| int32_t* frac_src_offset, Bookkeeping* info); |
| |
| float filter_data_[2 * DestChanCount] = {0.0f}; |
| }; |
| |
| // TODO(mpuryear): MTWN-75 factor to minimize LinearSamplerImpl code duplication |
| template <typename SrcSampleType> |
| class NxNLinearSamplerImpl : public LinearSampler { |
| public: |
| NxNLinearSamplerImpl(size_t channelCount) |
| : LinearSampler(FRAC_ONE - 1, FRAC_ONE - 1), chan_count_(channelCount) { |
| filter_data_u_ = std::make_unique<float[]>(2 * chan_count_); |
| |
| ::memset(filter_data_u_.get(), 0, |
| 2 * chan_count_ * sizeof(filter_data_u_[0])); |
| } |
| |
| bool Mix(float* dest, uint32_t dest_frames, uint32_t* dest_offset, |
| const void* src, uint32_t frac_src_frames, int32_t* frac_src_offset, |
| bool accumulate, Bookkeeping* info) override; |
| |
| // If/when Bookkeeping is included in this class, clear src_pos_modulo here. |
| void Reset() override { |
| ::memset(filter_data_u_.get(), 0, |
| 2 * chan_count_ * sizeof(filter_data_u_[0])); |
| } |
| |
| private: |
| template <ScalerType ScaleType, bool DoAccumulate, bool HasModulo> |
| inline bool Mix(float* dest, uint32_t dest_frames, uint32_t* dest_offset, |
| const void* src, uint32_t frac_src_frames, |
| int32_t* frac_src_offset, Bookkeeping* info, |
| size_t chan_count); |
| |
| size_t chan_count_; |
| std::unique_ptr<float[]> filter_data_u_; |
| }; |
| |
| // If upper layers call with ScaleType MUTED, they must set DoAccumulate=TRUE. |
| // They guarantee new buffers are cleared before usage; we optimize accordingly. |
| template <size_t DestChanCount, typename SrcSampleType, size_t SrcChanCount> |
| template <ScalerType ScaleType, bool DoAccumulate, bool HasModulo> |
| inline bool LinearSamplerImpl<DestChanCount, SrcSampleType, SrcChanCount>::Mix( |
| float* dest, uint32_t dest_frames, uint32_t* dest_offset, |
| const void* src_void, uint32_t frac_src_frames, int32_t* frac_src_offset, |
| Bookkeeping* info) { |
| static_assert( |
| ScaleType != ScalerType::MUTED || DoAccumulate == true, |
| "Mixing muted streams without accumulation is explicitly unsupported"); |
| |
| // Although the number of source frames is expressed in fixed-point 19.13 |
| // format, the actual number of frames must always be an integer. |
| FXL_DCHECK((frac_src_frames & kPtsFractionalMask) == 0); |
| FXL_DCHECK(frac_src_frames >= FRAC_ONE); |
| // Interpolation offset is int32, so even though frac_src_frames is a uint32, |
| // callers should not exceed int32_t::max(). |
| FXL_DCHECK(frac_src_frames <= |
| static_cast<uint32_t>(std::numeric_limits<int32_t>::max())); |
| |
| using SR = SrcReader<SrcSampleType, SrcChanCount, DestChanCount>; |
| using DM = DestMixer<ScaleType, DoAccumulate>; |
| const auto* src = static_cast<const SrcSampleType*>(src_void); |
| |
| uint32_t dest_off = *dest_offset; |
| uint32_t dest_off_start = dest_off; // Only used when ramping. |
| |
| int32_t src_off = *frac_src_offset; |
| |
| // Cache these locally, in the template specialization that uses them. |
| // Only src_pos_modulo needs to be written back before returning. |
| uint32_t step_size = info->step_size; |
| uint32_t rate_modulo, denominator, src_pos_modulo; |
| if constexpr (HasModulo) { |
| rate_modulo = info->rate_modulo; |
| denominator = info->denominator; |
| src_pos_modulo = info->src_pos_modulo; |
| |
| FXL_DCHECK(denominator > 0); |
| FXL_DCHECK(denominator > rate_modulo); |
| FXL_DCHECK(denominator > src_pos_modulo); |
| } |
| if constexpr (kVerboseRampDebug) { |
| FXL_LOG(INFO) << "Linear(" << this |
| << ") Ramping: " << (ScaleType == ScalerType::RAMPING) |
| << ", dest_frames: " << dest_frames |
| << ", dest_off: " << dest_off; |
| } |
| if constexpr (ScaleType == ScalerType::RAMPING) { |
| if (dest_frames > Bookkeeping::kScaleArrLen + dest_off) { |
| dest_frames = Bookkeeping::kScaleArrLen + dest_off; |
| } |
| } |
| |
| // "Source end" is the last valid input sub-frame that can be sampled. |
| auto src_end = static_cast<int32_t>(frac_src_frames - pos_filter_width() - 1); |
| |
| FXL_DCHECK(dest_off < dest_frames); |
| FXL_DCHECK(src_end >= 0); |
| FXL_DCHECK(frac_src_frames <= |
| static_cast<uint32_t>(std::numeric_limits<int32_t>::max())); |
| // "Source offset" can be negative, but within the bounds of pos_filter_width. |
| // Otherwise, all these samples are in the future and irrelevant here. Callers |
| // explicitly avoid calling Mix in this case, so we have detected an error. |
| // For linear_sampler it implies a requirement that src_off > -FRAC_ONE. |
| FXL_DCHECK(src_off + static_cast<int32_t>(pos_filter_width()) >= 0) |
| << std::hex << "min allowed: 0x" << -pos_filter_width() << ", src_off: 0x" |
| << src_off; |
| // Source offset must also be within neg_filter_width of our last sample. |
| // Otherwise, all these samples are in the past and irrelevant here. Callers |
| // explicitly avoid calling Mix in this case, so we have detected an error. |
| // For linear_sampler this implies that src_off < frac_src_frames. |
| FXL_DCHECK(src_off + FRAC_ONE <= frac_src_frames + neg_filter_width()) |
| << std::hex << "max allowed: 0x" |
| << frac_src_frames + neg_filter_width() - FRAC_ONE << ", src_off: 0x" |
| << src_off; |
| |
| Gain::AScale amplitude_scale; |
| if constexpr (ScaleType != ScalerType::RAMPING) { |
| amplitude_scale = info->gain.GetGainScale(); |
| } |
| |
| // TODO(mpuryear): optimize the logic below for common-case performance. |
| |
| // If we are not attenuated to the point of being muted, go ahead and perform |
| // the mix. Otherwise, just update the source and dest offsets and hold onto |
| // any relevant filter data from the end of the source. |
| if constexpr (ScaleType != ScalerType::MUTED) { |
| // If src_off is negative, we must incorporate previously-cached samples. |
| // Add a new sample, to complete the filter set, and compute the output. |
| if (src_off < 0) { |
| for (size_t D = 0; D < DestChanCount; ++D) { |
| filter_data_[DestChanCount + D] = SR::Read(src + (D / SR::DestPerSrc)); |
| } |
| |
| while ((dest_off < dest_frames) && (src_off < 0)) { |
| if constexpr (ScaleType == ScalerType::RAMPING) { |
| amplitude_scale = info->scale_arr[dest_off - dest_off_start]; |
| } |
| |
| float* out = dest + (dest_off * DestChanCount); |
| |
| for (size_t D = 0; D < DestChanCount; ++D) { |
| float sample = |
| Interpolate(filter_data_[D], filter_data_[DestChanCount + D], |
| src_off + FRAC_ONE); |
| out[D] = DM::Mix(out[D], sample, amplitude_scale); |
| } |
| |
| dest_off += 1; |
| src_off += step_size; |
| |
| if constexpr (HasModulo) { |
| src_pos_modulo += rate_modulo; |
| if (src_pos_modulo >= denominator) { |
| ++src_off; |
| src_pos_modulo -= denominator; |
| } |
| } |
| } |
| } |
| |
| // Now we are fully in the current buffer and need not rely on our cache. |
| while ((dest_off < dest_frames) && (src_off < src_end)) { |
| uint32_t S = (src_off >> kPtsFractionalBits) * SrcChanCount; |
| float* out = dest + (dest_off * DestChanCount); |
| if constexpr (ScaleType == ScalerType::RAMPING) { |
| amplitude_scale = info->scale_arr[dest_off - dest_off_start]; |
| } |
| |
| for (size_t D = 0; D < DestChanCount; ++D) { |
| float s1 = SR::Read(src + S + (D / SR::DestPerSrc)); |
| float s2 = SR::Read(src + S + (D / SR::DestPerSrc) + SrcChanCount); |
| float sample = Interpolate(s1, s2, src_off & FRAC_MASK); |
| out[D] = DM::Mix(out[D], sample, amplitude_scale); |
| } |
| |
| dest_off += 1; |
| src_off += step_size; |
| |
| if constexpr (HasModulo) { |
| src_pos_modulo += rate_modulo; |
| if (src_pos_modulo >= denominator) { |
| ++src_off; |
| src_pos_modulo -= denominator; |
| } |
| } |
| } |
| } else { |
| // We are muted. Don't mix, but figure out how many samples we WOULD have |
| // produced and update the src_off and dest_off values appropriately. |
| if ((dest_off < dest_frames) && (src_off < src_end)) { |
| uint32_t src_avail = (((src_end - src_off) + step_size - 1) / step_size); |
| uint32_t dest_avail = (dest_frames - dest_off); |
| uint32_t avail = std::min(src_avail, dest_avail); |
| |
| dest_off += avail; |
| src_off += avail * step_size; |
| |
| if constexpr (HasModulo) { |
| src_pos_modulo += (rate_modulo * avail); |
| src_off += (src_pos_modulo / denominator); |
| src_pos_modulo %= denominator; |
| } |
| } |
| } |
| |
| // If we have room for at least one more sample, and our sampling position |
| // hits the input buffer's final frame exactly ... |
| if ((dest_off < dest_frames) && (src_off == src_end)) { |
| // ... and if we are not muted, of course ... |
| if constexpr (ScaleType != ScalerType::MUTED) { |
| // ... then we can _point-sample_ one final frame into our output buffer. |
| // We need not _interpolate_ since fractional position is exactly zero. |
| uint32_t S = (src_off >> kPtsFractionalBits) * SrcChanCount; |
| float* out = dest + (dest_off * DestChanCount); |
| if constexpr (ScaleType == ScalerType::RAMPING) { |
| amplitude_scale = info->scale_arr[dest_off - dest_off_start]; |
| } |
| |
| for (size_t D = 0; D < DestChanCount; ++D) { |
| float sample = SR::Read(src + S + (D / SR::DestPerSrc)); |
| out[D] = DM::Mix(out[D], sample, amplitude_scale); |
| } |
| } |
| |
| dest_off += 1; |
| src_off += step_size; |
| |
| if constexpr (HasModulo) { |
| src_pos_modulo += rate_modulo; |
| if (src_pos_modulo >= denominator) { |
| ++src_off; |
| src_pos_modulo -= denominator; |
| } |
| } |
| } |
| |
| // Update all our returned in-out parameters |
| *dest_offset = dest_off; |
| *frac_src_offset = src_off; |
| if constexpr (HasModulo) { |
| info->src_pos_modulo = src_pos_modulo; |
| } |
| |
| // If next source position to consume is beyond start of last frame ... |
| if (src_off > src_end) { |
| uint32_t S = (src_end >> kPtsFractionalBits) * SrcChanCount; |
| // ... and if we are not mute, of course... |
| if constexpr (ScaleType != ScalerType::MUTED) { |
| // ... cache our final frame for use in future interpolation ... |
| for (size_t D = 0; D < DestChanCount; ++D) { |
| filter_data_[D] = SR::Read(src + S + (D / SR::DestPerSrc)); |
| } |
| } else { |
| // ... otherwise cache silence (which is what we actually produced). |
| for (size_t D = 0; D < DestChanCount; ++D) { |
| filter_data_[D] = 0; |
| } |
| } |
| |
| // At this point the source offset (src_off) is either somewhere within |
| // the last source sample, or entirely beyond the end of the source buffer |
| // (if frac_step_size is greater than unity). Either way, we've extracted |
| // all of the information from this source buffer, and can return TRUE. |
| return true; |
| } |
| |
| // Source offset (src_off) is at or before the start of the last source |
| // sample. We have not exhausted this source buffer -- return FALSE. |
| return false; |
| } |
| |
| template <size_t DestChanCount, typename SrcSampleType, size_t SrcChanCount> |
| bool LinearSamplerImpl<DestChanCount, SrcSampleType, SrcChanCount>::Mix( |
| float* dest, uint32_t dest_frames, uint32_t* dest_offset, const void* src, |
| uint32_t frac_src_frames, int32_t* frac_src_offset, bool accumulate, |
| Bookkeeping* info) { |
| FXL_DCHECK(info != nullptr); |
| |
| bool hasModulo = (info->denominator > 0 && info->rate_modulo > 0); |
| |
| if (info->gain.IsUnity()) { |
| return accumulate |
| ? (hasModulo ? Mix<ScalerType::EQ_UNITY, true, true>( |
| dest, dest_frames, dest_offset, src, |
| frac_src_frames, frac_src_offset, info) |
| : Mix<ScalerType::EQ_UNITY, true, false>( |
| dest, dest_frames, dest_offset, src, |
| frac_src_frames, frac_src_offset, info)) |
| : (hasModulo ? Mix<ScalerType::EQ_UNITY, false, true>( |
| dest, dest_frames, dest_offset, src, |
| frac_src_frames, frac_src_offset, info) |
| : Mix<ScalerType::EQ_UNITY, false, false>( |
| dest, dest_frames, dest_offset, src, |
| frac_src_frames, frac_src_offset, info)); |
| } else if (info->gain.IsSilent()) { |
| return (hasModulo ? Mix<ScalerType::MUTED, true, true>( |
| dest, dest_frames, dest_offset, src, |
| frac_src_frames, frac_src_offset, info) |
| : Mix<ScalerType::MUTED, true, false>( |
| dest, dest_frames, dest_offset, src, |
| frac_src_frames, frac_src_offset, info)); |
| } else if (info->gain.IsRamping()) { |
| return accumulate |
| ? (hasModulo ? Mix<ScalerType::RAMPING, true, true>( |
| dest, dest_frames, dest_offset, src, |
| frac_src_frames, frac_src_offset, info) |
| : Mix<ScalerType::RAMPING, true, false>( |
| dest, dest_frames, dest_offset, src, |
| frac_src_frames, frac_src_offset, info)) |
| : (hasModulo ? Mix<ScalerType::RAMPING, false, true>( |
| dest, dest_frames, dest_offset, src, |
| frac_src_frames, frac_src_offset, info) |
| : Mix<ScalerType::RAMPING, false, false>( |
| dest, dest_frames, dest_offset, src, |
| frac_src_frames, frac_src_offset, info)); |
| } else { |
| return accumulate |
| ? (hasModulo ? Mix<ScalerType::NE_UNITY, true, true>( |
| dest, dest_frames, dest_offset, src, |
| frac_src_frames, frac_src_offset, info) |
| : Mix<ScalerType::NE_UNITY, true, false>( |
| dest, dest_frames, dest_offset, src, |
| frac_src_frames, frac_src_offset, info)) |
| : (hasModulo ? Mix<ScalerType::NE_UNITY, false, true>( |
| dest, dest_frames, dest_offset, src, |
| frac_src_frames, frac_src_offset, info) |
| : Mix<ScalerType::NE_UNITY, false, false>( |
| dest, dest_frames, dest_offset, src, |
| frac_src_frames, frac_src_offset, info)); |
| } |
| } |
| |
| // If upper layers call with ScaleType MUTED, they must set DoAccumulate=TRUE. |
| // They guarantee new buffers are cleared before usage; we optimize accordingly. |
| template <typename SrcSampleType> |
| template <ScalerType ScaleType, bool DoAccumulate, bool HasModulo> |
| inline bool NxNLinearSamplerImpl<SrcSampleType>::Mix( |
| float* dest, uint32_t dest_frames, uint32_t* dest_offset, |
| const void* src_void, uint32_t frac_src_frames, int32_t* frac_src_offset, |
| Bookkeeping* info, size_t chan_count) { |
| static_assert( |
| ScaleType != ScalerType::MUTED || DoAccumulate == true, |
| "Mixing muted streams without accumulation is explicitly unsupported"); |
| |
| // Although the number of source frames is expressed in fixed-point 19.13 |
| // format, the actual number of frames must always be an integer. |
| FXL_DCHECK((frac_src_frames & kPtsFractionalMask) == 0); |
| FXL_DCHECK(frac_src_frames >= FRAC_ONE); |
| // Interpolation offset is int32, so even though frac_src_frames is a uint32, |
| // callers should not exceed int32_t::max(). |
| FXL_DCHECK(frac_src_frames <= |
| static_cast<uint32_t>(std::numeric_limits<int32_t>::max())); |
| |
| using DM = DestMixer<ScaleType, DoAccumulate>; |
| const auto* src = static_cast<const SrcSampleType*>(src_void); |
| |
| uint32_t dest_off = *dest_offset; |
| uint32_t dest_off_start = dest_off; // Only used when ramping |
| |
| int32_t src_off = *frac_src_offset; |
| |
| // Cache these locally, in the template specialization that uses them. |
| // Only src_pos_modulo needs to be written back before returning. |
| uint32_t step_size = info->step_size; |
| uint32_t rate_modulo, denominator, src_pos_modulo; |
| if constexpr (HasModulo) { |
| rate_modulo = info->rate_modulo; |
| denominator = info->denominator; |
| src_pos_modulo = info->src_pos_modulo; |
| |
| FXL_DCHECK(denominator > 0); |
| FXL_DCHECK(denominator > rate_modulo); |
| FXL_DCHECK(denominator > src_pos_modulo); |
| } |
| if constexpr (kVerboseRampDebug) { |
| FXL_LOG(INFO) << "Linear-NxN(" << this |
| << ") Ramping: " << (ScaleType == ScalerType::RAMPING) |
| << ", dest_frames: " << dest_frames |
| << ", dest_off: " << dest_off; |
| } |
| if constexpr (ScaleType == ScalerType::RAMPING) { |
| if (dest_frames > Bookkeeping::kScaleArrLen + dest_off) { |
| dest_frames = Bookkeeping::kScaleArrLen + dest_off; |
| } |
| } |
| |
| // This is the last sub-frame at which we can output without additional data. |
| auto src_end = static_cast<int32_t>(frac_src_frames - pos_filter_width() - 1); |
| |
| FXL_DCHECK(dest_off < dest_frames); |
| FXL_DCHECK(src_end >= 0); |
| FXL_DCHECK(frac_src_frames <= |
| static_cast<uint32_t>(std::numeric_limits<int32_t>::max())); |
| |
| // "Source offset" can be negative, but within the bounds of pos_filter_width. |
| // For linear_sampler this means that src_off > -FRAC_ONE. |
| FXL_DCHECK(src_off + static_cast<int32_t>(pos_filter_width()) >= 0); |
| // Source offset must also be within neg_filter_width of our last sample. |
| FXL_DCHECK(src_off + FRAC_ONE <= frac_src_frames + neg_filter_width()); |
| |
| Gain::AScale amplitude_scale; |
| if constexpr (ScaleType != ScalerType::RAMPING) { |
| amplitude_scale = info->gain.GetGainScale(); |
| } |
| |
| // TODO(mpuryear): optimize the logic below for common-case performance. |
| |
| // If we are not attenuated to the point of being muted, go ahead and perform |
| // the mix. Otherwise, just update the source and dest offsets and hold onto |
| // any relevant filter data from the end of the source. |
| if constexpr (ScaleType != ScalerType::MUTED) { |
| // When starting "between buffers", we must rely on previously-cached vals. |
| if (src_off < 0) { |
| for (size_t D = 0; D < chan_count; ++D) { |
| filter_data_u_[chan_count + D] = |
| SampleNormalizer<SrcSampleType>::Read(src + D); |
| } |
| |
| while ((dest_off < dest_frames) && (src_off < 0)) { |
| if constexpr (ScaleType == ScalerType::RAMPING) { |
| amplitude_scale = info->scale_arr[dest_off - dest_off_start]; |
| } |
| |
| float* out = dest + (dest_off * chan_count); |
| |
| for (size_t D = 0; D < chan_count; ++D) { |
| float sample = Interpolate(filter_data_u_[chan_count + D], |
| filter_data_u_[D], -src_off); |
| out[D] = DM::Mix(out[D], sample, amplitude_scale); |
| } |
| |
| dest_off += 1; |
| src_off += step_size; |
| |
| if constexpr (HasModulo) { |
| src_pos_modulo += rate_modulo; |
| if (src_pos_modulo >= denominator) { |
| ++src_off; |
| src_pos_modulo -= denominator; |
| } |
| } |
| } |
| } |
| |
| // Now we are fully in the current buffer and need not rely on our cache. |
| while ((dest_off < dest_frames) && (src_off < src_end)) { |
| uint32_t S = (src_off >> kPtsFractionalBits) * chan_count; |
| float* out = dest + (dest_off * chan_count); |
| if constexpr (ScaleType == ScalerType::RAMPING) { |
| amplitude_scale = info->scale_arr[dest_off - dest_off_start]; |
| } |
| |
| for (size_t D = 0; D < chan_count; ++D) { |
| float s1 = SampleNormalizer<SrcSampleType>::Read(src + S + D); |
| float s2 = |
| SampleNormalizer<SrcSampleType>::Read(src + S + D + chan_count); |
| float sample = Interpolate(s1, s2, src_off & FRAC_MASK); |
| out[D] = DM::Mix(out[D], sample, amplitude_scale); |
| } |
| |
| dest_off += 1; |
| src_off += step_size; |
| |
| if constexpr (HasModulo) { |
| src_pos_modulo += rate_modulo; |
| if (src_pos_modulo >= denominator) { |
| ++src_off; |
| src_pos_modulo -= denominator; |
| } |
| } |
| } |
| } else { |
| // We are muted. Don't mix, but figure out how many samples we WOULD have |
| // produced and update the src_off and dest_off values appropriately. |
| if ((dest_off < dest_frames) && (src_off < src_end)) { |
| uint32_t src_avail = (((src_end - src_off) + step_size - 1) / step_size); |
| uint32_t dest_avail = (dest_frames - dest_off); |
| uint32_t avail = std::min(src_avail, dest_avail); |
| |
| dest_off += avail; |
| src_off += avail * step_size; |
| |
| if constexpr (HasModulo) { |
| src_pos_modulo += (rate_modulo * avail); |
| src_off += (src_pos_modulo / denominator); |
| src_pos_modulo %= denominator; |
| } |
| } |
| } |
| |
| // If we have room for at least one more sample, and our sampling position |
| // hits the input buffer's final frame exactly ... |
| if ((dest_off < dest_frames) && (src_off == src_end)) { |
| // ... and if we are not muted, of course ... |
| if constexpr (ScaleType != ScalerType::MUTED) { |
| // ... then we can _point-sample_ one final frame into our output buffer. |
| // We need not _interpolate_ since fractional position is exactly zero. |
| uint32_t S = (src_off >> kPtsFractionalBits) * chan_count; |
| float* out = dest + (dest_off * chan_count); |
| if constexpr (ScaleType == ScalerType::RAMPING) { |
| amplitude_scale = info->scale_arr[dest_off - dest_off_start]; |
| } |
| |
| for (size_t D = 0; D < chan_count; ++D) { |
| float sample = SampleNormalizer<SrcSampleType>::Read(src + S + D); |
| out[D] = DM::Mix(out[D], sample, amplitude_scale); |
| } |
| } |
| |
| dest_off += 1; |
| src_off += step_size; |
| |
| if constexpr (HasModulo) { |
| src_pos_modulo += rate_modulo; |
| if (src_pos_modulo >= denominator) { |
| ++src_off; |
| src_pos_modulo -= denominator; |
| } |
| } |
| } |
| |
| // Update all our returned in-out parameters |
| *dest_offset = dest_off; |
| *frac_src_offset = src_off; |
| if constexpr (HasModulo) { |
| info->src_pos_modulo = src_pos_modulo; |
| } |
| |
| // If next source position to consume is beyond start of last frame ... |
| if (src_off > src_end) { |
| uint32_t S = (src_end >> kPtsFractionalBits) * chan_count; |
| // ... and if we are not mute, of course... |
| if constexpr (ScaleType != ScalerType::MUTED) { |
| // ... cache our final frame for use in future interpolation ... |
| for (size_t D = 0; D < chan_count; ++D) { |
| filter_data_u_[D] = SampleNormalizer<SrcSampleType>::Read(src + S + D); |
| } |
| } else { |
| // ... otherwise cache silence (which is what we actually produced). |
| for (size_t D = 0; D < chan_count; ++D) { |
| filter_data_u_[D] = 0; |
| } |
| } |
| // At this point the source offset (src_off) is either somewhere within |
| // the last source sample, or entirely beyond the end of the source buffer |
| // (if frac_step_size is greater than unity). Either way, we've extracted |
| // all of the information from this source buffer, and can return TRUE. |
| return true; |
| } |
| |
| // Source offset (src_off) is at or before the start of the last source |
| // sample. We have not exhausted this source buffer -- return FALSE. |
| return false; |
| } |
| |
| template <typename SrcSampleType> |
| bool NxNLinearSamplerImpl<SrcSampleType>::Mix( |
| float* dest, uint32_t dest_frames, uint32_t* dest_offset, const void* src, |
| uint32_t frac_src_frames, int32_t* frac_src_offset, bool accumulate, |
| Bookkeeping* info) { |
| FXL_DCHECK(info != nullptr); |
| |
| bool hasModulo = (info->denominator > 0 && info->rate_modulo > 0); |
| |
| if (info->gain.IsUnity()) { |
| return accumulate |
| ? (hasModulo |
| ? Mix<ScalerType::EQ_UNITY, true, true>( |
| dest, dest_frames, dest_offset, src, |
| frac_src_frames, frac_src_offset, info, chan_count_) |
| : Mix<ScalerType::EQ_UNITY, true, false>( |
| dest, dest_frames, dest_offset, src, |
| frac_src_frames, frac_src_offset, info, |
| chan_count_)) |
| : (hasModulo |
| ? Mix<ScalerType::EQ_UNITY, false, true>( |
| dest, dest_frames, dest_offset, src, |
| frac_src_frames, frac_src_offset, info, chan_count_) |
| : Mix<ScalerType::EQ_UNITY, false, false>( |
| dest, dest_frames, dest_offset, src, |
| frac_src_frames, frac_src_offset, info, |
| chan_count_)); |
| } else if (info->gain.IsSilent()) { |
| return (hasModulo |
| ? Mix<ScalerType::MUTED, true, true>( |
| dest, dest_frames, dest_offset, src, frac_src_frames, |
| frac_src_offset, info, chan_count_) |
| : Mix<ScalerType::MUTED, true, false>( |
| dest, dest_frames, dest_offset, src, frac_src_frames, |
| frac_src_offset, info, chan_count_)); |
| } else if (info->gain.IsRamping()) { |
| return accumulate |
| ? (hasModulo |
| ? Mix<ScalerType::RAMPING, true, true>( |
| dest, dest_frames, dest_offset, src, |
| frac_src_frames, frac_src_offset, info, chan_count_) |
| : Mix<ScalerType::RAMPING, true, false>( |
| dest, dest_frames, dest_offset, src, |
| frac_src_frames, frac_src_offset, info, |
| chan_count_)) |
| : (hasModulo |
| ? Mix<ScalerType::RAMPING, false, true>( |
| dest, dest_frames, dest_offset, src, |
| frac_src_frames, frac_src_offset, info, chan_count_) |
| : Mix<ScalerType::RAMPING, false, false>( |
| dest, dest_frames, dest_offset, src, |
| frac_src_frames, frac_src_offset, info, |
| chan_count_)); |
| } else { |
| return accumulate |
| ? (hasModulo |
| ? Mix<ScalerType::NE_UNITY, true, true>( |
| dest, dest_frames, dest_offset, src, |
| frac_src_frames, frac_src_offset, info, chan_count_) |
| : Mix<ScalerType::NE_UNITY, true, false>( |
| dest, dest_frames, dest_offset, src, |
| frac_src_frames, frac_src_offset, info, |
| chan_count_)) |
| : (hasModulo |
| ? Mix<ScalerType::NE_UNITY, false, true>( |
| dest, dest_frames, dest_offset, src, |
| frac_src_frames, frac_src_offset, info, chan_count_) |
| : Mix<ScalerType::NE_UNITY, false, false>( |
| dest, dest_frames, dest_offset, src, |
| frac_src_frames, frac_src_offset, info, |
| chan_count_)); |
| } |
| } |
| |
| // Templates used to expand all of the different combinations of the possible |
| // LinearSampler Mixer configurations. |
| template <size_t DestChanCount, typename SrcSampleType, size_t SrcChanCount> |
| static inline MixerPtr SelectLSM( |
| const fuchsia::media::AudioStreamType& src_format, |
| const fuchsia::media::AudioStreamType& dest_format) { |
| return MixerPtr( |
| new LinearSamplerImpl<DestChanCount, SrcSampleType, SrcChanCount>()); |
| } |
| |
| template <size_t DestChanCount, typename SrcSampleType> |
| static inline MixerPtr SelectLSM( |
| const fuchsia::media::AudioStreamType& src_format, |
| const fuchsia::media::AudioStreamType& dest_format) { |
| switch (src_format.channels) { |
| case 1: |
| return SelectLSM<DestChanCount, SrcSampleType, 1>(src_format, |
| dest_format); |
| case 2: |
| return SelectLSM<DestChanCount, SrcSampleType, 2>(src_format, |
| dest_format); |
| default: |
| return nullptr; |
| } |
| } |
| |
| template <size_t DestChanCount> |
| static inline MixerPtr SelectLSM( |
| const fuchsia::media::AudioStreamType& src_format, |
| const fuchsia::media::AudioStreamType& dest_format) { |
| switch (src_format.sample_format) { |
| case fuchsia::media::AudioSampleFormat::UNSIGNED_8: |
| return SelectLSM<DestChanCount, uint8_t>(src_format, dest_format); |
| case fuchsia::media::AudioSampleFormat::SIGNED_16: |
| return SelectLSM<DestChanCount, int16_t>(src_format, dest_format); |
| case fuchsia::media::AudioSampleFormat::SIGNED_24_IN_32: |
| return SelectLSM<DestChanCount, int32_t>(src_format, dest_format); |
| case fuchsia::media::AudioSampleFormat::FLOAT: |
| return SelectLSM<DestChanCount, float>(src_format, dest_format); |
| default: |
| return nullptr; |
| } |
| } |
| |
| static inline MixerPtr SelectNxNLSM( |
| const fuchsia::media::AudioStreamType& src_format) { |
| switch (src_format.sample_format) { |
| case fuchsia::media::AudioSampleFormat::UNSIGNED_8: |
| return MixerPtr(new NxNLinearSamplerImpl<uint8_t>(src_format.channels)); |
| case fuchsia::media::AudioSampleFormat::SIGNED_16: |
| return MixerPtr(new NxNLinearSamplerImpl<int16_t>(src_format.channels)); |
| case fuchsia::media::AudioSampleFormat::SIGNED_24_IN_32: |
| return MixerPtr(new NxNLinearSamplerImpl<int32_t>(src_format.channels)); |
| case fuchsia::media::AudioSampleFormat::FLOAT: |
| return MixerPtr(new NxNLinearSamplerImpl<float>(src_format.channels)); |
| default: |
| return nullptr; |
| } |
| } |
| |
| MixerPtr LinearSampler::Select( |
| const fuchsia::media::AudioStreamType& src_format, |
| const fuchsia::media::AudioStreamType& dest_format) { |
| if (src_format.channels == dest_format.channels && src_format.channels > 2) { |
| return SelectNxNLSM(src_format); |
| } |
| |
| switch (dest_format.channels) { |
| case 1: |
| return SelectLSM<1>(src_format, dest_format); |
| case 2: |
| return SelectLSM<2>(src_format, dest_format); |
| default: |
| return nullptr; |
| } |
| } |
| |
| } // namespace media::audio::mixer |