blob: afa56ca2e430eb03ee0249a4639c7cc5da95978b [file] [log] [blame]
// Copyright 2019 The Cobalt Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "starboard/shared/starboard/player/filter/video_frame_rate_estimator.h"
#include <cmath>
#include "starboard/common/log.h"
namespace starboard {
namespace shared {
namespace starboard {
namespace player {
namespace filter {
VideoFrameRateEstimator::VideoFrameRateEstimator() {
Reset();
}
void VideoFrameRateEstimator::Update(const Frames& frames) {
if (frame_rate_ == kInvalidFrameRate) {
if (frames.size() < 2) {
return;
}
if (frames.back()->is_end_of_stream() && frames.size() < 3) {
return;
}
}
if (frame_rate_ == kInvalidFrameRate) {
CalculateInitialFrameRate(frames);
return;
}
if (!frames.empty()) {
RefineFrameRate(frames);
}
}
void VideoFrameRateEstimator::Reset() {
frame_rate_ = kInvalidFrameRate;
}
void VideoFrameRateEstimator::CalculateInitialFrameRate(
const Frames& frames,
SbTime previous_frame_duration) {
SB_DCHECK(frame_rate_ == kInvalidFrameRate);
SB_DCHECK(!frames.empty());
SB_DCHECK(frames.size() >= 2 || previous_frame_duration > 0);
if (previous_frame_duration > 0) {
accumulated_frame_durations_ = previous_frame_duration;
number_of_frame_durations_accumulated_ = 1;
last_checked_frame_timestamp_ = frames.front()->timestamp();
} else {
number_of_frame_durations_accumulated_ = 0;
}
for (auto current = frames.begin(); current != frames.end(); ++current) {
auto next = current;
++next;
if (next == frames.end() || (*next)->is_end_of_stream()) {
break;
}
auto current_frame_duration =
(*next)->timestamp() - (*current)->timestamp();
SB_DCHECK(current_frame_duration > 0);
if (number_of_frame_durations_accumulated_ == 0) {
accumulated_frame_durations_ = current_frame_duration;
number_of_frame_durations_accumulated_ = 1;
last_checked_frame_timestamp_ = (*next)->timestamp();
continue;
}
auto average_frame_duration =
accumulated_frame_durations_ / number_of_frame_durations_accumulated_;
auto ratio =
static_cast<double>(current_frame_duration) / average_frame_duration;
if (std::fabs(1.0 - ratio) > kFrameDurationRatioEpsilon) {
// We've encountered discontinuity, which is theoretically possible but
// should never happen. We handle this just in case.
SB_LOG(WARNING) << "Frame rate discontinuity detected, "
<< "current frame duration: " << current_frame_duration
<< ", average frame duration: " << average_frame_duration;
break;
}
++number_of_frame_durations_accumulated_;
accumulated_frame_durations_ += current_frame_duration;
}
auto average_frame_duration =
accumulated_frame_durations_ / number_of_frame_durations_accumulated_;
frame_rate_ = static_cast<double>(kSbTimeSecond) / average_frame_duration;
// Snap the frame rate to the nearest integer, so 29.97 will become 30.
if (frame_rate_ - std::floor(frame_rate_) < kFrameRateEpsilon) {
frame_rate_ = std::floor(frame_rate_);
} else if (std::ceil(frame_rate_) - frame_rate_ < kFrameRateEpsilon) {
frame_rate_ = std::ceil(frame_rate_);
}
}
void VideoFrameRateEstimator::RefineFrameRate(const Frames& frames) {
SB_DCHECK(frame_rate_ != kInvalidFrameRate);
SB_DCHECK(!frames.empty());
if (frames.front()->is_end_of_stream()) {
return;
}
auto current_timestamp = frames.front()->timestamp();
if (current_timestamp <= last_checked_frame_timestamp_) {
return;
}
auto last_frame_duration = current_timestamp - last_checked_frame_timestamp_;
auto average_frame_duration =
accumulated_frame_durations_ / number_of_frame_durations_accumulated_;
auto ratio =
static_cast<double>(last_frame_duration) / average_frame_duration;
if (std::fabs(1.0 - ratio) <= kFrameDurationRatioEpsilon) {
last_checked_frame_timestamp_ = current_timestamp;
++number_of_frame_durations_accumulated_;
accumulated_frame_durations_ += last_frame_duration;
return;
}
// We've encountered discontinuity, which is theoretically possible but should
// never happen. We handle this by recalculate the frame rate from scratch.
SB_LOG(WARNING) << "Frame rate discontinuity detected, "
<< "current frame duration: " << last_frame_duration
<< ", average frame duration: " << average_frame_duration;
Reset();
CalculateInitialFrameRate(frames, last_frame_duration);
}
} // namespace filter
} // namespace player
} // namespace starboard
} // namespace shared
} // namespace starboard