This CL has been generated with the following script: for m in PLOG \ LOG_TAG \ LOG_GLEM \ LOG_GLE_EX \ LOG_GLE \ LAST_SYSTEM_ERROR \ LOG_ERRNO_EX \ LOG_ERRNO \ LOG_ERR_EX \ LOG_ERR \ LOG_V \ LOG_F \ LOG_T_F \ LOG_E \ LOG_T \ LOG_CHECK_LEVEL_V \ LOG_CHECK_LEVEL \ LOG do git grep -l $m | xargs sed -i "s,\b$m\b,RTC_$m,g" done git checkout rtc_base/logging.h git cl format Bug: webrtc:8452 Change-Id: I1a53ef3e0a5ef6e244e62b2e012b864914784600 Reviewed-on: https://webrtc-review.googlesource.com/21325 Reviewed-by: Niels Moller <nisse@webrtc.org> Reviewed-by: Karl Wiberg <kwiberg@webrtc.org> Commit-Queue: Mirko Bonadei <mbonadei@webrtc.org> Cr-Commit-Position: refs/heads/master@{#20617}
382 lines
13 KiB
C++
382 lines
13 KiB
C++
/*
|
|
* Copyright (c) 2010 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.
|
|
*/
|
|
|
|
// Implementation file of class VideoCapturer.
|
|
|
|
#include "media/base/videocapturer.h"
|
|
|
|
#include <algorithm>
|
|
|
|
#include "api/video/i420_buffer.h"
|
|
#include "api/video/video_frame.h"
|
|
#include "rtc_base/logging.h"
|
|
#include "system_wrappers/include/field_trial.h"
|
|
|
|
namespace cricket {
|
|
|
|
namespace {
|
|
|
|
static const int64_t kMaxDistance = ~(static_cast<int64_t>(1) << 63);
|
|
#ifdef WEBRTC_LINUX
|
|
static const int kYU12Penalty = 16; // Needs to be higher than MJPG index.
|
|
#endif
|
|
static const char* kSimulcastScreenshareFieldTrialName =
|
|
"WebRTC-SimulcastScreenshare";
|
|
|
|
} // namespace
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
// Implementation of class VideoCapturer
|
|
/////////////////////////////////////////////////////////////////////
|
|
VideoCapturer::VideoCapturer() : apply_rotation_(false) {
|
|
thread_checker_.DetachFromThread();
|
|
Construct();
|
|
}
|
|
|
|
void VideoCapturer::Construct() {
|
|
enable_camera_list_ = false;
|
|
capture_state_ = CS_STOPPED;
|
|
scaled_width_ = 0;
|
|
scaled_height_ = 0;
|
|
enable_video_adapter_ = true;
|
|
}
|
|
|
|
const std::vector<VideoFormat>* VideoCapturer::GetSupportedFormats() const {
|
|
return &filtered_supported_formats_;
|
|
}
|
|
|
|
bool VideoCapturer::StartCapturing(const VideoFormat& capture_format) {
|
|
RTC_DCHECK(thread_checker_.CalledOnValidThread());
|
|
CaptureState result = Start(capture_format);
|
|
const bool success = (result == CS_RUNNING) || (result == CS_STARTING);
|
|
if (!success) {
|
|
return false;
|
|
}
|
|
if (result == CS_RUNNING) {
|
|
SetCaptureState(result);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void VideoCapturer::SetSupportedFormats(
|
|
const std::vector<VideoFormat>& formats) {
|
|
// This method is OK to call during initialization on a separate thread.
|
|
RTC_DCHECK(capture_state_ == CS_STOPPED ||
|
|
thread_checker_.CalledOnValidThread());
|
|
supported_formats_ = formats;
|
|
UpdateFilteredSupportedFormats();
|
|
}
|
|
|
|
bool VideoCapturer::GetBestCaptureFormat(const VideoFormat& format,
|
|
VideoFormat* best_format) {
|
|
RTC_DCHECK(thread_checker_.CalledOnValidThread());
|
|
// TODO(fbarchard): Directly support max_format.
|
|
UpdateFilteredSupportedFormats();
|
|
const std::vector<VideoFormat>* supported_formats = GetSupportedFormats();
|
|
|
|
if (supported_formats->empty()) {
|
|
return false;
|
|
}
|
|
RTC_LOG(LS_INFO) << " Capture Requested " << format.ToString();
|
|
int64_t best_distance = kMaxDistance;
|
|
std::vector<VideoFormat>::const_iterator best = supported_formats->end();
|
|
std::vector<VideoFormat>::const_iterator i;
|
|
for (i = supported_formats->begin(); i != supported_formats->end(); ++i) {
|
|
int64_t distance = GetFormatDistance(format, *i);
|
|
// TODO(fbarchard): Reduce to LS_VERBOSE if/when camera capture is
|
|
// relatively bug free.
|
|
RTC_LOG(LS_INFO) << " Supported " << i->ToString() << " distance "
|
|
<< distance;
|
|
if (distance < best_distance) {
|
|
best_distance = distance;
|
|
best = i;
|
|
}
|
|
}
|
|
if (supported_formats->end() == best) {
|
|
RTC_LOG(LS_ERROR) << " No acceptable camera format found";
|
|
return false;
|
|
}
|
|
|
|
if (best_format) {
|
|
best_format->width = best->width;
|
|
best_format->height = best->height;
|
|
best_format->fourcc = best->fourcc;
|
|
best_format->interval = best->interval;
|
|
RTC_LOG(LS_INFO) << " Best " << best_format->ToString() << " Interval "
|
|
<< best_format->interval << " distance " << best_distance;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void VideoCapturer::ConstrainSupportedFormats(const VideoFormat& max_format) {
|
|
RTC_DCHECK(thread_checker_.CalledOnValidThread());
|
|
max_format_.reset(new VideoFormat(max_format));
|
|
RTC_LOG(LS_VERBOSE) << " ConstrainSupportedFormats " << max_format.ToString();
|
|
UpdateFilteredSupportedFormats();
|
|
}
|
|
|
|
bool VideoCapturer::GetInputSize(int* width, int* height) {
|
|
rtc::CritScope cs(&frame_stats_crit_);
|
|
if (!input_size_valid_) {
|
|
return false;
|
|
}
|
|
*width = input_width_;
|
|
*height = input_height_;
|
|
|
|
return true;
|
|
}
|
|
|
|
void VideoCapturer::RemoveSink(
|
|
rtc::VideoSinkInterface<webrtc::VideoFrame>* sink) {
|
|
RTC_DCHECK(thread_checker_.CalledOnValidThread());
|
|
broadcaster_.RemoveSink(sink);
|
|
OnSinkWantsChanged(broadcaster_.wants());
|
|
}
|
|
|
|
void VideoCapturer::AddOrUpdateSink(
|
|
rtc::VideoSinkInterface<webrtc::VideoFrame>* sink,
|
|
const rtc::VideoSinkWants& wants) {
|
|
RTC_DCHECK(thread_checker_.CalledOnValidThread());
|
|
broadcaster_.AddOrUpdateSink(sink, wants);
|
|
OnSinkWantsChanged(broadcaster_.wants());
|
|
}
|
|
|
|
void VideoCapturer::OnSinkWantsChanged(const rtc::VideoSinkWants& wants) {
|
|
RTC_DCHECK(thread_checker_.CalledOnValidThread());
|
|
apply_rotation_ = wants.rotation_applied;
|
|
|
|
if (video_adapter()) {
|
|
video_adapter()->OnResolutionFramerateRequest(wants.target_pixel_count,
|
|
wants.max_pixel_count,
|
|
wants.max_framerate_fps);
|
|
}
|
|
}
|
|
|
|
bool VideoCapturer::AdaptFrame(int width,
|
|
int height,
|
|
int64_t camera_time_us,
|
|
int64_t system_time_us,
|
|
int* out_width,
|
|
int* out_height,
|
|
int* crop_width,
|
|
int* crop_height,
|
|
int* crop_x,
|
|
int* crop_y,
|
|
int64_t* translated_camera_time_us) {
|
|
if (translated_camera_time_us) {
|
|
*translated_camera_time_us =
|
|
timestamp_aligner_.TranslateTimestamp(camera_time_us, system_time_us);
|
|
}
|
|
if (!broadcaster_.frame_wanted()) {
|
|
return false;
|
|
}
|
|
|
|
bool simulcast_screenshare_enabled =
|
|
webrtc::field_trial::IsEnabled(kSimulcastScreenshareFieldTrialName);
|
|
if (enable_video_adapter_ &&
|
|
(!IsScreencast() || simulcast_screenshare_enabled)) {
|
|
if (!video_adapter_.AdaptFrameResolution(
|
|
width, height, camera_time_us * rtc::kNumNanosecsPerMicrosec,
|
|
crop_width, crop_height, out_width, out_height)) {
|
|
// VideoAdapter dropped the frame.
|
|
broadcaster_.OnDiscardedFrame();
|
|
return false;
|
|
}
|
|
*crop_x = (width - *crop_width) / 2;
|
|
*crop_y = (height - *crop_height) / 2;
|
|
} else {
|
|
*out_width = width;
|
|
*out_height = height;
|
|
*crop_width = width;
|
|
*crop_height = height;
|
|
*crop_x = 0;
|
|
*crop_y = 0;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void VideoCapturer::OnFrame(const webrtc::VideoFrame& frame,
|
|
int orig_width,
|
|
int orig_height) {
|
|
// For a child class which implements rotation itself, we should
|
|
// always have apply_rotation_ == false or frame.rotation() == 0.
|
|
// Except possibly during races where apply_rotation_ is changed
|
|
// mid-stream.
|
|
if (apply_rotation_ && frame.rotation() != webrtc::kVideoRotation_0) {
|
|
rtc::scoped_refptr<webrtc::VideoFrameBuffer> buffer(
|
|
frame.video_frame_buffer());
|
|
if (buffer->type() != webrtc::VideoFrameBuffer::Type::kI420) {
|
|
// Sources producing non-I420 frames must handle apply_rotation
|
|
// themselves. But even if they do, we may occasionally end up
|
|
// in this case, for frames in flight at the time
|
|
// applied_rotation is set to true. In that case, we just drop
|
|
// the frame.
|
|
RTC_LOG(LS_WARNING) << "Non-I420 frame requiring rotation. Discarding.";
|
|
return;
|
|
}
|
|
broadcaster_.OnFrame(webrtc::VideoFrame(
|
|
webrtc::I420Buffer::Rotate(*buffer->GetI420(), frame.rotation()),
|
|
webrtc::kVideoRotation_0, frame.timestamp_us()));
|
|
} else {
|
|
broadcaster_.OnFrame(frame);
|
|
}
|
|
UpdateInputSize(orig_width, orig_height);
|
|
}
|
|
|
|
void VideoCapturer::SetCaptureState(CaptureState state) {
|
|
RTC_DCHECK(thread_checker_.CalledOnValidThread());
|
|
if (state == capture_state_) {
|
|
// Don't trigger a state changed callback if the state hasn't changed.
|
|
return;
|
|
}
|
|
capture_state_ = state;
|
|
SignalStateChange(this, capture_state_);
|
|
}
|
|
|
|
// Get the distance between the supported and desired formats.
|
|
// Prioritization is done according to this algorithm:
|
|
// 1) Width closeness. If not same, we prefer wider.
|
|
// 2) Height closeness. If not same, we prefer higher.
|
|
// 3) Framerate closeness. If not same, we prefer faster.
|
|
// 4) Compression. If desired format has a specific fourcc, we need exact match;
|
|
// otherwise, we use preference.
|
|
int64_t VideoCapturer::GetFormatDistance(const VideoFormat& desired,
|
|
const VideoFormat& supported) {
|
|
RTC_DCHECK(thread_checker_.CalledOnValidThread());
|
|
int64_t distance = kMaxDistance;
|
|
|
|
// Check fourcc.
|
|
uint32_t supported_fourcc = CanonicalFourCC(supported.fourcc);
|
|
int64_t delta_fourcc = kMaxDistance;
|
|
if (FOURCC_ANY == desired.fourcc) {
|
|
// Any fourcc is OK for the desired. Use preference to find best fourcc.
|
|
std::vector<uint32_t> preferred_fourccs;
|
|
if (!GetPreferredFourccs(&preferred_fourccs)) {
|
|
return distance;
|
|
}
|
|
|
|
for (size_t i = 0; i < preferred_fourccs.size(); ++i) {
|
|
if (supported_fourcc == CanonicalFourCC(preferred_fourccs[i])) {
|
|
delta_fourcc = i;
|
|
#ifdef WEBRTC_LINUX
|
|
// For HD avoid YU12 which is a software conversion and has 2 bugs
|
|
// b/7326348 b/6960899. Reenable when fixed.
|
|
if (supported.height >= 720 && (supported_fourcc == FOURCC_YU12 ||
|
|
supported_fourcc == FOURCC_YV12)) {
|
|
delta_fourcc += kYU12Penalty;
|
|
}
|
|
#endif
|
|
break;
|
|
}
|
|
}
|
|
} else if (supported_fourcc == CanonicalFourCC(desired.fourcc)) {
|
|
delta_fourcc = 0; // Need exact match.
|
|
}
|
|
|
|
if (kMaxDistance == delta_fourcc) {
|
|
// Failed to match fourcc.
|
|
return distance;
|
|
}
|
|
|
|
// Check resolution and fps.
|
|
int desired_width = desired.width;
|
|
int desired_height = desired.height;
|
|
int64_t delta_w = supported.width - desired_width;
|
|
float supported_fps = VideoFormat::IntervalToFpsFloat(supported.interval);
|
|
float delta_fps =
|
|
supported_fps - VideoFormat::IntervalToFpsFloat(desired.interval);
|
|
// Check height of supported height compared to height we would like it to be.
|
|
int64_t aspect_h = desired_width
|
|
? supported.width * desired_height / desired_width
|
|
: desired_height;
|
|
int64_t delta_h = supported.height - aspect_h;
|
|
|
|
distance = 0;
|
|
// Set high penalty if the supported format is lower than the desired format.
|
|
// 3x means we would prefer down to down to 3/4, than up to double.
|
|
// But we'd prefer up to double than down to 1/2. This is conservative,
|
|
// strongly avoiding going down in resolution, similar to
|
|
// the old method, but not completely ruling it out in extreme situations.
|
|
// It also ignores framerate, which is often very low at high resolutions.
|
|
// TODO(fbarchard): Improve logic to use weighted factors.
|
|
static const int kDownPenalty = -3;
|
|
if (delta_w < 0) {
|
|
delta_w = delta_w * kDownPenalty;
|
|
}
|
|
if (delta_h < 0) {
|
|
delta_h = delta_h * kDownPenalty;
|
|
}
|
|
// Require camera fps to be at least 80% of what is requested if resolution
|
|
// matches.
|
|
// Require camera fps to be at least 96% of what is requested, or higher,
|
|
// if resolution differs. 96% allows for slight variations in fps. e.g. 29.97
|
|
if (delta_fps < 0) {
|
|
float min_desirable_fps = delta_w ?
|
|
VideoFormat::IntervalToFpsFloat(desired.interval) * 28.f / 30.f :
|
|
VideoFormat::IntervalToFpsFloat(desired.interval) * 23.f / 30.f;
|
|
delta_fps = -delta_fps;
|
|
if (supported_fps < min_desirable_fps) {
|
|
distance |= static_cast<int64_t>(1) << 62;
|
|
} else {
|
|
distance |= static_cast<int64_t>(1) << 15;
|
|
}
|
|
}
|
|
int64_t idelta_fps = static_cast<int>(delta_fps);
|
|
|
|
// 12 bits for width and height and 8 bits for fps and fourcc.
|
|
distance |=
|
|
(delta_w << 28) | (delta_h << 16) | (idelta_fps << 8) | delta_fourcc;
|
|
|
|
return distance;
|
|
}
|
|
|
|
void VideoCapturer::UpdateFilteredSupportedFormats() {
|
|
filtered_supported_formats_.clear();
|
|
filtered_supported_formats_ = supported_formats_;
|
|
if (!max_format_) {
|
|
return;
|
|
}
|
|
std::vector<VideoFormat>::iterator iter = filtered_supported_formats_.begin();
|
|
while (iter != filtered_supported_formats_.end()) {
|
|
if (ShouldFilterFormat(*iter)) {
|
|
iter = filtered_supported_formats_.erase(iter);
|
|
} else {
|
|
++iter;
|
|
}
|
|
}
|
|
if (filtered_supported_formats_.empty()) {
|
|
// The device only captures at resolutions higher than |max_format_| this
|
|
// indicates that |max_format_| should be ignored as it is better to capture
|
|
// at too high a resolution than to not capture at all.
|
|
filtered_supported_formats_ = supported_formats_;
|
|
}
|
|
}
|
|
|
|
bool VideoCapturer::ShouldFilterFormat(const VideoFormat& format) const {
|
|
RTC_DCHECK(thread_checker_.CalledOnValidThread());
|
|
if (!enable_camera_list_) {
|
|
return false;
|
|
}
|
|
return format.width > max_format_->width ||
|
|
format.height > max_format_->height;
|
|
}
|
|
|
|
void VideoCapturer::UpdateInputSize(int width, int height) {
|
|
// Update stats protected from fetches from different thread.
|
|
rtc::CritScope cs(&frame_stats_crit_);
|
|
|
|
input_size_valid_ = true;
|
|
input_width_ = width;
|
|
input_height_ = height;
|
|
}
|
|
|
|
} // namespace cricket
|