1 /*
2  * libjingle
3  * Copyright 2011 Google Inc.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  *
8  *  1. Redistributions of source code must retain the above copyright notice,
9  *     this list of conditions and the following disclaimer.
10  *  2. Redistributions in binary form must reproduce the above copyright notice,
11  *     this list of conditions and the following disclaimer in the documentation
12  *     and/or other materials provided with the distribution.
13  *  3. The name of the author may not be used to endorse or promote products
14  *     derived from this software without specific prior written permission.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
17  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19  * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27 
28 #include "talk/media/webrtc/webrtcvideocapturer.h"
29 
30 #ifdef HAVE_CONFIG_H
31 #include <config.h>
32 #endif
33 
34 #ifdef HAVE_WEBRTC_VIDEO
35 #include "talk/media/webrtc/webrtcvideoframe.h"
36 #include "talk/media/webrtc/webrtcvideoframefactory.h"
37 #include "webrtc/base/arraysize.h"
38 #include "webrtc/base/bind.h"
39 #include "webrtc/base/checks.h"
40 #include "webrtc/base/criticalsection.h"
41 #include "webrtc/base/logging.h"
42 #include "webrtc/base/safe_conversions.h"
43 #include "webrtc/base/thread.h"
44 #include "webrtc/base/timeutils.h"
45 
46 #include "webrtc/base/win32.h"  // Need this to #include the impl files.
47 #include "webrtc/modules/video_capture/video_capture_factory.h"
48 #include "webrtc/system_wrappers/include/field_trial.h"
49 
50 namespace cricket {
51 
52 struct kVideoFourCCEntry {
53   uint32_t fourcc;
54   webrtc::RawVideoType webrtc_type;
55 };
56 
57 // This indicates our format preferences and defines a mapping between
58 // webrtc::RawVideoType (from video_capture_defines.h) to our FOURCCs.
59 static kVideoFourCCEntry kSupportedFourCCs[] = {
60   { FOURCC_I420, webrtc::kVideoI420 },   // 12 bpp, no conversion.
61   { FOURCC_YV12, webrtc::kVideoYV12 },   // 12 bpp, no conversion.
62   { FOURCC_YUY2, webrtc::kVideoYUY2 },   // 16 bpp, fast conversion.
63   { FOURCC_UYVY, webrtc::kVideoUYVY },   // 16 bpp, fast conversion.
64   { FOURCC_NV12, webrtc::kVideoNV12 },   // 12 bpp, fast conversion.
65   { FOURCC_NV21, webrtc::kVideoNV21 },   // 12 bpp, fast conversion.
66   { FOURCC_MJPG, webrtc::kVideoMJPEG },  // compressed, slow conversion.
67   { FOURCC_ARGB, webrtc::kVideoARGB },   // 32 bpp, slow conversion.
68   { FOURCC_24BG, webrtc::kVideoRGB24 },  // 24 bpp, slow conversion.
69 };
70 
71 class WebRtcVcmFactory : public WebRtcVcmFactoryInterface {
72  public:
Create(int id,const char * device)73   virtual webrtc::VideoCaptureModule* Create(int id, const char* device) {
74     return webrtc::VideoCaptureFactory::Create(id, device);
75   }
CreateDeviceInfo(int id)76   virtual webrtc::VideoCaptureModule::DeviceInfo* CreateDeviceInfo(int id) {
77     return webrtc::VideoCaptureFactory::CreateDeviceInfo(id);
78   }
DestroyDeviceInfo(webrtc::VideoCaptureModule::DeviceInfo * info)79   virtual void DestroyDeviceInfo(webrtc::VideoCaptureModule::DeviceInfo* info) {
80     delete info;
81   }
82 };
83 
CapabilityToFormat(const webrtc::VideoCaptureCapability & cap,VideoFormat * format)84 static bool CapabilityToFormat(const webrtc::VideoCaptureCapability& cap,
85                                VideoFormat* format) {
86   uint32_t fourcc = 0;
87   for (size_t i = 0; i < arraysize(kSupportedFourCCs); ++i) {
88     if (kSupportedFourCCs[i].webrtc_type == cap.rawType) {
89       fourcc = kSupportedFourCCs[i].fourcc;
90       break;
91     }
92   }
93   if (fourcc == 0) {
94     return false;
95   }
96 
97   format->fourcc = fourcc;
98   format->width = cap.width;
99   format->height = cap.height;
100   format->interval = VideoFormat::FpsToInterval(cap.maxFPS);
101   return true;
102 }
103 
FormatToCapability(const VideoFormat & format,webrtc::VideoCaptureCapability * cap)104 static bool FormatToCapability(const VideoFormat& format,
105                                webrtc::VideoCaptureCapability* cap) {
106   webrtc::RawVideoType webrtc_type = webrtc::kVideoUnknown;
107   for (size_t i = 0; i < arraysize(kSupportedFourCCs); ++i) {
108     if (kSupportedFourCCs[i].fourcc == format.fourcc) {
109       webrtc_type = kSupportedFourCCs[i].webrtc_type;
110       break;
111     }
112   }
113   if (webrtc_type == webrtc::kVideoUnknown) {
114     return false;
115   }
116 
117   cap->width = format.width;
118   cap->height = format.height;
119   cap->maxFPS = VideoFormat::IntervalToFps(format.interval);
120   cap->expectedCaptureDelay = 0;
121   cap->rawType = webrtc_type;
122   cap->codecType = webrtc::kVideoCodecUnknown;
123   cap->interlaced = false;
124   return true;
125 }
126 
127 ///////////////////////////////////////////////////////////////////////////
128 // Implementation of class WebRtcVideoCapturer
129 ///////////////////////////////////////////////////////////////////////////
130 
WebRtcVideoCapturer()131 WebRtcVideoCapturer::WebRtcVideoCapturer()
132     : factory_(new WebRtcVcmFactory),
133       module_(nullptr),
134       captured_frames_(0),
135       start_thread_(nullptr),
136       async_invoker_(nullptr) {
137   set_frame_factory(new WebRtcVideoFrameFactory());
138 }
139 
WebRtcVideoCapturer(WebRtcVcmFactoryInterface * factory)140 WebRtcVideoCapturer::WebRtcVideoCapturer(WebRtcVcmFactoryInterface* factory)
141     : factory_(factory),
142       module_(nullptr),
143       captured_frames_(0),
144       start_thread_(nullptr),
145       async_invoker_(nullptr) {
146   set_frame_factory(new WebRtcVideoFrameFactory());
147 }
148 
~WebRtcVideoCapturer()149 WebRtcVideoCapturer::~WebRtcVideoCapturer() {
150   if (module_) {
151     module_->Release();
152   }
153 }
154 
Init(const Device & device)155 bool WebRtcVideoCapturer::Init(const Device& device) {
156   RTC_DCHECK(!start_thread_);
157   if (module_) {
158     LOG(LS_ERROR) << "The capturer is already initialized";
159     return false;
160   }
161 
162   webrtc::VideoCaptureModule::DeviceInfo* info = factory_->CreateDeviceInfo(0);
163   if (!info) {
164     return false;
165   }
166 
167   // Find the desired camera, by name.
168   // In the future, comparing IDs will be more robust.
169   // TODO(juberti): Figure what's needed to allow this.
170   int num_cams = info->NumberOfDevices();
171   char vcm_id[256] = "";
172   bool found = false;
173   for (int index = 0; index < num_cams; ++index) {
174     char vcm_name[256];
175     if (info->GetDeviceName(index, vcm_name, arraysize(vcm_name), vcm_id,
176                             arraysize(vcm_id)) != -1) {
177       if (device.name == reinterpret_cast<char*>(vcm_name)) {
178         found = true;
179         break;
180       }
181     }
182   }
183   if (!found) {
184     LOG(LS_WARNING) << "Failed to find capturer for id: " << device.id;
185     factory_->DestroyDeviceInfo(info);
186     return false;
187   }
188 
189   // Enumerate the supported formats.
190   // TODO(juberti): Find out why this starts/stops the camera...
191   std::vector<VideoFormat> supported;
192   int32_t num_caps = info->NumberOfCapabilities(vcm_id);
193   for (int32_t i = 0; i < num_caps; ++i) {
194     webrtc::VideoCaptureCapability cap;
195     if (info->GetCapability(vcm_id, i, cap) != -1) {
196       VideoFormat format;
197       if (CapabilityToFormat(cap, &format)) {
198         supported.push_back(format);
199       } else {
200         LOG(LS_WARNING) << "Ignoring unsupported WebRTC capture format "
201                         << cap.rawType;
202       }
203     }
204   }
205   factory_->DestroyDeviceInfo(info);
206 
207   if (supported.empty()) {
208     LOG(LS_ERROR) << "Failed to find usable formats for id: " << device.id;
209     return false;
210   }
211 
212   module_ = factory_->Create(0, vcm_id);
213   if (!module_) {
214     LOG(LS_ERROR) << "Failed to create capturer for id: " << device.id;
215     return false;
216   }
217 
218   // It is safe to change member attributes now.
219   module_->AddRef();
220   SetId(device.id);
221   SetSupportedFormats(supported);
222 
223   // Ensure these 2 have the same value.
224   SetApplyRotation(module_->GetApplyRotation());
225 
226   return true;
227 }
228 
Init(webrtc::VideoCaptureModule * module)229 bool WebRtcVideoCapturer::Init(webrtc::VideoCaptureModule* module) {
230   RTC_DCHECK(!start_thread_);
231   if (module_) {
232     LOG(LS_ERROR) << "The capturer is already initialized";
233     return false;
234   }
235   if (!module) {
236     LOG(LS_ERROR) << "Invalid VCM supplied";
237     return false;
238   }
239   // TODO(juberti): Set id and formats.
240   (module_ = module)->AddRef();
241   return true;
242 }
243 
GetBestCaptureFormat(const VideoFormat & desired,VideoFormat * best_format)244 bool WebRtcVideoCapturer::GetBestCaptureFormat(const VideoFormat& desired,
245                                                VideoFormat* best_format) {
246   if (!best_format) {
247     return false;
248   }
249 
250   if (!VideoCapturer::GetBestCaptureFormat(desired, best_format)) {
251     // We maybe using a manually injected VCM which doesn't support enum.
252     // Use the desired format as the best format.
253     best_format->width = desired.width;
254     best_format->height = desired.height;
255     best_format->fourcc = FOURCC_I420;
256     best_format->interval = desired.interval;
257     LOG(LS_INFO) << "Failed to find best capture format,"
258                  << " fall back to the requested format "
259                  << best_format->ToString();
260   }
261   return true;
262 }
SetApplyRotation(bool enable)263 bool WebRtcVideoCapturer::SetApplyRotation(bool enable) {
264   // Can't take lock here as this will cause deadlock with
265   // OnIncomingCapturedFrame. In fact, the whole method, including methods it
266   // calls, can't take lock.
267   RTC_DCHECK(module_);
268 
269   const std::string group_name =
270       webrtc::field_trial::FindFullName("WebRTC-CVO");
271 
272   if (group_name == "Disabled") {
273     return true;
274   }
275 
276   if (!VideoCapturer::SetApplyRotation(enable)) {
277     return false;
278   }
279   return module_->SetApplyRotation(enable);
280 }
281 
Start(const VideoFormat & capture_format)282 CaptureState WebRtcVideoCapturer::Start(const VideoFormat& capture_format) {
283   if (!module_) {
284     LOG(LS_ERROR) << "The capturer has not been initialized";
285     return CS_NO_DEVICE;
286   }
287   if (start_thread_) {
288     LOG(LS_ERROR) << "The capturer is already running";
289     RTC_DCHECK(start_thread_->IsCurrent())
290         << "Trying to start capturer on different threads";
291     return CS_FAILED;
292   }
293 
294   start_thread_ = rtc::Thread::Current();
295   RTC_DCHECK(!async_invoker_);
296   async_invoker_.reset(new rtc::AsyncInvoker());
297   captured_frames_ = 0;
298 
299   SetCaptureFormat(&capture_format);
300 
301   webrtc::VideoCaptureCapability cap;
302   if (!FormatToCapability(capture_format, &cap)) {
303     LOG(LS_ERROR) << "Invalid capture format specified";
304     return CS_FAILED;
305   }
306 
307   uint32_t start = rtc::Time();
308   module_->RegisterCaptureDataCallback(*this);
309   if (module_->StartCapture(cap) != 0) {
310     LOG(LS_ERROR) << "Camera '" << GetId() << "' failed to start";
311     module_->DeRegisterCaptureDataCallback();
312     async_invoker_.reset();
313     SetCaptureFormat(nullptr);
314     start_thread_ = nullptr;
315     return CS_FAILED;
316   }
317 
318   LOG(LS_INFO) << "Camera '" << GetId() << "' started with format "
319                << capture_format.ToString() << ", elapsed time "
320                << rtc::TimeSince(start) << " ms";
321 
322   SetCaptureState(CS_RUNNING);
323   return CS_STARTING;
324 }
325 
Stop()326 void WebRtcVideoCapturer::Stop() {
327   if (!start_thread_) {
328     LOG(LS_ERROR) << "The capturer is already stopped";
329     return;
330   }
331   RTC_DCHECK(start_thread_);
332   RTC_DCHECK(start_thread_->IsCurrent());
333   RTC_DCHECK(async_invoker_);
334   if (IsRunning()) {
335     // The module is responsible for OnIncomingCapturedFrame being called, if
336     // we stop it we will get no further callbacks.
337     module_->StopCapture();
338   }
339   module_->DeRegisterCaptureDataCallback();
340 
341   // TODO(juberti): Determine if the VCM exposes any drop stats we can use.
342   double drop_ratio = 0.0;
343   LOG(LS_INFO) << "Camera '" << GetId() << "' stopped after capturing "
344                << captured_frames_ << " frames and dropping "
345                << drop_ratio << "%";
346 
347   // Clear any pending async invokes (that OnIncomingCapturedFrame may have
348   // caused).
349   async_invoker_.reset();
350 
351   SetCaptureFormat(NULL);
352   start_thread_ = nullptr;
353   SetCaptureState(CS_STOPPED);
354 }
355 
IsRunning()356 bool WebRtcVideoCapturer::IsRunning() {
357   return (module_ != NULL && module_->CaptureStarted());
358 }
359 
GetPreferredFourccs(std::vector<uint32_t> * fourccs)360 bool WebRtcVideoCapturer::GetPreferredFourccs(std::vector<uint32_t>* fourccs) {
361   if (!fourccs) {
362     return false;
363   }
364 
365   fourccs->clear();
366   for (size_t i = 0; i < arraysize(kSupportedFourCCs); ++i) {
367     fourccs->push_back(kSupportedFourCCs[i].fourcc);
368   }
369   return true;
370 }
371 
OnIncomingCapturedFrame(const int32_t id,const webrtc::VideoFrame & sample)372 void WebRtcVideoCapturer::OnIncomingCapturedFrame(
373     const int32_t id,
374     const webrtc::VideoFrame& sample) {
375   // This can only happen between Start() and Stop().
376   RTC_DCHECK(start_thread_);
377   RTC_DCHECK(async_invoker_);
378   if (start_thread_->IsCurrent()) {
379     SignalFrameCapturedOnStartThread(sample);
380   } else {
381     // This currently happens on with at least VideoCaptureModuleV4L2 and
382     // possibly other implementations of WebRTC's VideoCaptureModule.
383     // In order to maintain the threading contract with the upper layers and
384     // consistency with other capturers such as in Chrome, we need to do a
385     // thread hop.
386     // Note that Stop() can cause the async invoke call to be cancelled.
387     async_invoker_->AsyncInvoke<void>(
388         start_thread_,
389         // Note that Bind captures by value, so there's an intermediate copy
390         // of sample.
391         rtc::Bind(&WebRtcVideoCapturer::SignalFrameCapturedOnStartThread, this,
392                   sample));
393   }
394 }
395 
OnCaptureDelayChanged(const int32_t id,const int32_t delay)396 void WebRtcVideoCapturer::OnCaptureDelayChanged(const int32_t id,
397                                                 const int32_t delay) {
398   LOG(LS_INFO) << "Capture delay changed to " << delay << " ms";
399 }
400 
SignalFrameCapturedOnStartThread(const webrtc::VideoFrame & frame)401 void WebRtcVideoCapturer::SignalFrameCapturedOnStartThread(
402     const webrtc::VideoFrame& frame) {
403   // This can only happen between Start() and Stop().
404   RTC_DCHECK(start_thread_);
405   RTC_DCHECK(start_thread_->IsCurrent());
406   RTC_DCHECK(async_invoker_);
407 
408   ++captured_frames_;
409   // Log the size and pixel aspect ratio of the first captured frame.
410   if (1 == captured_frames_) {
411     LOG(LS_INFO) << "Captured frame size "
412                  << frame.width() << "x" << frame.height()
413                  << ". Expected format " << GetCaptureFormat()->ToString();
414   }
415 
416   // Signal down stream components on captured frame.
417   // The CapturedFrame class doesn't support planes. We have to ExtractBuffer
418   // to one block for it.
419   size_t length =
420       webrtc::CalcBufferSize(webrtc::kI420, frame.width(), frame.height());
421   capture_buffer_.resize(length);
422   // TODO(magjed): Refactor the WebRtcCapturedFrame to avoid memory copy or
423   // take over ownership of the buffer held by |frame| if that's possible.
424   webrtc::ExtractBuffer(frame, length, &capture_buffer_[0]);
425   WebRtcCapturedFrame webrtc_frame(frame, &capture_buffer_[0], length);
426   SignalFrameCaptured(this, &webrtc_frame);
427 }
428 
429 // WebRtcCapturedFrame
WebRtcCapturedFrame(const webrtc::VideoFrame & sample,void * buffer,size_t length)430 WebRtcCapturedFrame::WebRtcCapturedFrame(const webrtc::VideoFrame& sample,
431                                          void* buffer,
432                                          size_t length) {
433   width = sample.width();
434   height = sample.height();
435   fourcc = FOURCC_I420;
436   // TODO(hellner): Support pixel aspect ratio (for OSX).
437   pixel_width = 1;
438   pixel_height = 1;
439   // Convert units from VideoFrame RenderTimeMs to CapturedFrame (nanoseconds).
440   time_stamp = sample.render_time_ms() * rtc::kNumNanosecsPerMillisec;
441   data_size = rtc::checked_cast<uint32_t>(length);
442   data = buffer;
443   rotation = sample.rotation();
444 }
445 
446 }  // namespace cricket
447 
448 #endif  // HAVE_WEBRTC_VIDEO
449