// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "remoting/host/cast_video_capturer_adapter.h"
#include "third_party/webrtc/modules/desktop_capture/desktop_frame.h"
namespace remoting {
// Number of frames to be captured per second.
const int kFramesPerSec = 10;
CastVideoCapturerAdapter::CastVideoCapturerAdapter(
scoped_ptr<webrtc::DesktopCapturer> capturer)
: desktop_capturer_(capturer.Pass()) {
DCHECK(desktop_capturer_);
thread_checker_.DetachFromThread();
// Disable video adaptation since we don't intend to use it.
set_enable_video_adapter(false);
}
CastVideoCapturerAdapter::~CastVideoCapturerAdapter() {
DCHECK(!capture_timer_);
}
webrtc::SharedMemory* CastVideoCapturerAdapter::CreateSharedMemory(
size_t size) {
return NULL;
}
void CastVideoCapturerAdapter::OnCaptureCompleted(webrtc::DesktopFrame* frame) {
scoped_ptr<webrtc::DesktopFrame> owned_frame(frame);
// Drop the owned_frame if there were no changes.
if (!owned_frame || owned_frame->updated_region().is_empty()) {
owned_frame.reset();
return;
}
// Convert the webrtc::DesktopFrame to a cricket::CapturedFrame.
cricket::CapturedFrame captured_frame;
captured_frame.width = owned_frame->size().width();
captured_frame.height = owned_frame->size().height();
base::TimeTicks current_time = base::TimeTicks::Now();
captured_frame.elapsed_time = (current_time - start_time_).InMicroseconds() *
base::Time::kNanosecondsPerMicrosecond;
captured_frame.time_stamp =
current_time.ToInternalValue() * base::Time::kNanosecondsPerMicrosecond;
captured_frame.data = owned_frame->data();
// The data_size attribute must be set. If multiple formats are supported,
// this should be set appropriately for each one.
captured_frame.data_size =
(captured_frame.width * webrtc::DesktopFrame::kBytesPerPixel * 8 + 7) /
8 * captured_frame.height;
captured_frame.fourcc = cricket::FOURCC_ARGB;
SignalFrameCaptured(this, &captured_frame);
}
bool CastVideoCapturerAdapter::GetBestCaptureFormat(
const cricket::VideoFormat& desired,
cricket::VideoFormat* best_format) {
DCHECK(thread_checker_.CalledOnValidThread());
// For now, just used the desired width and height.
best_format->width = desired.width;
best_format->height = desired.height;
best_format->fourcc = cricket::FOURCC_ARGB;
best_format->interval = FPS_TO_INTERVAL(kFramesPerSec);
return true;
}
cricket::CaptureState CastVideoCapturerAdapter::Start(
const cricket::VideoFormat& capture_format) {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(!capture_timer_);
DCHECK_EQ(capture_format.fourcc, (static_cast<uint32>(cricket::FOURCC_ARGB)));
if (!desktop_capturer_) {
VLOG(1) << "CastVideoCapturerAdapter failed to start.";
return cricket::CS_FAILED;
}
// This is required to tell the cricket::VideoCapturer base class what the
// capture format will be.
SetCaptureFormat(&capture_format);
desktop_capturer_->Start(this);
// Save the Start() time of |desktop_capturer_|. This will be used
// to estimate the creation time of the frame source, to set the elapsed_time
// of future CapturedFrames in OnCaptureCompleted().
start_time_ = base::TimeTicks::Now();
capture_timer_.reset(new base::RepeatingTimer<CastVideoCapturerAdapter>());
capture_timer_->Start(FROM_HERE,
base::TimeDelta::FromMicroseconds(
GetCaptureFormat()->interval /
(base::Time::kNanosecondsPerMicrosecond)),
this,
&CastVideoCapturerAdapter::CaptureNextFrame);
return cricket::CS_RUNNING;
}
// Similar to the base class implementation with some important differences:
// 1. Does not call either Stop() or Start(), as those would affect the state of
// |desktop_capturer_|.
// 2. Does not support unpausing after stopping the capturer. It is unclear
// if that flow needs to be supported.
bool CastVideoCapturerAdapter::Pause(bool pause) {
DCHECK(thread_checker_.CalledOnValidThread());
if (pause) {
if (capture_state() == cricket::CS_PAUSED)
return true;
bool running = capture_state() == cricket::CS_STARTING ||
capture_state() == cricket::CS_RUNNING;
DCHECK_EQ(running, IsRunning());
if (!running) {
LOG(ERROR)
<< "Cannot pause CastVideoCapturerAdapter.";
return false;
}
// Stop |capture_timer_| and set capture state to cricket::CS_PAUSED.
capture_timer_->Stop();
SetCaptureState(cricket::CS_PAUSED);
VLOG(1) << "CastVideoCapturerAdapter paused.";
return true;
} else { // Unpausing.
if (capture_state() != cricket::CS_PAUSED || !GetCaptureFormat() ||
!capture_timer_) {
LOG(ERROR) << "Cannot unpause CastVideoCapturerAdapter.";
return false;
}
// Restart |capture_timer_| and set capture state to cricket::CS_RUNNING;
capture_timer_->Start(FROM_HERE,
base::TimeDelta::FromMicroseconds(
GetCaptureFormat()->interval /
(base::Time::kNanosecondsPerMicrosecond)),
this,
&CastVideoCapturerAdapter::CaptureNextFrame);
SetCaptureState(cricket::CS_RUNNING);
VLOG(1) << "CastVideoCapturerAdapter unpaused.";
}
return true;
}
void CastVideoCapturerAdapter::Stop() {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK_NE(capture_state(), cricket::CS_STOPPED);
capture_timer_.reset();
SetCaptureFormat(NULL);
SetCaptureState(cricket::CS_STOPPED);
VLOG(1) << "CastVideoCapturerAdapter stopped.";
}
bool CastVideoCapturerAdapter::IsRunning() {
DCHECK(thread_checker_.CalledOnValidThread());
return capture_timer_->IsRunning();
}
bool CastVideoCapturerAdapter::IsScreencast() const {
return true;
}
bool CastVideoCapturerAdapter::GetPreferredFourccs(
std::vector<uint32>* fourccs) {
DCHECK(thread_checker_.CalledOnValidThread());
if (!fourccs)
return false;
fourccs->push_back(cricket::FOURCC_ARGB);
return true;
}
void CastVideoCapturerAdapter::CaptureNextFrame() {
// If we are paused, then don't capture.
if (!IsRunning())
return;
desktop_capturer_->Capture(webrtc::DesktopRegion());
}
} // namespace remoting