Route min/max FPS constraints to VideoStreamEncoder.
This change
- adds new type VideoTrackSourceConstraints expressing min/max FPS
constraints.
- adds new method VideoTrackSourceInterface::ProcessConstraints.
- adds new method VideoSinkInterface<>::OnConstraintsChanged.
- updates AdaptedVideoTrackSource and VideoBroadcaster to forward
the constraints to sinks.
- adds several unit tests for the added functionality.
- and finally, implements OnConstraintsChanged in VideoStreamEncoder.
Chromium will be updated in coming CLs to supply constraints set
through the MediaStream module.
go/rtc-0hz-present
Bug: chromium:1255737
No-Try: true
Change-Id: Iffef239217269c332a1aaa902ddeae2440929e22
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/235040
Commit-Queue: Markus Handell <handellm@webrtc.org>
Reviewed-by: Ilya Nikolaevskiy <ilnik@webrtc.org>
Reviewed-by: Henrik Andreassson <henrika@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#35197}
diff --git a/api/BUILD.gn b/api/BUILD.gn
index e969fec..0352f99 100644
--- a/api/BUILD.gn
+++ b/api/BUILD.gn
@@ -98,6 +98,12 @@
absl_deps = [ "//third_party/abseil-cpp/absl/types:optional" ]
}
+rtc_source_set("video_track_source_constraints") {
+ visibility = [ "*" ]
+ sources = [ "video_track_source_constraints.h" ]
+ absl_deps = [ "//third_party/abseil-cpp/absl/types:optional" ]
+}
+
rtc_library("media_stream_interface") {
visibility = [ "*" ]
sources = [
@@ -110,6 +116,7 @@
":audio_options_api",
":rtp_parameters",
":scoped_refptr",
+ ":video_track_source_constraints",
"../modules/audio_processing:audio_processing_statistics",
"../rtc_base:checks",
"../rtc_base:refcount",
diff --git a/api/media_stream_interface.h b/api/media_stream_interface.h
index 874b4db..ad497d9 100644
--- a/api/media_stream_interface.h
+++ b/api/media_stream_interface.h
@@ -28,6 +28,7 @@
#include "api/video/video_frame.h"
#include "api/video/video_sink_interface.h"
#include "api/video/video_source_interface.h"
+#include "api/video_track_source_constraints.h"
#include "modules/audio_processing/include/audio_processing_statistics.h"
#include "rtc_base/ref_count.h"
#include "rtc_base/system/rtc_export.h"
@@ -146,8 +147,6 @@
// Add an encoded video sink to the source and additionally cause
// a key frame to be generated from the source. The sink will be
// invoked from a decoder queue.
- // TODO(bugs.webrtc.org/11114): make pure virtual once downstream project
- // adapts.
virtual void AddEncodedSink(
rtc::VideoSinkInterface<RecordableEncodedFrame>* sink) = 0;
@@ -155,6 +154,13 @@
virtual void RemoveEncodedSink(
rtc::VideoSinkInterface<RecordableEncodedFrame>* sink) = 0;
+ // Notify about constraints set on the source. The information eventually gets
+ // routed to attached sinks via VideoSinkInterface<>::OnConstraintsChanged.
+ // The call is expected to happen on the network thread.
+ // TODO(crbug/1255737): make pure virtual once downstream project adapts.
+ virtual void ProcessConstraints(
+ const webrtc::VideoTrackSourceConstraints& constraints) {}
+
protected:
~VideoTrackSourceInterface() override = default;
};
diff --git a/api/video/BUILD.gn b/api/video/BUILD.gn
index ec74869..e6052fe 100644
--- a/api/video/BUILD.gn
+++ b/api/video/BUILD.gn
@@ -60,6 +60,7 @@
"..:array_view",
"..:rtp_packet_info",
"..:scoped_refptr",
+ "..:video_track_source_constraints",
"../../rtc_base:checks",
"../../rtc_base:rtc_base_approved",
"../../rtc_base/memory:aligned_malloc",
diff --git a/api/video/video_sink_interface.h b/api/video/video_sink_interface.h
index 88cf9d9..9c1f5f3 100644
--- a/api/video/video_sink_interface.h
+++ b/api/video/video_sink_interface.h
@@ -11,6 +11,8 @@
#ifndef API_VIDEO_VIDEO_SINK_INTERFACE_H_
#define API_VIDEO_VIDEO_SINK_INTERFACE_H_
+#include "absl/types/optional.h"
+#include "api/video_track_source_constraints.h"
#include "rtc_base/checks.h"
namespace rtc {
@@ -25,6 +27,11 @@
// Should be called by the source when it discards the frame due to rate
// limiting.
virtual void OnDiscardedFrame() {}
+
+ // Called on the network thread when video constraints change.
+ // TODO(crbug/1255737): make pure virtual once downstream project adapts.
+ virtual void OnConstraintsChanged(
+ const webrtc::VideoTrackSourceConstraints& constraints) {}
};
} // namespace rtc
diff --git a/api/video_track_source_constraints.h b/api/video_track_source_constraints.h
new file mode 100644
index 0000000..55e5396
--- /dev/null
+++ b/api/video_track_source_constraints.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2021 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+// This file contains interfaces for MediaStream, MediaTrack and MediaSource.
+// These interfaces are used for implementing MediaStream and MediaTrack as
+// defined in http://dev.w3.org/2011/webrtc/editor/webrtc.html#stream-api. These
+// interfaces must be used only with PeerConnection.
+
+#ifndef API_VIDEO_TRACK_SOURCE_CONSTRAINTS_H_
+#define API_VIDEO_TRACK_SOURCE_CONSTRAINTS_H_
+
+#include "absl/types/optional.h"
+
+namespace webrtc {
+
+// This struct definition describes constraints on the video source that may be
+// set with VideoTrackSourceInterface::ProcessConstraints.
+struct VideoTrackSourceConstraints {
+ absl::optional<double> min_fps;
+ absl::optional<double> max_fps;
+};
+
+} // namespace webrtc
+
+#endif // API_VIDEO_TRACK_SOURCE_CONSTRAINTS_H_
diff --git a/media/base/adapted_video_track_source.cc b/media/base/adapted_video_track_source.cc
index 2fce973..f8f8f2d 100644
--- a/media/base/adapted_video_track_source.cc
+++ b/media/base/adapted_video_track_source.cc
@@ -114,4 +114,9 @@
return true;
}
+void AdaptedVideoTrackSource::ProcessConstraints(
+ const webrtc::VideoTrackSourceConstraints& constraints) {
+ broadcaster_.ProcessConstraints(constraints);
+}
+
} // namespace rtc
diff --git a/media/base/adapted_video_track_source.h b/media/base/adapted_video_track_source.h
index d40baef..1386fbd 100644
--- a/media/base/adapted_video_track_source.h
+++ b/media/base/adapted_video_track_source.h
@@ -86,6 +86,8 @@
rtc::VideoSinkInterface<webrtc::RecordableEncodedFrame>* sink) override {}
void RemoveEncodedSink(
rtc::VideoSinkInterface<webrtc::RecordableEncodedFrame>* sink) override {}
+ void ProcessConstraints(
+ const webrtc::VideoTrackSourceConstraints& constraints) override;
cricket::VideoAdapter video_adapter_;
diff --git a/media/base/video_broadcaster.cc b/media/base/video_broadcaster.cc
index 1b55786..1167d7f 100644
--- a/media/base/video_broadcaster.cc
+++ b/media/base/video_broadcaster.cc
@@ -32,6 +32,13 @@
if (!FindSinkPair(sink)) {
// `Sink` is a new sink, which didn't receive previous frame.
previous_frame_sent_to_all_sinks_ = false;
+
+ if (last_constraints_.has_value()) {
+ RTC_LOG(LS_INFO) << __func__ << " forwarding stored constraints min_fps "
+ << last_constraints_->min_fps.value_or(-1) << " max_fps "
+ << last_constraints_->max_fps.value_or(-1);
+ sink->OnConstraintsChanged(*last_constraints_);
+ }
}
VideoSourceBase::AddOrUpdateSink(sink, wants);
UpdateWants();
@@ -100,6 +107,18 @@
}
}
+void VideoBroadcaster::ProcessConstraints(
+ const webrtc::VideoTrackSourceConstraints& constraints) {
+ webrtc::MutexLock lock(&sinks_and_wants_lock_);
+ RTC_LOG(LS_INFO) << __func__ << " min_fps "
+ << constraints.min_fps.value_or(-1) << " max_fps "
+ << constraints.max_fps.value_or(-1) << " broadcasting to "
+ << sink_pairs().size() << " sinks.";
+ last_constraints_ = constraints;
+ for (auto& sink_pair : sink_pairs())
+ sink_pair.sink->OnConstraintsChanged(constraints);
+}
+
void VideoBroadcaster::UpdateWants() {
VideoSinkWants wants;
wants.rotation_applied = false;
diff --git a/media/base/video_broadcaster.h b/media/base/video_broadcaster.h
index 2f4e578..c253d44 100644
--- a/media/base/video_broadcaster.h
+++ b/media/base/video_broadcaster.h
@@ -11,6 +11,7 @@
#ifndef MEDIA_BASE_VIDEO_BROADCASTER_H_
#define MEDIA_BASE_VIDEO_BROADCASTER_H_
+#include "api/media_stream_interface.h"
#include "api/scoped_refptr.h"
#include "api/sequence_checker.h"
#include "api/video/video_frame_buffer.h"
@@ -31,6 +32,11 @@
public:
VideoBroadcaster();
~VideoBroadcaster() override;
+
+ // Adds a new, or updates an already existing sink. If the sink is new and
+ // ProcessConstraints has been called previously, the new sink's
+ // OnConstraintsCalled method will be invoked with the most recent
+ // constraints.
void AddOrUpdateSink(VideoSinkInterface<webrtc::VideoFrame>* sink,
const VideoSinkWants& wants) override;
void RemoveSink(VideoSinkInterface<webrtc::VideoFrame>* sink) override;
@@ -50,6 +56,11 @@
void OnDiscardedFrame() override;
+ // Called on the network thread when constraints change. Forwards the
+ // constraints to sinks added with AddOrUpdateSink via OnConstraintsChanged.
+ void ProcessConstraints(
+ const webrtc::VideoTrackSourceConstraints& constraints);
+
protected:
void UpdateWants() RTC_EXCLUSIVE_LOCKS_REQUIRED(sinks_and_wants_lock_);
const rtc::scoped_refptr<webrtc::VideoFrameBuffer>& GetBlackFrameBuffer(
@@ -62,6 +73,8 @@
rtc::scoped_refptr<webrtc::VideoFrameBuffer> black_frame_buffer_;
bool previous_frame_sent_to_all_sinks_ RTC_GUARDED_BY(sinks_and_wants_lock_) =
true;
+ absl::optional<webrtc::VideoTrackSourceConstraints> last_constraints_
+ RTC_GUARDED_BY(sinks_and_wants_lock_);
};
} // namespace rtc
diff --git a/media/base/video_broadcaster_unittest.cc b/media/base/video_broadcaster_unittest.cc
index b007278..b967237 100644
--- a/media/base/video_broadcaster_unittest.cc
+++ b/media/base/video_broadcaster_unittest.cc
@@ -16,13 +16,31 @@
#include "api/video/i420_buffer.h"
#include "api/video/video_frame.h"
#include "api/video/video_rotation.h"
+#include "api/video/video_source_interface.h"
#include "media/base/fake_video_renderer.h"
+#include "test/gmock.h"
#include "test/gtest.h"
using cricket::FakeVideoRenderer;
using rtc::VideoBroadcaster;
using rtc::VideoSinkWants;
+using ::testing::AllOf;
+using ::testing::Eq;
+using ::testing::Field;
+using ::testing::Mock;
+using ::testing::Optional;
+
+class MockSink : public rtc::VideoSinkInterface<webrtc::VideoFrame> {
+ public:
+ void OnFrame(const webrtc::VideoFrame&) override {}
+
+ MOCK_METHOD(void,
+ OnConstraintsChanged,
+ (const webrtc::VideoTrackSourceConstraints& constraints),
+ (override));
+};
+
TEST(VideoBroadcasterTest, frame_wanted) {
VideoBroadcaster broadcaster;
EXPECT_FALSE(broadcaster.frame_wanted());
@@ -232,3 +250,83 @@
EXPECT_TRUE(sink2.black_frame());
EXPECT_EQ(30, sink2.timestamp_us());
}
+
+TEST(VideoBroadcasterTest, ConstraintsChangedNotCalledOnSinkAddition) {
+ MockSink sink;
+ VideoBroadcaster broadcaster;
+ EXPECT_CALL(sink, OnConstraintsChanged).Times(0);
+ broadcaster.AddOrUpdateSink(&sink, VideoSinkWants());
+}
+
+TEST(VideoBroadcasterTest, ForwardsLastConstraintsOnAdd) {
+ MockSink sink;
+ VideoBroadcaster broadcaster;
+ broadcaster.ProcessConstraints(webrtc::VideoTrackSourceConstraints{2, 3});
+ broadcaster.ProcessConstraints(webrtc::VideoTrackSourceConstraints{1, 4});
+ EXPECT_CALL(
+ sink,
+ OnConstraintsChanged(AllOf(
+ Field(&webrtc::VideoTrackSourceConstraints::min_fps, Optional(1)),
+ Field(&webrtc::VideoTrackSourceConstraints::max_fps, Optional(4)))));
+ broadcaster.AddOrUpdateSink(&sink, VideoSinkWants());
+}
+
+TEST(VideoBroadcasterTest, UpdatesOnlyNewSinksWithConstraints) {
+ MockSink sink1;
+ VideoBroadcaster broadcaster;
+ broadcaster.AddOrUpdateSink(&sink1, VideoSinkWants());
+ broadcaster.ProcessConstraints(webrtc::VideoTrackSourceConstraints{1, 4});
+ Mock::VerifyAndClearExpectations(&sink1);
+ EXPECT_CALL(sink1, OnConstraintsChanged).Times(0);
+ MockSink sink2;
+ EXPECT_CALL(
+ sink2,
+ OnConstraintsChanged(AllOf(
+ Field(&webrtc::VideoTrackSourceConstraints::min_fps, Optional(1)),
+ Field(&webrtc::VideoTrackSourceConstraints::max_fps, Optional(4)))));
+ broadcaster.AddOrUpdateSink(&sink2, VideoSinkWants());
+}
+
+TEST(VideoBroadcasterTest, ForwardsConstraintsToSink) {
+ MockSink sink;
+ VideoBroadcaster broadcaster;
+ EXPECT_CALL(sink, OnConstraintsChanged).Times(0);
+ broadcaster.AddOrUpdateSink(&sink, VideoSinkWants());
+ Mock::VerifyAndClearExpectations(&sink);
+
+ EXPECT_CALL(sink, OnConstraintsChanged(AllOf(
+ Field(&webrtc::VideoTrackSourceConstraints::min_fps,
+ Eq(absl::nullopt)),
+ Field(&webrtc::VideoTrackSourceConstraints::max_fps,
+ Eq(absl::nullopt)))));
+ broadcaster.ProcessConstraints(
+ webrtc::VideoTrackSourceConstraints{absl::nullopt, absl::nullopt});
+ Mock::VerifyAndClearExpectations(&sink);
+
+ EXPECT_CALL(
+ sink,
+ OnConstraintsChanged(AllOf(
+ Field(&webrtc::VideoTrackSourceConstraints::min_fps,
+ Eq(absl::nullopt)),
+ Field(&webrtc::VideoTrackSourceConstraints::max_fps, Optional(3)))));
+ broadcaster.ProcessConstraints(
+ webrtc::VideoTrackSourceConstraints{absl::nullopt, 3});
+ Mock::VerifyAndClearExpectations(&sink);
+
+ EXPECT_CALL(
+ sink,
+ OnConstraintsChanged(AllOf(
+ Field(&webrtc::VideoTrackSourceConstraints::min_fps, Optional(2)),
+ Field(&webrtc::VideoTrackSourceConstraints::max_fps,
+ Eq(absl::nullopt)))));
+ broadcaster.ProcessConstraints(
+ webrtc::VideoTrackSourceConstraints{2, absl::nullopt});
+ Mock::VerifyAndClearExpectations(&sink);
+
+ EXPECT_CALL(
+ sink,
+ OnConstraintsChanged(AllOf(
+ Field(&webrtc::VideoTrackSourceConstraints::min_fps, Optional(2)),
+ Field(&webrtc::VideoTrackSourceConstraints::max_fps, Optional(3)))));
+ broadcaster.ProcessConstraints(webrtc::VideoTrackSourceConstraints{2, 3});
+}
diff --git a/pc/video_track_source_proxy.h b/pc/video_track_source_proxy.h
index 8914dd0..6e71bb1 100644
--- a/pc/video_track_source_proxy.h
+++ b/pc/video_track_source_proxy.h
@@ -42,6 +42,9 @@
PROXY_SECONDARY_METHOD1(void,
RemoveEncodedSink,
rtc::VideoSinkInterface<RecordableEncodedFrame>*)
+PROXY_SECONDARY_METHOD1(void,
+ ProcessConstraints,
+ const webrtc::VideoTrackSourceConstraints&)
END_PROXY_MAP(VideoTrackSource)
} // namespace webrtc
diff --git a/video/video_stream_encoder.cc b/video/video_stream_encoder.cc
index 9180bbb..f3872cc 100644
--- a/video/video_stream_encoder.cc
+++ b/video/video_stream_encoder.cc
@@ -1372,6 +1372,18 @@
VideoStreamEncoderObserver::DropReason::kSource);
}
+void VideoStreamEncoder::OnConstraintsChanged(
+ const webrtc::VideoTrackSourceConstraints& constraints) {
+ // This method is called on the network thread.
+ RTC_LOG(LS_INFO) << __func__ << " min_fps "
+ << constraints.min_fps.value_or(-1) << " max_fps "
+ << constraints.max_fps.value_or(-1);
+ main_queue_->PostTask(ToQueuedTask(task_safety_, [this, constraints] {
+ RTC_DCHECK_RUN_ON(main_queue_);
+ source_constraints_ = constraints;
+ }));
+}
+
bool VideoStreamEncoder::EncoderPaused() const {
RTC_DCHECK_RUN_ON(&encoder_queue_);
// Pause video if paused by caller or as long as the network is down or the
diff --git a/video/video_stream_encoder.h b/video/video_stream_encoder.h
index 2b18155..93c7903 100644
--- a/video/video_stream_encoder.h
+++ b/video/video_stream_encoder.h
@@ -176,6 +176,8 @@
// Implements VideoSinkInterface.
void OnFrame(const VideoFrame& video_frame) override;
void OnDiscardedFrame() override;
+ void OnConstraintsChanged(
+ const webrtc::VideoTrackSourceConstraints& constraints) override;
void MaybeEncodeVideoFrame(const VideoFrame& frame,
int64_t time_when_posted_in_ms);
@@ -239,6 +241,10 @@
encoder_selector_;
VideoStreamEncoderObserver* const encoder_stats_observer_;
+ // The source's constraints.
+ absl::optional<VideoTrackSourceConstraints> source_constraints_
+ RTC_GUARDED_BY(main_queue_);
+
VideoEncoderConfig encoder_config_ RTC_GUARDED_BY(&encoder_queue_);
std::unique_ptr<VideoEncoder> encoder_ RTC_GUARDED_BY(&encoder_queue_)
RTC_PT_GUARDED_BY(&encoder_queue_);