| // 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 |