diff --git a/api/video_track_source_proxy.h b/api/video_track_source_proxy.h index 85405f0a64..528b7cf701 100644 --- a/api/video_track_source_proxy.h +++ b/api/video_track_source_proxy.h @@ -35,7 +35,7 @@ PROXY_WORKER_METHOD1(void, RemoveSink, rtc::VideoSinkInterface*) PROXY_METHOD1(void, RegisterObserver, ObserverInterface*) PROXY_METHOD1(void, UnregisterObserver, ObserverInterface*) PROXY_CONSTMETHOD0(bool, SupportsEncodedOutput) -PROXY_METHOD0(void, GenerateKeyFrame) +PROXY_WORKER_METHOD0(void, GenerateKeyFrame) PROXY_WORKER_METHOD1(void, AddEncodedSink, rtc::VideoSinkInterface*) diff --git a/pc/video_rtp_receiver.cc b/pc/video_rtp_receiver.cc index e6de6c71b3..d9d2a2e810 100644 --- a/pc/video_rtp_receiver.cc +++ b/pc/video_rtp_receiver.cc @@ -42,7 +42,7 @@ VideoRtpReceiver::VideoRtpReceiver( const std::vector>& streams) : worker_thread_(worker_thread), id_(receiver_id), - source_(new RefCountedObject()), + source_(new RefCountedObject(this)), track_(VideoTrackProxy::Create( rtc::Thread::Current(), worker_thread, @@ -66,6 +66,9 @@ VideoRtpReceiver::~VideoRtpReceiver() { // Since cricket::VideoRenderer is not reference counted, // we need to remove it from the channel before we are deleted. Stop(); + // Make sure we can't be called by the |source_| anymore. + worker_thread_->Invoke(RTC_FROM_HERE, + [this] { source_->ClearCallback(); }); } std::vector VideoRtpReceiver::stream_ids() const { @@ -246,4 +249,12 @@ std::vector VideoRtpReceiver::GetSources() const { RTC_FROM_HERE, [&] { return media_channel_->GetSources(*ssrc_); }); } +void VideoRtpReceiver::OnGenerateKeyFrame() { + RTC_DCHECK_RUN_ON(worker_thread_); +} + +void VideoRtpReceiver::OnEncodedSinkEnabled(bool enable) { + RTC_DCHECK_RUN_ON(worker_thread_); +} + } // namespace webrtc diff --git a/pc/video_rtp_receiver.h b/pc/video_rtp_receiver.h index d5543a2270..0bb54e797c 100644 --- a/pc/video_rtp_receiver.h +++ b/pc/video_rtp_receiver.h @@ -35,7 +35,8 @@ namespace webrtc { -class VideoRtpReceiver : public rtc::RefCountedObject { +class VideoRtpReceiver : public rtc::RefCountedObject, + public VideoRtpTrackSource::Callback { public: // An SSRC of 0 will create a receiver that will match the first SSRC it // sees. Must be called on signaling thread. @@ -112,7 +113,12 @@ class VideoRtpReceiver : public rtc::RefCountedObject { void RestartMediaChannel(absl::optional ssrc); bool SetSink(rtc::VideoSinkInterface* sink); + // VideoRtpTrackSource::Callback + void OnGenerateKeyFrame() override; + void OnEncodedSinkEnabled(bool enable) override; + rtc::Thread* const worker_thread_; + const std::string id_; cricket::VideoMediaChannel* media_channel_ = nullptr; absl::optional ssrc_; diff --git a/pc/video_rtp_track_source.cc b/pc/video_rtp_track_source.cc index 02c334dcea..2f15c42b4d 100644 --- a/pc/video_rtp_track_source.cc +++ b/pc/video_rtp_track_source.cc @@ -12,8 +12,15 @@ namespace webrtc { -VideoRtpTrackSource::VideoRtpTrackSource() - : VideoTrackSource(true /* remote */) {} +VideoRtpTrackSource::VideoRtpTrackSource(Callback* callback) + : VideoTrackSource(true /* remote */), callback_(callback) { + worker_sequence_checker_.Detach(); +} + +void VideoRtpTrackSource::ClearCallback() { + RTC_DCHECK_RUN_ON(&worker_sequence_checker_); + callback_ = nullptr; +} rtc::VideoSourceInterface* VideoRtpTrackSource::source() { return &broadcaster_; @@ -22,4 +29,57 @@ rtc::VideoSinkInterface* VideoRtpTrackSource::sink() { return &broadcaster_; } +void VideoRtpTrackSource::BroadcastRecordableEncodedFrame( + const RecordableEncodedFrame& frame) const { + rtc::CritScope cs(&mu_); + for (rtc::VideoSinkInterface* sink : encoded_sinks_) { + sink->OnFrame(frame); + } +} + +bool VideoRtpTrackSource::SupportsEncodedOutput() const { + return true; +} + +void VideoRtpTrackSource::GenerateKeyFrame() { + RTC_DCHECK_RUN_ON(&worker_sequence_checker_); + if (callback_) { + callback_->OnGenerateKeyFrame(); + } +} + +void VideoRtpTrackSource::AddEncodedSink( + rtc::VideoSinkInterface* sink) { + RTC_DCHECK_RUN_ON(&worker_sequence_checker_); + RTC_DCHECK(sink); + size_t size = 0; + { + rtc::CritScope cs(&mu_); + RTC_DCHECK(std::find(encoded_sinks_.begin(), encoded_sinks_.end(), sink) == + encoded_sinks_.end()); + encoded_sinks_.push_back(sink); + size = encoded_sinks_.size(); + } + if (size == 1 && callback_) { + callback_->OnEncodedSinkEnabled(true); + } +} + +void VideoRtpTrackSource::RemoveEncodedSink( + rtc::VideoSinkInterface* sink) { + RTC_DCHECK_RUN_ON(&worker_sequence_checker_); + size_t size = 0; + { + rtc::CritScope cs(&mu_); + auto it = std::find(encoded_sinks_.begin(), encoded_sinks_.end(), sink); + if (it != encoded_sinks_.end()) { + encoded_sinks_.erase(it); + } + size = encoded_sinks_.size(); + } + if (size == 0 && callback_) { + callback_->OnEncodedSinkEnabled(false); + } +} + } // namespace webrtc diff --git a/pc/video_rtp_track_source.h b/pc/video_rtp_track_source.h index becdc8e562..e62cda70c3 100644 --- a/pc/video_rtp_track_source.h +++ b/pc/video_rtp_track_source.h @@ -11,25 +11,71 @@ #ifndef PC_VIDEO_RTP_TRACK_SOURCE_H_ #define PC_VIDEO_RTP_TRACK_SOURCE_H_ +#include + #include "media/base/video_broadcaster.h" #include "pc/video_track_source.h" +#include "rtc_base/callback.h" +#include "rtc_base/critical_section.h" namespace webrtc { // Video track source in use by VideoRtpReceiver class VideoRtpTrackSource : public VideoTrackSource { public: - VideoRtpTrackSource(); + class Callback { + public: + virtual ~Callback() = default; + + // Called when a keyframe should be generated + virtual void OnGenerateKeyFrame() = 0; + + // Called when the implementor should eventually start to serve encoded + // frames using BroadcastEncodedFrameBuffer. + // The implementor should cause a keyframe to be eventually generated. + virtual void OnEncodedSinkEnabled(bool enable) = 0; + }; + + explicit VideoRtpTrackSource(Callback* callback); + + // Call before the object implementing Callback finishes it's destructor. No + // more callbacks will be fired after completion. Must be called on the + // worker thread + void ClearCallback(); + + // Call to broadcast an encoded frame to registered sinks. + // This method can be called on any thread or queue. + void BroadcastRecordableEncodedFrame( + const RecordableEncodedFrame& frame) const; // VideoTrackSource rtc::VideoSourceInterface* source() override; rtc::VideoSinkInterface* sink(); + // Returns true. This method can be called on any thread. + bool SupportsEncodedOutput() const override; + + // Generates a key frame. Must be called on the worker thread. + void GenerateKeyFrame() override; + + // Adds an encoded sink. Must be called on the worker thread. + void AddEncodedSink( + rtc::VideoSinkInterface* sink) override; + + // Removes an encoded sink. Must be called on the worker thread. + void RemoveEncodedSink( + rtc::VideoSinkInterface* sink) override; + private: + SequenceChecker worker_sequence_checker_; // |broadcaster_| is needed since the decoder can only handle one sink. // It might be better if the decoder can handle multiple sinks and consider // the VideoSinkWants. rtc::VideoBroadcaster broadcaster_; + rtc::CriticalSection mu_; + std::vector*> encoded_sinks_ + RTC_GUARDED_BY(mu_); + Callback* callback_ RTC_GUARDED_BY(worker_sequence_checker_); RTC_DISALLOW_COPY_AND_ASSIGN(VideoRtpTrackSource); }; diff --git a/pc/video_rtp_track_source_unittest.cc b/pc/video_rtp_track_source_unittest.cc index e1b6a2d19c..dd527bf59b 100644 --- a/pc/video_rtp_track_source_unittest.cc +++ b/pc/video_rtp_track_source_unittest.cc @@ -17,10 +17,120 @@ namespace webrtc { namespace { -TEST(VideoRtpTrackSourceTest, CreatesWithRemoteAtttributeSet) { +class MockCallback : public VideoRtpTrackSource::Callback { + public: + MOCK_METHOD0(OnGenerateKeyFrame, void()); + MOCK_METHOD1(OnEncodedSinkEnabled, void(bool)); +}; + +class MockSink : public rtc::VideoSinkInterface { + public: + MOCK_METHOD1(OnFrame, void(const RecordableEncodedFrame&)); +}; + +rtc::scoped_refptr MakeSource( + VideoRtpTrackSource::Callback* callback) { rtc::scoped_refptr source( - new rtc::RefCountedObject()); - EXPECT_TRUE(source->remote()); + new rtc::RefCountedObject(callback)); + return source; +} + +TEST(VideoRtpTrackSourceTest, CreatesWithRemoteAtttributeSet) { + EXPECT_TRUE(MakeSource(nullptr)->remote()); +} + +TEST(VideoRtpTrackSourceTest, EnablesEncodingOutputOnAddingSink) { + MockCallback mock_callback; + EXPECT_CALL(mock_callback, OnGenerateKeyFrame).Times(0); + auto source = MakeSource(&mock_callback); + MockSink sink; + EXPECT_CALL(mock_callback, OnEncodedSinkEnabled(true)); + source->AddEncodedSink(&sink); +} + +TEST(VideoRtpTrackSourceTest, EnablesEncodingOutputOnceOnAddingTwoSinks) { + MockCallback mock_callback; + EXPECT_CALL(mock_callback, OnGenerateKeyFrame).Times(0); + auto source = MakeSource(&mock_callback); + MockSink sink; + EXPECT_CALL(mock_callback, OnEncodedSinkEnabled(true)).Times(1); + source->AddEncodedSink(&sink); + MockSink sink2; + source->AddEncodedSink(&sink2); +} + +TEST(VideoRtpTrackSourceTest, DisablesEncodingOutputOnOneSinkRemoved) { + MockCallback mock_callback; + EXPECT_CALL(mock_callback, OnGenerateKeyFrame).Times(0); + EXPECT_CALL(mock_callback, OnEncodedSinkEnabled(true)); + EXPECT_CALL(mock_callback, OnEncodedSinkEnabled(false)).Times(0); + auto source = MakeSource(&mock_callback); + MockSink sink; + source->AddEncodedSink(&sink); + testing::Mock::VerifyAndClearExpectations(&mock_callback); + EXPECT_CALL(mock_callback, OnEncodedSinkEnabled(false)); + source->RemoveEncodedSink(&sink); +} + +TEST(VideoRtpTrackSourceTest, DisablesEncodingOutputOnLastSinkRemoved) { + MockCallback mock_callback; + EXPECT_CALL(mock_callback, OnGenerateKeyFrame).Times(0); + EXPECT_CALL(mock_callback, OnEncodedSinkEnabled(true)); + auto source = MakeSource(&mock_callback); + MockSink sink; + source->AddEncodedSink(&sink); + MockSink sink2; + source->AddEncodedSink(&sink2); + source->RemoveEncodedSink(&sink); + testing::Mock::VerifyAndClearExpectations(&mock_callback); + EXPECT_CALL(mock_callback, OnEncodedSinkEnabled(false)); + source->RemoveEncodedSink(&sink2); +} + +TEST(VideoRtpTrackSourceTest, GeneratesKeyFrameWhenRequested) { + MockCallback mock_callback; + auto source = MakeSource(&mock_callback); + EXPECT_CALL(mock_callback, OnGenerateKeyFrame); + source->GenerateKeyFrame(); +} + +TEST(VideoRtpTrackSourceTest, NoCallbacksAfterClearedCallback) { + testing::StrictMock mock_callback; + auto source = MakeSource(&mock_callback); + source->ClearCallback(); + MockSink sink; + source->AddEncodedSink(&sink); + source->GenerateKeyFrame(); + source->RemoveEncodedSink(&sink); +} + +class TestFrame : public RecordableEncodedFrame { + public: + rtc::scoped_refptr encoded_buffer() + const override { + return nullptr; + } + absl::optional color_space() const override { + return absl::nullopt; + } + VideoCodecType codec() const override { return kVideoCodecGeneric; } + bool is_key_frame() const override { return false; } + EncodedResolution resolution() const override { + return EncodedResolution{0, 0}; + } + Timestamp render_time() const override { return Timestamp::ms(0); } +}; + +TEST(VideoRtpTrackSourceTest, BroadcastsFrames) { + auto source = MakeSource(nullptr); + MockSink sink; + source->AddEncodedSink(&sink); + MockSink sink2; + source->AddEncodedSink(&sink2); + TestFrame frame; + EXPECT_CALL(sink, OnFrame); + EXPECT_CALL(sink2, OnFrame); + source->BroadcastRecordableEncodedFrame(frame); } } // namespace