-// Copyright 2014 The Chromium Authors. All rights reserved.
+// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <string>
#include <utility>
-#include "base/bind.h"
#include "base/containers/flat_map.h"
+#include "base/functional/bind.h"
#include "base/location.h"
-#include "base/macros.h"
#include "base/metrics/histogram_macros.h"
+#include "base/sequence_checker.h"
#include "base/strings/string_number_conversions.h"
+#include "base/task/bind_post_task.h"
+#include "base/task/sequenced_task_runner.h"
+#include "base/task/single_thread_task_runner.h"
#include "base/trace_event/trace_event.h"
#include "build/build_config.h"
-#include "media/base/bind_to_current_loop.h"
#include "media/base/limits.h"
+#include "media/base/timestamp_constants.h"
#include "media/base/video_util.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/renderer/modules/mediastream/video_track_adapter_settings.h"
#include "third_party/blink/renderer/platform/scheduler/public/post_cross_thread_task.h"
+#include "third_party/blink/renderer/platform/wtf/cross_thread_copier_base.h"
+#include "third_party/blink/renderer/platform/wtf/cross_thread_copier_gfx.h"
+#include "third_party/blink/renderer/platform/wtf/functional.h"
#include "third_party/blink/renderer/platform/wtf/thread_safe_ref_counted.h"
namespace WTF {
const float kFirstFrameTimeoutInFrameIntervals = 100.0f;
const float kNormalFrameTimeoutInFrameIntervals = 25.0f;
-// Min delta time between two frames allowed without being dropped if a max
-// frame rate is specified.
-const double kMinTimeInMsBetweenFrames = 5;
+// |kMaxDeltaDeviationFactor| is used to determine |max_delta_deviation_| which
+// specifies the allowed deviation from |target_delta_| before dropping a frame.
+// It's set to 20% to be aligned with the previous logic in this file.
+constexpr float kMaxDeltaDeviationFactor = 0.2;
+
// If the delta between two frames is bigger than this, we will consider it to
// be invalid and reset the fps calculation.
-const double kMaxTimeInMsBetweenFrames = 1000;
+constexpr base::TimeDelta kMaxTimeBetweenFrames = base::Milliseconds(1000);
-const double kFrameRateChangeIntervalInSeconds = 1;
+constexpr base::TimeDelta kFrameRateChangeInterval = base::Seconds(1);
const double kFrameRateChangeRate = 0.01;
-const double kFrameRateUpdateIntervalInSeconds = 5;
+constexpr base::TimeDelta kFrameRateUpdateInterval = base::Seconds(5);
struct ComputedSettings {
gfx::Size frame_size;
void ComputeFrameRate(const base::TimeDelta& frame_timestamp,
double* frame_rate,
base::TimeDelta* prev_frame_timestamp) {
+ if (frame_timestamp == media::kNoTimestamp)
+ return;
+
const double delta_ms =
(frame_timestamp - *prev_frame_timestamp).InMillisecondsF();
*prev_frame_timestamp = frame_timestamp;
// reported value.
if (std::abs(settings->frame_rate - settings->last_updated_frame_rate) >
settings->last_updated_frame_rate * kFrameRateChangeRate) {
- if ((now - settings->new_frame_rate_timestamp).InSecondsF() >
- kFrameRateChangeIntervalInSeconds) {
+ if (now - settings->new_frame_rate_timestamp > kFrameRateChangeInterval) {
settings->new_frame_rate_timestamp = now;
settings->last_update_timestamp = now;
settings->last_updated_frame_rate = settings->frame_rate;
// Update frame rate if it hasn't been updated in the last
// kFrameRateUpdateIntervalInSeconds seconds.
- if ((now - settings->last_update_timestamp).InSecondsF() >
- kFrameRateUpdateIntervalInSeconds) {
+ if (now - settings->last_update_timestamp > kFrameRateUpdateInterval) {
settings->last_update_timestamp = now;
settings->last_updated_frame_rate = settings->frame_rate;
return true;
return false;
}
+VideoTrackAdapterSettings ReturnSettingsMaybeOverrideMaxFps(
+ const VideoTrackAdapterSettings& settings) {
+ VideoTrackAdapterSettings new_settings = settings;
+ absl::optional<double> max_fps_override =
+ Platform::Current()->GetWebRtcMaxCaptureFrameRate();
+ if (max_fps_override) {
+ DVLOG(1) << "Overriding max frame rate. Was="
+ << settings.max_frame_rate().value_or(-1)
+ << ", Now=" << *max_fps_override;
+ new_settings.set_max_frame_rate(*max_fps_override);
+ }
+ return new_settings;
+}
+
} // anonymous namespace
-// VideoFrameResolutionAdapter is created on and lives on the IO-thread. It does
-// the resolution adaptation and delivers frames to all registered tracks on the
-// IO-thread. All method calls must be on the IO-thread.
+// VideoFrameResolutionAdapter is created on and lives on the video task runner.
+// It does the resolution adaptation and delivers frames to all registered
+// tracks on the video task runner. All method calls must be on the video task
+// runner.
class VideoTrackAdapter::VideoFrameResolutionAdapter
: public WTF::ThreadSafeRefCounted<VideoFrameResolutionAdapter> {
public:
struct VideoTrackCallbacks {
VideoCaptureDeliverFrameInternalCallback frame_callback;
+ VideoCaptureNotifyFrameDroppedInternalCallback
+ notify_frame_dropped_callback;
DeliverEncodedVideoFrameInternalCallback encoded_frame_callback;
+ VideoCaptureSubCaptureTargetVersionInternalCallback
+ sub_capture_target_version_callback;
VideoTrackSettingsInternalCallback settings_callback;
VideoTrackFormatInternalCallback format_callback;
};
// Setting |max_frame_rate| to 0.0, means that no frame rate limitation
// will be done.
VideoFrameResolutionAdapter(
- scoped_refptr<base::SingleThreadTaskRunner> render_message_loop,
+ scoped_refptr<base::SingleThreadTaskRunner> reader_task_runner,
const VideoTrackAdapterSettings& settings,
base::WeakPtr<MediaStreamVideoSource> media_stream_video_source);
+ VideoFrameResolutionAdapter(const VideoFrameResolutionAdapter&) = delete;
+ VideoFrameResolutionAdapter& operator=(const VideoFrameResolutionAdapter&) =
+ delete;
+
// Add |frame_callback|, |encoded_frame_callback| to receive video frames on
- // the IO-thread and |settings_callback| to set track settings on the main
- // thread. |frame_callback| will however be released on the main render
- // thread.
+ // the video task runner, |sub_capture_target_version_callback| to receive
+ // notifications when a new sub-capture-target version is acknowledged, and
+ // |settings_callback| to set track settings on the main thread.
+ // |frame_callback| will however be released on the main render thread.
void AddCallbacks(
const MediaStreamVideoTrack* track,
VideoCaptureDeliverFrameInternalCallback frame_callback,
+ VideoCaptureNotifyFrameDroppedInternalCallback
+ notify_frame_dropped_callback,
DeliverEncodedVideoFrameInternalCallback encoded_frame_callback,
+ VideoCaptureSubCaptureTargetVersionInternalCallback
+ sub_capture_target_version_callback,
VideoTrackSettingsInternalCallback settings_callback,
VideoTrackFormatInternalCallback format_callback);
// callbacks if |track| was not present in the adapter.
VideoTrackCallbacks RemoveAndGetCallbacks(const MediaStreamVideoTrack* track);
+ // The source has provided us with a frame.
void DeliverFrame(
scoped_refptr<media::VideoFrame> frame,
- std::vector<scoped_refptr<media::VideoFrame>> scaled_video_frames,
const base::TimeTicks& estimated_capture_time,
bool is_device_rotated);
+ // This method is called when a frame is dropped, whether dropped by the
+ // source (via VideoTrackAdapter::OnFrameDroppedOnVideoTaskRunner) or
+ // internally (in DeliverFrame).
+ void OnFrameDropped(media::VideoCaptureFrameDropReason reason);
void DeliverEncodedVideoFrame(scoped_refptr<EncodedVideoFrame> frame,
base::TimeTicks estimated_capture_time);
+ void NewSubCaptureTargetVersionOnVideoTaskRunner(
+ uint32_t sub_capture_target_version);
+
// Returns true if all arguments match with the output of this adapter.
bool SettingsMatch(const VideoTrackAdapterSettings& settings) const;
void DoDeliverFrame(
scoped_refptr<media::VideoFrame> video_frame,
- std::vector<scoped_refptr<media::VideoFrame>> scaled_video_frames,
const base::TimeTicks& estimated_capture_time);
// Returns |true| if the input frame rate is higher that the requested max
// or frame rate have changed since last update.
void MaybeUpdateTracksFormat(const media::VideoFrame& frame);
- void PostFrameDroppedToMainTaskRunner(
- media::VideoCaptureFrameDropReason reason);
-
- // Bound to the IO-thread.
- THREAD_CHECKER(io_thread_checker_);
+ // Bound to the video task runner.
+ SEQUENCE_CHECKER(video_sequence_checker_);
// The task runner where we will release VideoCaptureDeliverFrameCB
// registered in AddCallbacks.
base::WeakPtr<MediaStreamVideoSource> media_stream_video_source_;
- VideoTrackAdapterSettings settings_;
- double frame_rate_;
- base::TimeDelta last_time_stamp_;
- double keep_frame_counter_;
+ const VideoTrackAdapterSettings settings_;
+
+ // The target timestamp delta between video frames, corresponding to the max
+ // fps.
+ const absl::optional<base::TimeDelta> target_delta_;
+
+ // The maximum allowed deviation from |target_delta_| before dropping a frame.
+ const absl::optional<base::TimeDelta> max_delta_deviation_;
+
+ // The timestamp of the last delivered video frame.
+ base::TimeDelta timestamp_last_delivered_frame_ = base::TimeDelta::Max();
+
+ // Stores the accumulated difference between |target_delta_| and the actual
+ // timestamp delta between frames that are delivered. Clamped to
+ // [-max_delta_deviation, target_delta_ / 2]. This is used to allow some
+ // frames to be closer than |target_delta_| in order to maintain
+ // |target_delta_| on average. Without it we may end up with an average fps
+ // that is half of max fps.
+ base::TimeDelta accumulated_drift_;
ComputedSettings track_settings_;
ComputedSettings source_format_settings_;
base::flat_map<const MediaStreamVideoTrack*, VideoTrackCallbacks> callbacks_;
-
- DISALLOW_COPY_AND_ASSIGN(VideoFrameResolutionAdapter);
};
VideoTrackAdapter::VideoFrameResolutionAdapter::VideoFrameResolutionAdapter(
- scoped_refptr<base::SingleThreadTaskRunner> render_message_loop,
+ scoped_refptr<base::SingleThreadTaskRunner> reader_task_runner,
const VideoTrackAdapterSettings& settings,
base::WeakPtr<MediaStreamVideoSource> media_stream_video_source)
- : renderer_task_runner_(render_message_loop),
+ : renderer_task_runner_(reader_task_runner),
media_stream_video_source_(media_stream_video_source),
- settings_(settings),
- frame_rate_(MediaStreamVideoSource::kDefaultFrameRate),
- last_time_stamp_(base::TimeDelta::Max()),
- keep_frame_counter_(0.0) {
+ settings_(ReturnSettingsMaybeOverrideMaxFps(settings)),
+ target_delta_(settings_.max_frame_rate()
+ ? absl::make_optional(base::Seconds(
+ 1.0 / settings_.max_frame_rate().value()))
+ : absl::nullopt),
+ max_delta_deviation_(target_delta_
+ ? absl::make_optional(kMaxDeltaDeviationFactor *
+ target_delta_.value())
+ : absl::nullopt) {
+ DVLOG(1) << __func__ << " max_framerate "
+ << settings.max_frame_rate().value_or(-1);
DCHECK(renderer_task_runner_.get());
- DCHECK_CALLED_ON_VALID_THREAD(io_thread_checker_);
+ DCHECK_CALLED_ON_VALID_SEQUENCE(video_sequence_checker_);
CHECK_NE(0, settings_.max_aspect_ratio());
-
- absl::optional<double> max_fps_override =
- Platform::Current()->GetWebRtcMaxCaptureFrameRate();
- if (max_fps_override) {
- DVLOG(1) << "Overriding max frame rate. Was=" << settings_.max_frame_rate()
- << ", Now=" << *max_fps_override;
- settings_.set_max_frame_rate(*max_fps_override);
- }
}
VideoTrackAdapter::VideoFrameResolutionAdapter::~VideoFrameResolutionAdapter() {
- DCHECK_CALLED_ON_VALID_THREAD(io_thread_checker_);
+ DCHECK_CALLED_ON_VALID_SEQUENCE(video_sequence_checker_);
DCHECK(callbacks_.empty());
}
void VideoTrackAdapter::VideoFrameResolutionAdapter::AddCallbacks(
const MediaStreamVideoTrack* track,
VideoCaptureDeliverFrameInternalCallback frame_callback,
+ VideoCaptureNotifyFrameDroppedInternalCallback
+ notify_frame_dropped_callback,
DeliverEncodedVideoFrameInternalCallback encoded_frame_callback,
+ VideoCaptureSubCaptureTargetVersionInternalCallback
+ sub_capture_target_version_callback,
VideoTrackSettingsInternalCallback settings_callback,
VideoTrackFormatInternalCallback format_callback) {
- DCHECK_CALLED_ON_VALID_THREAD(io_thread_checker_);
+ DCHECK_CALLED_ON_VALID_SEQUENCE(video_sequence_checker_);
+
+ // The new track's settings should match the resolution adapter's current
+ // |track_settings_| as set for existing track(s) with matching
+ // VideoTrackAdapterSettings.
+ if (!callbacks_.empty() && track_settings_.frame_size.width() > 0 &&
+ track_settings_.frame_size.height() > 0) {
+ settings_callback.Run(track_settings_.frame_size,
+ track_settings_.frame_rate);
+ }
VideoTrackCallbacks track_callbacks = {
- std::move(frame_callback), std::move(encoded_frame_callback),
- std::move(settings_callback), std::move(format_callback)};
+ std::move(frame_callback),
+ std::move(notify_frame_dropped_callback),
+ std::move(encoded_frame_callback),
+ std::move(sub_capture_target_version_callback),
+ std::move(settings_callback),
+ std::move(format_callback)};
callbacks_.emplace(track, std::move(track_callbacks));
}
void VideoTrackAdapter::VideoFrameResolutionAdapter::RemoveCallbacks(
const MediaStreamVideoTrack* track) {
- DCHECK_CALLED_ON_VALID_THREAD(io_thread_checker_);
+ DCHECK_CALLED_ON_VALID_SEQUENCE(video_sequence_checker_);
callbacks_.erase(track);
}
VideoTrackAdapter::VideoFrameResolutionAdapter::VideoTrackCallbacks
VideoTrackAdapter::VideoFrameResolutionAdapter::RemoveAndGetCallbacks(
const MediaStreamVideoTrack* track) {
- DCHECK_CALLED_ON_VALID_THREAD(io_thread_checker_);
+ DCHECK_CALLED_ON_VALID_SEQUENCE(video_sequence_checker_);
VideoTrackCallbacks track_callbacks;
auto it = callbacks_.find(track);
if (it == callbacks_.end())
void VideoTrackAdapter::VideoFrameResolutionAdapter::DeliverFrame(
scoped_refptr<media::VideoFrame> video_frame,
- std::vector<scoped_refptr<media::VideoFrame>> scaled_video_frames,
const base::TimeTicks& estimated_capture_time,
bool is_device_rotated) {
- DCHECK_CALLED_ON_VALID_THREAD(io_thread_checker_);
+ DCHECK_CALLED_ON_VALID_SEQUENCE(video_sequence_checker_);
if (!video_frame) {
DLOG(ERROR) << "Incoming frame is not valid.";
- PostFrameDroppedToMainTaskRunner(
+ OnFrameDropped(
media::VideoCaptureFrameDropReason::kResolutionAdapterFrameIsNotValid);
return;
}
auto frame_drop_reason = media::VideoCaptureFrameDropReason::kNone;
if (MaybeDropFrame(*video_frame, frame_rate, &frame_drop_reason)) {
- PostFrameDroppedToMainTaskRunner(frame_drop_reason);
+ OnFrameDropped(frame_drop_reason);
return;
}
if (video_frame->HasTextures() &&
video_frame->storage_type() !=
media::VideoFrame::STORAGE_GPU_MEMORY_BUFFER) {
- DoDeliverFrame(std::move(video_frame), std::move(scaled_video_frames),
- estimated_capture_time);
+ DoDeliverFrame(std::move(video_frame), estimated_capture_time);
return;
}
// The video frame we deliver may or may not get cropping and scaling
// soft-applied. Ultimately the listener will decide whether to use the
- // |delivered_video_frame| or one of the |scaled_video_frames|. When frames
- // arrive to their final destination, if a scaled frame already has the
- // destination dimensions there is no need to apply the soft scale calculated
- // here. But that is not always possible.
+ // |delivered_video_frame|.
scoped_refptr<media::VideoFrame> delivered_video_frame = video_frame;
gfx::Size desired_size;
gfx::Rect region_in_frame = media::ComputeLetterboxRegion(
video_frame->visible_rect(), desired_size);
- if (video_frame->HasTextures() || video_frame->HasGpuMemoryBuffer()) {
- // ComputeLetterboxRegion() produces in some cases odd dimensions due to
- // internal rounding errors; |region_in_frame| is always smaller or equal
- // to video_frame->visible_rect(), we can "grow it" if the dimensions are
- // odd.
- region_in_frame.set_width((region_in_frame.width() + 1) & ~1);
- region_in_frame.set_height((region_in_frame.height() + 1) & ~1);
- }
+ // Some consumers (for example
+ // ImageCaptureFrameGrabber::SingleShotFrameHandler::ConvertAndDeliverFrame)
+ // don't support pixel format conversions when the source format is YUV with
+ // UV subsampled and vsible_rect().x() being odd. The conversion ends up
+ // miscomputing the UV plane and ends up with a VU plane leading to a blue
+ // face tint. Round x() to even to avoid. See crbug.com/1307304.
+ region_in_frame.set_x(region_in_frame.x() & ~1);
+ region_in_frame.set_y(region_in_frame.y() & ~1);
+
+ // ComputeLetterboxRegion() sometimes produces odd dimensions due to
+ // internal rounding errors; allow to round upwards if there's slack
+ // otherwise round downwards.
+ bool width_has_slack =
+ region_in_frame.right() < video_frame->visible_rect().right();
+ region_in_frame.set_width((region_in_frame.width() + width_has_slack) & ~1);
+ bool height_has_slack =
+ region_in_frame.bottom() < video_frame->visible_rect().bottom();
+ region_in_frame.set_height((region_in_frame.height() + height_has_slack) &
+ ~1);
delivered_video_frame = media::VideoFrame::WrapVideoFrame(
video_frame, video_frame->format(), region_in_frame, desired_size);
if (!delivered_video_frame) {
- PostFrameDroppedToMainTaskRunner(
- media::VideoCaptureFrameDropReason::
- kResolutionAdapterWrappingFrameForCroppingFailed);
+ OnFrameDropped(media::VideoCaptureFrameDropReason::
+ kResolutionAdapterWrappingFrameForCroppingFailed);
return;
}
<< " output visible rect "
<< delivered_video_frame->visible_rect().ToString();
}
- DoDeliverFrame(std::move(delivered_video_frame),
- std::move(scaled_video_frames), estimated_capture_time);
+ DoDeliverFrame(std::move(delivered_video_frame), estimated_capture_time);
}
void VideoTrackAdapter::VideoFrameResolutionAdapter::DeliverEncodedVideoFrame(
scoped_refptr<EncodedVideoFrame> frame,
base::TimeTicks estimated_capture_time) {
- DCHECK_CALLED_ON_VALID_THREAD(io_thread_checker_);
+ DCHECK_CALLED_ON_VALID_SEQUENCE(video_sequence_checker_);
for (const auto& callback : callbacks_) {
callback.second.encoded_frame_callback.Run(frame, estimated_capture_time);
}
}
+void VideoTrackAdapter::VideoFrameResolutionAdapter::
+ NewSubCaptureTargetVersionOnVideoTaskRunner(
+ uint32_t sub_capture_target_version) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(video_sequence_checker_);
+ for (const auto& callback : callbacks_) {
+ callback.second.sub_capture_target_version_callback.Run(
+ sub_capture_target_version);
+ }
+}
+
bool VideoTrackAdapter::VideoFrameResolutionAdapter::SettingsMatch(
const VideoTrackAdapterSettings& settings) const {
- DCHECK_CALLED_ON_VALID_THREAD(io_thread_checker_);
+ DCHECK_CALLED_ON_VALID_SEQUENCE(video_sequence_checker_);
return settings_ == settings;
}
bool VideoTrackAdapter::VideoFrameResolutionAdapter::IsEmpty() const {
- DCHECK_CALLED_ON_VALID_THREAD(io_thread_checker_);
+ DCHECK_CALLED_ON_VALID_SEQUENCE(video_sequence_checker_);
return callbacks_.empty();
}
void VideoTrackAdapter::VideoFrameResolutionAdapter::DoDeliverFrame(
scoped_refptr<media::VideoFrame> video_frame,
- std::vector<scoped_refptr<media::VideoFrame>> scaled_video_frames,
const base::TimeTicks& estimated_capture_time) {
- DCHECK_CALLED_ON_VALID_THREAD(io_thread_checker_);
+ DCHECK_CALLED_ON_VALID_SEQUENCE(video_sequence_checker_);
if (callbacks_.empty()) {
- PostFrameDroppedToMainTaskRunner(
+ OnFrameDropped(
media::VideoCaptureFrameDropReason::kResolutionAdapterHasNoCallbacks);
}
for (const auto& callback : callbacks_) {
MaybeUpdateTrackSettings(callback.second.settings_callback, *video_frame);
- callback.second.frame_callback.Run(video_frame, scaled_video_frames,
- estimated_capture_time);
+ callback.second.frame_callback.Run(video_frame, estimated_capture_time);
+ }
+}
+
+void VideoTrackAdapter::VideoFrameResolutionAdapter::OnFrameDropped(
+ media::VideoCaptureFrameDropReason reason) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(video_sequence_checker_);
+ // Notify callbacks, such as
+ // MediaStreamVideoTrack::FrameDeliverer::NotifyFrameDroppedOnVideoTaskRunner.
+ for (const auto& callback : callbacks_) {
+ callback.second.notify_frame_dropped_callback.Run(reason);
}
}
const media::VideoFrame& frame,
float source_frame_rate,
media::VideoCaptureFrameDropReason* reason) {
- DCHECK_CALLED_ON_VALID_THREAD(io_thread_checker_);
-
- // Do not drop frames if max frame rate hasn't been specified.
- if (settings_.max_frame_rate() == 0.0f ||
- (base::FeatureList::IsEnabled(
- features::kMediaStreamTrackUseConfigMaxFrameRate) &&
- source_frame_rate > 0 &&
- source_frame_rate <= settings_.max_frame_rate())) {
- last_time_stamp_ = frame.timestamp();
+ DCHECK_CALLED_ON_VALID_SEQUENCE(video_sequence_checker_);
+
+ // Never drop frames if the max frame rate has not been specified.
+ if (!settings_.max_frame_rate().has_value()) {
+ timestamp_last_delivered_frame_ = frame.timestamp();
return false;
}
- const double delta_ms =
- (frame.timestamp() - last_time_stamp_).InMillisecondsF();
-
- // Check if the time since the last frame is completely off.
- if (delta_ms < 0 || delta_ms > kMaxTimeInMsBetweenFrames) {
- // Reset |last_time_stamp_| and fps calculation.
- last_time_stamp_ = frame.timestamp();
- frame_rate_ = MediaStreamVideoSource::kDefaultFrameRate;
- keep_frame_counter_ = 0.0;
+ const base::TimeDelta delta =
+ (frame.timestamp() - timestamp_last_delivered_frame_);
+
+ // Keep the frame if the time since the last frame is completely off.
+ if (delta.is_negative() || delta > kMaxTimeBetweenFrames) {
+ // Reset |timestamp_last_delivered_frame_| and |accumulated_drift|.
+ timestamp_last_delivered_frame_ = frame.timestamp();
+ accumulated_drift_ = base::Milliseconds(0.0);
return false;
}
- if (delta_ms < kMinTimeInMsBetweenFrames) {
- // We have seen video frames being delivered from camera devices back to
- // back. The simple AR filter for frame rate calculation is too short to
- // handle that. https://crbug/394315
- // TODO(perkj): Can we come up with a way to fix the times stamps and the
- // timing when frames are delivered so all frames can be used?
- // The time stamps are generated by Chrome and not the actual device.
- // Most likely the back to back problem is caused by software and not the
- // actual camera.
- DVLOG(3) << "Drop frame since delta time since previous frame is "
- << delta_ms << "ms.";
+ DCHECK(target_delta_ && max_delta_deviation_);
+ if (delta < target_delta_.value() - max_delta_deviation_.value() -
+ accumulated_drift_) {
+ // Drop the frame because the input frame rate is too high.
*reason = media::VideoCaptureFrameDropReason::
- kResolutionAdapterTimestampTooCloseToPrevious;
+ kResolutionAdapterFrameRateIsHigherThanRequested;
return true;
}
- last_time_stamp_ = frame.timestamp();
- // Calculate the frame rate using a simple AR filter.
- // Use a simple filter with 0.1 weight of the current sample.
- frame_rate_ = 100 / delta_ms + 0.9 * frame_rate_;
-
- // Prefer to not drop frames.
- if (settings_.max_frame_rate() + 0.5f > frame_rate_)
- return false; // Keep this frame.
-
- // The input frame rate is higher than requested.
- // Decide if we should keep this frame or drop it.
- keep_frame_counter_ += settings_.max_frame_rate() / frame_rate_;
- if (keep_frame_counter_ >= 1) {
- keep_frame_counter_ -= 1;
- // Keep the frame.
- return false;
- }
- DVLOG(3) << "Drop frame. Input frame_rate_ " << frame_rate_ << ".";
- *reason = media::VideoCaptureFrameDropReason::
- kResolutionAdapterFrameRateIsHigherThanRequested;
- return true;
+
+ // Keep the frame and store the accumulated drift.
+ timestamp_last_delivered_frame_ = frame.timestamp();
+ accumulated_drift_ += delta - target_delta_.value();
+ DCHECK_GE(accumulated_drift_, -max_delta_deviation_.value());
+ // Limit the maximum accumulated drift to half of the target delta. If we
+ // don't do this, it may happen that we output a series of frames too quickly
+ // after a period of no frames. There is no need to actively limit the minimum
+ // accumulated drift because that happens automatically when we drop frames
+ // that are too close in time.
+ accumulated_drift_ = std::min(accumulated_drift_, target_delta_.value() / 2);
+ return false;
}
void VideoTrackAdapter::VideoFrameResolutionAdapter::MaybeUpdateTrackSettings(
const VideoTrackSettingsInternalCallback& settings_callback,
const media::VideoFrame& frame) {
- DCHECK_CALLED_ON_VALID_THREAD(io_thread_checker_);
+ DCHECK_CALLED_ON_VALID_SEQUENCE(video_sequence_checker_);
ComputeFrameRate(frame.timestamp(), &track_settings_.frame_rate,
&track_settings_.prev_frame_timestamp);
if (MaybeUpdateFrameRate(&track_settings_) ||
}
void VideoTrackAdapter::VideoFrameResolutionAdapter::MaybeUpdateTracksFormat(
const media::VideoFrame& frame) {
- DCHECK_CALLED_ON_VALID_THREAD(io_thread_checker_);
+ DCHECK_CALLED_ON_VALID_SEQUENCE(video_sequence_checker_);
if (MaybeUpdateFrameRate(&source_format_settings_) ||
frame.natural_size() != track_settings_.frame_size) {
source_format_settings_.frame_size = frame.natural_size();
}
void VideoTrackAdapter::VideoFrameResolutionAdapter::ResetFrameRate() {
- DCHECK_CALLED_ON_VALID_THREAD(io_thread_checker_);
+ DCHECK_CALLED_ON_VALID_SEQUENCE(video_sequence_checker_);
for (const auto& callback : callbacks_) {
callback.second.settings_callback.Run(track_settings_.frame_size, 0.0);
}
}
-void VideoTrackAdapter::VideoFrameResolutionAdapter::
- PostFrameDroppedToMainTaskRunner(
- media::VideoCaptureFrameDropReason reason) {
- PostCrossThreadTask(
- *renderer_task_runner_, FROM_HERE,
- CrossThreadBindOnce(&MediaStreamVideoSource::OnFrameDropped,
- media_stream_video_source_, reason));
-}
-
VideoTrackAdapter::VideoTrackAdapter(
- scoped_refptr<base::SingleThreadTaskRunner> io_task_runner,
+ scoped_refptr<base::SequencedTaskRunner> video_task_runner,
base::WeakPtr<MediaStreamVideoSource> media_stream_video_source)
- : io_task_runner_(io_task_runner),
+ : video_task_runner_(video_task_runner),
media_stream_video_source_(media_stream_video_source),
- renderer_task_runner_(base::ThreadTaskRunnerHandle::Get()),
- monitoring_frame_rate_(false),
+ renderer_task_runner_(base::SingleThreadTaskRunner::GetCurrentDefault()),
muted_state_(false),
frame_counter_(0),
+ old_frame_counter_snapshot_(0),
source_frame_rate_(0.0f) {
- DCHECK(io_task_runner);
+ DCHECK(video_task_runner);
}
VideoTrackAdapter::~VideoTrackAdapter() {
- DCHECK(adapters_.IsEmpty());
+ DCHECK(adapters_.empty());
+ DCHECK(!monitoring_frame_rate_timer_);
}
-void VideoTrackAdapter::AddTrack(const MediaStreamVideoTrack* track,
- VideoCaptureDeliverFrameCB frame_callback,
- EncodedVideoFrameCB encoded_frame_callback,
- VideoTrackSettingsCallback settings_callback,
- VideoTrackFormatCallback format_callback,
- const VideoTrackAdapterSettings& settings) {
- DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+void VideoTrackAdapter::AddTrack(
+ const MediaStreamVideoTrack* track,
+ VideoCaptureDeliverFrameCB frame_callback,
+ VideoCaptureNotifyFrameDroppedCB notify_frame_dropped_callback,
+ EncodedVideoFrameCB encoded_frame_callback,
+ VideoCaptureSubCaptureTargetVersionCB sub_capture_target_version_callback,
+ VideoTrackSettingsCallback settings_callback,
+ VideoTrackFormatCallback format_callback,
+ const VideoTrackAdapterSettings& settings) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
PostCrossThreadTask(
- *io_task_runner_, FROM_HERE,
+ *video_task_runner_, FROM_HERE,
CrossThreadBindOnce(
- &VideoTrackAdapter::AddTrackOnIO, WTF::CrossThreadUnretained(this),
- WTF::CrossThreadUnretained(track),
+ &VideoTrackAdapter::AddTrackOnVideoTaskRunner,
+ WTF::CrossThreadUnretained(this), WTF::CrossThreadUnretained(track),
CrossThreadBindRepeating(std::move(frame_callback)),
+ CrossThreadBindRepeating(std::move(notify_frame_dropped_callback)),
CrossThreadBindRepeating(std::move(encoded_frame_callback)),
+ CrossThreadBindRepeating(
+ std::move(sub_capture_target_version_callback)),
CrossThreadBindRepeating(std::move(settings_callback)),
CrossThreadBindRepeating(std::move(format_callback)), settings));
}
-void VideoTrackAdapter::AddTrackOnIO(
+void VideoTrackAdapter::AddTrackOnVideoTaskRunner(
const MediaStreamVideoTrack* track,
VideoCaptureDeliverFrameInternalCallback frame_callback,
+ VideoCaptureNotifyFrameDroppedInternalCallback
+ notify_frame_dropped_callback,
DeliverEncodedVideoFrameInternalCallback encoded_frame_callback,
+ VideoCaptureSubCaptureTargetVersionInternalCallback
+ sub_capture_target_version_callback,
VideoTrackSettingsInternalCallback settings_callback,
VideoTrackFormatInternalCallback format_callback,
const VideoTrackAdapterSettings& settings) {
- DCHECK(io_task_runner_->BelongsToCurrentThread());
+ DCHECK(video_task_runner_->RunsTasksInCurrentSequence());
scoped_refptr<VideoFrameResolutionAdapter> adapter;
for (const auto& frame_adapter : adapters_) {
if (frame_adapter->SettingsMatch(settings)) {
adapters_.push_back(adapter);
}
- adapter->AddCallbacks(
- track, std::move(frame_callback), std::move(encoded_frame_callback),
- std::move(settings_callback), std::move(format_callback));
+ adapter->AddCallbacks(track, std::move(frame_callback),
+ std::move(notify_frame_dropped_callback),
+ std::move(encoded_frame_callback),
+ std::move(sub_capture_target_version_callback),
+ std::move(settings_callback),
+ std::move(format_callback));
}
void VideoTrackAdapter::RemoveTrack(const MediaStreamVideoTrack* track) {
- DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
PostCrossThreadTask(
- *io_task_runner_, FROM_HERE,
- CrossThreadBindOnce(&VideoTrackAdapter::RemoveTrackOnIO,
+ *video_task_runner_, FROM_HERE,
+ CrossThreadBindOnce(&VideoTrackAdapter::RemoveTrackOnVideoTaskRunner,
WrapRefCounted(this), CrossThreadUnretained(track)));
}
void VideoTrackAdapter::ReconfigureTrack(
const MediaStreamVideoTrack* track,
const VideoTrackAdapterSettings& settings) {
- DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
PostCrossThreadTask(
- *io_task_runner_, FROM_HERE,
- CrossThreadBindOnce(&VideoTrackAdapter::ReconfigureTrackOnIO,
+ *video_task_runner_, FROM_HERE,
+ CrossThreadBindOnce(&VideoTrackAdapter::ReconfigureTrackOnVideoTaskRunner,
WrapRefCounted(this), CrossThreadUnretained(track),
settings));
}
void VideoTrackAdapter::StartFrameMonitoring(
double source_frame_rate,
const OnMutedCallback& on_muted_callback) {
- DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
VideoTrackAdapter::OnMutedCallback bound_on_muted_callback =
- media::BindToCurrentLoop(on_muted_callback);
+ base::BindPostTaskToCurrentDefault(on_muted_callback);
PostCrossThreadTask(
- *io_task_runner_, FROM_HERE,
+ *video_task_runner_, FROM_HERE,
CrossThreadBindOnce(
- &VideoTrackAdapter::StartFrameMonitoringOnIO, WrapRefCounted(this),
+ &VideoTrackAdapter::StartFrameMonitoringOnVideoTaskRunner,
+ WrapRefCounted(this),
CrossThreadBindRepeating(std::move(bound_on_muted_callback)),
source_frame_rate));
}
void VideoTrackAdapter::StopFrameMonitoring() {
- DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
PostCrossThreadTask(
- *io_task_runner_, FROM_HERE,
- CrossThreadBindOnce(&VideoTrackAdapter::StopFrameMonitoringOnIO,
- WrapRefCounted(this)));
+ *video_task_runner_, FROM_HERE,
+ CrossThreadBindOnce(
+ &VideoTrackAdapter::StopFrameMonitoringOnVideoTaskRunner,
+ WrapRefCounted(this)));
}
void VideoTrackAdapter::SetSourceFrameSize(const gfx::Size& source_frame_size) {
- DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
PostCrossThreadTask(
- *io_task_runner_, FROM_HERE,
- CrossThreadBindOnce(&VideoTrackAdapter::SetSourceFrameSizeOnIO,
- WrapRefCounted(this), source_frame_size));
+ *video_task_runner_, FROM_HERE,
+ CrossThreadBindOnce(
+ &VideoTrackAdapter::SetSourceFrameSizeOnVideoTaskRunner,
+ WrapRefCounted(this), source_frame_size));
}
bool VideoTrackAdapter::CalculateDesiredSize(
return true;
}
-void VideoTrackAdapter::StartFrameMonitoringOnIO(
+void VideoTrackAdapter::StartFrameMonitoringOnVideoTaskRunner(
OnMutedInternalCallback on_muted_callback,
double source_frame_rate) {
- DCHECK(io_task_runner_->BelongsToCurrentThread());
- DCHECK(!monitoring_frame_rate_);
+ DCHECK(video_task_runner_->RunsTasksInCurrentSequence());
+ DCHECK(!monitoring_frame_rate_timer_);
- monitoring_frame_rate_ = true;
+ on_muted_callback_ = std::move(on_muted_callback);
+ monitoring_frame_rate_timer_ = std::make_unique<LowPrecisionTimer>(
+ video_task_runner_,
+ ConvertToBaseRepeatingCallback(CrossThreadBindRepeating(
+ &VideoTrackAdapter::CheckFramesReceivedOnVideoTaskRunner,
+ WrapRefCounted(this))));
// If the source does not know the frame rate, set one by default.
if (source_frame_rate == 0.0f)
source_frame_rate_ = source_frame_rate;
DVLOG(1) << "Monitoring frame creation, first (large) delay: "
<< (kFirstFrameTimeoutInFrameIntervals / source_frame_rate_) << "s";
- PostDelayedCrossThreadTask(
- *io_task_runner_, FROM_HERE,
- CrossThreadBindOnce(&VideoTrackAdapter::CheckFramesReceivedOnIO,
- WrapRefCounted(this), std::move(on_muted_callback),
- frame_counter_),
- base::TimeDelta::FromSecondsD(kFirstFrameTimeoutInFrameIntervals /
- source_frame_rate_));
+ old_frame_counter_snapshot_ = frame_counter_;
+ monitoring_frame_rate_timer_->StartOneShot(
+ base::Seconds(kFirstFrameTimeoutInFrameIntervals / source_frame_rate_));
}
-void VideoTrackAdapter::StopFrameMonitoringOnIO() {
- DCHECK(io_task_runner_->BelongsToCurrentThread());
- monitoring_frame_rate_ = false;
+void VideoTrackAdapter::StopFrameMonitoringOnVideoTaskRunner() {
+ DCHECK(video_task_runner_->RunsTasksInCurrentSequence());
+ if (!monitoring_frame_rate_timer_) {
+ // Already stopped.
+ return;
+ }
+ monitoring_frame_rate_timer_->Shutdown();
+ monitoring_frame_rate_timer_.reset();
+ on_muted_callback_ = OnMutedInternalCallback();
}
-void VideoTrackAdapter::SetSourceFrameSizeOnIO(
+void VideoTrackAdapter::SetSourceFrameSizeOnVideoTaskRunner(
const gfx::Size& source_frame_size) {
- DCHECK(io_task_runner_->BelongsToCurrentThread());
+ DCHECK(video_task_runner_->RunsTasksInCurrentSequence());
source_frame_size_ = source_frame_size;
}
-void VideoTrackAdapter::RemoveTrackOnIO(const MediaStreamVideoTrack* track) {
- DCHECK(io_task_runner_->BelongsToCurrentThread());
+void VideoTrackAdapter::RemoveTrackOnVideoTaskRunner(
+ const MediaStreamVideoTrack* track) {
+ DCHECK(video_task_runner_->RunsTasksInCurrentSequence());
for (auto* it = adapters_.begin(); it != adapters_.end(); ++it) {
(*it)->RemoveCallbacks(track);
if ((*it)->IsEmpty()) {
}
}
-void VideoTrackAdapter::ReconfigureTrackOnIO(
+void VideoTrackAdapter::ReconfigureTrackOnVideoTaskRunner(
const MediaStreamVideoTrack* track,
const VideoTrackAdapterSettings& settings) {
- DCHECK(io_task_runner_->BelongsToCurrentThread());
+ DCHECK(video_task_runner_->RunsTasksInCurrentSequence());
VideoFrameResolutionAdapter::VideoTrackCallbacks track_callbacks;
// Remove the track.
// If the track was found, re-add it with new settings.
if (track_callbacks.frame_callback) {
- AddTrackOnIO(track, std::move(track_callbacks.frame_callback),
- std::move(track_callbacks.encoded_frame_callback),
- std::move(track_callbacks.settings_callback),
- std::move(track_callbacks.format_callback), settings);
+ AddTrackOnVideoTaskRunner(
+ track, std::move(track_callbacks.frame_callback),
+ std::move(track_callbacks.notify_frame_dropped_callback),
+ std::move(track_callbacks.encoded_frame_callback),
+ std::move(track_callbacks.sub_capture_target_version_callback),
+ std::move(track_callbacks.settings_callback),
+ std::move(track_callbacks.format_callback), settings);
}
}
-void VideoTrackAdapter::DeliverFrameOnIO(
+void VideoTrackAdapter::DeliverFrameOnVideoTaskRunner(
scoped_refptr<media::VideoFrame> video_frame,
- std::vector<scoped_refptr<media::VideoFrame>> scaled_video_frames,
base::TimeTicks estimated_capture_time) {
- DCHECK(io_task_runner_->BelongsToCurrentThread());
- TRACE_EVENT0("media", "VideoTrackAdapter::DeliverFrameOnIO");
+ DCHECK(video_task_runner_->RunsTasksInCurrentSequence());
+ TRACE_EVENT0("media", "VideoTrackAdapter::DeliverFrameOnVideoTaskRunner");
++frame_counter_;
bool is_device_rotated = false;
- // It's sufficient to look at |video_frame| since |scaled_video_frames| are
- // scaled versions of the same image.
// TODO(guidou): Use actual device information instead of this heuristic to
// detect frames from rotated devices. https://crbug.com/722748
if (source_frame_size_ &&
video_frame->natural_size().height() == source_frame_size_->width()) {
is_device_rotated = true;
}
- if (adapters_.IsEmpty()) {
- PostCrossThreadTask(
- *renderer_task_runner_, FROM_HERE,
- CrossThreadBindOnce(&MediaStreamVideoSource::OnFrameDropped,
- media_stream_video_source_,
- media::VideoCaptureFrameDropReason::
- kVideoTrackAdapterHasNoResolutionAdapters));
- }
for (const auto& adapter : adapters_) {
- adapter->DeliverFrame(video_frame, scaled_video_frames,
- estimated_capture_time, is_device_rotated);
+ adapter->DeliverFrame(video_frame, estimated_capture_time,
+ is_device_rotated);
}
}
-void VideoTrackAdapter::DeliverEncodedVideoFrameOnIO(
+void VideoTrackAdapter::DeliverEncodedVideoFrameOnVideoTaskRunner(
scoped_refptr<EncodedVideoFrame> frame,
base::TimeTicks estimated_capture_time) {
- DCHECK(io_task_runner_->BelongsToCurrentThread());
- TRACE_EVENT0("media", "VideoTrackAdapter::DeliverEncodedVideoFrameOnIO");
+ DCHECK(video_task_runner_->RunsTasksInCurrentSequence());
+ TRACE_EVENT0("media",
+ "VideoTrackAdapter::DeliverEncodedVideoFrameOnVideoTaskRunner");
for (const auto& adapter : adapters_)
adapter->DeliverEncodedVideoFrame(frame, estimated_capture_time);
}
-void VideoTrackAdapter::CheckFramesReceivedOnIO(
- OnMutedInternalCallback set_muted_state_callback,
- uint64_t old_frame_counter_snapshot) {
- DCHECK(io_task_runner_->BelongsToCurrentThread());
+void VideoTrackAdapter::OnFrameDroppedOnVideoTaskRunner(
+ media::VideoCaptureFrameDropReason reason) {
+ DCHECK(video_task_runner_->RunsTasksInCurrentSequence());
+ TRACE_EVENT0("media", "VideoTrackAdapter::OnFrameDroppedOnVideoTaskRunner");
+ for (const auto& adapter : adapters_) {
+ adapter->OnFrameDropped(reason);
+ }
+}
- if (!monitoring_frame_rate_)
- return;
+void VideoTrackAdapter::NewSubCaptureTargetVersionOnVideoTaskRunner(
+ uint32_t sub_capture_target_version) {
+ DCHECK(video_task_runner_->RunsTasksInCurrentSequence());
+ TRACE_EVENT0(
+ "media",
+ "VideoTrackAdapter::NewSubCaptureTargetVersionOnVideoTaskRunner");
+ for (const auto& adapter : adapters_) {
+ adapter->NewSubCaptureTargetVersionOnVideoTaskRunner(
+ sub_capture_target_version);
+ }
+}
- DVLOG_IF(1, old_frame_counter_snapshot == frame_counter_)
- << "No frames have passed, setting source as Muted.";
+void VideoTrackAdapter::CheckFramesReceivedOnVideoTaskRunner() {
+ DCHECK(video_task_runner_->RunsTasksInCurrentSequence());
- bool muted_state = old_frame_counter_snapshot == frame_counter_;
+ DVLOG_IF(1, old_frame_counter_snapshot_ == frame_counter_)
+ << "No frames have passed, setting source as Muted.";
+ bool muted_state = old_frame_counter_snapshot_ == frame_counter_;
if (muted_state_ != muted_state) {
- set_muted_state_callback.Run(muted_state);
+ on_muted_callback_.Run(muted_state);
muted_state_ = muted_state;
if (muted_state_) {
for (const auto& adapter : adapters_)
}
}
- PostDelayedCrossThreadTask(
- *io_task_runner_, FROM_HERE,
- CrossThreadBindOnce(&VideoTrackAdapter::CheckFramesReceivedOnIO,
- WrapRefCounted(this),
- std::move(set_muted_state_callback), frame_counter_),
- base::TimeDelta::FromSecondsD(kNormalFrameTimeoutInFrameIntervals /
- source_frame_rate_));
+ old_frame_counter_snapshot_ = frame_counter_;
+ monitoring_frame_rate_timer_->StartOneShot(
+ base::Seconds(kNormalFrameTimeoutInFrameIntervals / source_frame_rate_));
}
} // namespace blink