blob: 50503cda8194351fa360888b60ba7fb43d0a7c74 [file] [log] [blame]
// Copyright 2014 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 "cobalt/media/filters/audio_clock.h"
#include <algorithm>
#include <cmath>
#include "base/logging.h"
#include "starboard/types.h"
namespace cobalt {
namespace media {
AudioClock::AudioClock(base::TimeDelta start_timestamp, int sample_rate)
: start_timestamp_(start_timestamp),
microseconds_per_frame_(
static_cast<double>(base::Time::kMicrosecondsPerSecond) /
sample_rate),
total_buffered_frames_(0),
front_timestamp_micros_(start_timestamp.InMicroseconds()),
back_timestamp_micros_(start_timestamp.InMicroseconds()) {}
AudioClock::~AudioClock() {}
void AudioClock::WroteAudio(int frames_written, int frames_requested,
int delay_frames, double 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_.InMicroseconds() == front_timestamp_micros_ &&
buffered_.empty()) {
PushBufferedAudioData(delay_frames, 0.0);
}
// 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);
PushBufferedAudioData(frames_written, playback_rate);
PushBufferedAudioData(frames_requested - frames_written, 0.0);
PopBufferedAudioData(frames_played);
// Update our front and back timestamps. The back timestamp is considered the
// authoritative source of truth, so base the front timestamp on range of data
// buffered. Doing so avoids accumulation errors on the front timestamp.
back_timestamp_micros_ +=
frames_written * playback_rate * microseconds_per_frame_;
// Don't let front timestamp move earlier in time, as could occur due to delay
// frames pushed in the first write, above.
front_timestamp_micros_ =
std::max(front_timestamp_micros_,
back_timestamp_micros_ - ComputeBufferedMediaDurationMicros());
DCHECK_GE(front_timestamp_micros_, start_timestamp_.InMicroseconds());
DCHECK_LE(front_timestamp_micros_, back_timestamp_micros_);
}
void AudioClock::CompensateForSuspendedWrites(base::TimeDelta elapsed,
int delay_frames) {
const int64_t frames_elapsed =
elapsed.InMicroseconds() / microseconds_per_frame_ + 0.5;
// No need to do anything if we're within the limits of our played out audio
// or there are no delay frames, the next WroteAudio() call will expire
// everything correctly.
if (frames_elapsed < total_buffered_frames_ || !delay_frames) return;
// Otherwise, flush everything and prime with the delay frames.
WroteAudio(0, 0, 0, 0);
DCHECK(buffered_.empty());
PushBufferedAudioData(delay_frames, 0.0);
}
base::TimeDelta AudioClock::TimeUntilPlayback(base::TimeDelta timestamp) const {
// Use front/back_timestamp() methods rather than internal members. The public
// methods round to the nearest microsecond for conversion to TimeDelta and
// the rounded value will likely be used by the caller.
DCHECK_GE(timestamp, front_timestamp());
DCHECK_LE(timestamp, back_timestamp());
int64_t frames_until_timestamp = 0;
double timestamp_us = timestamp.InMicroseconds();
double media_time_us = front_timestamp().InMicroseconds();
for (size_t i = 0; i < buffered_.size(); ++i) {
// Leading silence is always accounted prior to anything else.
if (buffered_[i].playback_rate == 0) {
frames_until_timestamp += buffered_[i].frames;
continue;
}
// Calculate upper bound on media time for current block of buffered frames.
double delta_us = buffered_[i].frames * buffered_[i].playback_rate *
microseconds_per_frame_;
double max_media_time_us = media_time_us + delta_us;
// Determine amount of media time to convert to frames for current block. If
// target timestamp falls within current block, scale the amount of frames
// based on remaining amount of media time.
if (timestamp_us <= max_media_time_us) {
frames_until_timestamp +=
buffered_[i].frames * (timestamp_us - media_time_us) / delta_us;
break;
}
media_time_us = max_media_time_us;
frames_until_timestamp += buffered_[i].frames;
}
return base::TimeDelta::FromMicroseconds(
std::round(frames_until_timestamp * microseconds_per_frame_));
}
void AudioClock::ContiguousAudioDataBufferedForTesting(
base::TimeDelta* total, base::TimeDelta* same_rate_total) const {
double scaled_frames = 0;
double scaled_frames_at_same_rate = 0;
bool found_silence = false;
for (size_t i = 0; i < buffered_.size(); ++i) {
if (buffered_[i].playback_rate == 0) {
found_silence = true;
continue;
}
// Any buffered silence breaks our contiguous stretch of audio data.
if (found_silence) break;
scaled_frames += (buffered_[i].frames * buffered_[i].playback_rate);
if (i == 0) scaled_frames_at_same_rate = scaled_frames;
}
*total = base::TimeDelta::FromMicroseconds(scaled_frames *
microseconds_per_frame_);
*same_rate_total = base::TimeDelta::FromMicroseconds(
scaled_frames_at_same_rate * microseconds_per_frame_);
}
AudioClock::AudioData::AudioData(int64_t frames, double playback_rate)
: frames(frames), playback_rate(playback_rate) {}
void AudioClock::PushBufferedAudioData(int64_t frames, double playback_rate) {
if (frames == 0) return;
total_buffered_frames_ += frames;
// Avoid creating extra elements where possible.
if (!buffered_.empty() && buffered_.back().playback_rate == playback_rate) {
buffered_.back().frames += frames;
return;
}
buffered_.push_back(AudioData(frames, playback_rate));
}
void AudioClock::PopBufferedAudioData(int64_t frames) {
DCHECK_LE(frames, total_buffered_frames_);
total_buffered_frames_ -= frames;
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();
frames -= frames_to_pop;
}
}
double AudioClock::ComputeBufferedMediaDurationMicros() const {
double scaled_frames = 0;
for (const auto& buffer : buffered_)
scaled_frames += buffer.frames * buffer.playback_rate;
return scaled_frames * microseconds_per_frame_;
}
} // namespace media
} // namespace cobalt