blob: f8a9f748a80ff70dfaeb951d3b0ec9fb463cca63 [file] [log] [blame]
/*
* Copyright (c) 2011 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.
*/
#include "media/engine/webrtcvideocapturer.h"
#include "rtc_base/arraysize.h"
#include "rtc_base/bind.h"
#include "rtc_base/checks.h"
#include "rtc_base/criticalsection.h"
#include "rtc_base/logging.h"
#include "rtc_base/numerics/safe_conversions.h"
#include "rtc_base/thread.h"
#include "rtc_base/timeutils.h"
#include "modules/video_capture/video_capture_factory.h"
#if defined(WEBRTC_WIN)
#include "rtc_base/win32.h" // Need this to #include the impl files.
#endif // WEBRTC_WIN
#include "system_wrappers/include/field_trial.h"
namespace cricket {
namespace {
struct kVideoFourCCEntry {
uint32_t fourcc;
webrtc::VideoType webrtc_type;
};
// This indicates our format preferences and defines a mapping between
// webrtc::RawVideoType (from video_capture_defines.h) to our FOURCCs.
kVideoFourCCEntry kSupportedFourCCs[] = {
{FOURCC_I420, webrtc::VideoType::kI420}, // 12 bpp, no conversion.
{FOURCC_YV12, webrtc::VideoType::kYV12}, // 12 bpp, no conversion.
{FOURCC_YUY2, webrtc::VideoType::kYUY2}, // 16 bpp, fast conversion.
{FOURCC_UYVY, webrtc::VideoType::kUYVY}, // 16 bpp, fast conversion.
{FOURCC_NV12, webrtc::VideoType::kNV12}, // 12 bpp, fast conversion.
{FOURCC_NV21, webrtc::VideoType::kNV21}, // 12 bpp, fast conversion.
{FOURCC_MJPG, webrtc::VideoType::kMJPEG}, // compressed, slow conversion.
{FOURCC_ARGB, webrtc::VideoType::kARGB}, // 32 bpp, slow conversion.
{FOURCC_24BG, webrtc::VideoType::kRGB24}, // 24 bpp, slow conversion.
};
bool CapabilityToFormat(const webrtc::VideoCaptureCapability& cap,
VideoFormat* format) {
uint32_t fourcc = 0;
for (size_t i = 0; i < arraysize(kSupportedFourCCs); ++i) {
if (kSupportedFourCCs[i].webrtc_type == cap.videoType) {
fourcc = kSupportedFourCCs[i].fourcc;
break;
}
}
if (fourcc == 0) {
return false;
}
format->fourcc = fourcc;
format->width = cap.width;
format->height = cap.height;
format->interval = VideoFormat::FpsToInterval(cap.maxFPS);
return true;
}
bool FormatToCapability(const VideoFormat& format,
webrtc::VideoCaptureCapability* cap) {
webrtc::VideoType webrtc_type = webrtc::VideoType::kUnknown;
for (size_t i = 0; i < arraysize(kSupportedFourCCs); ++i) {
if (kSupportedFourCCs[i].fourcc == format.fourcc) {
webrtc_type = kSupportedFourCCs[i].webrtc_type;
break;
}
}
if (webrtc_type == webrtc::VideoType::kUnknown) {
return false;
}
cap->width = format.width;
cap->height = format.height;
cap->maxFPS = VideoFormat::IntervalToFps(format.interval);
cap->videoType = webrtc_type;
cap->interlaced = false;
return true;
}
} // namespace
class WebRtcVcmFactory : public WebRtcVcmFactoryInterface {
public:
virtual rtc::scoped_refptr<webrtc::VideoCaptureModule> Create(
const char* device) {
return webrtc::VideoCaptureFactory::Create(device);
}
virtual webrtc::VideoCaptureModule::DeviceInfo* CreateDeviceInfo() {
return webrtc::VideoCaptureFactory::CreateDeviceInfo();
}
virtual void DestroyDeviceInfo(webrtc::VideoCaptureModule::DeviceInfo* info) {
delete info;
}
};
///////////////////////////////////////////////////////////////////////////
// Implementation of class WebRtcVideoCapturer
///////////////////////////////////////////////////////////////////////////
WebRtcVideoCapturer::WebRtcVideoCapturer()
: factory_(new WebRtcVcmFactory),
module_(nullptr),
captured_frames_(0),
start_thread_(nullptr) {}
WebRtcVideoCapturer::WebRtcVideoCapturer(WebRtcVcmFactoryInterface* factory)
: factory_(factory),
module_(nullptr),
captured_frames_(0),
start_thread_(nullptr) {}
WebRtcVideoCapturer::~WebRtcVideoCapturer() {}
bool WebRtcVideoCapturer::Init(const Device& device) {
RTC_DCHECK(!start_thread_);
if (module_) {
RTC_LOG(LS_ERROR) << "The capturer is already initialized";
return false;
}
webrtc::VideoCaptureModule::DeviceInfo* info = factory_->CreateDeviceInfo();
if (!info) {
return false;
}
// Find the desired camera, by name.
// In the future, comparing IDs will be more robust.
// TODO(juberti): Figure what's needed to allow this.
int num_cams = info->NumberOfDevices();
char vcm_id[256] = "";
bool found = false;
for (int index = 0; index < num_cams; ++index) {
char vcm_name[256];
if (info->GetDeviceName(index, vcm_name, arraysize(vcm_name), vcm_id,
arraysize(vcm_id)) != -1) {
if (device.name == reinterpret_cast<char*>(vcm_name)) {
found = true;
break;
}
}
}
if (!found) {
RTC_LOG(LS_WARNING) << "Failed to find capturer for id: " << device.id;
factory_->DestroyDeviceInfo(info);
return false;
}
// Enumerate the supported formats.
// TODO(juberti): Find out why this starts/stops the camera...
std::vector<VideoFormat> supported;
int32_t num_caps = info->NumberOfCapabilities(vcm_id);
for (int32_t i = 0; i < num_caps; ++i) {
webrtc::VideoCaptureCapability cap;
if (info->GetCapability(vcm_id, i, cap) != -1) {
VideoFormat format;
if (CapabilityToFormat(cap, &format)) {
supported.push_back(format);
} else {
RTC_LOG(LS_WARNING) << "Ignoring unsupported WebRTC capture format "
<< static_cast<int>(cap.videoType);
}
}
}
factory_->DestroyDeviceInfo(info);
if (supported.empty()) {
RTC_LOG(LS_ERROR) << "Failed to find usable formats for id: " << device.id;
return false;
}
module_ = factory_->Create(vcm_id);
if (!module_) {
RTC_LOG(LS_ERROR) << "Failed to create capturer for id: " << device.id;
return false;
}
// It is safe to change member attributes now.
SetId(device.id);
SetSupportedFormats(supported);
return true;
}
bool WebRtcVideoCapturer::Init(
const rtc::scoped_refptr<webrtc::VideoCaptureModule>& module) {
RTC_DCHECK(!start_thread_);
if (module_) {
RTC_LOG(LS_ERROR) << "The capturer is already initialized";
return false;
}
if (!module) {
RTC_LOG(LS_ERROR) << "Invalid VCM supplied";
return false;
}
// TODO(juberti): Set id and formats.
module_ = module;
return true;
}
bool WebRtcVideoCapturer::GetBestCaptureFormat(const VideoFormat& desired,
VideoFormat* best_format) {
if (!best_format) {
return false;
}
if (!VideoCapturer::GetBestCaptureFormat(desired, best_format)) {
// We maybe using a manually injected VCM which doesn't support enum.
// Use the desired format as the best format.
best_format->width = desired.width;
best_format->height = desired.height;
best_format->fourcc = FOURCC_I420;
best_format->interval = desired.interval;
RTC_LOG(LS_INFO) << "Failed to find best capture format,"
<< " fall back to the requested format "
<< best_format->ToString();
}
return true;
}
void WebRtcVideoCapturer::OnSinkWantsChanged(const rtc::VideoSinkWants& wants) {
// Can't take lock here as this will cause deadlock with
// OnIncomingCapturedFrame. In fact, the whole method, including methods it
// calls, can't take lock.
RTC_DCHECK(module_);
if (webrtc::field_trial::FindFullName("WebRTC-CVO").find("Disabled") == 0)
return;
VideoCapturer::OnSinkWantsChanged(wants);
bool result = module_->SetApplyRotation(wants.rotation_applied);
RTC_CHECK(result);
return;
}
CaptureState WebRtcVideoCapturer::Start(const VideoFormat& capture_format) {
if (!module_) {
RTC_LOG(LS_ERROR) << "The capturer has not been initialized";
return CS_FAILED;
}
if (start_thread_) {
RTC_LOG(LS_ERROR) << "The capturer is already running";
RTC_DCHECK(start_thread_->IsCurrent())
<< "Trying to start capturer on different threads";
return CS_FAILED;
}
start_thread_ = rtc::Thread::Current();
captured_frames_ = 0;
SetCaptureFormat(&capture_format);
webrtc::VideoCaptureCapability cap;
if (!FormatToCapability(capture_format, &cap)) {
RTC_LOG(LS_ERROR) << "Invalid capture format specified";
return CS_FAILED;
}
int64_t start = rtc::TimeMillis();
module_->RegisterCaptureDataCallback(this);
if (module_->StartCapture(cap) != 0) {
RTC_LOG(LS_ERROR) << "Camera '" << GetId() << "' failed to start";
module_->DeRegisterCaptureDataCallback();
SetCaptureFormat(nullptr);
start_thread_ = nullptr;
return CS_FAILED;
}
RTC_LOG(LS_INFO) << "Camera '" << GetId() << "' started with format "
<< capture_format.ToString() << ", elapsed time "
<< rtc::TimeSince(start) << " ms";
SetCaptureState(CS_RUNNING);
return CS_STARTING;
}
void WebRtcVideoCapturer::Stop() {
if (!start_thread_) {
RTC_LOG(LS_ERROR) << "The capturer is already stopped";
return;
}
RTC_DCHECK(start_thread_);
RTC_DCHECK(start_thread_->IsCurrent());
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;
RTC_LOG(LS_INFO) << "Camera '" << GetId() << "' stopped after capturing "
<< captured_frames_ << " frames and dropping " << drop_ratio
<< "%";
SetCaptureFormat(NULL);
start_thread_ = nullptr;
SetCaptureState(CS_STOPPED);
}
bool WebRtcVideoCapturer::IsRunning() {
return (module_ != NULL && module_->CaptureStarted());
}
bool WebRtcVideoCapturer::GetPreferredFourccs(std::vector<uint32_t>* fourccs) {
if (!fourccs) {
return false;
}
fourccs->clear();
for (size_t i = 0; i < arraysize(kSupportedFourCCs); ++i) {
fourccs->push_back(kSupportedFourCCs[i].fourcc);
}
return true;
}
void WebRtcVideoCapturer::OnFrame(const webrtc::VideoFrame& sample) {
// This can only happen between Start() and Stop().
RTC_DCHECK(start_thread_);
++captured_frames_;
// Log the size and pixel aspect ratio of the first captured frame.
if (1 == captured_frames_) {
RTC_LOG(LS_INFO) << "Captured frame size " << sample.width() << "x"
<< sample.height() << ". Expected format "
<< GetCaptureFormat()->ToString();
}
VideoCapturer::OnFrame(sample, sample.width(), sample.height());
}
} // namespace cricket