blob: 97f0811384159ff35868bb45f10eabdc7a94d86b [file] [log] [blame]
// 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/audio_buffer.h"
#include "media/base/audio_bus.h"
namespace media {
// 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;
// The maximum size in frames for the |audio_buffer_|. Arbitrarily determined.
// This number represents 3 seconds of 96kHz/16 bit 7.1 surround sound.
static const int kMaxBufferSizeInFrames = 3 * 96000;
// 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),
playback_rate_(0),
frames_in_crossfade_(0),
index_into_window_(0),
crossfade_frame_number_(0),
muted_(false),
muted_partial_frame_(0),
window_size_(0),
capacity_(kStartingBufferSizeInFrames) {
}
AudioRendererAlgorithm::~AudioRendererAlgorithm() {}
void AudioRendererAlgorithm::Initialize(float initial_playback_rate,
const AudioParameters& params) {
CHECK(params.IsValid());
channels_ = params.channels();
samples_per_second_ = params.sample_rate();
SetPlaybackRate(initial_playback_rate);
window_size_ = samples_per_second_ * kWindowDuration;
frames_in_crossfade_ = samples_per_second_ * kCrossfadeDuration;
crossfade_buffer_ = AudioBus::Create(channels_, frames_in_crossfade_);
}
int AudioRendererAlgorithm::FillBuffer(AudioBus* dest, int requested_frames) {
if (playback_rate_ == 0)
return 0;
// Optimize the |muted_| case to issue a single clear instead of performing
// the full crossfade and clearing each crossfaded frame.
if (muted_) {
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->ZeroFrames(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(window_size_ * playback_rate_);
int faster_step = ceil(window_size_ / playback_rate_);
// Optimize the most common |playback_rate_| ~= 1 case to use a single copy
// instead of copying frame by frame.
if (window_size_ <= faster_step && slower_step >= window_size_) {
const int frames_to_copy =
std::min(audio_buffer_.frames(), requested_frames);
const int frames_read = audio_buffer_.ReadFrames(frames_to_copy, 0, dest);
DCHECK_EQ(frames_read, frames_to_copy);
return frames_read;
}
int total_frames_rendered = 0;
while (total_frames_rendered < requested_frames) {
if (index_into_window_ >= window_size_)
ResetWindow();
int rendered_frames = 0;
if (window_size_ > faster_step) {
rendered_frames =
OutputFasterPlayback(dest,
total_frames_rendered,
requested_frames - total_frames_rendered,
window_size_,
faster_step);
} else if (slower_step < window_size_) {
rendered_frames =
OutputSlowerPlayback(dest,
total_frames_rendered,
requested_frames - total_frames_rendered,
slower_step,
window_size_);
} else {
NOTREACHED();
}
if (rendered_frames == 0)
break;
total_frames_rendered += rendered_frames;
}
return total_frames_rendered;
}
void AudioRendererAlgorithm::ResetWindow() {
DCHECK_LE(index_into_window_, window_size_);
index_into_window_ = 0;
crossfade_frame_number_ = 0;
}
int AudioRendererAlgorithm::OutputFasterPlayback(AudioBus* dest,
int dest_offset,
int requested_frames,
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);
DCHECK(!muted_);
if (audio_buffer_.frames() < 1)
return 0;
// 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_|.
DCHECK_LE(frames_in_crossfade_, output_step);
// This is the index of the end of phase a, beginning of phase b.
int outtro_crossfade_begin = output_step - frames_in_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 - frames_in_crossfade_;
// a) Output raw frames if we haven't reached the crossfade section.
if (index_into_window_ < outtro_crossfade_begin) {
// Read as many frames as we can and return the count. If it's not enough,
// we will get called again.
const int frames_to_copy =
std::min(requested_frames, outtro_crossfade_begin - index_into_window_);
int copied = audio_buffer_.ReadFrames(frames_to_copy, dest_offset, dest);
index_into_window_ += copied;
return copied;
}
// b) Save outtro crossfade frames into intermediate buffer, but do not output
// anything to |dest|.
if (index_into_window_ < outtro_crossfade_end) {
// This phase only applies if there are bytes to crossfade.
DCHECK_GT(frames_in_crossfade_, 0);
int crossfade_start = index_into_window_ - outtro_crossfade_begin;
int crossfade_count = outtro_crossfade_end - index_into_window_;
int copied = audio_buffer_.ReadFrames(
crossfade_count, crossfade_start, crossfade_buffer_.get());
index_into_window_ += copied;
// Did we get all the frames we need? If not, return and let subsequent
// calls try to get the rest.
if (copied != crossfade_count)
return 0;
}
// c) Drop frames until we reach the intro crossfade section.
if (index_into_window_ < intro_crossfade_begin) {
// Check if there is enough data to skip all the frames needed. If not,
// return 0 and let subsequent calls try to skip it all.
int seek_frames = intro_crossfade_begin - index_into_window_;
if (audio_buffer_.frames() < seek_frames)
return 0;
audio_buffer_.SeekFrames(seek_frames);
// We've dropped all the frames that need to be dropped.
index_into_window_ += seek_frames;
}
// d) Crossfade and output a frame, as long as we have data.
if (audio_buffer_.frames() < 1)
return 0;
DCHECK_GT(frames_in_crossfade_, 0);
DCHECK_LT(index_into_window_, window_size_);
int offset_into_buffer = index_into_window_ - intro_crossfade_begin;
int copied = audio_buffer_.ReadFrames(1, dest_offset, dest);
DCHECK_EQ(copied, 1);
CrossfadeFrame(crossfade_buffer_.get(),
offset_into_buffer,
dest,
dest_offset,
offset_into_buffer);
index_into_window_ += copied;
return copied;
}
int AudioRendererAlgorithm::OutputSlowerPlayback(AudioBus* dest,
int dest_offset,
int requested_frames,
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);
DCHECK(!muted_);
if (audio_buffer_.frames() < 1)
return 0;
// 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_|.
DCHECK_LE(frames_in_crossfade_, input_step);
// This is the index of the end of phase a, beginning of phase b.
int intro_crossfade_begin = input_step - frames_in_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 - frames_in_crossfade_;
// a) Output raw frames.
if (index_into_window_ < intro_crossfade_begin) {
// Read as many frames as we can and return the count. If it's not enough,
// we will get called again.
const int frames_to_copy =
std::min(requested_frames, intro_crossfade_begin - index_into_window_);
int copied = audio_buffer_.ReadFrames(frames_to_copy, dest_offset, dest);
index_into_window_ += copied;
return copied;
}
// b) Save the raw frames for the intro crossfade section, then copy the
// same frames to |dest|.
if (index_into_window_ < intro_crossfade_end) {
const int frames_to_copy =
std::min(requested_frames, intro_crossfade_end - index_into_window_);
int offset = index_into_window_ - intro_crossfade_begin;
int copied = audio_buffer_.ReadFrames(
frames_to_copy, offset, crossfade_buffer_.get());
crossfade_buffer_->CopyPartialFramesTo(offset, copied, dest_offset, dest);
index_into_window_ += copied;
return copied;
}
// c) Output a raw frame into |dest| without advancing the |audio_buffer_|
// cursor.
int audio_buffer_offset = index_into_window_ - intro_crossfade_end;
DCHECK_GE(audio_buffer_offset, 0);
if (audio_buffer_.frames() <= audio_buffer_offset)
return 0;
int copied =
audio_buffer_.PeekFrames(1, audio_buffer_offset, dest_offset, dest);
DCHECK_EQ(1, copied);
// 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;
CrossfadeFrame(dest,
dest_offset,
crossfade_buffer_.get(),
offset_into_crossfade_buffer,
offset_into_crossfade_buffer);
}
index_into_window_ += copied;
return copied;
}
void AudioRendererAlgorithm::CrossfadeFrame(AudioBus* intro,
int intro_offset,
AudioBus* outtro,
int outtro_offset,
int fade_offset) {
float crossfade_ratio =
static_cast<float>(fade_offset) / frames_in_crossfade_;
for (int channel = 0; channel < channels_; ++channel) {
outtro->channel(channel)[outtro_offset] =
(1.0f - crossfade_ratio) * intro->channel(channel)[intro_offset] +
(crossfade_ratio) * outtro->channel(channel)[outtro_offset];
}
}
void AudioRendererAlgorithm::SetPlaybackRate(float new_rate) {
DCHECK_GE(new_rate, 0);
playback_rate_ = new_rate;
muted_ =
playback_rate_ < kMinPlaybackRate || playback_rate_ > kMaxPlaybackRate;
ResetWindow();
}
void AudioRendererAlgorithm::FlushBuffers() {
ResetWindow();
// Clear the queue of decoded packets (releasing the buffers).
audio_buffer_.Clear();
}
base::TimeDelta AudioRendererAlgorithm::GetTime() {
return audio_buffer_.current_time();
}
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() {
capacity_ = std::min(2 * capacity_, kMaxBufferSizeInFrames);
}
} // namespace media