Skip to content

Commit

Permalink
Make media::AudioClock track frames written to compute time.
Browse files Browse the repository at this point in the history
Instead of using AudioRendererAlgorithm's timestamp as the ending
timestamp and counting "backwards", count "forwards" from a starting
timestamp to compute the current media time. Doing so produces more
accurate time calculations during periods where the playback rate
is changing.

last_endpoint_timestamp() is replaced by a new method that computes
the amount of contiguous media data buffered by audio hardware. Using
this value gives a more accurate maximum time value to use when doing
linear interpolation.

BUG=367343,370634

Review URL: https://codereview.chromium.org/436053002

Cr-Commit-Position: refs/heads/master@{#288445}
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@288445 0039d316-1c4b-4281-b951-d872f2087c98
  • Loading branch information
scherkus@chromium.org committed Aug 8, 2014
1 parent aed1236 commit 5b6ce11
Show file tree
Hide file tree
Showing 6 changed files with 397 additions and 271 deletions.
199 changes: 96 additions & 103 deletions media/filters/audio_clock.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4,144 +4,137 @@

#include "media/filters/audio_clock.h"

#include <algorithm>

#include "base/logging.h"
#include "media/base/buffers.h"

namespace media {

AudioClock::AudioClock(int sample_rate)
: sample_rate_(sample_rate), last_endpoint_timestamp_(kNoTimestamp()) {
AudioClock::AudioClock(base::TimeDelta start_timestamp, int sample_rate)
: start_timestamp_(start_timestamp),
sample_rate_(sample_rate),
microseconds_per_frame_(
static_cast<double>(base::Time::kMicrosecondsPerSecond) /
sample_rate),
total_buffered_frames_(0),
current_media_timestamp_(start_timestamp),
audio_data_buffered_(0) {
}

AudioClock::~AudioClock() {
}

void AudioClock::WroteAudio(int frames,
void AudioClock::WroteAudio(int frames_written,
int frames_requested,
int delay_frames,
float playback_rate,
base::TimeDelta timestamp) {
CHECK_GT(playback_rate, 0);
CHECK(timestamp != kNoTimestamp());
DCHECK_GE(frames, 0);
float playback_rate) {
DCHECK_GE(frames_written, 0);
DCHECK_LE(frames_written, frames_requested);
DCHECK_GE(delay_frames, 0);
DCHECK_GE(playback_rate, 0);

// First write: initialize buffer with silence.
if (start_timestamp_ == current_media_timestamp_ && buffered_.empty())
PushBufferedAudioData(delay_frames, 0.0f);

// Move frames from |buffered_| into the computed timestamp based on
// |delay_frames|.
//
// The ordering of compute -> push -> pop eliminates unnecessary memory
// reallocations in cases where |buffered_| gets emptied.
int64_t frames_played =
std::max(INT64_C(0), total_buffered_frames_ - delay_frames);
current_media_timestamp_ += ComputeBufferedMediaTime(frames_played);
PushBufferedAudioData(frames_written, playback_rate);
PushBufferedAudioData(frames_requested - frames_written, 0.0f);
PopBufferedAudioData(frames_played);

// Update cached values.
double scaled_frames = 0;
double scaled_frames_at_same_rate = 0;
bool found_silence = false;
audio_data_buffered_ = false;
for (size_t i = 0; i < buffered_.size(); ++i) {
if (buffered_[i].playback_rate == 0) {
found_silence = true;
continue;
}

if (last_endpoint_timestamp_ == kNoTimestamp())
PushBufferedAudio(delay_frames, 0, kNoTimestamp());

TrimBufferedAudioToMatchDelay(delay_frames);
PushBufferedAudio(frames, playback_rate, timestamp);
audio_data_buffered_ = true;

last_endpoint_timestamp_ = timestamp;
}
// Any buffered silence breaks our contiguous stretch of audio data.
if (found_silence)
break;

void AudioClock::WroteSilence(int frames, int delay_frames) {
DCHECK_GE(frames, 0);
DCHECK_GE(delay_frames, 0);
scaled_frames += (buffered_[i].frames * buffered_[i].playback_rate);

if (last_endpoint_timestamp_ == kNoTimestamp())
PushBufferedAudio(delay_frames, 0, kNoTimestamp());
if (i == 0)
scaled_frames_at_same_rate = scaled_frames;
}

TrimBufferedAudioToMatchDelay(delay_frames);
PushBufferedAudio(frames, 0, kNoTimestamp());
contiguous_audio_data_buffered_ = base::TimeDelta::FromMicroseconds(
scaled_frames * microseconds_per_frame_);
contiguous_audio_data_buffered_at_same_rate_ =
base::TimeDelta::FromMicroseconds(scaled_frames_at_same_rate *
microseconds_per_frame_);
}

base::TimeDelta AudioClock::CurrentMediaTimestamp(
base::TimeDelta AudioClock::CurrentMediaTimestampSinceWriting(
base::TimeDelta time_since_writing) const {
int frames_to_skip =
static_cast<int>(time_since_writing.InSecondsF() * sample_rate_);
int silence_frames = 0;
for (size_t i = 0; i < buffered_audio_.size(); ++i) {
int frames = buffered_audio_[i].frames;
if (frames_to_skip > 0) {
if (frames <= frames_to_skip) {
frames_to_skip -= frames;
continue;
}
frames -= frames_to_skip;
frames_to_skip = 0;
}

// Account for silence ahead of the buffer closest to being played.
if (buffered_audio_[i].playback_rate == 0) {
silence_frames += frames;
continue;
}

// Multiply by playback rate as frames represent time-scaled audio.
return buffered_audio_[i].endpoint_timestamp -
base::TimeDelta::FromMicroseconds(
((frames * buffered_audio_[i].playback_rate) + silence_frames) /
sample_rate_ * base::Time::kMicrosecondsPerSecond);
}
int64_t frames_played_since_writing = std::min(
total_buffered_frames_,
static_cast<int64_t>(time_since_writing.InSecondsF() * sample_rate_));
return current_media_timestamp_ +
ComputeBufferedMediaTime(frames_played_since_writing);
}

// Either:
// 1) AudioClock is uninitialziated and we'll return kNoTimestamp()
// 2) All previously buffered audio has been replaced by silence,
// meaning media time is now at the last endpoint
return last_endpoint_timestamp_;
AudioClock::AudioData::AudioData(int64_t frames, float playback_rate)
: frames(frames), playback_rate(playback_rate) {
}

void AudioClock::TrimBufferedAudioToMatchDelay(int delay_frames) {
if (buffered_audio_.empty())
void AudioClock::PushBufferedAudioData(int64_t frames, float playback_rate) {
if (frames == 0)
return;

size_t i = buffered_audio_.size() - 1;
while (true) {
if (buffered_audio_[i].frames <= delay_frames) {
// Reached the end before accounting for all of |delay_frames|. This
// means we haven't written enough audio data yet to account for hardware
// delay. In this case, do nothing.
if (i == 0)
return;

// Keep accounting for |delay_frames|.
delay_frames -= buffered_audio_[i].frames;
--i;
continue;
}
total_buffered_frames_ += frames;

// All of |delay_frames| has been accounted for: adjust amount of frames
// left in current buffer. All preceeding elements with index < |i| should
// be considered played out and hence discarded.
buffered_audio_[i].frames = delay_frames;
break;
// Avoid creating extra elements where possible.
if (!buffered_.empty() && buffered_.back().playback_rate == playback_rate) {
buffered_.back().frames += frames;
return;
}

// At this point |i| points at what will be the new head of |buffered_audio_|
// however if it contains no audio it should be removed as well.
if (buffered_audio_[i].frames == 0)
++i;

buffered_audio_.erase(buffered_audio_.begin(), buffered_audio_.begin() + i);
buffered_.push_back(AudioData(frames, playback_rate));
}

void AudioClock::PushBufferedAudio(int frames,
float playback_rate,
base::TimeDelta endpoint_timestamp) {
if (playback_rate == 0)
DCHECK(endpoint_timestamp == kNoTimestamp());
void AudioClock::PopBufferedAudioData(int64_t frames) {
DCHECK_LE(frames, total_buffered_frames_);

if (frames == 0)
return;
total_buffered_frames_ -= frames;

// Avoid creating extra elements where possible.
if (!buffered_audio_.empty() &&
buffered_audio_.back().playback_rate == playback_rate) {
buffered_audio_.back().frames += frames;
buffered_audio_.back().endpoint_timestamp = endpoint_timestamp;
return;
}
while (frames > 0) {
int64_t frames_to_pop = std::min(buffered_.front().frames, frames);
buffered_.front().frames -= frames_to_pop;
if (buffered_.front().frames == 0)
buffered_.pop_front();

buffered_audio_.push_back(
BufferedAudio(frames, playback_rate, endpoint_timestamp));
frames -= frames_to_pop;
}
}

AudioClock::BufferedAudio::BufferedAudio(int frames,
float playback_rate,
base::TimeDelta endpoint_timestamp)
: frames(frames),
playback_rate(playback_rate),
endpoint_timestamp(endpoint_timestamp) {
base::TimeDelta AudioClock::ComputeBufferedMediaTime(int64_t frames) const {
DCHECK_LE(frames, total_buffered_frames_);

double scaled_frames = 0;
for (size_t i = 0; i < buffered_.size() && frames > 0; ++i) {
int64_t min_frames = std::min(buffered_[i].frames, frames);
scaled_frames += min_frames * buffered_[i].playback_rate;
frames -= min_frames;
}

return base::TimeDelta::FromMicroseconds(scaled_frames *
microseconds_per_frame_);
}

} // namespace media
84 changes: 50 additions & 34 deletions media/filters/audio_clock.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,59 +18,75 @@ namespace media {
// a playback pipeline with large delay.
class MEDIA_EXPORT AudioClock {
public:
explicit AudioClock(int sample_rate);
AudioClock(base::TimeDelta start_timestamp, int sample_rate);
~AudioClock();

// |frames| amount of audio data scaled to |playback_rate| was written.
// |frames_written| amount of audio data scaled to |playback_rate| written.
// |frames_requested| amount of audio data requested by hardware.
// |delay_frames| is the current amount of hardware delay.
// |timestamp| is the endpoint media timestamp of the audio data written.
void WroteAudio(int frames,
void WroteAudio(int frames_written,
int frames_requested,
int delay_frames,
float playback_rate,
base::TimeDelta timestamp);

// |frames| amount of silence was written.
// |delay_frames| is the current amount of hardware delay.
void WroteSilence(int frames, int delay_frames);
float playback_rate);

// Calculates the current media timestamp taking silence and changes in
// playback rate into account.
//
base::TimeDelta current_media_timestamp() const {
return current_media_timestamp_;
}

// Clients can provide |time_since_writing| to simulate the passage of time
// since last writing audio to get a more accurate current media timestamp.
base::TimeDelta CurrentMediaTimestamp(
base::TimeDelta CurrentMediaTimestampSinceWriting(
base::TimeDelta time_since_writing) const;

// Returns the last endpoint timestamp provided to WroteAudio().
base::TimeDelta last_endpoint_timestamp() const {
return last_endpoint_timestamp_;
// Returns the amount of contiguous media time buffered at the head of the
// audio hardware buffer. Silence introduced into the audio hardware buffer is
// treated as a break in media time.
base::TimeDelta contiguous_audio_data_buffered() const {
return contiguous_audio_data_buffered_;
}

private:
void TrimBufferedAudioToMatchDelay(int delay_frames);
void PushBufferedAudio(int frames,
float playback_rate,
base::TimeDelta endpoint_timestamp);

const int sample_rate_;
// Same as above, but also treats changes in playback rate as a break in media
// time.
base::TimeDelta contiguous_audio_data_buffered_at_same_rate() const {
return contiguous_audio_data_buffered_at_same_rate_;
}

// Initially set to kNoTimestamp(), otherwise is the last endpoint timestamp
// delivered to WroteAudio(). A copy is kept outside of |buffered_audio_| to
// handle the case where all of |buffered_audio_| has been replaced with
// silence.
base::TimeDelta last_endpoint_timestamp_;
// Returns true if there is any audio data buffered by the audio hardware,
// even if there is silence mixed in.
bool audio_data_buffered() const { return audio_data_buffered_; }

struct BufferedAudio {
BufferedAudio(int frames,
float playback_rate,
base::TimeDelta endpoint_timestamp);
private:
// Even with a ridiculously high sample rate of 256kHz, using 64 bits will
// permit tracking up to 416999965 days worth of time (that's 1141 millenia).
//
// 32 bits on the other hand would top out at measly 2 hours and 20 minutes.
struct AudioData {
AudioData(int64_t frames, float playback_rate);

int frames;
int64_t frames;
float playback_rate;
base::TimeDelta endpoint_timestamp;
};

std::deque<BufferedAudio> buffered_audio_;
// Helpers for operating on |buffered_|.
void PushBufferedAudioData(int64_t frames, float playback_rate);
void PopBufferedAudioData(int64_t frames);
base::TimeDelta ComputeBufferedMediaTime(int64_t frames) const;

const base::TimeDelta start_timestamp_;
const int sample_rate_;
const double microseconds_per_frame_;

std::deque<AudioData> buffered_;
int64_t total_buffered_frames_;

base::TimeDelta current_media_timestamp_;

// Cached results of last call to WroteAudio().
bool audio_data_buffered_;
base::TimeDelta contiguous_audio_data_buffered_;
base::TimeDelta contiguous_audio_data_buffered_at_same_rate_;

DISALLOW_COPY_AND_ASSIGN(AudioClock);
};
Expand Down
Loading

0 comments on commit 5b6ce11

Please sign in to comment.