| Index: media/filters/audio_renderer_algorithm.cc
|
| diff --git a/media/filters/audio_renderer_algorithm.cc b/media/filters/audio_renderer_algorithm.cc
|
| deleted file mode 100644
|
| index 16e903c6a3b62f9dd5b190dd8565afca9457ca76..0000000000000000000000000000000000000000
|
| --- a/media/filters/audio_renderer_algorithm.cc
|
| +++ /dev/null
|
| @@ -1,388 +0,0 @@
|
| -// Copyright (c) 2012 The Chromium 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 "media/filters/audio_renderer_algorithm.h"
|
| -
|
| -#include <algorithm>
|
| -#include <cmath>
|
| -
|
| -#include "base/logging.h"
|
| -#include "base/memory/scoped_ptr.h"
|
| -#include "media/base/audio_buffer.h"
|
| -#include "media/base/audio_bus.h"
|
| -#include "media/base/limits.h"
|
| -#include "media/filters/wsola_internals.h"
|
| -
|
| -namespace media {
|
| -
|
| -
|
| -// Waveform Similarity Overlap-and-add (WSOLA).
|
| -//
|
| -// One WSOLA iteration
|
| -//
|
| -// 1) Extract |target_block_| as input frames at indices
|
| -// [|target_block_index_|, |target_block_index_| + |ola_window_size_|).
|
| -// Note that |target_block_| is the "natural" continuation of the output.
|
| -//
|
| -// 2) Extract |search_block_| as input frames at indices
|
| -// [|search_block_index_|,
|
| -// |search_block_index_| + |num_candidate_blocks_| + |ola_window_size_|).
|
| -//
|
| -// 3) Find a block within the |search_block_| that is most similar
|
| -// to |target_block_|. Let |optimal_index| be the index of such block and
|
| -// write it to |optimal_block_|.
|
| -//
|
| -// 4) Update:
|
| -// |optimal_block_| = |transition_window_| * |target_block_| +
|
| -// (1 - |transition_window_|) * |optimal_block_|.
|
| -//
|
| -// 5) Overlap-and-add |optimal_block_| to the |wsola_output_|.
|
| -//
|
| -// 6) Update:
|
| -// |target_block_| = |optimal_index| + |ola_window_size_| / 2.
|
| -// |output_index_| = |output_index_| + |ola_window_size_| / 2,
|
| -// |search_block_center_offset_| = |output_index_| * |playback_rate|, and
|
| -// |search_block_index_| = |search_block_center_offset_| -
|
| -// |search_block_center_offset_|.
|
| -
|
| -// Max/min supported playback rates for fast/slow audio. Audio outside of these
|
| -// ranges are muted.
|
| -// Audio at these speeds would sound better under a frequency domain algorithm.
|
| -static const float kMinPlaybackRate = 0.5f;
|
| -static const float kMaxPlaybackRate = 4.0f;
|
| -
|
| -// Overlap-and-add window size in milliseconds.
|
| -static const int kOlaWindowSizeMs = 20;
|
| -
|
| -// Size of search interval in milliseconds. The search interval is
|
| -// [-delta delta] around |output_index_| * |playback_rate|. So the search
|
| -// interval is 2 * delta.
|
| -static const int kWsolaSearchIntervalMs = 30;
|
| -
|
| -// The maximum size in seconds for the |audio_buffer_|. Arbitrarily determined.
|
| -static const int kMaxCapacityInSeconds = 3;
|
| -
|
| -// The starting size in frames for |audio_buffer_|. Previous usage maintained a
|
| -// queue of 16 AudioBuffers, each of 512 frames. This worked well, so we
|
| -// maintain this number of frames.
|
| -static const int kStartingBufferSizeInFrames = 16 * 512;
|
| -
|
| -static_assert(kStartingBufferSizeInFrames <
|
| - (kMaxCapacityInSeconds * limits::kMinSampleRate),
|
| - "max capacity smaller than starting buffer size");
|
| -
|
| -AudioRendererAlgorithm::AudioRendererAlgorithm()
|
| - : channels_(0),
|
| - samples_per_second_(0),
|
| - muted_partial_frame_(0),
|
| - capacity_(kStartingBufferSizeInFrames),
|
| - output_time_(0.0),
|
| - search_block_center_offset_(0),
|
| - search_block_index_(0),
|
| - num_candidate_blocks_(0),
|
| - target_block_index_(0),
|
| - ola_window_size_(0),
|
| - ola_hop_size_(0),
|
| - num_complete_frames_(0) {
|
| -}
|
| -
|
| -AudioRendererAlgorithm::~AudioRendererAlgorithm() {}
|
| -
|
| -void AudioRendererAlgorithm::Initialize(const AudioParameters& params) {
|
| - CHECK(params.IsValid());
|
| -
|
| - channels_ = params.channels();
|
| - samples_per_second_ = params.sample_rate();
|
| - num_candidate_blocks_ = (kWsolaSearchIntervalMs * samples_per_second_) / 1000;
|
| - ola_window_size_ = kOlaWindowSizeMs * samples_per_second_ / 1000;
|
| -
|
| - // Make sure window size in an even number.
|
| - ola_window_size_ += ola_window_size_ & 1;
|
| - ola_hop_size_ = ola_window_size_ / 2;
|
| -
|
| - // |num_candidate_blocks_| / 2 is the offset of the center of the search
|
| - // block to the center of the first (left most) candidate block. The offset
|
| - // of the center of a candidate block to its left most point is
|
| - // |ola_window_size_| / 2 - 1. Note that |ola_window_size_| is even and in
|
| - // our convention the center belongs to the left half, so we need to subtract
|
| - // one frame to get the correct offset.
|
| - //
|
| - // Search Block
|
| - // <------------------------------------------->
|
| - //
|
| - // |ola_window_size_| / 2 - 1
|
| - // <----
|
| - //
|
| - // |num_candidate_blocks_| / 2
|
| - // <----------------
|
| - // center
|
| - // X----X----------------X---------------X-----X
|
| - // <----------> <---------->
|
| - // Candidate ... Candidate
|
| - // 1, ... |num_candidate_blocks_|
|
| - search_block_center_offset_ = num_candidate_blocks_ / 2 +
|
| - (ola_window_size_ / 2 - 1);
|
| -
|
| - ola_window_.reset(new float[ola_window_size_]);
|
| - internal::GetSymmetricHanningWindow(ola_window_size_, ola_window_.get());
|
| -
|
| - transition_window_.reset(new float[ola_window_size_ * 2]);
|
| - internal::GetSymmetricHanningWindow(2 * ola_window_size_,
|
| - transition_window_.get());
|
| -
|
| - wsola_output_ = AudioBus::Create(channels_, ola_window_size_ + ola_hop_size_);
|
| - wsola_output_->Zero(); // Initialize for overlap-and-add of the first block.
|
| -
|
| - // Auxiliary containers.
|
| - optimal_block_ = AudioBus::Create(channels_, ola_window_size_);
|
| - search_block_ = AudioBus::Create(
|
| - channels_, num_candidate_blocks_ + (ola_window_size_ - 1));
|
| - target_block_ = AudioBus::Create(channels_, ola_window_size_);
|
| -}
|
| -
|
| -int AudioRendererAlgorithm::FillBuffer(AudioBus* dest,
|
| - int dest_offset,
|
| - int requested_frames,
|
| - float playback_rate) {
|
| - if (playback_rate == 0)
|
| - return 0;
|
| -
|
| - DCHECK_EQ(channels_, dest->channels());
|
| -
|
| - // Optimize the muted case to issue a single clear instead of performing
|
| - // the full crossfade and clearing each crossfaded frame.
|
| - if (playback_rate < kMinPlaybackRate || playback_rate > kMaxPlaybackRate) {
|
| - int frames_to_render =
|
| - std::min(static_cast<int>(audio_buffer_.frames() / playback_rate),
|
| - requested_frames);
|
| -
|
| - // Compute accurate number of frames to actually skip in the source data.
|
| - // Includes the leftover partial frame from last request. However, we can
|
| - // only skip over complete frames, so a partial frame may remain for next
|
| - // time.
|
| - muted_partial_frame_ += frames_to_render * playback_rate;
|
| - int seek_frames = static_cast<int>(muted_partial_frame_);
|
| - dest->ZeroFramesPartial(dest_offset, frames_to_render);
|
| - audio_buffer_.SeekFrames(seek_frames);
|
| -
|
| - // Determine the partial frame that remains to be skipped for next call. If
|
| - // the user switches back to playing, it may be off time by this partial
|
| - // frame, which would be undetectable. If they subsequently switch to
|
| - // another playback rate that mutes, the code will attempt to line up the
|
| - // frames again.
|
| - muted_partial_frame_ -= seek_frames;
|
| - return frames_to_render;
|
| - }
|
| -
|
| - int slower_step = ceil(ola_window_size_ * playback_rate);
|
| - int faster_step = ceil(ola_window_size_ / playback_rate);
|
| -
|
| - // Optimize the most common |playback_rate| ~= 1 case to use a single copy
|
| - // instead of copying frame by frame.
|
| - if (ola_window_size_ <= faster_step && slower_step >= ola_window_size_) {
|
| - const int frames_to_copy =
|
| - std::min(audio_buffer_.frames(), requested_frames);
|
| - const int frames_read =
|
| - audio_buffer_.ReadFrames(frames_to_copy, dest_offset, dest);
|
| - DCHECK_EQ(frames_read, frames_to_copy);
|
| - return frames_read;
|
| - }
|
| -
|
| - int rendered_frames = 0;
|
| - do {
|
| - rendered_frames +=
|
| - WriteCompletedFramesTo(requested_frames - rendered_frames,
|
| - dest_offset + rendered_frames, dest);
|
| - } while (rendered_frames < requested_frames &&
|
| - RunOneWsolaIteration(playback_rate));
|
| - return rendered_frames;
|
| -}
|
| -
|
| -void AudioRendererAlgorithm::FlushBuffers() {
|
| - // Clear the queue of decoded packets (releasing the buffers).
|
| - audio_buffer_.Clear();
|
| - output_time_ = 0.0;
|
| - search_block_index_ = 0;
|
| - target_block_index_ = 0;
|
| - wsola_output_->Zero();
|
| - num_complete_frames_ = 0;
|
| -
|
| - // Reset |capacity_| so growth triggered by underflows doesn't penalize
|
| - // seek time.
|
| - capacity_ = kStartingBufferSizeInFrames;
|
| -}
|
| -
|
| -void AudioRendererAlgorithm::EnqueueBuffer(
|
| - const scoped_refptr<AudioBuffer>& buffer_in) {
|
| - DCHECK(!buffer_in->end_of_stream());
|
| - audio_buffer_.Append(buffer_in);
|
| -}
|
| -
|
| -bool AudioRendererAlgorithm::IsQueueFull() {
|
| - return audio_buffer_.frames() >= capacity_;
|
| -}
|
| -
|
| -void AudioRendererAlgorithm::IncreaseQueueCapacity() {
|
| - int max_capacity = kMaxCapacityInSeconds * samples_per_second_;
|
| - DCHECK_LE(capacity_, max_capacity);
|
| -
|
| - capacity_ = std::min(2 * capacity_, max_capacity);
|
| -}
|
| -
|
| -bool AudioRendererAlgorithm::CanPerformWsola() const {
|
| - const int search_block_size = num_candidate_blocks_ + (ola_window_size_ - 1);
|
| - const int frames = audio_buffer_.frames();
|
| - return target_block_index_ + ola_window_size_ <= frames &&
|
| - search_block_index_ + search_block_size <= frames;
|
| -}
|
| -
|
| -bool AudioRendererAlgorithm::RunOneWsolaIteration(float playback_rate) {
|
| - if (!CanPerformWsola())
|
| - return false;
|
| -
|
| - GetOptimalBlock();
|
| -
|
| - // Overlap-and-add.
|
| - for (int k = 0; k < channels_; ++k) {
|
| - const float* const ch_opt_frame = optimal_block_->channel(k);
|
| - float* ch_output = wsola_output_->channel(k) + num_complete_frames_;
|
| - for (int n = 0; n < ola_hop_size_; ++n) {
|
| - ch_output[n] = ch_output[n] * ola_window_[ola_hop_size_ + n] +
|
| - ch_opt_frame[n] * ola_window_[n];
|
| - }
|
| -
|
| - // Copy the second half to the output.
|
| - memcpy(&ch_output[ola_hop_size_], &ch_opt_frame[ola_hop_size_],
|
| - sizeof(*ch_opt_frame) * ola_hop_size_);
|
| - }
|
| -
|
| - num_complete_frames_ += ola_hop_size_;
|
| - UpdateOutputTime(playback_rate, ola_hop_size_);
|
| - RemoveOldInputFrames(playback_rate);
|
| - return true;
|
| -}
|
| -
|
| -void AudioRendererAlgorithm::UpdateOutputTime(float playback_rate,
|
| - double time_change) {
|
| - output_time_ += time_change;
|
| - // Center of the search region, in frames.
|
| - const int search_block_center_index = static_cast<int>(
|
| - output_time_ * playback_rate + 0.5);
|
| - search_block_index_ = search_block_center_index - search_block_center_offset_;
|
| -}
|
| -
|
| -void AudioRendererAlgorithm::RemoveOldInputFrames(float playback_rate) {
|
| - const int earliest_used_index = std::min(target_block_index_,
|
| - search_block_index_);
|
| - if (earliest_used_index <= 0)
|
| - return; // Nothing to remove.
|
| -
|
| - // Remove frames from input and adjust indices accordingly.
|
| - audio_buffer_.SeekFrames(earliest_used_index);
|
| - target_block_index_ -= earliest_used_index;
|
| -
|
| - // Adjust output index.
|
| - double output_time_change = static_cast<double>(earliest_used_index) /
|
| - playback_rate;
|
| - CHECK_GE(output_time_, output_time_change);
|
| - UpdateOutputTime(playback_rate, -output_time_change);
|
| -}
|
| -
|
| -int AudioRendererAlgorithm::WriteCompletedFramesTo(
|
| - int requested_frames, int dest_offset, AudioBus* dest) {
|
| - int rendered_frames = std::min(num_complete_frames_, requested_frames);
|
| -
|
| - if (rendered_frames == 0)
|
| - return 0; // There is nothing to read from |wsola_output_|, return.
|
| -
|
| - wsola_output_->CopyPartialFramesTo(0, rendered_frames, dest_offset, dest);
|
| -
|
| - // Remove the frames which are read.
|
| - int frames_to_move = wsola_output_->frames() - rendered_frames;
|
| - for (int k = 0; k < channels_; ++k) {
|
| - float* ch = wsola_output_->channel(k);
|
| - memmove(ch, &ch[rendered_frames], sizeof(*ch) * frames_to_move);
|
| - }
|
| - num_complete_frames_ -= rendered_frames;
|
| - return rendered_frames;
|
| -}
|
| -
|
| -bool AudioRendererAlgorithm::TargetIsWithinSearchRegion() const {
|
| - const int search_block_size = num_candidate_blocks_ + (ola_window_size_ - 1);
|
| -
|
| - return target_block_index_ >= search_block_index_ &&
|
| - target_block_index_ + ola_window_size_ <=
|
| - search_block_index_ + search_block_size;
|
| -}
|
| -
|
| -void AudioRendererAlgorithm::GetOptimalBlock() {
|
| - int optimal_index = 0;
|
| -
|
| - // An interval around last optimal block which is excluded from the search.
|
| - // This is to reduce the buzzy sound. The number 160 is rather arbitrary and
|
| - // derived heuristically.
|
| - const int kExcludeIntervalLengthFrames = 160;
|
| - if (TargetIsWithinSearchRegion()) {
|
| - optimal_index = target_block_index_;
|
| - PeekAudioWithZeroPrepend(optimal_index, optimal_block_.get());
|
| - } else {
|
| - PeekAudioWithZeroPrepend(target_block_index_, target_block_.get());
|
| - PeekAudioWithZeroPrepend(search_block_index_, search_block_.get());
|
| - int last_optimal = target_block_index_ - ola_hop_size_ -
|
| - search_block_index_;
|
| - internal::Interval exclude_iterval = std::make_pair(
|
| - last_optimal - kExcludeIntervalLengthFrames / 2,
|
| - last_optimal + kExcludeIntervalLengthFrames / 2);
|
| -
|
| - // |optimal_index| is in frames and it is relative to the beginning of the
|
| - // |search_block_|.
|
| - optimal_index = internal::OptimalIndex(
|
| - search_block_.get(), target_block_.get(), exclude_iterval);
|
| -
|
| - // Translate |index| w.r.t. the beginning of |audio_buffer_| and extract the
|
| - // optimal block.
|
| - optimal_index += search_block_index_;
|
| - PeekAudioWithZeroPrepend(optimal_index, optimal_block_.get());
|
| -
|
| - // Make a transition from target block to the optimal block if different.
|
| - // Target block has the best continuation to the current output.
|
| - // Optimal block is the most similar block to the target, however, it might
|
| - // introduce some discontinuity when over-lap-added. Therefore, we combine
|
| - // them for a smoother transition. The length of transition window is twice
|
| - // as that of the optimal-block which makes it like a weighting function
|
| - // where target-block has higher weight close to zero (weight of 1 at index
|
| - // 0) and lower weight close the end.
|
| - for (int k = 0; k < channels_; ++k) {
|
| - float* ch_opt = optimal_block_->channel(k);
|
| - const float* const ch_target = target_block_->channel(k);
|
| - for (int n = 0; n < ola_window_size_; ++n) {
|
| - ch_opt[n] = ch_opt[n] * transition_window_[n] + ch_target[n] *
|
| - transition_window_[ola_window_size_ + n];
|
| - }
|
| - }
|
| - }
|
| -
|
| - // Next target is one hop ahead of the current optimal.
|
| - target_block_index_ = optimal_index + ola_hop_size_;
|
| -}
|
| -
|
| -void AudioRendererAlgorithm::PeekAudioWithZeroPrepend(
|
| - int read_offset_frames, AudioBus* dest) {
|
| - CHECK_LE(read_offset_frames + dest->frames(), audio_buffer_.frames());
|
| -
|
| - int write_offset = 0;
|
| - int num_frames_to_read = dest->frames();
|
| - if (read_offset_frames < 0) {
|
| - int num_zero_frames_appended = std::min(-read_offset_frames,
|
| - num_frames_to_read);
|
| - read_offset_frames = 0;
|
| - num_frames_to_read -= num_zero_frames_appended;
|
| - write_offset = num_zero_frames_appended;
|
| - dest->ZeroFrames(num_zero_frames_appended);
|
| - }
|
| - audio_buffer_.PeekFrames(num_frames_to_read, read_offset_frames,
|
| - write_offset, dest);
|
| -}
|
| -
|
| -} // namespace media
|
|
|