blob: 1ce4d77517a423d3b164526244f6c186c0882c0c [file] [log] [blame]
henrike@webrtc.org28e20752013-07-10 00:45:36 +00001// libjingle
2// Copyright 2011 Google Inc.
3//
4// Redistribution and use in source and binary forms, with or without
5// modification, are permitted provided that the following conditions are met:
6//
7// 1. Redistributions of source code must retain the above copyright notice,
8// this list of conditions and the following disclaimer.
9// 2. Redistributions in binary form must reproduce the above copyright notice,
10// this list of conditions and the following disclaimer in the documentation
11// and/or other materials provided with the distribution.
12// 3. The name of the author may not be used to endorse or promote products
13// derived from this software without specific prior written permission.
14//
15// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
16// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
17// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
18// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
19// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
21// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
22// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
23// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
24// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25//
26// Implementation of class WebRtcVideoCapturer.
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
wu@webrtc.orgf6d6ed02014-01-03 22:08:47 +000035#include "talk/base/criticalsection.h"
henrike@webrtc.org28e20752013-07-10 00:45:36 +000036#include "talk/base/logging.h"
37#include "talk/base/thread.h"
38#include "talk/base/timeutils.h"
39#include "talk/media/webrtc/webrtcvideoframe.h"
40
41#include "talk/base/win32.h" // Need this to #include the impl files.
42#include "webrtc/modules/video_capture/include/video_capture_factory.h"
43
44namespace cricket {
45
46struct kVideoFourCCEntry {
47 uint32 fourcc;
48 webrtc::RawVideoType webrtc_type;
49};
50
51// This indicates our format preferences and defines a mapping between
52// webrtc::RawVideoType (from video_capture_defines.h) to our FOURCCs.
53static kVideoFourCCEntry kSupportedFourCCs[] = {
54 { FOURCC_I420, webrtc::kVideoI420 }, // 12 bpp, no conversion.
55 { FOURCC_YV12, webrtc::kVideoYV12 }, // 12 bpp, no conversion.
56 { FOURCC_NV12, webrtc::kVideoNV12 }, // 12 bpp, fast conversion.
57 { FOURCC_NV21, webrtc::kVideoNV21 }, // 12 bpp, fast conversion.
58 { FOURCC_YUY2, webrtc::kVideoYUY2 }, // 16 bpp, fast conversion.
59 { FOURCC_UYVY, webrtc::kVideoUYVY }, // 16 bpp, fast conversion.
60 { FOURCC_MJPG, webrtc::kVideoMJPEG }, // compressed, slow conversion.
61 { FOURCC_ARGB, webrtc::kVideoARGB }, // 32 bpp, slow conversion.
62 { FOURCC_24BG, webrtc::kVideoRGB24 }, // 24 bpp, slow conversion.
63};
64
65class WebRtcVcmFactory : public WebRtcVcmFactoryInterface {
66 public:
67 virtual webrtc::VideoCaptureModule* Create(int id, const char* device) {
68 return webrtc::VideoCaptureFactory::Create(id, device);
69 }
70 virtual webrtc::VideoCaptureModule::DeviceInfo* CreateDeviceInfo(int id) {
71 return webrtc::VideoCaptureFactory::CreateDeviceInfo(id);
72 }
73 virtual void DestroyDeviceInfo(webrtc::VideoCaptureModule::DeviceInfo* info) {
74 delete info;
75 }
76};
77
78static bool CapabilityToFormat(const webrtc::VideoCaptureCapability& cap,
79 VideoFormat* format) {
80 uint32 fourcc = 0;
81 for (size_t i = 0; i < ARRAY_SIZE(kSupportedFourCCs); ++i) {
82 if (kSupportedFourCCs[i].webrtc_type == cap.rawType) {
83 fourcc = kSupportedFourCCs[i].fourcc;
84 break;
85 }
86 }
87 if (fourcc == 0) {
88 return false;
89 }
90
91 format->fourcc = fourcc;
92 format->width = cap.width;
93 format->height = cap.height;
94 format->interval = VideoFormat::FpsToInterval(cap.maxFPS);
95 return true;
96}
97
98static bool FormatToCapability(const VideoFormat& format,
99 webrtc::VideoCaptureCapability* cap) {
100 webrtc::RawVideoType webrtc_type = webrtc::kVideoUnknown;
101 for (size_t i = 0; i < ARRAY_SIZE(kSupportedFourCCs); ++i) {
102 if (kSupportedFourCCs[i].fourcc == format.fourcc) {
103 webrtc_type = kSupportedFourCCs[i].webrtc_type;
104 break;
105 }
106 }
107 if (webrtc_type == webrtc::kVideoUnknown) {
108 return false;
109 }
110
111 cap->width = format.width;
112 cap->height = format.height;
113 cap->maxFPS = VideoFormat::IntervalToFps(format.interval);
114 cap->expectedCaptureDelay = 0;
115 cap->rawType = webrtc_type;
116 cap->codecType = webrtc::kVideoCodecUnknown;
117 cap->interlaced = false;
118 return true;
119}
120
121///////////////////////////////////////////////////////////////////////////
122// Implementation of class WebRtcVideoCapturer
123///////////////////////////////////////////////////////////////////////////
124
125WebRtcVideoCapturer::WebRtcVideoCapturer()
126 : factory_(new WebRtcVcmFactory),
127 module_(NULL),
128 captured_frames_(0) {
129}
130
131WebRtcVideoCapturer::WebRtcVideoCapturer(WebRtcVcmFactoryInterface* factory)
132 : factory_(factory),
133 module_(NULL),
134 captured_frames_(0) {
135}
136
137WebRtcVideoCapturer::~WebRtcVideoCapturer() {
138 if (module_) {
139 module_->Release();
140 }
141}
142
143bool WebRtcVideoCapturer::Init(const Device& device) {
144 if (module_) {
145 LOG(LS_ERROR) << "The capturer is already initialized";
146 return false;
147 }
148
149 webrtc::VideoCaptureModule::DeviceInfo* info = factory_->CreateDeviceInfo(0);
150 if (!info) {
151 return false;
152 }
153
154 // Find the desired camera, by name.
155 // In the future, comparing IDs will be more robust.
156 // TODO(juberti): Figure what's needed to allow this.
157 int num_cams = info->NumberOfDevices();
158 char vcm_id[256] = "";
159 bool found = false;
160 for (int index = 0; index < num_cams; ++index) {
161 char vcm_name[256];
162 if (info->GetDeviceName(index, vcm_name, ARRAY_SIZE(vcm_name),
163 vcm_id, ARRAY_SIZE(vcm_id)) != -1) {
164 if (device.name == reinterpret_cast<char*>(vcm_name)) {
165 found = true;
166 break;
167 }
168 }
169 }
170 if (!found) {
171 LOG(LS_WARNING) << "Failed to find capturer for id: " << device.id;
172 factory_->DestroyDeviceInfo(info);
173 return false;
174 }
175
176 // Enumerate the supported formats.
177 // TODO(juberti): Find out why this starts/stops the camera...
178 std::vector<VideoFormat> supported;
179 int32_t num_caps = info->NumberOfCapabilities(vcm_id);
180 for (int32_t i = 0; i < num_caps; ++i) {
181 webrtc::VideoCaptureCapability cap;
182 if (info->GetCapability(vcm_id, i, cap) != -1) {
183 VideoFormat format;
184 if (CapabilityToFormat(cap, &format)) {
185 supported.push_back(format);
186 } else {
187 LOG(LS_WARNING) << "Ignoring unsupported WebRTC capture format "
188 << cap.rawType;
189 }
190 }
191 }
192 factory_->DestroyDeviceInfo(info);
193 if (supported.empty()) {
194 LOG(LS_ERROR) << "Failed to find usable formats for id: " << device.id;
195 return false;
196 }
197
198 module_ = factory_->Create(0, vcm_id);
199 if (!module_) {
200 LOG(LS_ERROR) << "Failed to create capturer for id: " << device.id;
201 return false;
202 }
203
204 // It is safe to change member attributes now.
205 module_->AddRef();
206 SetId(device.id);
207 SetSupportedFormats(supported);
208 return true;
209}
210
211bool WebRtcVideoCapturer::Init(webrtc::VideoCaptureModule* module) {
212 if (module_) {
213 LOG(LS_ERROR) << "The capturer is already initialized";
214 return false;
215 }
216 if (!module) {
217 LOG(LS_ERROR) << "Invalid VCM supplied";
218 return false;
219 }
220 // TODO(juberti): Set id and formats.
221 (module_ = module)->AddRef();
222 return true;
223}
224
225bool WebRtcVideoCapturer::GetBestCaptureFormat(const VideoFormat& desired,
226 VideoFormat* best_format) {
227 if (!best_format) {
228 return false;
229 }
230
231 if (!VideoCapturer::GetBestCaptureFormat(desired, best_format)) {
232 // We maybe using a manually injected VCM which doesn't support enum.
233 // Use the desired format as the best format.
234 best_format->width = desired.width;
235 best_format->height = desired.height;
236 best_format->fourcc = FOURCC_I420;
237 best_format->interval = desired.interval;
238 LOG(LS_INFO) << "Failed to find best capture format,"
239 << " fall back to the requested format "
240 << best_format->ToString();
241 }
242 return true;
243}
244
245CaptureState WebRtcVideoCapturer::Start(const VideoFormat& capture_format) {
246 if (!module_) {
247 LOG(LS_ERROR) << "The capturer has not been initialized";
248 return CS_NO_DEVICE;
249 }
250
251 // TODO(hellner): weird to return failure when it is in fact actually running.
252 if (IsRunning()) {
253 LOG(LS_ERROR) << "The capturer is already running";
254 return CS_FAILED;
255 }
256
257 SetCaptureFormat(&capture_format);
258
259 webrtc::VideoCaptureCapability cap;
260 if (!FormatToCapability(capture_format, &cap)) {
261 LOG(LS_ERROR) << "Invalid capture format specified";
262 return CS_FAILED;
263 }
264
265 std::string camera_id(GetId());
266 uint32 start = talk_base::Time();
mallinath@webrtc.org7433a082014-01-29 00:56:02 +0000267 module_->RegisterCaptureDataCallback(*this);
268 if (module_->StartCapture(cap) != 0) {
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000269 LOG(LS_ERROR) << "Camera '" << camera_id << "' failed to start";
270 return CS_FAILED;
271 }
272
273 LOG(LS_INFO) << "Camera '" << camera_id << "' started with format "
274 << capture_format.ToString() << ", elapsed time "
275 << talk_base::TimeSince(start) << " ms";
276
277 captured_frames_ = 0;
278 SetCaptureState(CS_RUNNING);
279 return CS_STARTING;
280}
281
wu@webrtc.orgf6d6ed02014-01-03 22:08:47 +0000282// Critical section blocks Stop from shutting down during callbacks from capture
283// thread to OnIncomingCapturedFrame. Note that the crit is try-locked in
284// OnFrameCaptured, as the lock ordering between this and the system component
285// controlling the camera is reversed: system frame -> OnIncomingCapturedFrame;
286// Stop -> system stop camera).
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000287void WebRtcVideoCapturer::Stop() {
wu@webrtc.orgf6d6ed02014-01-03 22:08:47 +0000288 talk_base::CritScope cs(&critical_section_stopping_);
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000289 if (IsRunning()) {
290 talk_base::Thread::Current()->Clear(this);
291 module_->StopCapture();
292 module_->DeRegisterCaptureDataCallback();
293
294 // TODO(juberti): Determine if the VCM exposes any drop stats we can use.
295 double drop_ratio = 0.0;
296 std::string camera_id(GetId());
297 LOG(LS_INFO) << "Camera '" << camera_id << "' stopped after capturing "
298 << captured_frames_ << " frames and dropping "
299 << drop_ratio << "%";
300 }
301 SetCaptureFormat(NULL);
302}
303
304bool WebRtcVideoCapturer::IsRunning() {
305 return (module_ != NULL && module_->CaptureStarted());
306}
307
308bool WebRtcVideoCapturer::GetPreferredFourccs(
309 std::vector<uint32>* fourccs) {
310 if (!fourccs) {
311 return false;
312 }
313
314 fourccs->clear();
315 for (size_t i = 0; i < ARRAY_SIZE(kSupportedFourCCs); ++i) {
316 fourccs->push_back(kSupportedFourCCs[i].fourcc);
317 }
318 return true;
319}
320
321void WebRtcVideoCapturer::OnIncomingCapturedFrame(const int32_t id,
322 webrtc::I420VideoFrame& sample) {
wu@webrtc.orgf6d6ed02014-01-03 22:08:47 +0000323 // This would be a normal CritScope, except that it's possible that:
324 // (1) whatever system component producing this frame has taken a lock, and
325 // (2) Stop() probably calls back into that system component, which may take
326 // the same lock. Due to the reversed order, we have to try-lock in order to
327 // avoid a potential deadlock. Besides, if we can't enter because we're
328 // stopping, we may as well drop the frame.
329 talk_base::TryCritScope cs(&critical_section_stopping_);
330 if (!cs.locked() || !IsRunning()) {
331 // Capturer has been stopped or is in the process of stopping.
332 return;
333 }
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000334
335 ++captured_frames_;
336 // Log the size and pixel aspect ratio of the first captured frame.
337 if (1 == captured_frames_) {
338 LOG(LS_INFO) << "Captured frame size "
339 << sample.width() << "x" << sample.height()
340 << ". Expected format " << GetCaptureFormat()->ToString();
341 }
342
343 // Signal down stream components on captured frame.
344 // The CapturedFrame class doesn't support planes. We have to ExtractBuffer
345 // to one block for it.
346 int length = webrtc::CalcBufferSize(webrtc::kI420,
347 sample.width(), sample.height());
wu@webrtc.org16d62542013-11-05 23:45:14 +0000348 capture_buffer_.resize(length);
349 // TODO(ronghuawu): Refactor the WebRtcCapturedFrame to avoid memory copy.
350 webrtc::ExtractBuffer(sample, length, &capture_buffer_[0]);
351 WebRtcCapturedFrame frame(sample, &capture_buffer_[0], length);
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000352 SignalFrameCaptured(this, &frame);
353}
354
355void WebRtcVideoCapturer::OnCaptureDelayChanged(const int32_t id,
356 const int32_t delay) {
357 LOG(LS_INFO) << "Capture delay changed to " << delay << " ms";
358}
359
360// WebRtcCapturedFrame
361WebRtcCapturedFrame::WebRtcCapturedFrame(const webrtc::I420VideoFrame& sample,
362 void* buffer,
363 int length) {
364 width = sample.width();
365 height = sample.height();
366 fourcc = FOURCC_I420;
367 // TODO(hellner): Support pixel aspect ratio (for OSX).
368 pixel_width = 1;
369 pixel_height = 1;
370 // Convert units from VideoFrame RenderTimeMs to CapturedFrame (nanoseconds).
371 elapsed_time = sample.render_time_ms() * talk_base::kNumNanosecsPerMillisec;
372 time_stamp = elapsed_time;
373 data_size = length;
374 data = buffer;
375}
376
377} // namespace cricket
378
379#endif // HAVE_WEBRTC_VIDEO