Reland 237634 and 237645.
(1) Implement DesktopCaptureDeviceAsh based on CopyOutputRequest. The new implementation captures the Ash desktop by issuing CopyOutputRequest on the layer associated with the root window of the desktop. Desktop capture using this path is only feasible on Ash at this moment, where the aura root window corresponds to the entire desktop. On other platforms the aura root window is mapped to a native window. Refactor WebContentsVideoCaptureDevice::Impl and share the class ThreadSafeCaptureOracle between web contents capture and desktop capture. The existing X11 desktop capture path is inefficient especially on ARM devices. This CL leverages hardware-accelerated copy and encoding to achieve performance on par with tab capture. Measured desktop capture frame rate on daisy: Before this CL: 6~7 fps After this CL: 18~23 fps depending on content (2) Fix memory leak in DesktopCaptureDeviceAshTest on Linux Chromium OS ASAN. (3) Disable failing DesktopCaptureApiTest.ChooseDesktopMedia on ChromeOS. See http://crbug.com/324179 BUG=310372 TEST=trybots, verify tab/screen capture locally on devices R=creis@chromium.org, sergeyu@chromium.org TBR=ernstm@chromium.org Review URL: https://codereview.chromium.org/93583003 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@237732 0039d316-1c4b-4281-b951-d872f2087c98
This commit is contained in:
chrome/browser/extensions/api/desktop_capture
content
@ -143,7 +143,8 @@ class DesktopCaptureApiTest : public ExtensionApiTest {
|
||||
} // namespace
|
||||
|
||||
// Flaky on Windows: http://crbug.com/301887
|
||||
#if defined(OS_WIN)
|
||||
// Failing on ChromeOS: http://crbug.com/324179
|
||||
#if defined(OS_WIN) || defined(OS_CHROMEOS)
|
||||
#define MAYBE_ChooseDesktopMedia DISABLED_ChooseDesktopMedia
|
||||
#else
|
||||
#define MAYBE_ChooseDesktopMedia ChooseDesktopMedia
|
||||
|
@ -0,0 +1,299 @@
|
||||
// Copyright 2013 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 "content/browser/renderer_host/media/desktop_capture_device_ash.h"
|
||||
|
||||
#include "base/logging.h"
|
||||
#include "base/timer/timer.h"
|
||||
#include "cc/output/copy_output_request.h"
|
||||
#include "cc/output/copy_output_result.h"
|
||||
#include "content/browser/aura/image_transport_factory.h"
|
||||
#include "content/browser/renderer_host/media/video_capture_device_impl.h"
|
||||
#include "content/common/gpu/client/gl_helper.h"
|
||||
#include "content/public/browser/browser_thread.h"
|
||||
#include "media/base/video_util.h"
|
||||
#include "media/video/capture/video_capture_types.h"
|
||||
#include "third_party/skia/include/core/SkBitmap.h"
|
||||
#include "ui/aura/window.h"
|
||||
#include "ui/aura/window_observer.h"
|
||||
#include "ui/compositor/compositor.h"
|
||||
#include "ui/compositor/dip_util.h"
|
||||
#include "ui/compositor/layer.h"
|
||||
#include "ui/gfx/screen.h"
|
||||
|
||||
namespace content {
|
||||
|
||||
namespace {
|
||||
|
||||
class DesktopVideoCaptureMachine
|
||||
: public VideoCaptureMachine,
|
||||
public aura::WindowObserver,
|
||||
public ui::CompositorObserver,
|
||||
public base::SupportsWeakPtr<DesktopVideoCaptureMachine> {
|
||||
public:
|
||||
DesktopVideoCaptureMachine(const DesktopMediaID& source);
|
||||
virtual ~DesktopVideoCaptureMachine();
|
||||
|
||||
// VideoCaptureFrameSource overrides.
|
||||
virtual bool Start(
|
||||
const scoped_refptr<ThreadSafeCaptureOracle>& oracle_proxy) OVERRIDE;
|
||||
virtual void Stop() OVERRIDE;
|
||||
|
||||
// Implements aura::WindowObserver.
|
||||
virtual void OnWindowDestroyed(aura::Window* window) OVERRIDE;
|
||||
|
||||
// Implements ui::CompositorObserver.
|
||||
virtual void OnCompositingDidCommit(ui::Compositor* compositor) OVERRIDE {}
|
||||
virtual void OnCompositingStarted(ui::Compositor* compositor,
|
||||
base::TimeTicks start_time) OVERRIDE {}
|
||||
virtual void OnCompositingEnded(ui::Compositor* compositor) OVERRIDE;
|
||||
virtual void OnCompositingAborted(ui::Compositor* compositor) OVERRIDE {}
|
||||
virtual void OnCompositingLockStateChanged(
|
||||
ui::Compositor* compositor) OVERRIDE {}
|
||||
virtual void OnUpdateVSyncParameters(ui::Compositor* compositor,
|
||||
base::TimeTicks timebase,
|
||||
base::TimeDelta interval) OVERRIDE {}
|
||||
|
||||
private:
|
||||
// Captures a frame.
|
||||
// |dirty| is false for timer polls and true for compositor updates.
|
||||
void Capture(bool dirty);
|
||||
|
||||
// Response callback for cc::Layer::RequestCopyOfOutput().
|
||||
void DidCopyOutput(
|
||||
scoped_refptr<media::VideoFrame> video_frame,
|
||||
base::Time start_time,
|
||||
const ThreadSafeCaptureOracle::CaptureFrameCallback& capture_frame_cb,
|
||||
scoped_ptr<cc::CopyOutputResult> result);
|
||||
|
||||
// The window associated with the desktop.
|
||||
aura::Window* desktop_window_;
|
||||
|
||||
// The layer associated with the desktop.
|
||||
ui::Layer* desktop_layer_;
|
||||
|
||||
// The timer that kicks off period captures.
|
||||
base::Timer timer_;
|
||||
|
||||
// The desktop id.
|
||||
DesktopMediaID desktop_id_;
|
||||
|
||||
// Makes all the decisions about which frames to copy, and how.
|
||||
scoped_refptr<ThreadSafeCaptureOracle> oracle_proxy_;
|
||||
|
||||
// YUV readback pipeline.
|
||||
scoped_ptr<content::ReadbackYUVInterface> yuv_readback_pipeline_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(DesktopVideoCaptureMachine);
|
||||
};
|
||||
|
||||
DesktopVideoCaptureMachine::DesktopVideoCaptureMachine(
|
||||
const DesktopMediaID& source)
|
||||
: desktop_window_(NULL),
|
||||
desktop_layer_(NULL),
|
||||
timer_(true, true),
|
||||
desktop_id_(source) {}
|
||||
|
||||
DesktopVideoCaptureMachine::~DesktopVideoCaptureMachine() {}
|
||||
|
||||
bool DesktopVideoCaptureMachine::Start(
|
||||
const scoped_refptr<ThreadSafeCaptureOracle>& oracle_proxy) {
|
||||
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
|
||||
|
||||
// TODO(hshi): get the correct display specified by |desktop_id_|.
|
||||
const gfx::Display& primary_display =
|
||||
gfx::Screen::GetNativeScreen()->GetPrimaryDisplay();
|
||||
desktop_window_ = gfx::Screen::GetNativeScreen()->GetWindowAtScreenPoint(
|
||||
primary_display.bounds().CenterPoint())->GetRootWindow();
|
||||
if (!desktop_window_)
|
||||
return false;
|
||||
|
||||
// If the desktop layer is already destroyed then return failure.
|
||||
desktop_layer_ = desktop_window_->layer();
|
||||
if (!desktop_layer_)
|
||||
return false;
|
||||
|
||||
DCHECK(oracle_proxy.get());
|
||||
oracle_proxy_ = oracle_proxy;
|
||||
|
||||
// Start observing window events.
|
||||
desktop_window_->AddObserver(this);
|
||||
|
||||
// Start observing compositor updates.
|
||||
ui::Compositor* compositor = desktop_layer_->GetCompositor();
|
||||
if (!compositor)
|
||||
return false;
|
||||
|
||||
compositor->AddObserver(this);
|
||||
|
||||
// Starts timer.
|
||||
timer_.Start(FROM_HERE, oracle_proxy_->capture_period(),
|
||||
base::Bind(&DesktopVideoCaptureMachine::Capture, AsWeakPtr(),
|
||||
false));
|
||||
|
||||
started_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void DesktopVideoCaptureMachine::Stop() {
|
||||
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
|
||||
|
||||
// Stop observing window events.
|
||||
if (desktop_window_)
|
||||
desktop_window_->RemoveObserver(this);
|
||||
|
||||
// Stop observing compositor updates.
|
||||
if (desktop_layer_) {
|
||||
ui::Compositor* compositor = desktop_layer_->GetCompositor();
|
||||
if (compositor)
|
||||
compositor->RemoveObserver(this);
|
||||
}
|
||||
|
||||
// Stop timer.
|
||||
timer_.Stop();
|
||||
|
||||
started_ = false;
|
||||
}
|
||||
|
||||
void DesktopVideoCaptureMachine::Capture(bool dirty) {
|
||||
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
|
||||
|
||||
// Do not capture if the desktop layer is already destroyed.
|
||||
if (!desktop_layer_)
|
||||
return;
|
||||
|
||||
scoped_refptr<media::VideoFrame> frame;
|
||||
ThreadSafeCaptureOracle::CaptureFrameCallback capture_frame_cb;
|
||||
|
||||
const base::Time start_time = base::Time::Now();
|
||||
const VideoCaptureOracle::Event event =
|
||||
dirty ? VideoCaptureOracle::kCompositorUpdate
|
||||
: VideoCaptureOracle::kTimerPoll;
|
||||
if (oracle_proxy_->ObserveEventAndDecideCapture(
|
||||
event, start_time, &frame, &capture_frame_cb)) {
|
||||
scoped_ptr<cc::CopyOutputRequest> request =
|
||||
cc::CopyOutputRequest::CreateRequest(
|
||||
base::Bind(&DesktopVideoCaptureMachine::DidCopyOutput,
|
||||
AsWeakPtr(), frame, start_time, capture_frame_cb));
|
||||
gfx::Rect desktop_size = ui::ConvertRectToPixel(
|
||||
desktop_layer_, desktop_layer_->bounds());
|
||||
request->set_area(desktop_size);
|
||||
desktop_layer_->RequestCopyOfOutput(request.Pass());
|
||||
}
|
||||
}
|
||||
|
||||
static void CopyOutputFinishedForVideo(
|
||||
base::Time start_time,
|
||||
const ThreadSafeCaptureOracle::CaptureFrameCallback& capture_frame_cb,
|
||||
scoped_ptr<cc::SingleReleaseCallback> release_callback,
|
||||
bool result) {
|
||||
release_callback->Run(0, false);
|
||||
capture_frame_cb.Run(start_time, result);
|
||||
}
|
||||
|
||||
void DesktopVideoCaptureMachine::DidCopyOutput(
|
||||
scoped_refptr<media::VideoFrame> video_frame,
|
||||
base::Time start_time,
|
||||
const ThreadSafeCaptureOracle::CaptureFrameCallback& capture_frame_cb,
|
||||
scoped_ptr<cc::CopyOutputResult> result) {
|
||||
if (result->IsEmpty() || result->size().IsEmpty())
|
||||
return;
|
||||
|
||||
// Compute the dest size we want after the letterboxing resize. Make the
|
||||
// coordinates and sizes even because we letterbox in YUV space
|
||||
// (see CopyRGBToVideoFrame). They need to be even for the UV samples to
|
||||
// line up correctly.
|
||||
// The video frame's coded_size() and the result's size() are both physical
|
||||
// pixels.
|
||||
gfx::Rect region_in_frame =
|
||||
media::ComputeLetterboxRegion(gfx::Rect(video_frame->coded_size()),
|
||||
result->size());
|
||||
region_in_frame = gfx::Rect(region_in_frame.x() & ~1,
|
||||
region_in_frame.y() & ~1,
|
||||
region_in_frame.width() & ~1,
|
||||
region_in_frame.height() & ~1);
|
||||
if (region_in_frame.IsEmpty())
|
||||
return;
|
||||
|
||||
ImageTransportFactory* factory = ImageTransportFactory::GetInstance();
|
||||
GLHelper* gl_helper = factory->GetGLHelper();
|
||||
if (!gl_helper)
|
||||
return;
|
||||
|
||||
cc::TextureMailbox texture_mailbox;
|
||||
scoped_ptr<cc::SingleReleaseCallback> release_callback;
|
||||
result->TakeTexture(&texture_mailbox, &release_callback);
|
||||
DCHECK(texture_mailbox.IsTexture());
|
||||
if (!texture_mailbox.IsTexture())
|
||||
return;
|
||||
|
||||
gfx::Rect result_rect(result->size());
|
||||
if (!yuv_readback_pipeline_ ||
|
||||
yuv_readback_pipeline_->scaler()->SrcSize() != result_rect.size() ||
|
||||
yuv_readback_pipeline_->scaler()->SrcSubrect() != result_rect ||
|
||||
yuv_readback_pipeline_->scaler()->DstSize() != region_in_frame.size()) {
|
||||
yuv_readback_pipeline_.reset(
|
||||
gl_helper->CreateReadbackPipelineYUV(GLHelper::SCALER_QUALITY_FAST,
|
||||
result_rect.size(),
|
||||
result_rect,
|
||||
video_frame->coded_size(),
|
||||
region_in_frame,
|
||||
true,
|
||||
true));
|
||||
}
|
||||
yuv_readback_pipeline_->ReadbackYUV(
|
||||
texture_mailbox.name(), texture_mailbox.sync_point(), video_frame.get(),
|
||||
base::Bind(&CopyOutputFinishedForVideo, start_time, capture_frame_cb,
|
||||
base::Passed(&release_callback)));
|
||||
}
|
||||
|
||||
void DesktopVideoCaptureMachine::OnWindowDestroyed(aura::Window* window) {
|
||||
DCHECK(desktop_window_ && window == desktop_window_);
|
||||
desktop_window_ = NULL;
|
||||
desktop_layer_ = NULL;
|
||||
|
||||
// Post task to stop capture on UI thread.
|
||||
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(
|
||||
&DesktopVideoCaptureMachine::Stop, AsWeakPtr()));
|
||||
}
|
||||
|
||||
void DesktopVideoCaptureMachine::OnCompositingEnded(
|
||||
ui::Compositor* compositor) {
|
||||
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(
|
||||
&DesktopVideoCaptureMachine::Capture, AsWeakPtr(), true));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
DesktopCaptureDeviceAsh::DesktopCaptureDeviceAsh(
|
||||
const DesktopMediaID& source)
|
||||
: impl_(new VideoCaptureDeviceImpl(scoped_ptr<VideoCaptureMachine>(
|
||||
new DesktopVideoCaptureMachine(source)))) {}
|
||||
|
||||
DesktopCaptureDeviceAsh::~DesktopCaptureDeviceAsh() {
|
||||
DVLOG(2) << "DesktopCaptureDeviceAsh@" << this << " destroying.";
|
||||
}
|
||||
|
||||
// static
|
||||
media::VideoCaptureDevice* DesktopCaptureDeviceAsh::Create(
|
||||
const DesktopMediaID& source) {
|
||||
// This implementation only supports screen capture.
|
||||
if (source.type != DesktopMediaID::TYPE_SCREEN)
|
||||
return NULL;
|
||||
|
||||
return new DesktopCaptureDeviceAsh(source);
|
||||
}
|
||||
|
||||
void DesktopCaptureDeviceAsh::AllocateAndStart(
|
||||
const media::VideoCaptureParams& params,
|
||||
scoped_ptr<Client> client) {
|
||||
DVLOG(1) << "Allocating " << params.requested_format.frame_size.ToString();
|
||||
impl_->AllocateAndStart(params, client.Pass());
|
||||
}
|
||||
|
||||
void DesktopCaptureDeviceAsh::StopAndDeAllocate() {
|
||||
impl_->StopAndDeAllocate();
|
||||
}
|
||||
|
||||
} // namespace content
|
@ -0,0 +1,48 @@
|
||||
// Copyright 2013 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.
|
||||
|
||||
#ifndef CONTENT_BROWSER_RENDERER_HOST_MEDIA_DESKTOP_CAPTURE_DEVICE_ASH_H_
|
||||
#define CONTENT_BROWSER_RENDERER_HOST_MEDIA_DESKTOP_CAPTURE_DEVICE_ASH_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "base/memory/scoped_ptr.h"
|
||||
#include "content/common/content_export.h"
|
||||
#include "content/public/common/desktop_media_id.h"
|
||||
#include "media/video/capture/video_capture_device.h"
|
||||
|
||||
namespace aura {
|
||||
class Window;
|
||||
} // namespace aura
|
||||
|
||||
namespace content {
|
||||
|
||||
class VideoCaptureDeviceImpl;
|
||||
|
||||
// An implementation of VideoCaptureDevice that mirrors the desktop on Ash.
|
||||
class CONTENT_EXPORT DesktopCaptureDeviceAsh
|
||||
: public media::VideoCaptureDevice {
|
||||
public:
|
||||
// Creates a VideoCaptureDevice for the Ash desktop.
|
||||
static media::VideoCaptureDevice* Create(const DesktopMediaID& source);
|
||||
|
||||
virtual ~DesktopCaptureDeviceAsh();
|
||||
|
||||
// VideoCaptureDevice implementation.
|
||||
virtual void AllocateAndStart(const media::VideoCaptureParams& params,
|
||||
scoped_ptr<Client> client) OVERRIDE;
|
||||
virtual void StopAndDeAllocate() OVERRIDE;
|
||||
|
||||
private:
|
||||
DesktopCaptureDeviceAsh(const DesktopMediaID& source);
|
||||
|
||||
const scoped_ptr<class VideoCaptureDeviceImpl> impl_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(DesktopCaptureDeviceAsh);
|
||||
};
|
||||
|
||||
|
||||
} // namespace content
|
||||
|
||||
#endif // CONTENT_BROWSER_RENDERER_HOST_MEDIA_DESKTOP_CAPTURE_DEVICE_ASH_H_
|
@ -0,0 +1,115 @@
|
||||
// Copyright 2013 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 "content/browser/renderer_host/media/desktop_capture_device_ash.h"
|
||||
|
||||
#include "base/synchronization/waitable_event.h"
|
||||
#include "content/browser/browser_thread_impl.h"
|
||||
#include "media/video/capture/video_capture_types.h"
|
||||
#include "testing/gmock/include/gmock/gmock.h"
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
#include "ui/aura/client/window_tree_client.h"
|
||||
#include "ui/aura/test/aura_test_helper.h"
|
||||
#include "ui/aura/test/test_window_delegate.h"
|
||||
#include "ui/aura/window.h"
|
||||
|
||||
using ::testing::_;
|
||||
using ::testing::AnyNumber;
|
||||
using ::testing::DoAll;
|
||||
using ::testing::Expectation;
|
||||
using ::testing::InvokeWithoutArgs;
|
||||
using ::testing::SaveArg;
|
||||
|
||||
namespace content {
|
||||
namespace {
|
||||
|
||||
const int kFrameRate = 30;
|
||||
|
||||
class MockDeviceClient : public media::VideoCaptureDevice::Client {
|
||||
public:
|
||||
MOCK_METHOD2(ReserveOutputBuffer,
|
||||
scoped_refptr<Buffer>(media::VideoFrame::Format format,
|
||||
const gfx::Size& dimensions));
|
||||
MOCK_METHOD0(OnError, void());
|
||||
MOCK_METHOD7(OnIncomingCapturedFrame,
|
||||
void(const uint8* data,
|
||||
int length,
|
||||
base::Time timestamp,
|
||||
int rotation,
|
||||
bool flip_vert,
|
||||
bool flip_horiz,
|
||||
const media::VideoCaptureFormat& frame_format));
|
||||
MOCK_METHOD5(OnIncomingCapturedBuffer,
|
||||
void(const scoped_refptr<Buffer>& buffer,
|
||||
media::VideoFrame::Format format,
|
||||
const gfx::Size& dimensions,
|
||||
base::Time timestamp,
|
||||
int frame_rate));
|
||||
};
|
||||
|
||||
// Test harness that sets up a minimal environment with necessary stubs.
|
||||
class DesktopCaptureDeviceAshTest : public testing::Test {
|
||||
public:
|
||||
DesktopCaptureDeviceAshTest()
|
||||
: browser_thread_for_ui_(BrowserThread::UI, &message_loop_) {}
|
||||
virtual ~DesktopCaptureDeviceAshTest() {}
|
||||
|
||||
protected:
|
||||
virtual void SetUp() OVERRIDE {
|
||||
helper_.reset(new aura::test::AuraTestHelper(&message_loop_));
|
||||
helper_->SetUp();
|
||||
|
||||
// We need a window to cover desktop area so that DesktopCaptureDeviceAsh
|
||||
// can use gfx::NativeWindow::GetWindowAtScreenPoint() to locate the
|
||||
// root window associated with the primary display.
|
||||
gfx::Rect desktop_bounds = root_window()->bounds();
|
||||
window_delegate_.reset(new aura::test::TestWindowDelegate());
|
||||
desktop_window_.reset(new aura::Window(window_delegate_.get()));
|
||||
desktop_window_->Init(ui::LAYER_TEXTURED);
|
||||
desktop_window_->SetBounds(desktop_bounds);
|
||||
aura::client::ParentWindowWithContext(
|
||||
desktop_window_.get(), root_window(), desktop_bounds);
|
||||
desktop_window_->Show();
|
||||
}
|
||||
|
||||
virtual void TearDown() OVERRIDE {
|
||||
helper_->RunAllPendingInMessageLoop();
|
||||
root_window()->RemoveChild(desktop_window_.get());
|
||||
desktop_window_.reset();
|
||||
window_delegate_.reset();
|
||||
helper_->TearDown();
|
||||
}
|
||||
|
||||
aura::Window* root_window() { return helper_->root_window(); }
|
||||
|
||||
private:
|
||||
base::MessageLoopForUI message_loop_;
|
||||
BrowserThreadImpl browser_thread_for_ui_;
|
||||
scoped_ptr<aura::test::AuraTestHelper> helper_;
|
||||
scoped_ptr<aura::Window> desktop_window_;
|
||||
scoped_ptr<aura::test::TestWindowDelegate> window_delegate_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(DesktopCaptureDeviceAshTest);
|
||||
};
|
||||
|
||||
TEST_F(DesktopCaptureDeviceAshTest, StartAndStop) {
|
||||
DesktopMediaID source(DesktopMediaID::TYPE_SCREEN, 0);
|
||||
scoped_ptr<media::VideoCaptureDevice> capture_device(
|
||||
DesktopCaptureDeviceAsh::Create(source));
|
||||
|
||||
scoped_ptr<MockDeviceClient> client(new MockDeviceClient());
|
||||
EXPECT_CALL(*client, OnError()).Times(0);
|
||||
|
||||
media::VideoCaptureParams capture_params;
|
||||
capture_params.requested_format.frame_size.SetSize(640, 480);
|
||||
capture_params.requested_format.frame_rate = kFrameRate;
|
||||
capture_params.requested_format.pixel_format = media::PIXEL_FORMAT_I420;
|
||||
capture_params.allow_resolution_change = false;
|
||||
capture_device->AllocateAndStart(
|
||||
capture_params, client.PassAs<media::VideoCaptureDevice::Client>());
|
||||
capture_device->StopAndDeAllocate();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace content
|
280
content/browser/renderer_host/media/video_capture_device_impl.cc
Normal file
280
content/browser/renderer_host/media/video_capture_device_impl.cc
Normal file
@ -0,0 +1,280 @@
|
||||
// Copyright 2013 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 "content/browser/renderer_host/media/video_capture_device_impl.h"
|
||||
|
||||
#include "base/basictypes.h"
|
||||
#include "base/bind.h"
|
||||
#include "base/callback_forward.h"
|
||||
#include "base/callback_helpers.h"
|
||||
#include "base/debug/trace_event.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/memory/scoped_ptr.h"
|
||||
#include "base/memory/weak_ptr.h"
|
||||
#include "base/message_loop/message_loop_proxy.h"
|
||||
#include "base/metrics/histogram.h"
|
||||
#include "base/sequenced_task_runner.h"
|
||||
#include "base/strings/stringprintf.h"
|
||||
#include "base/synchronization/lock.h"
|
||||
#include "base/threading/thread.h"
|
||||
#include "base/threading/thread_checker.h"
|
||||
#include "base/time/time.h"
|
||||
#include "content/public/browser/browser_thread.h"
|
||||
#include "media/base/bind_to_loop.h"
|
||||
#include "media/base/video_frame.h"
|
||||
#include "media/video/capture/video_capture_types.h"
|
||||
#include "ui/gfx/rect.h"
|
||||
|
||||
namespace content {
|
||||
|
||||
namespace {
|
||||
|
||||
void DeleteCaptureMachineOnUIThread(
|
||||
scoped_ptr<VideoCaptureMachine> capture_machine) {
|
||||
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
|
||||
if (capture_machine) {
|
||||
capture_machine->Stop();
|
||||
capture_machine.reset();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ThreadSafeCaptureOracle::ThreadSafeCaptureOracle(
|
||||
scoped_ptr<media::VideoCaptureDevice::Client> client,
|
||||
scoped_ptr<VideoCaptureOracle> oracle,
|
||||
const gfx::Size& capture_size,
|
||||
int frame_rate)
|
||||
: client_(client.Pass()),
|
||||
oracle_(oracle.Pass()),
|
||||
capture_size_(capture_size),
|
||||
frame_rate_(frame_rate) {}
|
||||
|
||||
ThreadSafeCaptureOracle::~ThreadSafeCaptureOracle() {}
|
||||
|
||||
bool ThreadSafeCaptureOracle::ObserveEventAndDecideCapture(
|
||||
VideoCaptureOracle::Event event,
|
||||
base::Time event_time,
|
||||
scoped_refptr<media::VideoFrame>* storage,
|
||||
CaptureFrameCallback* callback) {
|
||||
base::AutoLock guard(lock_);
|
||||
|
||||
if (!client_)
|
||||
return false; // Capture is stopped.
|
||||
|
||||
scoped_refptr<media::VideoCaptureDevice::Client::Buffer> output_buffer =
|
||||
client_->ReserveOutputBuffer(media::VideoFrame::I420, capture_size_);
|
||||
const bool should_capture =
|
||||
oracle_->ObserveEventAndDecideCapture(event, event_time);
|
||||
const bool content_is_dirty =
|
||||
(event == VideoCaptureOracle::kCompositorUpdate ||
|
||||
event == VideoCaptureOracle::kSoftwarePaint);
|
||||
const char* event_name =
|
||||
(event == VideoCaptureOracle::kTimerPoll ? "poll" :
|
||||
(event == VideoCaptureOracle::kCompositorUpdate ? "gpu" :
|
||||
"paint"));
|
||||
|
||||
// Consider the various reasons not to initiate a capture.
|
||||
if (should_capture && !output_buffer) {
|
||||
TRACE_EVENT_INSTANT1("mirroring",
|
||||
"EncodeLimited",
|
||||
TRACE_EVENT_SCOPE_THREAD,
|
||||
"trigger",
|
||||
event_name);
|
||||
return false;
|
||||
} else if (!should_capture && output_buffer) {
|
||||
if (content_is_dirty) {
|
||||
// This is a normal and acceptable way to drop a frame. We've hit our
|
||||
// capture rate limit: for example, the content is animating at 60fps but
|
||||
// we're capturing at 30fps.
|
||||
TRACE_EVENT_INSTANT1("mirroring", "FpsRateLimited",
|
||||
TRACE_EVENT_SCOPE_THREAD,
|
||||
"trigger", event_name);
|
||||
}
|
||||
return false;
|
||||
} else if (!should_capture && !output_buffer) {
|
||||
// We decided not to capture, but we wouldn't have been able to if we wanted
|
||||
// to because no output buffer was available.
|
||||
TRACE_EVENT_INSTANT1("mirroring", "NearlyEncodeLimited",
|
||||
TRACE_EVENT_SCOPE_THREAD,
|
||||
"trigger", event_name);
|
||||
return false;
|
||||
}
|
||||
int frame_number = oracle_->RecordCapture();
|
||||
TRACE_EVENT_ASYNC_BEGIN2("mirroring", "Capture", output_buffer.get(),
|
||||
"frame_number", frame_number,
|
||||
"trigger", event_name);
|
||||
*callback = base::Bind(&ThreadSafeCaptureOracle::DidCaptureFrame,
|
||||
this,
|
||||
output_buffer,
|
||||
frame_number);
|
||||
*storage = media::VideoFrame::WrapExternalPackedMemory(
|
||||
media::VideoFrame::I420,
|
||||
capture_size_,
|
||||
gfx::Rect(capture_size_),
|
||||
capture_size_,
|
||||
static_cast<uint8*>(output_buffer->data()),
|
||||
output_buffer->size(),
|
||||
base::SharedMemory::NULLHandle(),
|
||||
base::TimeDelta(),
|
||||
base::Closure());
|
||||
return true;
|
||||
}
|
||||
|
||||
void ThreadSafeCaptureOracle::Stop() {
|
||||
base::AutoLock guard(lock_);
|
||||
client_.reset();
|
||||
}
|
||||
|
||||
void ThreadSafeCaptureOracle::ReportError() {
|
||||
base::AutoLock guard(lock_);
|
||||
if (client_)
|
||||
client_->OnError();
|
||||
}
|
||||
|
||||
void ThreadSafeCaptureOracle::DidCaptureFrame(
|
||||
scoped_refptr<media::VideoCaptureDevice::Client::Buffer> buffer,
|
||||
int frame_number,
|
||||
base::Time timestamp,
|
||||
bool success) {
|
||||
base::AutoLock guard(lock_);
|
||||
TRACE_EVENT_ASYNC_END2("mirroring", "Capture", buffer.get(),
|
||||
"success", success,
|
||||
"timestamp", timestamp.ToInternalValue());
|
||||
|
||||
if (!client_)
|
||||
return; // Capture is stopped.
|
||||
|
||||
if (success) {
|
||||
if (oracle_->CompleteCapture(frame_number, timestamp)) {
|
||||
client_->OnIncomingCapturedBuffer(buffer,
|
||||
media::VideoFrame::I420,
|
||||
capture_size_,
|
||||
timestamp,
|
||||
frame_rate_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VideoCaptureDeviceImpl::AllocateAndStart(
|
||||
const media::VideoCaptureParams& params,
|
||||
scoped_ptr<media::VideoCaptureDevice::Client> client) {
|
||||
DCHECK(thread_checker_.CalledOnValidThread());
|
||||
|
||||
if (state_ != kIdle) {
|
||||
DVLOG(1) << "Allocate() invoked when not in state Idle.";
|
||||
return;
|
||||
}
|
||||
|
||||
if (params.requested_format.frame_rate <= 0) {
|
||||
DVLOG(1) << "invalid frame_rate: " << params.requested_format.frame_rate;
|
||||
client->OnError();
|
||||
return;
|
||||
}
|
||||
|
||||
// Frame dimensions must each be a positive, even integer, since the client
|
||||
// wants (or will convert to) YUV420.
|
||||
gfx::Size frame_size(MakeEven(params.requested_format.frame_size.width()),
|
||||
MakeEven(params.requested_format.frame_size.height()));
|
||||
if (frame_size.width() < kMinFrameWidth ||
|
||||
frame_size.height() < kMinFrameHeight) {
|
||||
DVLOG(1) << "invalid frame size: " << frame_size.ToString();
|
||||
client->OnError();
|
||||
return;
|
||||
}
|
||||
|
||||
base::TimeDelta capture_period = base::TimeDelta::FromMicroseconds(
|
||||
1000000.0 / params.requested_format.frame_rate + 0.5);
|
||||
|
||||
scoped_ptr<VideoCaptureOracle> oracle(
|
||||
new VideoCaptureOracle(capture_period,
|
||||
kAcceleratedSubscriberIsSupported));
|
||||
oracle_proxy_ =
|
||||
new ThreadSafeCaptureOracle(client.Pass(),
|
||||
oracle.Pass(),
|
||||
frame_size,
|
||||
params.requested_format.frame_rate);
|
||||
|
||||
// Starts the capture machine asynchronously.
|
||||
BrowserThread::PostTaskAndReplyWithResult(
|
||||
BrowserThread::UI, FROM_HERE,
|
||||
base::Bind(&VideoCaptureMachine::Start,
|
||||
base::Unretained(capture_machine_.get()),
|
||||
oracle_proxy_),
|
||||
base::Bind(&VideoCaptureDeviceImpl::CaptureStarted,
|
||||
AsWeakPtr()));
|
||||
|
||||
TransitionStateTo(kCapturing);
|
||||
}
|
||||
|
||||
void VideoCaptureDeviceImpl::StopAndDeAllocate() {
|
||||
DCHECK(thread_checker_.CalledOnValidThread());
|
||||
|
||||
if (state_ != kCapturing)
|
||||
return;
|
||||
|
||||
oracle_proxy_->Stop();
|
||||
oracle_proxy_ = NULL;
|
||||
|
||||
TransitionStateTo(kIdle);
|
||||
|
||||
// Stops the capture machine asynchronously.
|
||||
BrowserThread::PostTask(
|
||||
BrowserThread::UI, FROM_HERE, base::Bind(
|
||||
&VideoCaptureMachine::Stop,
|
||||
base::Unretained(capture_machine_.get())));
|
||||
}
|
||||
|
||||
void VideoCaptureDeviceImpl::CaptureStarted(bool success) {
|
||||
DCHECK(thread_checker_.CalledOnValidThread());
|
||||
if (!success) {
|
||||
DVLOG(1) << "Failed to start capture machine.";
|
||||
Error();
|
||||
}
|
||||
}
|
||||
|
||||
VideoCaptureDeviceImpl::VideoCaptureDeviceImpl(
|
||||
scoped_ptr<VideoCaptureMachine> capture_machine)
|
||||
: state_(kIdle),
|
||||
capture_machine_(capture_machine.Pass()) {}
|
||||
|
||||
VideoCaptureDeviceImpl::~VideoCaptureDeviceImpl() {
|
||||
// If capture_machine is not NULL, then we need to return to the UI thread to
|
||||
// safely stop the capture machine.
|
||||
if (capture_machine_) {
|
||||
BrowserThread::PostTask(
|
||||
BrowserThread::UI, FROM_HERE, base::Bind(
|
||||
&DeleteCaptureMachineOnUIThread, base::Passed(&capture_machine_)));
|
||||
}
|
||||
DVLOG(1) << "VideoCaptureDeviceImpl@" << this << " destroying.";
|
||||
}
|
||||
|
||||
void VideoCaptureDeviceImpl::TransitionStateTo(State next_state) {
|
||||
DCHECK(thread_checker_.CalledOnValidThread());
|
||||
|
||||
#ifndef NDEBUG
|
||||
static const char* kStateNames[] = {
|
||||
"Idle", "Allocated", "Capturing", "Error"
|
||||
};
|
||||
DVLOG(1) << "State change: " << kStateNames[state_]
|
||||
<< " --> " << kStateNames[next_state];
|
||||
#endif
|
||||
|
||||
state_ = next_state;
|
||||
}
|
||||
|
||||
void VideoCaptureDeviceImpl::Error() {
|
||||
DCHECK(thread_checker_.CalledOnValidThread());
|
||||
|
||||
if (state_ == kIdle)
|
||||
return;
|
||||
|
||||
if (oracle_proxy_)
|
||||
oracle_proxy_->ReportError();
|
||||
|
||||
StopAndDeAllocate();
|
||||
TransitionStateTo(kError);
|
||||
}
|
||||
|
||||
} // namespace content
|
182
content/browser/renderer_host/media/video_capture_device_impl.h
Normal file
182
content/browser/renderer_host/media/video_capture_device_impl.h
Normal file
@ -0,0 +1,182 @@
|
||||
// Copyright 2013 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.
|
||||
|
||||
#ifndef CONTENT_BROWSER_RENDERER_HOST_VIDEO_CAPTURE_DEVICE_IMPL_H_
|
||||
#define CONTENT_BROWSER_RENDERER_HOST_VIDEO_CAPTURE_DEVICE_IMPL_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "base/memory/scoped_ptr.h"
|
||||
#include "base/memory/weak_ptr.h"
|
||||
#include "base/threading/thread.h"
|
||||
#include "base/threading/thread_checker.h"
|
||||
#include "content/browser/renderer_host/media/video_capture_oracle.h"
|
||||
#include "content/common/content_export.h"
|
||||
#include "media/video/capture/video_capture_device.h"
|
||||
|
||||
namespace content {
|
||||
|
||||
const int kMinFrameWidth = 2;
|
||||
const int kMinFrameHeight = 2;
|
||||
|
||||
// Returns the nearest even integer closer to zero.
|
||||
template<typename IntType>
|
||||
IntType MakeEven(IntType x) {
|
||||
return x & static_cast<IntType>(-2);
|
||||
}
|
||||
|
||||
// TODO(nick): Remove this once frame subscription is supported on Aura and
|
||||
// Linux.
|
||||
#if (defined(OS_WIN) || defined(OS_MACOSX)) || defined(USE_AURA)
|
||||
const bool kAcceleratedSubscriberIsSupported = true;
|
||||
#else
|
||||
const bool kAcceleratedSubscriberIsSupported = false;
|
||||
#endif
|
||||
|
||||
class VideoCaptureMachine;
|
||||
|
||||
// Thread-safe, refcounted proxy to the VideoCaptureOracle. This proxy wraps
|
||||
// the VideoCaptureOracle, which decides which frames to capture, and a
|
||||
// VideoCaptureDevice::Client, which allocates and receives the captured
|
||||
// frames, in a lock to synchronize state between the two.
|
||||
class ThreadSafeCaptureOracle
|
||||
: public base::RefCountedThreadSafe<ThreadSafeCaptureOracle> {
|
||||
public:
|
||||
ThreadSafeCaptureOracle(scoped_ptr<media::VideoCaptureDevice::Client> client,
|
||||
scoped_ptr<VideoCaptureOracle> oracle,
|
||||
const gfx::Size& capture_size,
|
||||
int frame_rate);
|
||||
|
||||
// Called when a captured frame is available or an error has occurred.
|
||||
// If |success| is true then the frame provided is valid and |timestamp|
|
||||
// indicates when the frame was painted.
|
||||
// If |success| is false, both the frame provided and |timestamp| are invalid.
|
||||
typedef base::Callback<void(base::Time timestamp, bool success)>
|
||||
CaptureFrameCallback;
|
||||
|
||||
bool ObserveEventAndDecideCapture(VideoCaptureOracle::Event event,
|
||||
base::Time event_time,
|
||||
scoped_refptr<media::VideoFrame>* storage,
|
||||
CaptureFrameCallback* callback);
|
||||
|
||||
base::TimeDelta capture_period() const {
|
||||
return oracle_->capture_period();
|
||||
}
|
||||
|
||||
// Stop new captures from happening (but doesn't forget the client).
|
||||
void Stop();
|
||||
|
||||
// Signal an error to the client.
|
||||
void ReportError();
|
||||
|
||||
private:
|
||||
friend class base::RefCountedThreadSafe<ThreadSafeCaptureOracle>;
|
||||
virtual ~ThreadSafeCaptureOracle();
|
||||
|
||||
// Callback invoked on completion of all captures.
|
||||
void DidCaptureFrame(
|
||||
scoped_refptr<media::VideoCaptureDevice::Client::Buffer> buffer,
|
||||
int frame_number,
|
||||
base::Time timestamp,
|
||||
bool success);
|
||||
// Protects everything below it.
|
||||
base::Lock lock_;
|
||||
|
||||
// Recipient of our capture activity.
|
||||
scoped_ptr<media::VideoCaptureDevice::Client> client_;
|
||||
|
||||
// Makes the decision to capture a frame.
|
||||
const scoped_ptr<VideoCaptureOracle> oracle_;
|
||||
|
||||
// The current capturing resolution and frame rate.
|
||||
const gfx::Size capture_size_;
|
||||
const int frame_rate_;
|
||||
};
|
||||
|
||||
// Keeps track of the video capture source frames and executes copying on the
|
||||
// UI BrowserThread.
|
||||
class VideoCaptureMachine {
|
||||
public:
|
||||
VideoCaptureMachine() : started_(false) {}
|
||||
virtual ~VideoCaptureMachine() {}
|
||||
|
||||
// This should only be checked on the UI thread.
|
||||
bool started() const { return started_; }
|
||||
|
||||
// Starts capturing. Returns true if succeeded.
|
||||
// Must be run on the UI BrowserThread.
|
||||
virtual bool Start(
|
||||
const scoped_refptr<ThreadSafeCaptureOracle>& oracle_proxy) = 0;
|
||||
|
||||
// Stops capturing. Must be run on the UI BrowserThread.
|
||||
virtual void Stop() = 0;
|
||||
|
||||
protected:
|
||||
bool started_;
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(VideoCaptureMachine);
|
||||
};
|
||||
|
||||
// The "meat" of the video capture implementation.
|
||||
//
|
||||
// Separating this from the "shell classes" WebContentsVideoCaptureDevice and
|
||||
// BrowserCompositorCaptureDevice allows safe destruction without needing to
|
||||
// block any threads (e.g., the IO BrowserThread), as well as code sharing.
|
||||
//
|
||||
// VideoCaptureDeviceImpl manages a simple state machine and the pipeline (see
|
||||
// notes at top of this file). It times the start of successive
|
||||
// captures and facilitates the processing of each through the stages of the
|
||||
// pipeline.
|
||||
class CONTENT_EXPORT VideoCaptureDeviceImpl
|
||||
: public base::SupportsWeakPtr<VideoCaptureDeviceImpl> {
|
||||
public:
|
||||
VideoCaptureDeviceImpl(scoped_ptr<VideoCaptureMachine> capture_machine);
|
||||
virtual ~VideoCaptureDeviceImpl();
|
||||
|
||||
// Asynchronous requests to change VideoCaptureDeviceImpl state.
|
||||
void AllocateAndStart(const media::VideoCaptureParams& params,
|
||||
scoped_ptr<media::VideoCaptureDevice::Client> client);
|
||||
void StopAndDeAllocate();
|
||||
|
||||
private:
|
||||
// Flag indicating current state.
|
||||
enum State {
|
||||
kIdle,
|
||||
kCapturing,
|
||||
kError
|
||||
};
|
||||
|
||||
void TransitionStateTo(State next_state);
|
||||
|
||||
// Called on the IO thread in response to StartCaptureMachine().
|
||||
// |success| is true if capture machine succeeded to start.
|
||||
void CaptureStarted(bool success);
|
||||
|
||||
// Stops capturing and notifies client_ of an error state.
|
||||
void Error();
|
||||
|
||||
// Tracks that all activity occurs on the media stream manager's thread.
|
||||
base::ThreadChecker thread_checker_;
|
||||
|
||||
// Current lifecycle state.
|
||||
State state_;
|
||||
|
||||
// Tracks the CaptureMachine that's doing work on our behalf on the UI thread.
|
||||
// This value should never be dereferenced by this class, other than to
|
||||
// create and destroy it on the UI thread.
|
||||
scoped_ptr<VideoCaptureMachine> capture_machine_;
|
||||
|
||||
// Our thread-safe capture oracle which serves as the gateway to the video
|
||||
// capture pipeline. Besides the WCVCD itself, it is the only component of the
|
||||
// system with direct access to |client_|.
|
||||
scoped_refptr<ThreadSafeCaptureOracle> oracle_proxy_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(VideoCaptureDeviceImpl);
|
||||
};
|
||||
|
||||
|
||||
} // namespace content
|
||||
|
||||
#endif // CONTENT_BROWSER_RENDERER_HOST_VIDEO_CAPTURE_DEVICE_IMPL_H_
|
@ -25,6 +25,9 @@
|
||||
|
||||
#if defined(ENABLE_SCREEN_CAPTURE)
|
||||
#include "content/browser/renderer_host/media/desktop_capture_device.h"
|
||||
#if defined(OS_CHROMEOS)
|
||||
#include "content/browser/renderer_host/media/desktop_capture_device_ash.h"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
namespace content {
|
||||
@ -162,7 +165,12 @@ void VideoCaptureManager::DoStartDeviceOnDeviceThread(
|
||||
#if defined(ENABLE_SCREEN_CAPTURE)
|
||||
DesktopMediaID id = DesktopMediaID::Parse(entry->id);
|
||||
if (id.type != DesktopMediaID::TYPE_NONE) {
|
||||
#if defined(OS_CHROMEOS)
|
||||
// TODO(hshi): enable this path for Ash windows in metro mode.
|
||||
video_capture_device.reset(DesktopCaptureDeviceAsh::Create(id));
|
||||
#else
|
||||
video_capture_device = DesktopCaptureDevice::Create(id);
|
||||
#endif
|
||||
}
|
||||
#endif // defined(ENABLE_SCREEN_CAPTURE)
|
||||
break;
|
||||
|
@ -50,26 +50,18 @@
|
||||
|
||||
#include "content/browser/renderer_host/media/web_contents_video_capture_device.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <list>
|
||||
#include <string>
|
||||
|
||||
#include "base/basictypes.h"
|
||||
#include "base/bind.h"
|
||||
#include "base/callback_forward.h"
|
||||
#include "base/callback_helpers.h"
|
||||
#include "base/debug/trace_event.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/memory/scoped_ptr.h"
|
||||
#include "base/memory/weak_ptr.h"
|
||||
#include "base/message_loop/message_loop_proxy.h"
|
||||
#include "base/metrics/histogram.h"
|
||||
#include "base/sequenced_task_runner.h"
|
||||
#include "base/strings/stringprintf.h"
|
||||
#include "base/synchronization/lock.h"
|
||||
#include "base/threading/thread.h"
|
||||
#include "base/threading/thread_checker.h"
|
||||
#include "base/time/time.h"
|
||||
#include "content/browser/renderer_host/media/video_capture_device_impl.h"
|
||||
#include "content/browser/renderer_host/media/video_capture_oracle.h"
|
||||
#include "content/browser/renderer_host/media/web_contents_capture_util.h"
|
||||
#include "content/browser/renderer_host/render_widget_host_impl.h"
|
||||
@ -79,43 +71,19 @@
|
||||
#include "content/public/browser/browser_thread.h"
|
||||
#include "content/public/browser/notification_source.h"
|
||||
#include "content/public/browser/notification_types.h"
|
||||
#include "content/public/browser/render_process_host.h"
|
||||
#include "content/public/browser/render_view_host.h"
|
||||
#include "content/public/browser/render_widget_host_view.h"
|
||||
#include "content/public/browser/web_contents.h"
|
||||
#include "content/public/browser/web_contents_observer.h"
|
||||
#include "media/base/bind_to_loop.h"
|
||||
#include "media/base/video_frame.h"
|
||||
#include "media/base/video_util.h"
|
||||
#include "media/base/yuv_convert.h"
|
||||
#include "media/video/capture/video_capture_types.h"
|
||||
#include "skia/ext/image_operations.h"
|
||||
#include "third_party/skia/include/core/SkBitmap.h"
|
||||
#include "third_party/skia/include/core/SkColor.h"
|
||||
#include "ui/gfx/rect.h"
|
||||
#include "ui/gfx/skia_util.h"
|
||||
|
||||
namespace content {
|
||||
|
||||
namespace {
|
||||
|
||||
const int kMinFrameWidth = 2;
|
||||
const int kMinFrameHeight = 2;
|
||||
|
||||
// TODO(nick): Remove this once frame subscription is supported on Aura and
|
||||
// Linux.
|
||||
#if (defined(OS_WIN) || defined(OS_MACOSX)) || defined(USE_AURA)
|
||||
const bool kAcceleratedSubscriberIsSupported = true;
|
||||
#else
|
||||
const bool kAcceleratedSubscriberIsSupported = false;
|
||||
#endif
|
||||
|
||||
// Returns the nearest even integer closer to zero.
|
||||
template<typename IntType>
|
||||
IntType MakeEven(IntType x) {
|
||||
return x & static_cast<IntType>(-2);
|
||||
}
|
||||
|
||||
// Compute a letterbox region, aligned to even coordinates.
|
||||
gfx::Rect ComputeYV12LetterboxRegion(const gfx::Size& frame_size,
|
||||
const gfx::Size& content_size) {
|
||||
@ -131,57 +99,14 @@ gfx::Rect ComputeYV12LetterboxRegion(const gfx::Size& frame_size,
|
||||
return result;
|
||||
}
|
||||
|
||||
// Thread-safe, refcounted proxy to the VideoCaptureOracle. This proxy wraps
|
||||
// the VideoCaptureOracle, which decides which frames to capture, and a
|
||||
// VideoCaptureDevice::Client, which allocates and receives the captured
|
||||
// frames, in a lock to synchronize state between the two.
|
||||
class ThreadSafeCaptureOracle
|
||||
: public base::RefCountedThreadSafe<ThreadSafeCaptureOracle> {
|
||||
public:
|
||||
ThreadSafeCaptureOracle(scoped_ptr<media::VideoCaptureDevice::Client> client,
|
||||
scoped_ptr<VideoCaptureOracle> oracle,
|
||||
const gfx::Size& capture_size,
|
||||
int frame_rate);
|
||||
|
||||
bool ObserveEventAndDecideCapture(
|
||||
VideoCaptureOracle::Event event,
|
||||
base::Time event_time,
|
||||
scoped_refptr<media::VideoFrame>* storage,
|
||||
RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback* callback);
|
||||
|
||||
base::TimeDelta capture_period() const {
|
||||
return oracle_->capture_period();
|
||||
}
|
||||
|
||||
// Stop new captures from happening (but doesn't forget the client).
|
||||
void Stop();
|
||||
|
||||
// Signal an error to the client.
|
||||
void ReportError();
|
||||
|
||||
private:
|
||||
friend class base::RefCountedThreadSafe<ThreadSafeCaptureOracle>;
|
||||
virtual ~ThreadSafeCaptureOracle() {}
|
||||
|
||||
// Callback invoked on completion of all captures.
|
||||
void DidCaptureFrame(
|
||||
scoped_refptr<media::VideoCaptureDevice::Client::Buffer> buffer,
|
||||
int frame_number,
|
||||
base::Time timestamp,
|
||||
bool success);
|
||||
// Protects everything below it.
|
||||
base::Lock lock_;
|
||||
|
||||
// Recipient of our capture activity.
|
||||
scoped_ptr<media::VideoCaptureDevice::Client> client_;
|
||||
|
||||
// Makes the decision to capture a frame.
|
||||
const scoped_ptr<VideoCaptureOracle> oracle_;
|
||||
|
||||
// The current capturing resolution and frame rate.
|
||||
const gfx::Size capture_size_;
|
||||
const int frame_rate_;
|
||||
};
|
||||
// Wrapper function to invoke ThreadSafeCaptureOracle::CaptureFrameCallback, is
|
||||
// compatible with RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback.
|
||||
void InvokeCaptureFrameCallback(
|
||||
const ThreadSafeCaptureOracle::CaptureFrameCallback& capture_frame_cb,
|
||||
base::Time timestamp,
|
||||
bool frame_captured) {
|
||||
capture_frame_cb.Run(timestamp, frame_captured);
|
||||
}
|
||||
|
||||
// FrameSubscriber is a proxy to the ThreadSafeCaptureOracle that's compatible
|
||||
// with RenderWidgetHostViewFrameSubscriber. We create one per event type.
|
||||
@ -275,18 +200,18 @@ void RenderVideoFrame(const SkBitmap& input,
|
||||
// implementation is currently asynchronous -- in our case, the "rvh changed"
|
||||
// notification would get posted back to the UI thread and processed later, and
|
||||
// this seems disadvantageous.
|
||||
class CaptureMachine : public WebContentsObserver,
|
||||
public base::SupportsWeakPtr<CaptureMachine> {
|
||||
class WebContentsCaptureMachine
|
||||
: public VideoCaptureMachine,
|
||||
public WebContentsObserver,
|
||||
public base::SupportsWeakPtr<WebContentsCaptureMachine> {
|
||||
public:
|
||||
virtual ~CaptureMachine();
|
||||
WebContentsCaptureMachine(int render_process_id, int render_view_id);
|
||||
virtual ~WebContentsCaptureMachine();
|
||||
|
||||
// Creates a CaptureMachine. Must be run on the UI BrowserThread. Returns
|
||||
// NULL if the indicated render view cannot be found.
|
||||
static scoped_ptr<CaptureMachine> Create(
|
||||
int render_process_id,
|
||||
int render_view_id,
|
||||
const scoped_refptr<base::SequencedTaskRunner>& render_task_runner,
|
||||
const scoped_refptr<ThreadSafeCaptureOracle>& oracle_proxy);
|
||||
// VideoCaptureMachine overrides.
|
||||
virtual bool Start(
|
||||
const scoped_refptr<ThreadSafeCaptureOracle>& oracle_proxy) OVERRIDE;
|
||||
virtual void Stop() OVERRIDE;
|
||||
|
||||
// Starts a copy from the backing store or the composited surface. Must be run
|
||||
// on the UI BrowserThread. |deliver_frame_cb| will be run when the operation
|
||||
@ -328,13 +253,8 @@ class CaptureMachine : public WebContentsObserver,
|
||||
virtual void WebContentsDestroyed(WebContents* web_contents) OVERRIDE;
|
||||
|
||||
private:
|
||||
CaptureMachine(
|
||||
const scoped_refptr<base::SequencedTaskRunner>& render_task_runner,
|
||||
const scoped_refptr<ThreadSafeCaptureOracle>& oracle_proxy);
|
||||
|
||||
// Starts observing the web contents, returning false if lookup fails.
|
||||
bool StartObservingWebContents(int initial_render_process_id,
|
||||
int initial_render_view_id);
|
||||
bool StartObservingWebContents();
|
||||
|
||||
// Helper function to determine the view that we are currently tracking.
|
||||
RenderWidgetHost* GetTarget();
|
||||
@ -360,12 +280,16 @@ class CaptureMachine : public WebContentsObserver,
|
||||
// attached views.
|
||||
void RenewFrameSubscription();
|
||||
|
||||
// The task runner of the thread on which SkBitmap->VideoFrame conversion will
|
||||
// Parameters saved in constructor.
|
||||
const int initial_render_process_id_;
|
||||
const int initial_render_view_id_;
|
||||
|
||||
// A dedicated worker thread on which SkBitmap->VideoFrame conversion will
|
||||
// occur. Only used when this activity cannot be done on the GPU.
|
||||
const scoped_refptr<base::SequencedTaskRunner> render_task_runner_;
|
||||
base::Thread render_thread_;
|
||||
|
||||
// Makes all the decisions about which frames to copy, and how.
|
||||
const scoped_refptr<ThreadSafeCaptureOracle> oracle_proxy_;
|
||||
scoped_refptr<ThreadSafeCaptureOracle> oracle_proxy_;
|
||||
|
||||
// Routing ID of any active fullscreen render widget or MSG_ROUTING_NONE
|
||||
// otherwise.
|
||||
@ -378,7 +302,7 @@ class CaptureMachine : public WebContentsObserver,
|
||||
// oracle, and initiating captures accordingly.
|
||||
scoped_ptr<ContentCaptureSubscription> subscription_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(CaptureMachine);
|
||||
DISALLOW_COPY_AND_ASSIGN(WebContentsCaptureMachine);
|
||||
};
|
||||
|
||||
// Responsible for logging the effective frame rate.
|
||||
@ -401,121 +325,6 @@ class VideoFrameDeliveryLog {
|
||||
DISALLOW_COPY_AND_ASSIGN(VideoFrameDeliveryLog);
|
||||
};
|
||||
|
||||
ThreadSafeCaptureOracle::ThreadSafeCaptureOracle(
|
||||
scoped_ptr<media::VideoCaptureDevice::Client> client,
|
||||
scoped_ptr<VideoCaptureOracle> oracle,
|
||||
const gfx::Size& capture_size,
|
||||
int frame_rate)
|
||||
: client_(client.Pass()),
|
||||
oracle_(oracle.Pass()),
|
||||
capture_size_(capture_size),
|
||||
frame_rate_(frame_rate) {}
|
||||
|
||||
bool ThreadSafeCaptureOracle::ObserveEventAndDecideCapture(
|
||||
VideoCaptureOracle::Event event,
|
||||
base::Time event_time,
|
||||
scoped_refptr<media::VideoFrame>* storage,
|
||||
RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback* callback) {
|
||||
base::AutoLock guard(lock_);
|
||||
|
||||
if (!client_)
|
||||
return false; // Capture is stopped.
|
||||
|
||||
scoped_refptr<media::VideoCaptureDevice::Client::Buffer> output_buffer =
|
||||
client_->ReserveOutputBuffer(media::VideoFrame::I420, capture_size_);
|
||||
const bool should_capture =
|
||||
oracle_->ObserveEventAndDecideCapture(event, event_time);
|
||||
const bool content_is_dirty =
|
||||
(event == VideoCaptureOracle::kCompositorUpdate ||
|
||||
event == VideoCaptureOracle::kSoftwarePaint);
|
||||
const char* event_name =
|
||||
(event == VideoCaptureOracle::kTimerPoll ? "poll" :
|
||||
(event == VideoCaptureOracle::kCompositorUpdate ? "gpu" :
|
||||
"paint"));
|
||||
|
||||
// Consider the various reasons not to initiate a capture.
|
||||
if (should_capture && !output_buffer) {
|
||||
TRACE_EVENT_INSTANT1("mirroring",
|
||||
"EncodeLimited",
|
||||
TRACE_EVENT_SCOPE_THREAD,
|
||||
"trigger",
|
||||
event_name);
|
||||
return false;
|
||||
} else if (!should_capture && output_buffer) {
|
||||
if (content_is_dirty) {
|
||||
// This is a normal and acceptable way to drop a frame. We've hit our
|
||||
// capture rate limit: for example, the content is animating at 60fps but
|
||||
// we're capturing at 30fps.
|
||||
TRACE_EVENT_INSTANT1("mirroring", "FpsRateLimited",
|
||||
TRACE_EVENT_SCOPE_THREAD,
|
||||
"trigger", event_name);
|
||||
}
|
||||
return false;
|
||||
} else if (!should_capture && !output_buffer) {
|
||||
// We decided not to capture, but we wouldn't have been able to if we wanted
|
||||
// to because no output buffer was available.
|
||||
TRACE_EVENT_INSTANT1("mirroring", "NearlyEncodeLimited",
|
||||
TRACE_EVENT_SCOPE_THREAD,
|
||||
"trigger", event_name);
|
||||
return false;
|
||||
}
|
||||
int frame_number = oracle_->RecordCapture();
|
||||
TRACE_EVENT_ASYNC_BEGIN2("mirroring", "Capture", output_buffer.get(),
|
||||
"frame_number", frame_number,
|
||||
"trigger", event_name);
|
||||
|
||||
*callback = base::Bind(&ThreadSafeCaptureOracle::DidCaptureFrame,
|
||||
this,
|
||||
output_buffer,
|
||||
frame_number);
|
||||
*storage = media::VideoFrame::WrapExternalPackedMemory(
|
||||
media::VideoFrame::I420,
|
||||
capture_size_,
|
||||
gfx::Rect(capture_size_),
|
||||
capture_size_,
|
||||
static_cast<uint8*>(output_buffer->data()),
|
||||
output_buffer->size(),
|
||||
base::SharedMemory::NULLHandle(),
|
||||
base::TimeDelta(),
|
||||
base::Closure());
|
||||
return true;
|
||||
}
|
||||
|
||||
void ThreadSafeCaptureOracle::Stop() {
|
||||
base::AutoLock guard(lock_);
|
||||
client_.reset();
|
||||
}
|
||||
|
||||
void ThreadSafeCaptureOracle::ReportError() {
|
||||
base::AutoLock guard(lock_);
|
||||
if (client_)
|
||||
client_->OnError();
|
||||
}
|
||||
|
||||
void ThreadSafeCaptureOracle::DidCaptureFrame(
|
||||
scoped_refptr<media::VideoCaptureDevice::Client::Buffer> buffer,
|
||||
int frame_number,
|
||||
base::Time timestamp,
|
||||
bool success) {
|
||||
base::AutoLock guard(lock_);
|
||||
TRACE_EVENT_ASYNC_END2("mirroring", "Capture", buffer.get(),
|
||||
"success", success,
|
||||
"timestamp", timestamp.ToInternalValue());
|
||||
|
||||
if (!client_)
|
||||
return; // Capture is stopped.
|
||||
|
||||
if (success) {
|
||||
if (oracle_->CompleteCapture(frame_number, timestamp)) {
|
||||
client_->OnIncomingCapturedBuffer(buffer,
|
||||
media::VideoFrame::I420,
|
||||
capture_size_,
|
||||
timestamp,
|
||||
frame_rate_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool FrameSubscriber::ShouldCaptureFrame(
|
||||
base::Time present_time,
|
||||
scoped_refptr<media::VideoFrame>* storage,
|
||||
@ -523,8 +332,12 @@ bool FrameSubscriber::ShouldCaptureFrame(
|
||||
TRACE_EVENT1("mirroring", "FrameSubscriber::ShouldCaptureFrame",
|
||||
"instance", this);
|
||||
|
||||
return oracle_proxy_->ObserveEventAndDecideCapture(event_type_, present_time,
|
||||
storage, deliver_frame_cb);
|
||||
ThreadSafeCaptureOracle::CaptureFrameCallback capture_frame_cb;
|
||||
bool oracle_decision = oracle_proxy_->ObserveEventAndDecideCapture(
|
||||
event_type_, present_time, storage, &capture_frame_cb);
|
||||
|
||||
*deliver_frame_cb = base::Bind(&InvokeCaptureFrameCallback, capture_frame_cb);
|
||||
return oracle_decision;
|
||||
}
|
||||
|
||||
ContentCaptureSubscription::ContentCaptureSubscription(
|
||||
@ -552,7 +365,8 @@ ContentCaptureSubscription::ContentCaptureSubscription(
|
||||
}
|
||||
|
||||
// Subscribe to software paint events. This instance will service these by
|
||||
// reflecting them back to the CaptureMachine via |capture_callback|.
|
||||
// reflecting them back to the WebContentsCaptureMachine via
|
||||
// |capture_callback|.
|
||||
registrar_.Add(
|
||||
this, content::NOTIFICATION_RENDER_WIDGET_HOST_DID_UPDATE_BACKING_STORE,
|
||||
Source<RenderWidgetHost>(&source));
|
||||
@ -734,44 +548,47 @@ void VideoFrameDeliveryLog::ChronicleFrameDelivery(int frame_number) {
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
scoped_ptr<CaptureMachine> CaptureMachine::Create(
|
||||
int render_process_id,
|
||||
int render_view_id,
|
||||
const scoped_refptr<base::SequencedTaskRunner>& render_task_runner,
|
||||
const scoped_refptr<ThreadSafeCaptureOracle>& oracle_proxy) {
|
||||
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
|
||||
DCHECK(render_task_runner.get());
|
||||
DCHECK(oracle_proxy.get());
|
||||
scoped_ptr<CaptureMachine> machine(
|
||||
new CaptureMachine(render_task_runner, oracle_proxy));
|
||||
|
||||
if (!machine->StartObservingWebContents(render_process_id, render_view_id))
|
||||
machine.reset();
|
||||
|
||||
return machine.Pass();
|
||||
}
|
||||
|
||||
CaptureMachine::CaptureMachine(
|
||||
const scoped_refptr<base::SequencedTaskRunner>& render_task_runner,
|
||||
const scoped_refptr<ThreadSafeCaptureOracle>& oracle_proxy)
|
||||
: render_task_runner_(render_task_runner),
|
||||
oracle_proxy_(oracle_proxy),
|
||||
WebContentsCaptureMachine::WebContentsCaptureMachine(int render_process_id,
|
||||
int render_view_id)
|
||||
: initial_render_process_id_(render_process_id),
|
||||
initial_render_view_id_(render_view_id),
|
||||
render_thread_("WebContentsVideo_RenderThread"),
|
||||
fullscreen_widget_id_(MSG_ROUTING_NONE) {}
|
||||
|
||||
CaptureMachine::~CaptureMachine() {
|
||||
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI) ||
|
||||
!BrowserThread::IsMessageLoopValid(BrowserThread::UI));
|
||||
WebContentsCaptureMachine::~WebContentsCaptureMachine() {}
|
||||
|
||||
// Stop observing the web contents.
|
||||
bool WebContentsCaptureMachine::Start(
|
||||
const scoped_refptr<ThreadSafeCaptureOracle>& oracle_proxy) {
|
||||
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
|
||||
DCHECK(!started_);
|
||||
|
||||
DCHECK(oracle_proxy.get());
|
||||
oracle_proxy_ = oracle_proxy;
|
||||
|
||||
if (!render_thread_.Start()) {
|
||||
DVLOG(1) << "Failed to spawn render thread.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!StartObservingWebContents())
|
||||
return false;
|
||||
|
||||
started_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void WebContentsCaptureMachine::Stop() {
|
||||
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
|
||||
subscription_.reset();
|
||||
if (web_contents()) {
|
||||
web_contents()->DecrementCapturerCount();
|
||||
Observe(NULL);
|
||||
}
|
||||
render_thread_.Stop();
|
||||
started_ = false;
|
||||
}
|
||||
|
||||
void CaptureMachine::Capture(
|
||||
void WebContentsCaptureMachine::Capture(
|
||||
const base::Time& start_time,
|
||||
const scoped_refptr<media::VideoFrame>& target,
|
||||
const RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback&
|
||||
@ -806,27 +623,25 @@ void CaptureMachine::Capture(
|
||||
// backing store are not accessible.
|
||||
rwh->GetSnapshotFromRenderer(
|
||||
gfx::Rect(),
|
||||
base::Bind(&CaptureMachine::DidCopyFromBackingStore, this->AsWeakPtr(),
|
||||
start_time, target, deliver_frame_cb));
|
||||
base::Bind(&WebContentsCaptureMachine::DidCopyFromBackingStore,
|
||||
this->AsWeakPtr(), start_time, target, deliver_frame_cb));
|
||||
} else if (view->CanCopyToVideoFrame()) {
|
||||
view->CopyFromCompositingSurfaceToVideoFrame(
|
||||
gfx::Rect(view_size),
|
||||
target,
|
||||
base::Bind(&CaptureMachine::DidCopyFromCompositingSurfaceToVideoFrame,
|
||||
base::Bind(&WebContentsCaptureMachine::
|
||||
DidCopyFromCompositingSurfaceToVideoFrame,
|
||||
this->AsWeakPtr(), start_time, deliver_frame_cb));
|
||||
} else {
|
||||
rwh->CopyFromBackingStore(
|
||||
gfx::Rect(),
|
||||
fitted_size, // Size here is a request not always honored.
|
||||
base::Bind(&CaptureMachine::DidCopyFromBackingStore, this->AsWeakPtr(),
|
||||
start_time, target, deliver_frame_cb));
|
||||
base::Bind(&WebContentsCaptureMachine::DidCopyFromBackingStore,
|
||||
this->AsWeakPtr(), start_time, target, deliver_frame_cb));
|
||||
}
|
||||
}
|
||||
|
||||
bool CaptureMachine::StartObservingWebContents(int initial_render_process_id,
|
||||
int initial_render_view_id) {
|
||||
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
|
||||
|
||||
bool WebContentsCaptureMachine::StartObservingWebContents() {
|
||||
// Look-up the RenderViewHost and, from that, the WebContents that wraps it.
|
||||
// If successful, begin observing the WebContents instance.
|
||||
//
|
||||
@ -837,11 +652,11 @@ bool CaptureMachine::StartObservingWebContents(int initial_render_process_id,
|
||||
// a bit of indirection across threads. It's easily possible that, in the
|
||||
// meantime, the original RenderView may have gone away.
|
||||
RenderViewHost* const rvh =
|
||||
RenderViewHost::FromID(initial_render_process_id,
|
||||
initial_render_view_id);
|
||||
RenderViewHost::FromID(initial_render_process_id_,
|
||||
initial_render_view_id_);
|
||||
DVLOG_IF(1, !rvh) << "RenderViewHost::FromID("
|
||||
<< initial_render_process_id << ", "
|
||||
<< initial_render_view_id << ") returned NULL.";
|
||||
<< initial_render_process_id_ << ", "
|
||||
<< initial_render_view_id_ << ") returned NULL.";
|
||||
Observe(rvh ? WebContents::FromRenderViewHost(rvh) : NULL);
|
||||
|
||||
WebContentsImpl* contents = static_cast<WebContentsImpl*>(web_contents());
|
||||
@ -856,7 +671,8 @@ bool CaptureMachine::StartObservingWebContents(int initial_render_process_id,
|
||||
return false;
|
||||
}
|
||||
|
||||
void CaptureMachine::WebContentsDestroyed(WebContents* web_contents) {
|
||||
void WebContentsCaptureMachine::WebContentsDestroyed(
|
||||
WebContents* web_contents) {
|
||||
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
|
||||
|
||||
subscription_.reset();
|
||||
@ -864,7 +680,7 @@ void CaptureMachine::WebContentsDestroyed(WebContents* web_contents) {
|
||||
oracle_proxy_->ReportError();
|
||||
}
|
||||
|
||||
RenderWidgetHost* CaptureMachine::GetTarget() {
|
||||
RenderWidgetHost* WebContentsCaptureMachine::GetTarget() {
|
||||
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
|
||||
if (!web_contents())
|
||||
return NULL;
|
||||
@ -881,7 +697,7 @@ RenderWidgetHost* CaptureMachine::GetTarget() {
|
||||
return rwh;
|
||||
}
|
||||
|
||||
void CaptureMachine::DidCopyFromBackingStore(
|
||||
void WebContentsCaptureMachine::DidCopyFromBackingStore(
|
||||
const base::Time& start_time,
|
||||
const scoped_refptr<media::VideoFrame>& target,
|
||||
const RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback&
|
||||
@ -895,7 +711,7 @@ void CaptureMachine::DidCopyFromBackingStore(
|
||||
UMA_HISTOGRAM_TIMES("TabCapture.CopyTimeBitmap", now - start_time);
|
||||
TRACE_EVENT_ASYNC_STEP_INTO0("mirroring", "Capture", target.get(),
|
||||
"Render");
|
||||
render_task_runner_->PostTask(FROM_HERE, base::Bind(
|
||||
render_thread_.message_loop_proxy()->PostTask(FROM_HERE, base::Bind(
|
||||
&RenderVideoFrame, bitmap, target,
|
||||
base::Bind(deliver_frame_cb, start_time)));
|
||||
} else {
|
||||
@ -905,7 +721,7 @@ void CaptureMachine::DidCopyFromBackingStore(
|
||||
}
|
||||
}
|
||||
|
||||
void CaptureMachine::DidCopyFromCompositingSurfaceToVideoFrame(
|
||||
void WebContentsCaptureMachine::DidCopyFromCompositingSurfaceToVideoFrame(
|
||||
const base::Time& start_time,
|
||||
const RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback&
|
||||
deliver_frame_cb,
|
||||
@ -922,7 +738,7 @@ void CaptureMachine::DidCopyFromCompositingSurfaceToVideoFrame(
|
||||
deliver_frame_cb.Run(start_time, success);
|
||||
}
|
||||
|
||||
void CaptureMachine::RenewFrameSubscription() {
|
||||
void WebContentsCaptureMachine::RenewFrameSubscription() {
|
||||
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
|
||||
|
||||
// Always destroy the old subscription before creating a new one.
|
||||
@ -933,240 +749,15 @@ void CaptureMachine::RenewFrameSubscription() {
|
||||
return;
|
||||
|
||||
subscription_.reset(new ContentCaptureSubscription(*rwh, oracle_proxy_,
|
||||
base::Bind(&CaptureMachine::Capture, this->AsWeakPtr())));
|
||||
}
|
||||
|
||||
void DeleteCaptureMachineOnUIThread(
|
||||
scoped_ptr<CaptureMachine> capture_machine) {
|
||||
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
|
||||
capture_machine.reset();
|
||||
base::Bind(&WebContentsCaptureMachine::Capture, this->AsWeakPtr())));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// The "meat" of the video capture implementation, which is a ref-counted class.
|
||||
// Separating this from the "shell class" WebContentsVideoCaptureDevice allows
|
||||
// safe destruction without needing to block any threads (e.g., the IO
|
||||
// BrowserThread).
|
||||
//
|
||||
// WebContentsVideoCaptureDevice::Impl manages a simple state machine and the
|
||||
// pipeline (see notes at top of this file). It times the start of successive
|
||||
// captures and facilitates the processing of each through the stages of the
|
||||
// pipeline.
|
||||
class WebContentsVideoCaptureDevice::Impl : public base::SupportsWeakPtr<Impl> {
|
||||
public:
|
||||
|
||||
Impl(int render_process_id, int render_view_id);
|
||||
virtual ~Impl();
|
||||
|
||||
// Asynchronous requests to change WebContentsVideoCaptureDevice::Impl state.
|
||||
void AllocateAndStart(const media::VideoCaptureParams& params,
|
||||
scoped_ptr<media::VideoCaptureDevice::Client> client);
|
||||
void StopAndDeAllocate();
|
||||
|
||||
private:
|
||||
|
||||
// Flag indicating current state.
|
||||
enum State {
|
||||
kIdle,
|
||||
kCapturing,
|
||||
kError
|
||||
};
|
||||
|
||||
void TransitionStateTo(State next_state);
|
||||
|
||||
// Stops capturing and notifies client_ of an error state.
|
||||
void Error();
|
||||
|
||||
// Called in response to CaptureMachine::Create that runs on the UI thread.
|
||||
// It will assign the capture machine to the Impl class if it still exists
|
||||
// otherwise it will post a task to delete CaptureMachine on the UI thread.
|
||||
static void AssignCaptureMachine(
|
||||
base::WeakPtr<WebContentsVideoCaptureDevice::Impl> impl,
|
||||
scoped_ptr<CaptureMachine> capture_machine);
|
||||
|
||||
// Tracks that all activity occurs on the media stream manager's thread.
|
||||
base::ThreadChecker thread_checker_;
|
||||
|
||||
// These values identify the starting view that will be captured. After
|
||||
// capture starts, the target view IDs will change as navigation occurs, and
|
||||
// so these values are not relevant after the initial bootstrapping.
|
||||
const int initial_render_process_id_;
|
||||
const int initial_render_view_id_;
|
||||
|
||||
// Current lifecycle state.
|
||||
State state_;
|
||||
|
||||
// A dedicated worker thread for doing image operations. Started/joined here,
|
||||
// but used by the CaptureMachine.
|
||||
base::Thread render_thread_;
|
||||
|
||||
// Tracks the CaptureMachine that's doing work on our behalf on the UI thread.
|
||||
// This value should never be dereferenced by this class, other than to
|
||||
// create and destroy it on the UI thread.
|
||||
scoped_ptr<CaptureMachine> capture_machine_;
|
||||
|
||||
// Our thread-safe capture oracle which serves as the gateway to the video
|
||||
// capture pipeline. Besides the WCVCD itself, it is the only component of the
|
||||
// system with direct access to |client_|.
|
||||
scoped_refptr<ThreadSafeCaptureOracle> oracle_proxy_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(Impl);
|
||||
};
|
||||
|
||||
WebContentsVideoCaptureDevice::Impl::Impl(int render_process_id,
|
||||
int render_view_id)
|
||||
: initial_render_process_id_(render_process_id),
|
||||
initial_render_view_id_(render_view_id),
|
||||
state_(kIdle),
|
||||
render_thread_("WebContentsVideo_RenderThread") {}
|
||||
|
||||
void WebContentsVideoCaptureDevice::Impl::AllocateAndStart(
|
||||
const media::VideoCaptureParams& params,
|
||||
scoped_ptr<VideoCaptureDevice::Client> client) {
|
||||
DCHECK(thread_checker_.CalledOnValidThread());
|
||||
|
||||
if (state_ != kIdle) {
|
||||
DVLOG(1) << "Allocate() invoked when not in state Idle.";
|
||||
return;
|
||||
}
|
||||
|
||||
if (params.requested_format.frame_rate <= 0) {
|
||||
DVLOG(1) << "invalid frame_rate: " << params.requested_format.frame_rate;
|
||||
client->OnError();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!render_thread_.Start()) {
|
||||
DVLOG(1) << "Failed to spawn render thread.";
|
||||
client->OnError();
|
||||
return;
|
||||
}
|
||||
|
||||
// Frame dimensions must each be a positive, even integer, since the client
|
||||
// wants (or will convert to) YUV420.
|
||||
gfx::Size frame_size(MakeEven(params.requested_format.frame_size.width()),
|
||||
MakeEven(params.requested_format.frame_size.height()));
|
||||
if (frame_size.width() < kMinFrameWidth ||
|
||||
frame_size.height() < kMinFrameHeight) {
|
||||
DVLOG(1) << "invalid frame size: " << frame_size.ToString();
|
||||
client->OnError();
|
||||
return;
|
||||
}
|
||||
|
||||
base::TimeDelta capture_period = base::TimeDelta::FromMicroseconds(
|
||||
1000000.0 / params.requested_format.frame_rate + 0.5);
|
||||
|
||||
scoped_ptr<VideoCaptureOracle> oracle(
|
||||
new VideoCaptureOracle(capture_period,
|
||||
kAcceleratedSubscriberIsSupported));
|
||||
oracle_proxy_ =
|
||||
new ThreadSafeCaptureOracle(client.Pass(),
|
||||
oracle.Pass(),
|
||||
frame_size,
|
||||
params.requested_format.frame_rate);
|
||||
|
||||
// Allocates the CaptureMachine. The CaptureMachine will be tracking render
|
||||
// view swapping over its lifetime, and we don't want to lose our reference to
|
||||
// the current render view by starting over with the stale
|
||||
// |initial_render_view_id_|.
|
||||
DCHECK(!capture_machine_.get());
|
||||
BrowserThread::PostTaskAndReplyWithResult(
|
||||
BrowserThread::UI, FROM_HERE,
|
||||
base::Bind(&CaptureMachine::Create,
|
||||
initial_render_process_id_,
|
||||
initial_render_view_id_,
|
||||
render_thread_.message_loop_proxy(), oracle_proxy_),
|
||||
base::Bind(&Impl::AssignCaptureMachine, AsWeakPtr()));
|
||||
|
||||
TransitionStateTo(kCapturing);
|
||||
}
|
||||
|
||||
// static
|
||||
void WebContentsVideoCaptureDevice::Impl::AssignCaptureMachine(
|
||||
base::WeakPtr<WebContentsVideoCaptureDevice::Impl> impl,
|
||||
scoped_ptr<CaptureMachine> capture_machine) {
|
||||
DCHECK(!impl.get() || impl->thread_checker_.CalledOnValidThread());
|
||||
|
||||
if (!impl.get()) {
|
||||
// If WCVD::Impl was destroyed before we got back on it's thread and
|
||||
// capture_machine is not NULL, then we need to return to the UI thread to
|
||||
// safely cleanup the CaptureMachine.
|
||||
if (capture_machine) {
|
||||
BrowserThread::PostTask(
|
||||
BrowserThread::UI, FROM_HERE, base::Bind(
|
||||
&DeleteCaptureMachineOnUIThread, base::Passed(&capture_machine)));
|
||||
return;
|
||||
}
|
||||
} else if (!capture_machine) {
|
||||
impl->Error();
|
||||
} else {
|
||||
impl->capture_machine_ = capture_machine.Pass();
|
||||
}
|
||||
}
|
||||
|
||||
void WebContentsVideoCaptureDevice::Impl::StopAndDeAllocate() {
|
||||
DCHECK(thread_checker_.CalledOnValidThread());
|
||||
|
||||
if (state_ != kCapturing) {
|
||||
return;
|
||||
}
|
||||
oracle_proxy_->Stop();
|
||||
oracle_proxy_ = NULL;
|
||||
render_thread_.Stop();
|
||||
|
||||
TransitionStateTo(kIdle);
|
||||
|
||||
// There is still a capture pipeline running that is checking in with the
|
||||
// oracle, and processing captures that are already started in flight. That
|
||||
// pipeline must be shut down asynchronously, on the UI thread.
|
||||
if (capture_machine_) {
|
||||
// The task that is posted to the UI thread might not run if we are shutting
|
||||
// down, so we transfer ownership of CaptureMachine to the closure so that
|
||||
// it is still cleaned up when the closure is deleted.
|
||||
BrowserThread::PostTask(
|
||||
BrowserThread::UI, FROM_HERE, base::Bind(
|
||||
&DeleteCaptureMachineOnUIThread, base::Passed(&capture_machine_)));
|
||||
}
|
||||
}
|
||||
|
||||
WebContentsVideoCaptureDevice::Impl::~Impl() {
|
||||
DCHECK(!capture_machine_) << "Cleanup on UI thread did not happen.";
|
||||
DVLOG(1) << "WebContentsVideoCaptureDevice::Impl@" << this << " destroying.";
|
||||
}
|
||||
|
||||
void WebContentsVideoCaptureDevice::Impl::TransitionStateTo(State next_state) {
|
||||
DCHECK(thread_checker_.CalledOnValidThread());
|
||||
|
||||
#ifndef NDEBUG
|
||||
static const char* kStateNames[] = {
|
||||
"Idle", "Allocated", "Capturing", "Error"
|
||||
};
|
||||
DVLOG(1) << "State change: " << kStateNames[state_]
|
||||
<< " --> " << kStateNames[next_state];
|
||||
#endif
|
||||
|
||||
state_ = next_state;
|
||||
}
|
||||
|
||||
void WebContentsVideoCaptureDevice::Impl::Error() {
|
||||
DCHECK(thread_checker_.CalledOnValidThread());
|
||||
|
||||
if (state_ == kIdle)
|
||||
return;
|
||||
|
||||
if (oracle_proxy_)
|
||||
oracle_proxy_->ReportError();
|
||||
|
||||
StopAndDeAllocate();
|
||||
TransitionStateTo(kError);
|
||||
}
|
||||
|
||||
WebContentsVideoCaptureDevice::WebContentsVideoCaptureDevice(
|
||||
int render_process_id,
|
||||
int render_view_id)
|
||||
: impl_(new WebContentsVideoCaptureDevice::Impl(render_process_id,
|
||||
render_view_id)) {}
|
||||
int render_process_id, int render_view_id)
|
||||
: impl_(new VideoCaptureDeviceImpl(scoped_ptr<VideoCaptureMachine>(
|
||||
new WebContentsCaptureMachine(render_process_id, render_view_id)))) {}
|
||||
|
||||
WebContentsVideoCaptureDevice::~WebContentsVideoCaptureDevice() {
|
||||
DVLOG(2) << "WebContentsVideoCaptureDevice@" << this << " destroying.";
|
||||
|
@ -13,7 +13,7 @@
|
||||
|
||||
namespace content {
|
||||
|
||||
class RenderWidgetHost;
|
||||
class VideoCaptureDeviceImpl;
|
||||
|
||||
// A virtualized VideoCaptureDevice that mirrors the displayed contents of a
|
||||
// tab (accessed via its associated WebContents instance), producing a stream of
|
||||
@ -47,11 +47,9 @@ class CONTENT_EXPORT WebContentsVideoCaptureDevice
|
||||
virtual void StopAndDeAllocate() OVERRIDE;
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
|
||||
WebContentsVideoCaptureDevice(int render_process_id, int render_view_id);
|
||||
|
||||
const scoped_ptr<Impl> impl_;
|
||||
const scoped_ptr<VideoCaptureDeviceImpl> impl_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(WebContentsVideoCaptureDevice);
|
||||
};
|
||||
|
@ -921,6 +921,8 @@
|
||||
'browser/renderer_host/media/video_capture_controller.h',
|
||||
'browser/renderer_host/media/video_capture_controller_event_handler.cc',
|
||||
'browser/renderer_host/media/video_capture_controller_event_handler.h',
|
||||
'browser/renderer_host/media/video_capture_device_impl.cc',
|
||||
'browser/renderer_host/media/video_capture_device_impl.h',
|
||||
'browser/renderer_host/media/video_capture_host.cc',
|
||||
'browser/renderer_host/media/video_capture_host.h',
|
||||
'browser/renderer_host/media/video_capture_manager.cc',
|
||||
@ -1364,6 +1366,8 @@
|
||||
'sources': [
|
||||
'browser/renderer_host/media/desktop_capture_device.cc',
|
||||
'browser/renderer_host/media/desktop_capture_device.h',
|
||||
'browser/renderer_host/media/desktop_capture_device_ash.cc',
|
||||
'browser/renderer_host/media/desktop_capture_device_ash.h',
|
||||
],
|
||||
'dependencies': [
|
||||
'../third_party/webrtc/modules/modules.gyp:desktop_capture',
|
||||
|
@ -713,6 +713,11 @@
|
||||
'../third_party/webrtc/modules/modules.gyp:desktop_capture',
|
||||
],
|
||||
}],
|
||||
['enable_webrtc==1 and chromeos==1', {
|
||||
'sources': [
|
||||
'browser/renderer_host/media/desktop_capture_device_ash_unittest.cc',
|
||||
],
|
||||
}],
|
||||
# TODO(jrg): remove the OS=="android" section?
|
||||
# http://crbug.com/113172
|
||||
# Understand better how media_stream_ is tied into Chromium.
|
||||
|
Reference in New Issue
Block a user