Commit 2a181821 authored by Mina Almasry's avatar Mina Almasry Committed by Commit Bot

[Chromecast] Use precise PTS reporting from audio pipeline

The AV sync code needs a very accurate idea of the rate of playback of
the audio to function properly. The current APIs get the PTS at the
'current' time, which is vulnerable to thread de-scheduling while the
value is being measured.

Instead, define a new API in the audio pipeline, which atomically returns the
PTS at a timestamp.

Bug: None
Test: Manual
Change-Id: I5c4148066f8293424e7b32f8fd6b5e66ea62e4d0
Reviewed-on: https://chromium-review.googlesource.com/1121591
Commit-Queue: Mina Almasry <almasrymina@chromium.org>
Reviewed-by: default avatarKenneth MacKay <kmackay@chromium.org>
Cr-Commit-Position: refs/heads/master@{#572044}
parent 5811aa08
...@@ -188,6 +188,19 @@ float AudioDecoderForMixer::SetPlaybackRate(float rate) { ...@@ -188,6 +188,19 @@ float AudioDecoderForMixer::SetPlaybackRate(float rate) {
return rate; return rate;
} }
bool AudioDecoderForMixer::GetTimestampedPts(int64_t* timestamp,
int64_t* pts) const {
if (last_push_timestamp_ == kInvalidTimestamp ||
last_push_pts_ == kInvalidTimestamp)
return false;
// Hmm this timestamp may be in the future. That should be fine, but it's
// a bit weird.
*timestamp = last_push_timestamp_;
*pts = last_push_pts_;
return true;
}
int64_t AudioDecoderForMixer::GetCurrentPts() const { int64_t AudioDecoderForMixer::GetCurrentPts() const {
if (paused_pts_ != kInvalidTimestamp) if (paused_pts_ != kInvalidTimestamp)
return paused_pts_; return paused_pts_;
......
...@@ -46,6 +46,7 @@ class AudioDecoderForMixer : public MediaPipelineBackend::AudioDecoder, ...@@ -46,6 +46,7 @@ class AudioDecoderForMixer : public MediaPipelineBackend::AudioDecoder,
virtual bool Pause(); virtual bool Pause();
virtual bool Resume(); virtual bool Resume();
virtual float SetPlaybackRate(float rate); virtual float SetPlaybackRate(float rate);
virtual bool GetTimestampedPts(int64_t* timestamp, int64_t* pts) const;
virtual int64_t GetCurrentPts() const; virtual int64_t GetCurrentPts() const;
// MediaPipelineBackend::AudioDecoder implementation: // MediaPipelineBackend::AudioDecoder implementation:
......
...@@ -19,7 +19,6 @@ namespace chromecast { ...@@ -19,7 +19,6 @@ namespace chromecast {
namespace media { namespace media {
namespace { namespace {
const int64_t kInvalidTimestamp = std::numeric_limits<int64_t>::min();
// Threshold where the audio and video pts are far enough apart such that we // Threshold where the audio and video pts are far enough apart such that we
// want to do a small correction. // want to do a small correction.
...@@ -111,34 +110,23 @@ void AvSyncVideo::UpkeepAvSync() { ...@@ -111,34 +110,23 @@ void AvSyncVideo::UpkeepAvSync() {
first_video_pts_received_ = true; first_video_pts_received_ = true;
} }
// TODO(almasrymina): using GetCurrentPts is vulnerable to pairing it with an int64_t new_current_apts = 0;
// outdated 'now' if the thread gets descheduled in between. We currently see int64_t new_apts_timestamp = 0;
// extraneous corrections on real hardware and it's probably due to this.
// if (!backend_->audio_decoder()->GetTimestampedPts(&new_apts_timestamp,
// Consider either going back to a NotifyAudioBufferPushed approach that &new_current_apts)) {
// works, or improving GetCurrentPts such as it returns the timestamp this LOG(ERROR) << "Failed to get APTS.";
// was last updated at. return;
// TODO(almasrymina): b/78592779. AudioDecoderForMixer::GetCurrentPts seems }
// to return invalid values which are not kInvalidTimestamp, for unknown
// reasons. As a workaround until that issue is root caused, ignore audio_pts_->AddSample(new_apts_timestamp, new_current_apts, 1.0);
// GetCurrentPts values that are way off.
int64_t audio_pts = backend_->audio_decoder()->GetCurrentPts();
if (abs(audio_pts - new_current_vpts) >
base::TimeDelta::FromHours(1).InMicroseconds() ||
audio_pts == kInvalidTimestamp) {
LOG(WARNING) << "Audio decoder returned invalid pts=" << audio_pts
<< " new_current_vpts=" << new_current_vpts;
} else {
audio_pts_->AddSample(now, backend_->audio_decoder()->GetCurrentPts(), 1.0);
if (!first_audio_pts_received_) { if (!first_audio_pts_received_) {
LOG(INFO) << "Audio starting at difference=" LOG(INFO) << "Audio starting at difference="
<< (backend_->MonotonicClockNow() - << (new_apts_timestamp - new_current_apts) -
backend_->audio_decoder()->GetCurrentPts()) -
playback_start_timestamp_us_; playback_start_timestamp_us_;
first_audio_pts_received_ = true; first_audio_pts_received_ = true;
} }
}
if (video_pts_->num_samples() < 10 || audio_pts_->num_samples() < 20) { if (video_pts_->num_samples() < 10 || audio_pts_->num_samples() < 20) {
VLOG(4) << "Linear regression samples too little." VLOG(4) << "Linear regression samples too little."
...@@ -150,10 +138,13 @@ void AvSyncVideo::UpkeepAvSync() { ...@@ -150,10 +138,13 @@ void AvSyncVideo::UpkeepAvSync() {
int64_t current_vpts; int64_t current_vpts;
double vpts_slope; double vpts_slope;
double apts_slope; double apts_slope;
video_pts_->EstimateY(now, &current_vpts, &error); if (!video_pts_->EstimateY(now, &current_vpts, &error) ||
audio_pts_->EstimateY(now, &current_apts, &error); !audio_pts_->EstimateY(now, &current_apts, &error) ||
video_pts_->EstimateSlope(&vpts_slope, &error); !video_pts_->EstimateSlope(&vpts_slope, &error) ||
audio_pts_->EstimateSlope(&apts_slope, &error); !audio_pts_->EstimateSlope(&apts_slope, &error)) {
VLOG(3) << "Failed to get linear regression estimate.";
return;
}
error_->AddSample(now, current_apts - current_vpts, 1.0); error_->AddSample(now, current_apts - current_vpts, 1.0);
...@@ -165,14 +156,17 @@ void AvSyncVideo::UpkeepAvSync() { ...@@ -165,14 +156,17 @@ void AvSyncVideo::UpkeepAvSync() {
} }
int64_t difference; int64_t difference;
error_->EstimateY(now, &difference, &error); if (!error_->EstimateY(now, &difference, &error)) {
VLOG(3) << "Failed to get linear regression estimate.";
return;
}
VLOG(3) << "Pts_monitor." VLOG(3) << "Pts_monitor."
<< " difference=" << difference / 1000 << " apts_slope=" << apts_slope << " difference=" << difference / 1000 << " apts_slope=" << apts_slope
<< " vpts_slope=" << vpts_slope << " vpts_slope=" << vpts_slope
<< " current_audio_playback_rate_=" << current_audio_playback_rate_ << " current_audio_playback_rate_=" << current_audio_playback_rate_
<< " current_vpts=" << new_current_vpts << " current_vpts=" << new_current_vpts
<< " current_apts=" << backend_->audio_decoder()->GetCurrentPts() << " current_apts=" << new_current_apts
<< " current_time=" << backend_->MonotonicClockNow() << " current_time=" << backend_->MonotonicClockNow()
<< " video_start_error=" << " video_start_error="
<< (new_vpts_timestamp - new_current_vpts - << (new_vpts_timestamp - new_current_vpts -
...@@ -183,26 +177,20 @@ void AvSyncVideo::UpkeepAvSync() { ...@@ -183,26 +177,20 @@ void AvSyncVideo::UpkeepAvSync() {
++av_sync_difference_count_; ++av_sync_difference_count_;
if (abs(difference) > kSoftCorrectionThresholdUs) { if (abs(difference) > kSoftCorrectionThresholdUs) {
SoftCorrection(now); SoftCorrection(now, current_vpts, current_apts, apts_slope, vpts_slope,
difference);
} else { } else {
InSyncCorrection(now); InSyncCorrection(now, current_vpts, current_apts, apts_slope, vpts_slope,
difference);
} }
} }
void AvSyncVideo::SoftCorrection(int64_t now) { void AvSyncVideo::SoftCorrection(int64_t now,
int64_t current_apts = 0; int64_t current_vpts,
int64_t current_vpts = 0; int64_t current_apts,
int64_t difference = 0; double apts_slope,
double error = 0.0; double vpts_slope,
double apts_slope = 0.0; int64_t difference) {
double vpts_slope = 0.0;
video_pts_->EstimateY(now, &current_vpts, &error);
audio_pts_->EstimateY(now, &current_apts, &error);
video_pts_->EstimateSlope(&vpts_slope, &error);
audio_pts_->EstimateSlope(&apts_slope, &error);
error_->EstimateY(now, &difference, &error);
if (audio_pts_->num_samples() < 50) { if (audio_pts_->num_samples() < 50) {
VLOG(4) << "Not enough apts samples=" << audio_pts_->num_samples(); VLOG(4) << "Not enough apts samples=" << audio_pts_->num_samples();
return; return;
...@@ -253,23 +241,16 @@ void AvSyncVideo::SoftCorrection(int64_t now) { ...@@ -253,23 +241,16 @@ void AvSyncVideo::SoftCorrection(int64_t now) {
// sufficiently close to each other, and we no longer need to bridge a gap // sufficiently close to each other, and we no longer need to bridge a gap
// between them. This method will have it so that vpts_slope == apts_slope, and // between them. This method will have it so that vpts_slope == apts_slope, and
// the content should continue to play in sync from here on out. // the content should continue to play in sync from here on out.
void AvSyncVideo::InSyncCorrection(int64_t now) { void AvSyncVideo::InSyncCorrection(int64_t now,
int64_t current_vpts,
int64_t current_apts,
double apts_slope,
double vpts_slope,
int64_t difference) {
if (audio_pts_->num_samples() < 50 || !in_soft_correction_) { if (audio_pts_->num_samples() < 50 || !in_soft_correction_) {
return; return;
} }
int64_t current_apts = 0;
int64_t current_vpts = 0;
int64_t difference = 0;
double error = 0.0;
double apts_slope = 0.0;
double vpts_slope = 0.0;
video_pts_->EstimateY(now, &current_vpts, &error);
audio_pts_->EstimateY(now, &current_apts, &error);
video_pts_->EstimateSlope(&vpts_slope, &error);
audio_pts_->EstimateSlope(&apts_slope, &error);
current_audio_playback_rate_ *= vpts_slope / apts_slope; current_audio_playback_rate_ *= vpts_slope / apts_slope;
current_audio_playback_rate_ = current_audio_playback_rate_ =
backend_->audio_decoder()->SetPlaybackRate(current_audio_playback_rate_); backend_->audio_decoder()->SetPlaybackRate(current_audio_playback_rate_);
...@@ -340,6 +321,11 @@ void AvSyncVideo::GatherPlaybackStatistics() { ...@@ -340,6 +321,11 @@ void AvSyncVideo::GatherPlaybackStatistics() {
backend_->video_decoder()->GetCurrentPts(&accurate_vpts_timestamp, backend_->video_decoder()->GetCurrentPts(&accurate_vpts_timestamp,
&accurate_vpts); &accurate_vpts);
int64_t accurate_apts = 0;
int64_t accurate_apts_timestamp = 0;
backend_->video_decoder()->GetCurrentPts(&accurate_apts_timestamp,
&accurate_apts);
LOG(INFO) << "Playback diagnostics:" LOG(INFO) << "Playback diagnostics:"
<< " CurrentContentRefreshRate=" << " CurrentContentRefreshRate="
<< backend_->video_decoder()->GetCurrentContentRefreshRate() << backend_->video_decoder()->GetCurrentContentRefreshRate()
...@@ -352,15 +338,17 @@ void AvSyncVideo::GatherPlaybackStatistics() { ...@@ -352,15 +338,17 @@ void AvSyncVideo::GatherPlaybackStatistics() {
<< accurate_vpts_timestamp - accurate_vpts - << accurate_vpts_timestamp - accurate_vpts -
playback_start_timestamp_us_ playback_start_timestamp_us_
<< " audio_start_error_estimate=" << " audio_start_error_estimate="
<< backend_->MonotonicClockNow() - << accurate_apts_timestamp - accurate_apts -
backend_->audio_decoder()->GetCurrentPts() -
playback_start_timestamp_us_; playback_start_timestamp_us_;
int64_t current_vpts = 0; int64_t current_vpts = 0;
int64_t current_apts = 0; int64_t current_apts = 0;
double error = 0.0; double error = 0.0;
video_pts_->EstimateY(current_time, &current_vpts, &error); if (!video_pts_->EstimateY(current_time, &current_vpts, &error) ||
audio_pts_->EstimateY(current_time, &current_apts, &error); !audio_pts_->EstimateY(current_time, &current_apts, &error)) {
VLOG(3) << "Failed to get linear regression estimate.";
return;
}
if (delegate_) { if (delegate_) {
delegate_->NotifyAvSyncPlaybackStatistics( delegate_->NotifyAvSyncPlaybackStatistics(
......
...@@ -59,8 +59,18 @@ class AvSyncVideo : public AvSync { ...@@ -59,8 +59,18 @@ class AvSyncVideo : public AvSync {
void StopAvSync(); void StopAvSync();
void GatherPlaybackStatistics(); void GatherPlaybackStatistics();
void SoftCorrection(int64_t now); void SoftCorrection(int64_t now,
void InSyncCorrection(int64_t now); int64_t current_vpts,
int64_t current_apts,
double apts_slope,
double vpts_slope,
int64_t difference);
void InSyncCorrection(int64_t now,
int64_t current_vpts,
int64_t current_apts,
double apts_slope,
double vpts_slope,
int64_t difference);
Delegate* delegate_ = nullptr; Delegate* delegate_ = nullptr;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment