| // 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/audio/audio_util.h" |
| #include "media/base/buffers.h" |
| |
| namespace media { |
| |
| // The starting size in bytes for |audio_buffer_|. |
| // Previous usage maintained a deque of 16 Buffers, each of size 4Kb. This |
| // worked well, so we maintain this number of bytes (16 * 4096). |
| static const int kStartingBufferSizeInBytes = 65536; |
| |
| // The maximum size in bytes for the |audio_buffer_|. Arbitrarily determined. |
| // This number represents 3 seconds of 96kHz/16 bit 7.1 surround sound. |
| static const int kMaxBufferSizeInBytes = 4608000; |
| |
| // Duration of audio segments used for crossfading (in seconds). |
| static const double kWindowDuration = 0.08; |
| |
| // Duration of crossfade between audio segments (in seconds). |
| static const double kCrossfadeDuration = 0.008; |
| |
| // 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; |
| |
| AudioRendererAlgorithm::AudioRendererAlgorithm() |
| : channels_(0), |
| samples_per_second_(0), |
| bytes_per_channel_(0), |
| playback_rate_(0.0f), |
| audio_buffer_(0, kStartingBufferSizeInBytes), |
| bytes_in_crossfade_(0), |
| bytes_per_frame_(0), |
| index_into_window_(0), |
| crossfade_frame_number_(0), |
| muted_(false), |
| needs_more_data_(false), |
| window_size_(0) { |
| } |
| |
| AudioRendererAlgorithm::~AudioRendererAlgorithm() {} |
| |
| void AudioRendererAlgorithm::Initialize(float initial_playback_rate, |
| const AudioParameters& params, |
| const base::Closure& callback) { |
| CHECK(params.IsValid()); |
| DCHECK(!callback.is_null()); |
| |
| channels_ = params.channels(); |
| samples_per_second_ = params.sample_rate(); |
| bytes_per_channel_ = params.bits_per_sample() / 8; |
| bytes_per_frame_ = params.GetBytesPerFrame(); |
| request_read_cb_ = callback; |
| SetPlaybackRate(initial_playback_rate); |
| |
| window_size_ = |
| samples_per_second_ * bytes_per_channel_ * channels_ * kWindowDuration; |
| AlignToFrameBoundary(&window_size_); |
| |
| bytes_in_crossfade_ = |
| samples_per_second_ * bytes_per_channel_ * channels_ * kCrossfadeDuration; |
| AlignToFrameBoundary(&bytes_in_crossfade_); |
| |
| crossfade_buffer_.reset(new uint8[bytes_in_crossfade_]); |
| } |
| |
| int AudioRendererAlgorithm::FillBuffer( |
| uint8* dest, int requested_frames) { |
| DCHECK_NE(bytes_per_frame_, 0); |
| |
| if (playback_rate_ == 0.0f) |
| return 0; |
| |
| int slower_step = ceil(window_size_ * playback_rate_); |
| int faster_step = ceil(window_size_ / playback_rate_); |
| AlignToFrameBoundary(&slower_step); |
| AlignToFrameBoundary(&faster_step); |
| |
| int total_frames_rendered = 0; |
| uint8* output_ptr = dest; |
| while (total_frames_rendered < requested_frames) { |
| if (index_into_window_ == window_size_) |
| ResetWindow(); |
| |
| bool rendered_frame = true; |
| if (window_size_ > faster_step) { |
| rendered_frame = OutputFasterPlayback( |
| output_ptr, window_size_, faster_step); |
| } else if (slower_step < window_size_) { |
| rendered_frame = OutputSlowerPlayback( |
| output_ptr, slower_step, window_size_); |
| } else { |
| rendered_frame = OutputNormalPlayback(output_ptr); |
| } |
| |
| if (!rendered_frame) { |
| needs_more_data_ = true; |
| break; |
| } |
| |
| output_ptr += bytes_per_frame_; |
| total_frames_rendered++; |
| } |
| return total_frames_rendered; |
| } |
| |
| void AudioRendererAlgorithm::ResetWindow() { |
| DCHECK_LE(index_into_window_, window_size_); |
| index_into_window_ = 0; |
| crossfade_frame_number_ = 0; |
| } |
| |
| bool AudioRendererAlgorithm::OutputFasterPlayback(uint8* dest, |
| int input_step, |
| int output_step) { |
| // Ensure we don't run into OOB read/write situation. |
| CHECK_GT(input_step, output_step); |
| DCHECK_LT(index_into_window_, window_size_); |
| DCHECK_GT(playback_rate_, 1.0); |
| |
| if (audio_buffer_.forward_bytes() < bytes_per_frame_) |
| return false; |
| |
| // The audio data is output in a series of windows. For sped-up playback, |
| // the window is comprised of the following phases: |
| // |
| // a) Output raw data. |
| // b) Save bytes for crossfade in |crossfade_buffer_|. |
| // c) Drop data. |
| // d) Output crossfaded audio leading up to the next window. |
| // |
| // The duration of each phase is computed below based on the |window_size_| |
| // and |playback_rate_|. |
| int bytes_to_crossfade = bytes_in_crossfade_; |
| if (muted_ || bytes_to_crossfade > output_step) |
| bytes_to_crossfade = 0; |
| |
| // This is the index of the end of phase a, beginning of phase b. |
| int outtro_crossfade_begin = output_step - bytes_to_crossfade; |
| |
| // This is the index of the end of phase b, beginning of phase c. |
| int outtro_crossfade_end = output_step; |
| |
| // This is the index of the end of phase c, beginning of phase d. |
| // This phase continues until |index_into_window_| reaches |window_size_|, at |
| // which point the window restarts. |
| int intro_crossfade_begin = input_step - bytes_to_crossfade; |
| |
| // a) Output a raw frame if we haven't reached the crossfade section. |
| if (index_into_window_ < outtro_crossfade_begin) { |
| CopyWithAdvance(dest); |
| index_into_window_ += bytes_per_frame_; |
| return true; |
| } |
| |
| // b) Save outtro crossfade frames into intermediate buffer, but do not output |
| // anything to |dest|. |
| while (index_into_window_ < outtro_crossfade_end) { |
| if (audio_buffer_.forward_bytes() < bytes_per_frame_) |
| return false; |
| |
| // This phase only applies if there are bytes to crossfade. |
| DCHECK_GT(bytes_to_crossfade, 0); |
| uint8* place_to_copy = crossfade_buffer_.get() + |
| (index_into_window_ - outtro_crossfade_begin); |
| CopyWithAdvance(place_to_copy); |
| index_into_window_ += bytes_per_frame_; |
| } |
| |
| // c) Drop frames until we reach the intro crossfade section. |
| while (index_into_window_ < intro_crossfade_begin) { |
| if (audio_buffer_.forward_bytes() < bytes_per_frame_) |
| return false; |
| |
| DropFrame(); |
| index_into_window_ += bytes_per_frame_; |
| } |
| |
| // Return if we have run out of data after Phase c). |
| if (audio_buffer_.forward_bytes() < bytes_per_frame_) |
| return false; |
| |
| // Phase d) doesn't apply if there are no bytes to crossfade. |
| if (bytes_to_crossfade == 0) { |
| DCHECK_EQ(index_into_window_, window_size_); |
| return false; |
| } |
| |
| // d) Crossfade and output a frame. |
| DCHECK_LT(index_into_window_, window_size_); |
| int offset_into_buffer = index_into_window_ - intro_crossfade_begin; |
| memcpy(dest, crossfade_buffer_.get() + offset_into_buffer, |
| bytes_per_frame_); |
| scoped_array<uint8> intro_frame_ptr(new uint8[bytes_per_frame_]); |
| audio_buffer_.Read(intro_frame_ptr.get(), bytes_per_frame_); |
| OutputCrossfadedFrame(dest, intro_frame_ptr.get()); |
| index_into_window_ += bytes_per_frame_; |
| return true; |
| } |
| |
| bool AudioRendererAlgorithm::OutputSlowerPlayback(uint8* dest, |
| int input_step, |
| int output_step) { |
| // Ensure we don't run into OOB read/write situation. |
| CHECK_LT(input_step, output_step); |
| DCHECK_LT(index_into_window_, window_size_); |
| DCHECK_LT(playback_rate_, 1.0); |
| DCHECK_NE(playback_rate_, 0.0); |
| |
| if (audio_buffer_.forward_bytes() < bytes_per_frame_) |
| return false; |
| |
| // The audio data is output in a series of windows. For slowed down playback, |
| // the window is comprised of the following phases: |
| // |
| // a) Output raw data. |
| // b) Output and save bytes for crossfade in |crossfade_buffer_|. |
| // c) Output* raw data. |
| // d) Output* crossfaded audio leading up to the next window. |
| // |
| // * Phases c) and d) do not progress |audio_buffer_|'s cursor so that the |
| // |audio_buffer_|'s cursor is in the correct place for the next window. |
| // |
| // The duration of each phase is computed below based on the |window_size_| |
| // and |playback_rate_|. |
| int bytes_to_crossfade = bytes_in_crossfade_; |
| if (muted_ || bytes_to_crossfade > input_step) |
| bytes_to_crossfade = 0; |
| |
| // This is the index of the end of phase a, beginning of phase b. |
| int intro_crossfade_begin = input_step - bytes_to_crossfade; |
| |
| // This is the index of the end of phase b, beginning of phase c. |
| int intro_crossfade_end = input_step; |
| |
| // This is the index of the end of phase c, beginning of phase d. |
| // This phase continues until |index_into_window_| reaches |window_size_|, at |
| // which point the window restarts. |
| int outtro_crossfade_begin = output_step - bytes_to_crossfade; |
| |
| // a) Output a raw frame. |
| if (index_into_window_ < intro_crossfade_begin) { |
| CopyWithAdvance(dest); |
| index_into_window_ += bytes_per_frame_; |
| return true; |
| } |
| |
| // b) Save the raw frame for the intro crossfade section, then output the |
| // frame to |dest|. |
| if (index_into_window_ < intro_crossfade_end) { |
| int offset = index_into_window_ - intro_crossfade_begin; |
| uint8* place_to_copy = crossfade_buffer_.get() + offset; |
| CopyWithoutAdvance(place_to_copy); |
| CopyWithAdvance(dest); |
| index_into_window_ += bytes_per_frame_; |
| return true; |
| } |
| |
| int audio_buffer_offset = index_into_window_ - intro_crossfade_end; |
| |
| if (audio_buffer_.forward_bytes() < audio_buffer_offset + bytes_per_frame_) |
| return false; |
| |
| // c) Output a raw frame into |dest| without advancing the |audio_buffer_| |
| // cursor. See function-level comment. |
| DCHECK_GE(index_into_window_, intro_crossfade_end); |
| CopyWithoutAdvance(dest, audio_buffer_offset); |
| |
| // d) Crossfade the next frame of |crossfade_buffer_| into |dest| if we've |
| // reached the outtro crossfade section of the window. |
| if (index_into_window_ >= outtro_crossfade_begin) { |
| int offset_into_crossfade_buffer = |
| index_into_window_ - outtro_crossfade_begin; |
| uint8* intro_frame_ptr = |
| crossfade_buffer_.get() + offset_into_crossfade_buffer; |
| OutputCrossfadedFrame(dest, intro_frame_ptr); |
| } |
| |
| index_into_window_ += bytes_per_frame_; |
| return true; |
| } |
| |
| bool AudioRendererAlgorithm::OutputNormalPlayback(uint8* dest) { |
| if (audio_buffer_.forward_bytes() >= bytes_per_frame_) { |
| CopyWithAdvance(dest); |
| index_into_window_ += bytes_per_frame_; |
| return true; |
| } |
| return false; |
| } |
| |
| void AudioRendererAlgorithm::CopyWithAdvance(uint8* dest) { |
| CopyWithoutAdvance(dest); |
| DropFrame(); |
| } |
| |
| void AudioRendererAlgorithm::CopyWithoutAdvance(uint8* dest) { |
| CopyWithoutAdvance(dest, 0); |
| } |
| |
| void AudioRendererAlgorithm::CopyWithoutAdvance( |
| uint8* dest, int offset) { |
| if (muted_) { |
| memset(dest, 0, bytes_per_frame_); |
| return; |
| } |
| int copied = audio_buffer_.Peek(dest, bytes_per_frame_, offset); |
| DCHECK_EQ(bytes_per_frame_, copied); |
| } |
| |
| void AudioRendererAlgorithm::DropFrame() { |
| audio_buffer_.Seek(bytes_per_frame_); |
| |
| if (!IsQueueFull()) |
| request_read_cb_.Run(); |
| } |
| |
| void AudioRendererAlgorithm::OutputCrossfadedFrame( |
| uint8* outtro, const uint8* intro) { |
| DCHECK_LE(index_into_window_, window_size_); |
| DCHECK(!muted_); |
| |
| switch (bytes_per_channel_) { |
| case 4: |
| CrossfadeFrame<int32>(outtro, intro); |
| break; |
| case 2: |
| CrossfadeFrame<int16>(outtro, intro); |
| break; |
| case 1: |
| CrossfadeFrame<uint8>(outtro, intro); |
| break; |
| default: |
| NOTREACHED() << "Unsupported audio bit depth in crossfade."; |
| } |
| } |
| |
| template <class Type> |
| void AudioRendererAlgorithm::CrossfadeFrame( |
| uint8* outtro_bytes, const uint8* intro_bytes) { |
| Type* outtro = reinterpret_cast<Type*>(outtro_bytes); |
| const Type* intro = reinterpret_cast<const Type*>(intro_bytes); |
| |
| int frames_in_crossfade = bytes_in_crossfade_ / bytes_per_frame_; |
| float crossfade_ratio = |
| static_cast<float>(crossfade_frame_number_) / frames_in_crossfade; |
| for (int channel = 0; channel < channels_; ++channel) { |
| *outtro *= 1.0 - crossfade_ratio; |
| *outtro++ += (*intro++) * crossfade_ratio; |
| } |
| crossfade_frame_number_++; |
| } |
| |
| void AudioRendererAlgorithm::SetPlaybackRate(float new_rate) { |
| DCHECK_GE(new_rate, 0.0); |
| playback_rate_ = new_rate; |
| muted_ = |
| playback_rate_ < kMinPlaybackRate || playback_rate_ > kMaxPlaybackRate; |
| |
| ResetWindow(); |
| } |
| |
| void AudioRendererAlgorithm::AlignToFrameBoundary(int* value) { |
| (*value) -= ((*value) % bytes_per_frame_); |
| } |
| |
| void AudioRendererAlgorithm::FlushBuffers() { |
| ResetWindow(); |
| |
| // Clear the queue of decoded packets (releasing the buffers). |
| audio_buffer_.Clear(); |
| request_read_cb_.Run(); |
| } |
| |
| base::TimeDelta AudioRendererAlgorithm::GetTime() { |
| return audio_buffer_.current_time(); |
| } |
| |
| void AudioRendererAlgorithm::EnqueueBuffer(Buffer* buffer_in) { |
| DCHECK(!buffer_in->IsEndOfStream()); |
| audio_buffer_.Append(buffer_in); |
| needs_more_data_ = false; |
| |
| // If we still don't have enough data, request more. |
| if (!IsQueueFull()) |
| request_read_cb_.Run(); |
| } |
| |
| bool AudioRendererAlgorithm::CanFillBuffer() { |
| return audio_buffer_.forward_bytes() > 0 && !needs_more_data_; |
| } |
| |
| bool AudioRendererAlgorithm::IsQueueFull() { |
| return audio_buffer_.forward_bytes() >= audio_buffer_.forward_capacity(); |
| } |
| |
| int AudioRendererAlgorithm::QueueCapacity() { |
| return audio_buffer_.forward_capacity(); |
| } |
| |
| void AudioRendererAlgorithm::IncreaseQueueCapacity() { |
| audio_buffer_.set_forward_capacity( |
| std::min(2 * audio_buffer_.forward_capacity(), kMaxBufferSizeInBytes)); |
| } |
| |
| } // namespace media |