blob: e35310e9d7367929c3d44659b330c7e3629c18c1 [file] [log] [blame]
// Copyright 2021 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/android/shared/video_frame_tracker.h"
#include <cmath>
#include <cstdint>
#include <cstdlib>
#include <vector>
#include "starboard/common/log.h"
#include "starboard/common/mutex.h"
namespace starboard {
namespace android {
namespace shared {
namespace {
const int64_t kMaxAllowedSkew = 5'000; // 5ms
} // namespace
int64_t VideoFrameTracker::seek_to_time() const {
return seek_to_time_;
}
void VideoFrameTracker::OnInputBuffer(int64_t timestamp) {
SB_DCHECK(thread_checker_.CalledOnValidThread());
if (frames_to_be_rendered_.empty()) {
frames_to_be_rendered_.push_back(timestamp);
return;
}
if (frames_to_be_rendered_.size() > max_pending_frames_size_) {
// OnFrameRendered() is only available after API level 23. Cap the size
// of |frames_to_be_rendered_| in case OnFrameRendered() is not available.
frames_to_be_rendered_.pop_front();
}
// Sort by |timestamp|, because |timestamp| won't be monotonic if there are
// B frames.
for (auto it = frames_to_be_rendered_.end();
it != frames_to_be_rendered_.begin();) {
it--;
if (*it < timestamp) {
frames_to_be_rendered_.emplace(++it, timestamp);
return;
} else if (*it == timestamp) {
SB_LOG(WARNING) << "feed video AU with same time stamp " << timestamp;
return;
}
}
frames_to_be_rendered_.emplace_front(timestamp);
}
void VideoFrameTracker::OnFrameRendered(int64_t frame_timestamp) {
ScopedLock lock(rendered_frames_mutex_);
rendered_frames_on_decoder_thread_.push_back(frame_timestamp);
}
void VideoFrameTracker::Seek(int64_t seek_to_time) {
SB_DCHECK(thread_checker_.CalledOnValidThread());
// Ensure that all dropped frames before seeking are captured.
UpdateDroppedFrames();
frames_to_be_rendered_.clear();
seek_to_time_ = seek_to_time;
}
int VideoFrameTracker::UpdateAndGetDroppedFrames() {
SB_DCHECK(thread_checker_.CalledOnValidThread());
UpdateDroppedFrames();
return dropped_frames_;
}
void VideoFrameTracker::UpdateDroppedFrames() {
SB_DCHECK(thread_checker_.CalledOnValidThread());
{
ScopedLock lock(rendered_frames_mutex_);
rendered_frames_on_tracker_thread_.swap(rendered_frames_on_decoder_thread_);
}
while (frames_to_be_rendered_.front() < seek_to_time_) {
// It is possible that the initial frame rendered time is before the
// seek to time, when the platform decides to render a frame earlier
// than the seek to time during preroll. This shouldn't be an issue
// after we align seek time to the next video key frame.
frames_to_be_rendered_.pop_front();
}
// Loop over all timestamps from OnFrameRendered and compare against ones from
// OnInputBuffer.
for (auto rendered_timestamp : rendered_frames_on_tracker_thread_) {
auto to_render_timestamp = frames_to_be_rendered_.begin();
// Loop over all frames to render until we've caught up to the timestamp of
// the last rendered frame.
while (to_render_timestamp != frames_to_be_rendered_.end() &&
!(*to_render_timestamp - rendered_timestamp > kMaxAllowedSkew)) {
if (std::abs(*to_render_timestamp - rendered_timestamp) <=
kMaxAllowedSkew) {
// This frame was rendered, remove it from frames_to_be_rendered_.
to_render_timestamp = frames_to_be_rendered_.erase(to_render_timestamp);
} else if (rendered_timestamp - *to_render_timestamp > kMaxAllowedSkew) {
// The rendered frame is too far ahead. The to_render_timestamp frame
// was dropped.
SB_LOG(WARNING) << "Video frame dropped:" << *to_render_timestamp
<< ", current frame timestamp:" << rendered_timestamp
<< ", frames in the backlog:"
<< frames_to_be_rendered_.size();
++dropped_frames_;
to_render_timestamp = frames_to_be_rendered_.erase(to_render_timestamp);
} else {
// The rendered frame is too early to match the next frame to render.
// This could happen if a frame is reported to be rendered twice or if
// it is rendered more than kMaxAllowedSkew early. In the latter
// scenario the frame will be reported dropped in the next iteration of
// the outer loop.
++to_render_timestamp;
}
}
}
rendered_frames_on_tracker_thread_.clear();
}
} // namespace shared
} // namespace android
} // namespace starboard