This source file includes following definitions.
- ComputeYV12LetterboxRegion
- InvokeCaptureFrameCallback
- DeleteOnWorkerThread
- delivery_log_
- DidShowFullscreenWidget
- DidDestroyFullscreenWidget
- RenderViewReady
- AboutToNavigateRenderView
- DidNavigateMainFrame
- ShouldCaptureFrame
- timer_
- Observe
- OnTimer
- RenderVideoFrame
- count_frames_rendered_
- ChronicleFrameDelivery
- weak_ptr_factory_
- Start
- Stop
- StartObservingWebContents
- WebContentsDestroyed
- GetTarget
- DidCopyFromBackingStore
- DidCopyFromCompositingSurfaceToVideoFrame
- RenewFrameSubscription
- Create
- AllocateAndStart
- StopAndDeAllocate
#include "content/browser/media/capture/web_contents_video_capture_device.h"
#include "base/basictypes.h"
#include "base/bind.h"
#include "base/callback_helpers.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/threading/thread.h"
#include "base/threading/thread_checker.h"
#include "base/time/time.h"
#include "content/browser/media/capture/content_video_capture_device_core.h"
#include "content/browser/media/capture/video_capture_oracle.h"
#include "content/browser/media/capture/web_contents_capture_util.h"
#include "content/browser/renderer_host/render_widget_host_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/port/browser/render_widget_host_view_frame_subscriber.h"
#include "content/port/browser/render_widget_host_view_port.h"
#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_view_host.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/web_contents_observer.h"
#include "media/base/video_util.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"
namespace content {
namespace {
gfx::Rect ComputeYV12LetterboxRegion(const gfx::Size& frame_size,
const gfx::Size& content_size) {
gfx::Rect result = media::ComputeLetterboxRegion(gfx::Rect(frame_size),
content_size);
result.set_x(MakeEven(result.x()));
result.set_y(MakeEven(result.y()));
result.set_width(std::max(kMinFrameWidth, MakeEven(result.width())));
result.set_height(std::max(kMinFrameHeight, MakeEven(result.height())));
return result;
}
void InvokeCaptureFrameCallback(
const ThreadSafeCaptureOracle::CaptureFrameCallback& capture_frame_cb,
base::TimeTicks timestamp,
bool frame_captured) {
capture_frame_cb.Run(timestamp, frame_captured);
}
void DeleteOnWorkerThread(scoped_ptr<base::Thread> render_thread,
const base::Closure& callback) {
render_thread.reset();
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, callback);
}
class VideoFrameDeliveryLog {
public:
VideoFrameDeliveryLog();
void ChronicleFrameDelivery(base::TimeTicks frame_time);
private:
base::TimeTicks last_frame_rate_log_time_;
int count_frames_rendered_;
DISALLOW_COPY_AND_ASSIGN(VideoFrameDeliveryLog);
};
class FrameSubscriber : public RenderWidgetHostViewFrameSubscriber {
public:
FrameSubscriber(VideoCaptureOracle::Event event_type,
const scoped_refptr<ThreadSafeCaptureOracle>& oracle,
VideoFrameDeliveryLog* delivery_log)
: event_type_(event_type),
oracle_proxy_(oracle),
delivery_log_(delivery_log) {}
virtual bool ShouldCaptureFrame(
base::TimeTicks present_time,
scoped_refptr<media::VideoFrame>* storage,
RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback*
deliver_frame_cb) OVERRIDE;
private:
const VideoCaptureOracle::Event event_type_;
scoped_refptr<ThreadSafeCaptureOracle> oracle_proxy_;
VideoFrameDeliveryLog* const delivery_log_;
};
class ContentCaptureSubscription : public content::NotificationObserver {
public:
typedef base::Callback<
void(const base::TimeTicks&,
const scoped_refptr<media::VideoFrame>&,
const RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback&)>
CaptureCallback;
ContentCaptureSubscription(
const RenderWidgetHost& source,
const scoped_refptr<ThreadSafeCaptureOracle>& oracle_proxy,
const CaptureCallback& capture_callback);
virtual ~ContentCaptureSubscription();
virtual void Observe(int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) OVERRIDE;
private:
void OnTimer();
const int render_process_id_;
const int render_view_id_;
VideoFrameDeliveryLog delivery_log_;
FrameSubscriber paint_subscriber_;
FrameSubscriber timer_subscriber_;
content::NotificationRegistrar registrar_;
CaptureCallback capture_callback_;
base::Timer timer_;
DISALLOW_COPY_AND_ASSIGN(ContentCaptureSubscription);
};
void RenderVideoFrame(const SkBitmap& input,
const scoped_refptr<media::VideoFrame>& output,
const base::Callback<void(bool)>& done_cb);
class WebContentsCaptureMachine
: public VideoCaptureMachine,
public WebContentsObserver {
public:
WebContentsCaptureMachine(int render_process_id, int render_view_id);
virtual ~WebContentsCaptureMachine();
virtual bool Start(
const scoped_refptr<ThreadSafeCaptureOracle>& oracle_proxy) OVERRIDE;
virtual void Stop(const base::Closure& callback) OVERRIDE;
void Capture(const base::TimeTicks& start_time,
const scoped_refptr<media::VideoFrame>& target,
const RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback&
deliver_frame_cb);
virtual void DidShowFullscreenWidget(int routing_id) OVERRIDE {
fullscreen_widget_id_ = routing_id;
RenewFrameSubscription();
}
virtual void DidDestroyFullscreenWidget(int routing_id) OVERRIDE {
DCHECK_EQ(fullscreen_widget_id_, routing_id);
fullscreen_widget_id_ = MSG_ROUTING_NONE;
RenewFrameSubscription();
}
virtual void RenderViewReady() OVERRIDE {
RenewFrameSubscription();
}
virtual void AboutToNavigateRenderView(RenderViewHost* rvh) OVERRIDE {
RenewFrameSubscription();
}
virtual void DidNavigateMainFrame(
const LoadCommittedDetails& details,
const FrameNavigateParams& params) OVERRIDE {
RenewFrameSubscription();
}
virtual void WebContentsDestroyed(WebContents* web_contents) OVERRIDE;
private:
bool StartObservingWebContents();
RenderWidgetHost* GetTarget();
void DidCopyFromBackingStore(
const base::TimeTicks& start_time,
const scoped_refptr<media::VideoFrame>& target,
const RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback&
deliver_frame_cb,
bool success,
const SkBitmap& bitmap);
void DidCopyFromCompositingSurfaceToVideoFrame(
const base::TimeTicks& start_time,
const RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback&
deliver_frame_cb,
bool success);
void RenewFrameSubscription();
const int initial_render_process_id_;
const int initial_render_view_id_;
scoped_ptr<base::Thread> render_thread_;
scoped_refptr<ThreadSafeCaptureOracle> oracle_proxy_;
int fullscreen_widget_id_;
gfx::Size last_view_size_;
scoped_ptr<ContentCaptureSubscription> subscription_;
base::WeakPtrFactory<WebContentsCaptureMachine> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(WebContentsCaptureMachine);
};
bool FrameSubscriber::ShouldCaptureFrame(
base::TimeTicks present_time,
scoped_refptr<media::VideoFrame>* storage,
DeliverFrameCallback* deliver_frame_cb) {
TRACE_EVENT1("mirroring", "FrameSubscriber::ShouldCaptureFrame",
"instance", this);
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);
if (oracle_decision)
delivery_log_->ChronicleFrameDelivery(present_time);
return oracle_decision;
}
ContentCaptureSubscription::ContentCaptureSubscription(
const RenderWidgetHost& source,
const scoped_refptr<ThreadSafeCaptureOracle>& oracle_proxy,
const CaptureCallback& capture_callback)
: render_process_id_(source.GetProcess()->GetID()),
render_view_id_(source.GetRoutingID()),
delivery_log_(),
paint_subscriber_(VideoCaptureOracle::kSoftwarePaint, oracle_proxy,
&delivery_log_),
timer_subscriber_(VideoCaptureOracle::kTimerPoll, oracle_proxy,
&delivery_log_),
capture_callback_(capture_callback),
timer_(true, true) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
RenderWidgetHostViewPort* view =
RenderWidgetHostViewPort::FromRWHV(source.GetView());
if (view && kAcceleratedSubscriberIsSupported) {
scoped_ptr<RenderWidgetHostViewFrameSubscriber> subscriber(
new FrameSubscriber(VideoCaptureOracle::kCompositorUpdate,
oracle_proxy, &delivery_log_));
view->BeginFrameSubscription(subscriber.Pass());
}
registrar_.Add(
this, content::NOTIFICATION_RENDER_WIDGET_HOST_DID_UPDATE_BACKING_STORE,
Source<RenderWidgetHost>(&source));
timer_.Start(FROM_HERE, oracle_proxy->capture_period(),
base::Bind(&ContentCaptureSubscription::OnTimer,
base::Unretained(this)));
}
ContentCaptureSubscription::~ContentCaptureSubscription() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
if (kAcceleratedSubscriberIsSupported) {
RenderViewHost* source = RenderViewHost::FromID(render_process_id_,
render_view_id_);
if (source) {
RenderWidgetHostViewPort* view =
RenderWidgetHostViewPort::FromRWHV(source->GetView());
if (view)
view->EndFrameSubscription();
}
}
}
void ContentCaptureSubscription::Observe(
int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
DCHECK_EQ(NOTIFICATION_RENDER_WIDGET_HOST_DID_UPDATE_BACKING_STORE, type);
RenderWidgetHostImpl* rwh =
RenderWidgetHostImpl::From(Source<RenderWidgetHost>(source).ptr());
if (!rwh || !rwh->GetView())
return;
#if !defined(OS_MACOSX)
if (rwh->is_accelerated_compositing_active() &&
rwh->GetView()->IsSurfaceAvailableForCopy())
return;
#endif
TRACE_EVENT1("mirroring", "ContentCaptureSubscription::Observe",
"instance", this);
base::Closure copy_done_callback;
scoped_refptr<media::VideoFrame> frame;
RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback deliver_frame_cb;
const base::TimeTicks start_time = base::TimeTicks::Now();
if (paint_subscriber_.ShouldCaptureFrame(start_time,
&frame,
&deliver_frame_cb)) {
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::Bind(capture_callback_, start_time, frame, deliver_frame_cb));
}
}
void ContentCaptureSubscription::OnTimer() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
TRACE_EVENT0("mirroring", "ContentCaptureSubscription::OnTimer");
scoped_refptr<media::VideoFrame> frame;
RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback deliver_frame_cb;
const base::TimeTicks start_time = base::TimeTicks::Now();
if (timer_subscriber_.ShouldCaptureFrame(start_time,
&frame,
&deliver_frame_cb)) {
capture_callback_.Run(start_time, frame, deliver_frame_cb);
}
}
void RenderVideoFrame(const SkBitmap& input,
const scoped_refptr<media::VideoFrame>& output,
const base::Callback<void(bool)>& done_cb) {
base::ScopedClosureRunner failure_handler(base::Bind(done_cb, false));
SkAutoLockPixels locker(input);
if (input.empty() ||
!input.readyToDraw() ||
input.config() != SkBitmap::kARGB_8888_Config ||
input.width() < 2 || input.height() < 2) {
DVLOG(1) << "input unacceptable (size="
<< input.getSize()
<< ", ready=" << input.readyToDraw()
<< ", config=" << input.config() << ')';
return;
}
if (output->format() != media::VideoFrame::I420) {
NOTREACHED();
return;
}
gfx::Rect region_in_frame = ComputeYV12LetterboxRegion(
output->coded_size(), gfx::Size(input.width(), input.height()));
SkBitmap scaled_bitmap;
if (input.width() != region_in_frame.width() ||
input.height() != region_in_frame.height()) {
skia::ImageOperations::ResizeMethod method;
if (input.width() < region_in_frame.width() ||
input.height() < region_in_frame.height()) {
method = skia::ImageOperations::RESIZE_HAMMING1;
} else {
method = skia::ImageOperations::RESIZE_BOX;
}
TRACE_EVENT_ASYNC_STEP_INTO0("mirroring", "Capture", output.get(), "Scale");
scaled_bitmap = skia::ImageOperations::Resize(input, method,
region_in_frame.width(),
region_in_frame.height());
} else {
scaled_bitmap = input;
}
TRACE_EVENT_ASYNC_STEP_INTO0("mirroring", "Capture", output.get(), "YUV");
{
SkAutoLockPixels scaled_bitmap_locker(scaled_bitmap);
media::CopyRGBToVideoFrame(
reinterpret_cast<uint8*>(scaled_bitmap.getPixels()),
scaled_bitmap.rowBytes(),
region_in_frame,
output.get());
}
ignore_result(failure_handler.Release());
done_cb.Run(true);
}
VideoFrameDeliveryLog::VideoFrameDeliveryLog()
: last_frame_rate_log_time_(),
count_frames_rendered_(0) {
}
void VideoFrameDeliveryLog::ChronicleFrameDelivery(base::TimeTicks frame_time) {
static const base::TimeDelta kFrameRateLogInterval =
base::TimeDelta::FromSeconds(10);
if (last_frame_rate_log_time_.is_null()) {
last_frame_rate_log_time_ = frame_time;
count_frames_rendered_ = 0;
} else {
++count_frames_rendered_;
const base::TimeDelta elapsed = frame_time - last_frame_rate_log_time_;
if (elapsed >= kFrameRateLogInterval) {
const double measured_fps =
count_frames_rendered_ / elapsed.InSecondsF();
UMA_HISTOGRAM_COUNTS(
"TabCapture.FrameRate",
static_cast<int>(measured_fps));
VLOG(1) << "Current measured frame rate for "
<< "WebContentsVideoCaptureDevice is " << measured_fps << " FPS.";
last_frame_rate_log_time_ = frame_time;
count_frames_rendered_ = 0;
}
}
}
WebContentsCaptureMachine::WebContentsCaptureMachine(int render_process_id,
int render_view_id)
: initial_render_process_id_(render_process_id),
initial_render_view_id_(render_view_id),
fullscreen_widget_id_(MSG_ROUTING_NONE),
weak_ptr_factory_(this) {}
WebContentsCaptureMachine::~WebContentsCaptureMachine() {
BrowserThread::PostBlockingPoolTask(
FROM_HERE,
base::Bind(&DeleteOnWorkerThread, base::Passed(&render_thread_),
base::Bind(&base::DoNothing)));
}
bool WebContentsCaptureMachine::Start(
const scoped_refptr<ThreadSafeCaptureOracle>& oracle_proxy) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
DCHECK(!started_);
DCHECK(oracle_proxy.get());
oracle_proxy_ = oracle_proxy;
render_thread_.reset(new base::Thread("WebContentsVideo_RenderThread"));
if (!render_thread_->Start()) {
DVLOG(1) << "Failed to spawn render thread.";
render_thread_.reset();
return false;
}
if (!StartObservingWebContents()) {
DVLOG(1) << "Failed to observe web contents.";
render_thread_.reset();
return false;
}
started_ = true;
return true;
}
void WebContentsCaptureMachine::Stop(const base::Closure& callback) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
subscription_.reset();
if (web_contents()) {
web_contents()->DecrementCapturerCount();
Observe(NULL);
}
weak_ptr_factory_.InvalidateWeakPtrs();
BrowserThread::PostBlockingPoolTask(
FROM_HERE,
base::Bind(&DeleteOnWorkerThread, base::Passed(&render_thread_),
callback));
started_ = false;
}
void WebContentsCaptureMachine::Capture(
const base::TimeTicks& start_time,
const scoped_refptr<media::VideoFrame>& target,
const RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback&
deliver_frame_cb) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
RenderWidgetHost* rwh = GetTarget();
RenderWidgetHostViewPort* view =
rwh ? RenderWidgetHostViewPort::FromRWHV(rwh->GetView()) : NULL;
if (!view || !rwh) {
deliver_frame_cb.Run(base::TimeTicks(), false);
return;
}
gfx::Size video_size = target->coded_size();
gfx::Size view_size = view->GetViewBounds().size();
gfx::Size fitted_size;
if (!view_size.IsEmpty()) {
fitted_size = ComputeYV12LetterboxRegion(video_size, view_size).size();
}
if (view_size != last_view_size_) {
last_view_size_ = view_size;
UMA_HISTOGRAM_COUNTS_10000(
"TabCapture.ViewChangeKiloPixels",
view_size.width() * view_size.height() / 1024);
}
if (!view->IsSurfaceAvailableForCopy()) {
rwh->GetSnapshotFromRenderer(
gfx::Rect(),
base::Bind(&WebContentsCaptureMachine::DidCopyFromBackingStore,
weak_ptr_factory_.GetWeakPtr(),
start_time, target, deliver_frame_cb));
} else if (view->CanCopyToVideoFrame()) {
view->CopyFromCompositingSurfaceToVideoFrame(
gfx::Rect(view_size),
target,
base::Bind(&WebContentsCaptureMachine::
DidCopyFromCompositingSurfaceToVideoFrame,
weak_ptr_factory_.GetWeakPtr(),
start_time, deliver_frame_cb));
} else {
rwh->CopyFromBackingStore(
gfx::Rect(),
fitted_size,
base::Bind(&WebContentsCaptureMachine::DidCopyFromBackingStore,
weak_ptr_factory_.GetWeakPtr(),
start_time,
target,
deliver_frame_cb),
SkBitmap::kARGB_8888_Config);
}
}
bool WebContentsCaptureMachine::StartObservingWebContents() {
RenderViewHost* const rvh =
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.";
Observe(rvh ? WebContents::FromRenderViewHost(rvh) : NULL);
WebContentsImpl* contents = static_cast<WebContentsImpl*>(web_contents());
if (contents) {
contents->IncrementCapturerCount(oracle_proxy_->GetCaptureSize());
fullscreen_widget_id_ = contents->GetFullscreenWidgetRoutingID();
RenewFrameSubscription();
return true;
}
DVLOG(1) << "WebContents::FromRenderViewHost(" << rvh << ") returned NULL.";
return false;
}
void WebContentsCaptureMachine::WebContentsDestroyed(
WebContents* web_contents) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
subscription_.reset();
web_contents->DecrementCapturerCount();
oracle_proxy_->ReportError("WebContentsDestroyed()");
}
RenderWidgetHost* WebContentsCaptureMachine::GetTarget() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
if (!web_contents())
return NULL;
RenderWidgetHost* rwh = NULL;
if (fullscreen_widget_id_ != MSG_ROUTING_NONE) {
RenderProcessHost* process = web_contents()->GetRenderProcessHost();
if (process)
rwh = RenderWidgetHost::FromID(process->GetID(), fullscreen_widget_id_);
} else {
rwh = web_contents()->GetRenderViewHost();
}
return rwh;
}
void WebContentsCaptureMachine::DidCopyFromBackingStore(
const base::TimeTicks& start_time,
const scoped_refptr<media::VideoFrame>& target,
const RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback&
deliver_frame_cb,
bool success,
const SkBitmap& bitmap) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
base::TimeTicks now = base::TimeTicks::Now();
DCHECK(render_thread_.get());
if (success) {
UMA_HISTOGRAM_TIMES("TabCapture.CopyTimeBitmap", now - start_time);
TRACE_EVENT_ASYNC_STEP_INTO0("mirroring", "Capture", target.get(),
"Render");
render_thread_->message_loop_proxy()->PostTask(FROM_HERE, base::Bind(
&RenderVideoFrame, bitmap, target,
base::Bind(deliver_frame_cb, start_time)));
} else {
DVLOG(1) << "CopyFromBackingStore failed; skipping frame.";
deliver_frame_cb.Run(start_time, false);
}
}
void WebContentsCaptureMachine::DidCopyFromCompositingSurfaceToVideoFrame(
const base::TimeTicks& start_time,
const RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback&
deliver_frame_cb,
bool success) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
base::TimeTicks now = base::TimeTicks::Now();
if (success) {
UMA_HISTOGRAM_TIMES("TabCapture.CopyTimeVideoFrame", now - start_time);
} else {
DVLOG(1) << "CopyFromCompositingSurface failed; skipping frame.";
}
deliver_frame_cb.Run(start_time, success);
}
void WebContentsCaptureMachine::RenewFrameSubscription() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
subscription_.reset();
RenderWidgetHost* rwh = GetTarget();
if (!rwh || !rwh->GetView())
return;
subscription_.reset(new ContentCaptureSubscription(*rwh, oracle_proxy_,
base::Bind(&WebContentsCaptureMachine::Capture,
weak_ptr_factory_.GetWeakPtr())));
}
}
WebContentsVideoCaptureDevice::WebContentsVideoCaptureDevice(
int render_process_id, int render_view_id)
: core_(new ContentVideoCaptureDeviceCore(scoped_ptr<VideoCaptureMachine>(
new WebContentsCaptureMachine(render_process_id, render_view_id)))) {}
WebContentsVideoCaptureDevice::~WebContentsVideoCaptureDevice() {
DVLOG(2) << "WebContentsVideoCaptureDevice@" << this << " destroying.";
}
media::VideoCaptureDevice* WebContentsVideoCaptureDevice::Create(
const std::string& device_id) {
int render_process_id = -1;
int render_view_id = -1;
if (!WebContentsCaptureUtil::ExtractTabCaptureTarget(
device_id, &render_process_id, &render_view_id)) {
return NULL;
}
return new WebContentsVideoCaptureDevice(render_process_id, render_view_id);
}
void WebContentsVideoCaptureDevice::AllocateAndStart(
const media::VideoCaptureParams& params,
scoped_ptr<Client> client) {
DVLOG(1) << "Allocating " << params.requested_format.frame_size.ToString();
core_->AllocateAndStart(params, client.Pass());
}
void WebContentsVideoCaptureDevice::StopAndDeAllocate() {
core_->StopAndDeAllocate();
}
}