diff --git a/talk/media/webrtc/webrtcvideocapturer.cc b/talk/media/webrtc/webrtcvideocapturer.cc index a6a0176d58..8d8270a755 100644 --- a/talk/media/webrtc/webrtcvideocapturer.cc +++ b/talk/media/webrtc/webrtcvideocapturer.cc @@ -129,17 +129,19 @@ static bool FormatToCapability(const VideoFormat& format, WebRtcVideoCapturer::WebRtcVideoCapturer() : factory_(new WebRtcVcmFactory), - module_(NULL), + module_(nullptr), captured_frames_(0), - start_thread_(nullptr) { + start_thread_(nullptr), + async_invoker_(nullptr) { set_frame_factory(new WebRtcVideoFrameFactory()); } WebRtcVideoCapturer::WebRtcVideoCapturer(WebRtcVcmFactoryInterface* factory) : factory_(factory), - module_(NULL), + module_(nullptr), captured_frames_(0), - start_thread_(nullptr) { + start_thread_(nullptr), + async_invoker_(nullptr) { set_frame_factory(new WebRtcVideoFrameFactory()); } @@ -281,16 +283,17 @@ CaptureState WebRtcVideoCapturer::Start(const VideoFormat& capture_format) { LOG(LS_ERROR) << "The capturer has not been initialized"; return CS_NO_DEVICE; } - - rtc::CritScope cs(&critical_section_stopping_); - if (IsRunning()) { + if (start_thread_) { LOG(LS_ERROR) << "The capturer is already running"; + DCHECK(start_thread_->IsCurrent()) + << "Trying to start capturer on different threads"; return CS_FAILED; } - DCHECK(!start_thread_); - start_thread_ = rtc::Thread::Current(); + DCHECK(!async_invoker_); + async_invoker_.reset(new rtc::AsyncInvoker()); + captured_frames_ = 0; SetCaptureFormat(&capture_format); @@ -300,44 +303,50 @@ CaptureState WebRtcVideoCapturer::Start(const VideoFormat& capture_format) { return CS_FAILED; } - std::string camera_id(GetId()); uint32 start = rtc::Time(); module_->RegisterCaptureDataCallback(*this); if (module_->StartCapture(cap) != 0) { - LOG(LS_ERROR) << "Camera '" << camera_id << "' failed to start"; + LOG(LS_ERROR) << "Camera '" << GetId() << "' failed to start"; + module_->DeRegisterCaptureDataCallback(); + async_invoker_.reset(); + SetCaptureFormat(nullptr); start_thread_ = nullptr; return CS_FAILED; } - LOG(LS_INFO) << "Camera '" << camera_id << "' started with format " + LOG(LS_INFO) << "Camera '" << GetId() << "' started with format " << capture_format.ToString() << ", elapsed time " << rtc::TimeSince(start) << " ms"; - captured_frames_ = 0; SetCaptureState(CS_RUNNING); return CS_STARTING; } -// Critical section blocks Stop from shutting down during callbacks from capture -// thread to OnIncomingCapturedFrame. Note that the crit is try-locked in -// OnFrameCaptured, as the lock ordering between this and the system component -// controlling the camera is reversed: system frame -> OnIncomingCapturedFrame; -// Stop -> system stop camera). void WebRtcVideoCapturer::Stop() { - rtc::CritScope cs(&critical_section_stopping_); - if (IsRunning()) { - DCHECK(start_thread_); - rtc::Thread::Current()->Clear(this); - module_->StopCapture(); - module_->DeRegisterCaptureDataCallback(); - - // TODO(juberti): Determine if the VCM exposes any drop stats we can use. - double drop_ratio = 0.0; - std::string camera_id(GetId()); - LOG(LS_INFO) << "Camera '" << camera_id << "' stopped after capturing " - << captured_frames_ << " frames and dropping " - << drop_ratio << "%"; + if (!start_thread_) { + LOG(LS_ERROR) << "The capturer is already stopped"; + return; } + DCHECK(start_thread_); + DCHECK(start_thread_->IsCurrent()); + DCHECK(async_invoker_); + if (IsRunning()) { + // The module is responsible for OnIncomingCapturedFrame being called, if + // we stop it we will get no further callbacks. + module_->StopCapture(); + } + module_->DeRegisterCaptureDataCallback(); + + // TODO(juberti): Determine if the VCM exposes any drop stats we can use. + double drop_ratio = 0.0; + LOG(LS_INFO) << "Camera '" << GetId() << "' stopped after capturing " + << captured_frames_ << " frames and dropping " + << drop_ratio << "%"; + + // Clear any pending async invokes (that OnIncomingCapturedFrame may have + // caused). + async_invoker_.reset(); + SetCaptureFormat(NULL); start_thread_ = nullptr; } @@ -362,37 +371,22 @@ bool WebRtcVideoCapturer::GetPreferredFourccs( void WebRtcVideoCapturer::OnIncomingCapturedFrame( const int32_t id, const webrtc::I420VideoFrame& sample) { - // This would be a normal CritScope, except that it's possible that: - // (1) whatever system component producing this frame has taken a lock, and - // (2) Stop() probably calls back into that system component, which may take - // the same lock. Due to the reversed order, we have to try-lock in order to - // avoid a potential deadlock. Besides, if we can't enter because we're - // stopping, we may as well drop the frame. - rtc::TryCritScope cs(&critical_section_stopping_); - if (!cs.locked() || !IsRunning()) { - // Capturer has been stopped or is in the process of stopping. - return; - } - - ++captured_frames_; - // Log the size and pixel aspect ratio of the first captured frame. - if (1 == captured_frames_) { - LOG(LS_INFO) << "Captured frame size " - << sample.width() << "x" << sample.height() - << ". Expected format " << GetCaptureFormat()->ToString(); - } - + // This can only happen between Start() and Stop(). + DCHECK(start_thread_); + DCHECK(async_invoker_); if (start_thread_->IsCurrent()) { - SignalFrameCapturedOnStartThread(&sample); + SignalFrameCapturedOnStartThread(sample); } else { // This currently happens on with at least VideoCaptureModuleV4L2 and // possibly other implementations of WebRTC's VideoCaptureModule. // In order to maintain the threading contract with the upper layers and // consistency with other capturers such as in Chrome, we need to do a // thread hop. - start_thread_->Invoke( + // Note that Stop() can cause the async invoke call to be cancelled. + async_invoker_->AsyncInvoke(start_thread_, + // Note that this results in a shallow copying of the frame. rtc::Bind(&WebRtcVideoCapturer::SignalFrameCapturedOnStartThread, - this, &sample)); + this, sample)); } } @@ -402,18 +396,30 @@ void WebRtcVideoCapturer::OnCaptureDelayChanged(const int32_t id, } void WebRtcVideoCapturer::SignalFrameCapturedOnStartThread( - const webrtc::I420VideoFrame* frame) { + const webrtc::I420VideoFrame frame) { + // This can only happen between Start() and Stop(). + DCHECK(start_thread_); DCHECK(start_thread_->IsCurrent()); + DCHECK(async_invoker_); + + ++captured_frames_; + // Log the size and pixel aspect ratio of the first captured frame. + if (1 == captured_frames_) { + LOG(LS_INFO) << "Captured frame size " + << frame.width() << "x" << frame.height() + << ". Expected format " << GetCaptureFormat()->ToString(); + } + // Signal down stream components on captured frame. // The CapturedFrame class doesn't support planes. We have to ExtractBuffer // to one block for it. size_t length = - webrtc::CalcBufferSize(webrtc::kI420, frame->width(), frame->height()); + webrtc::CalcBufferSize(webrtc::kI420, frame.width(), frame.height()); capture_buffer_.resize(length); // TODO(magjed): Refactor the WebRtcCapturedFrame to avoid memory copy or // take over ownership of the buffer held by |frame| if that's possible. - webrtc::ExtractBuffer(*frame, length, &capture_buffer_[0]); - WebRtcCapturedFrame webrtc_frame(*frame, &capture_buffer_[0], length); + webrtc::ExtractBuffer(frame, length, &capture_buffer_[0]); + WebRtcCapturedFrame webrtc_frame(frame, &capture_buffer_[0], length); SignalFrameCaptured(this, &webrtc_frame); } diff --git a/talk/media/webrtc/webrtcvideocapturer.h b/talk/media/webrtc/webrtcvideocapturer.h index 56896f9cd6..737a7b2920 100644 --- a/talk/media/webrtc/webrtcvideocapturer.h +++ b/talk/media/webrtc/webrtcvideocapturer.h @@ -35,8 +35,9 @@ #include "talk/media/base/videocapturer.h" #include "talk/media/webrtc/webrtcvideoframe.h" -#include "webrtc/base/criticalsection.h" +#include "webrtc/base/asyncinvoker.h" #include "webrtc/base/messagehandler.h" +#include "webrtc/base/scoped_ptr.h" #include "webrtc/common_video/libyuv/include/webrtc_libyuv.h" #include "webrtc/modules/video_capture/include/video_capture.h" @@ -91,7 +92,7 @@ class WebRtcVideoCapturer : public VideoCapturer, // directly from OnIncomingCapturedFrame. // TODO(tommi): Remove this workaround when we've updated the WebRTC capturers // to follow the same contract. - void SignalFrameCapturedOnStartThread(const webrtc::I420VideoFrame* frame); + void SignalFrameCapturedOnStartThread(const webrtc::I420VideoFrame frame); rtc::scoped_ptr factory_; webrtc::VideoCaptureModule* module_; @@ -99,8 +100,7 @@ class WebRtcVideoCapturer : public VideoCapturer, std::vector capture_buffer_; rtc::Thread* start_thread_; // Set in Start(), unset in Stop(); - // Critical section to avoid Stop during an OnIncomingCapturedFrame callback. - rtc::CriticalSection critical_section_stopping_; + rtc::scoped_ptr async_invoker_; }; struct WebRtcCapturedFrame : public CapturedFrame {