blob: 6cde5f0d671d4ec5cb79539973dda154317529e1 [file] [log] [blame]
// Copyright (c) 2012 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_controller.h"
#include <stddef.h>
#include <stdint.h>
#include <map>
#include <set>
#include "base/bind.h"
#include "base/command_line.h"
#include "base/metrics/histogram.h"
#include "base/metrics/sparse_histogram.h"
#include "base/stl_util.h"
#include "build/build_config.h"
#include "content/browser/renderer_host/media/media_stream_manager.h"
#include "content/browser/renderer_host/media/video_capture_buffer_pool.h"
#include "content/browser/renderer_host/media/video_capture_device_client.h"
#include "content/browser/renderer_host/media/video_capture_manager.h"
#include "content/common/gpu/client/gl_helper.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/common/content_switches.h"
#include "media/base/video_frame.h"
#if !defined(OS_ANDROID)
#include "content/browser/compositor/image_transport_factory.h"
#endif
using media::VideoCaptureFormat;
using media::VideoFrame;
using media::VideoFrameMetadata;
namespace content {
namespace {
static const int kInfiniteRatio = 99999;
#define UMA_HISTOGRAM_ASPECT_RATIO(name, width, height) \
UMA_HISTOGRAM_SPARSE_SLOWLY( \
name, \
(height) ? ((width) * 100) / (height) : kInfiniteRatio);
class SyncTokenClientImpl : public VideoFrame::SyncTokenClient {
public:
explicit SyncTokenClientImpl(GLHelper* gl_helper) : gl_helper_(gl_helper) {}
~SyncTokenClientImpl() override {}
void GenerateSyncToken(gpu::SyncToken* sync_token) override {
gl_helper_->GenerateSyncToken(sync_token);
}
void WaitSyncToken(const gpu::SyncToken& sync_token) override {
gl_helper_->WaitSyncToken(sync_token);
}
private:
GLHelper* gl_helper_;
};
void ReturnVideoFrame(const scoped_refptr<VideoFrame>& video_frame,
const gpu::SyncToken& sync_token) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
#if defined(OS_ANDROID)
NOTREACHED();
#else
GLHelper* gl_helper = ImageTransportFactory::GetInstance()->GetGLHelper();
// UpdateReleaseSyncToken() creates a new sync_token using |gl_helper|, so
// wait the given |sync_token| using |gl_helper|.
if (gl_helper) {
gl_helper->WaitSyncToken(sync_token);
SyncTokenClientImpl client(gl_helper);
video_frame->UpdateReleaseSyncToken(&client);
}
#endif
}
} // anonymous namespace
struct VideoCaptureController::ControllerClient {
ControllerClient(VideoCaptureControllerID id,
VideoCaptureControllerEventHandler* handler,
base::ProcessHandle render_process,
media::VideoCaptureSessionId session_id,
const media::VideoCaptureParams& params)
: controller_id(id),
event_handler(handler),
render_process_handle(render_process),
session_id(session_id),
parameters(params),
session_closed(false),
paused(false) {}
~ControllerClient() {}
// ID used for identifying this object.
const VideoCaptureControllerID controller_id;
VideoCaptureControllerEventHandler* const event_handler;
// Handle to the render process that will receive the capture buffers.
const base::ProcessHandle render_process_handle;
const media::VideoCaptureSessionId session_id;
const media::VideoCaptureParams parameters;
// Buffers that are currently known to this client.
std::set<int> known_buffers;
// Buffers currently held by this client, and sync token callback to call when
// they are returned from the client.
typedef std::map<int, scoped_refptr<VideoFrame>> ActiveBufferMap;
ActiveBufferMap active_buffers;
// State of capture session, controlled by VideoCaptureManager directly. This
// transitions to true as soon as StopSession() occurs, at which point the
// client is sent an OnEnded() event. However, because the client retains a
// VideoCaptureController* pointer, its ControllerClient entry lives on until
// it unregisters itself via RemoveClient(), which may happen asynchronously.
//
// TODO(nick): If we changed the semantics of VideoCaptureHost so that
// OnEnded() events were processed synchronously (with the RemoveClient() done
// implicitly), we could avoid tracking this state here in the Controller, and
// simplify the code in both places.
bool session_closed;
// Indicates whether the client is paused, if true, VideoCaptureController
// stops updating its buffer.
bool paused;
};
VideoCaptureController::VideoCaptureController(int max_buffers)
: buffer_pool_(new VideoCaptureBufferPool(max_buffers)),
state_(VIDEO_CAPTURE_STATE_STARTED),
has_received_frames_(false),
weak_ptr_factory_(this) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
}
base::WeakPtr<VideoCaptureController>
VideoCaptureController::GetWeakPtrForIOThread() {
return weak_ptr_factory_.GetWeakPtr();
}
scoped_ptr<media::VideoCaptureDevice::Client>
VideoCaptureController::NewDeviceClient() {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
return make_scoped_ptr(new VideoCaptureDeviceClient(
this->GetWeakPtrForIOThread(), buffer_pool_));
}
void VideoCaptureController::AddClient(
VideoCaptureControllerID id,
VideoCaptureControllerEventHandler* event_handler,
base::ProcessHandle render_process,
media::VideoCaptureSessionId session_id,
const media::VideoCaptureParams& params) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DVLOG(1) << "VideoCaptureController::AddClient() -- id=" << id
<< ", session_id=" << session_id
<< ", params.requested_format="
<< media::VideoCaptureFormat::ToString(params.requested_format);
// Check that requested VideoCaptureParams are valid and supported. If not,
// report an error immediately and punt.
if (!params.IsValid() ||
params.requested_format.pixel_format != media::PIXEL_FORMAT_I420 ||
(params.requested_format.pixel_storage != media::PIXEL_STORAGE_CPU &&
params.requested_format.pixel_storage !=
media::PIXEL_STORAGE_GPUMEMORYBUFFER)) {
// Crash in debug builds since the renderer should not have asked for
// invalid or unsupported parameters.
LOG(DFATAL) << "Invalid or unsupported video capture parameters requested: "
<< media::VideoCaptureFormat::ToString(params.requested_format);
event_handler->OnError(id);
return;
}
// If this is the first client added to the controller, cache the parameters.
if (!controller_clients_.size())
video_capture_format_ = params.requested_format;
// Signal error in case device is already in error state.
if (state_ == VIDEO_CAPTURE_STATE_ERROR) {
event_handler->OnError(id);
return;
}
// Do nothing if this client has called AddClient before.
if (FindClient(id, event_handler, controller_clients_))
return;
ControllerClient* client = new ControllerClient(
id, event_handler, render_process, session_id, params);
// If we already have gotten frame_info from the device, repeat it to the new
// client.
if (state_ == VIDEO_CAPTURE_STATE_STARTED) {
controller_clients_.push_back(client);
return;
}
}
int VideoCaptureController::RemoveClient(
VideoCaptureControllerID id,
VideoCaptureControllerEventHandler* event_handler) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DVLOG(1) << "VideoCaptureController::RemoveClient, id " << id;
ControllerClient* client = FindClient(id, event_handler, controller_clients_);
if (!client)
return kInvalidMediaCaptureSessionId;
// Take back all buffers held by the |client|.
for (const auto& buffer : client->active_buffers)
buffer_pool_->RelinquishConsumerHold(buffer.first, 1);
client->active_buffers.clear();
int session_id = client->session_id;
controller_clients_.remove(client);
delete client;
return session_id;
}
void VideoCaptureController::PauseClient(
VideoCaptureControllerID id,
VideoCaptureControllerEventHandler* event_handler) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DVLOG(1) << "VideoCaptureController::PauseClient, id " << id;
ControllerClient* client = FindClient(id, event_handler, controller_clients_);
if (!client)
return;
DLOG_IF(WARNING, client->paused) << "Redundant client configuration";
client->paused = true;
}
bool VideoCaptureController::ResumeClient(
VideoCaptureControllerID id,
VideoCaptureControllerEventHandler* event_handler) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DVLOG(1) << "VideoCaptureController::ResumeClient, id " << id;
ControllerClient* client = FindClient(id, event_handler, controller_clients_);
if (!client)
return false;
if (!client->paused) {
DVLOG(1) << "Calling resume on unpaused client";
return false;
}
client->paused = false;
return true;
}
int VideoCaptureController::GetClientCount() const {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
return controller_clients_.size();
}
int VideoCaptureController::GetActiveClientCount() const {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
int active_client_count = 0;
for (ControllerClient* client : controller_clients_) {
if (!client->paused)
++active_client_count;
}
return active_client_count;
}
void VideoCaptureController::StopSession(int session_id) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DVLOG(1) << "VideoCaptureController::StopSession, id " << session_id;
ControllerClient* client = FindClient(session_id, controller_clients_);
if (client) {
client->session_closed = true;
client->event_handler->OnEnded(client->controller_id);
}
}
void VideoCaptureController::ReturnBuffer(
VideoCaptureControllerID id,
VideoCaptureControllerEventHandler* event_handler,
int buffer_id,
const gpu::SyncToken& sync_token,
double consumer_resource_utilization) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
ControllerClient* client = FindClient(id, event_handler, controller_clients_);
// If this buffer is not held by this client, or this client doesn't exist
// in controller, do nothing.
ControllerClient::ActiveBufferMap::iterator iter;
if (!client || (iter = client->active_buffers.find(buffer_id)) ==
client->active_buffers.end()) {
NOTREACHED();
return;
}
// Set the RESOURCE_UTILIZATION to the maximum of those provided by each
// consumer (via separate calls to this method that refer to the same
// VideoFrame). The producer of this VideoFrame may check this value, after
// all consumer holds are relinquished, to make quality versus performance
// trade-off decisions.
scoped_refptr<VideoFrame> frame = iter->second;
if (std::isfinite(consumer_resource_utilization) &&
consumer_resource_utilization >= 0.0) {
double resource_utilization = -1.0;
if (frame->metadata()->GetDouble(VideoFrameMetadata::RESOURCE_UTILIZATION,
&resource_utilization)) {
frame->metadata()->SetDouble(VideoFrameMetadata::RESOURCE_UTILIZATION,
std::max(consumer_resource_utilization,
resource_utilization));
} else {
frame->metadata()->SetDouble(VideoFrameMetadata::RESOURCE_UTILIZATION,
consumer_resource_utilization);
}
}
client->active_buffers.erase(iter);
buffer_pool_->RelinquishConsumerHold(buffer_id, 1);
#if defined(OS_ANDROID)
DCHECK(!sync_token.HasData());
#endif
if (sync_token.HasData())
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
base::Bind(&ReturnVideoFrame, frame, sync_token));
}
const media::VideoCaptureFormat&
VideoCaptureController::GetVideoCaptureFormat() const {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
return video_capture_format_;
}
VideoCaptureController::~VideoCaptureController() {
STLDeleteContainerPointers(controller_clients_.begin(),
controller_clients_.end());
}
void VideoCaptureController::DoIncomingCapturedVideoFrameOnIOThread(
scoped_ptr<media::VideoCaptureDevice::Client::Buffer> buffer,
const scoped_refptr<VideoFrame>& frame,
const base::TimeTicks& timestamp) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
const int buffer_id = buffer->id();
DCHECK_NE(buffer_id, VideoCaptureBufferPool::kInvalidId);
int count = 0;
if (state_ == VIDEO_CAPTURE_STATE_STARTED) {
if (!frame->metadata()->HasKey(VideoFrameMetadata::FRAME_RATE)) {
frame->metadata()->SetDouble(VideoFrameMetadata::FRAME_RATE,
video_capture_format_.frame_rate);
}
scoped_ptr<base::DictionaryValue> metadata(new base::DictionaryValue());
frame->metadata()->MergeInternalValuesInto(metadata.get());
// Only I420 pixel format is currently supported.
DCHECK_EQ(frame->format(), media::PIXEL_FORMAT_I420)
<< "Unsupported pixel format: "
<< media::VideoPixelFormatToString(frame->format());
// Sanity-checks to confirm |frame| is actually being backed by |buffer|.
DCHECK(frame->storage_type() == media::VideoFrame::STORAGE_SHMEM ||
(frame->storage_type() ==
media::VideoFrame::STORAGE_GPU_MEMORY_BUFFERS));
DCHECK(frame->data(media::VideoFrame::kYPlane) >= buffer->data(0) &&
(frame->data(media::VideoFrame::kYPlane) <
(reinterpret_cast<const uint8_t*>(buffer->data(0)) +
buffer->mapped_size())))
<< "VideoFrame does not appear to be backed by Buffer";
for (const auto& client : controller_clients_) {
if (client->session_closed || client->paused)
continue;
// On the first use of a buffer on a client, share the memory handles.
const bool is_new_buffer = client->known_buffers.insert(buffer_id).second;
if (is_new_buffer)
DoNewBufferOnIOThread(client, buffer.get(), frame);
client->event_handler->OnBufferReady(client->controller_id,
buffer_id,
frame,
timestamp);
const bool inserted =
client->active_buffers.insert(std::make_pair(buffer_id, frame))
.second;
DCHECK(inserted) << "Unexpected duplicate buffer: " << buffer_id;
count++;
}
}
if (!has_received_frames_) {
UMA_HISTOGRAM_COUNTS("Media.VideoCapture.Width",
frame->visible_rect().width());
UMA_HISTOGRAM_COUNTS("Media.VideoCapture.Height",
frame->visible_rect().height());
UMA_HISTOGRAM_ASPECT_RATIO("Media.VideoCapture.AspectRatio",
frame->visible_rect().width(),
frame->visible_rect().height());
double frame_rate = 0.0f;
if (!frame->metadata()->GetDouble(VideoFrameMetadata::FRAME_RATE,
&frame_rate)) {
frame_rate = video_capture_format_.frame_rate;
}
UMA_HISTOGRAM_COUNTS("Media.VideoCapture.FrameRate", frame_rate);
has_received_frames_ = true;
}
buffer_pool_->HoldForConsumers(buffer_id, count);
}
void VideoCaptureController::DoErrorOnIOThread() {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
state_ = VIDEO_CAPTURE_STATE_ERROR;
for (const auto* client : controller_clients_) {
if (client->session_closed)
continue;
client->event_handler->OnError(client->controller_id);
}
}
void VideoCaptureController::DoLogOnIOThread(const std::string& message) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
MediaStreamManager::SendMessageToNativeLog("Video capture: " + message);
}
void VideoCaptureController::DoBufferDestroyedOnIOThread(
int buffer_id_to_drop) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
for (auto* client : controller_clients_) {
if (client->session_closed)
continue;
if (client->known_buffers.erase(buffer_id_to_drop)) {
client->event_handler->OnBufferDestroyed(client->controller_id,
buffer_id_to_drop);
}
}
}
void VideoCaptureController::DoNewBufferOnIOThread(
ControllerClient* client,
media::VideoCaptureDevice::Client::Buffer* buffer,
const scoped_refptr<media::VideoFrame>& frame) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
const int buffer_id = buffer->id();
switch (frame->storage_type()) {
case media::VideoFrame::STORAGE_GPU_MEMORY_BUFFERS: {
std::vector<gfx::GpuMemoryBufferHandle> handles;
const size_t num_planes = media::VideoFrame::NumPlanes(frame->format());
for (size_t i = 0; i < num_planes; ++i) {
gfx::GpuMemoryBufferHandle remote_handle;
buffer_pool_->ShareToProcess2(
buffer_id, i, client->render_process_handle, &remote_handle);
handles.push_back(remote_handle);
}
client->event_handler->OnBufferCreated2(client->controller_id, handles,
buffer->dimensions(), buffer_id);
break;
}
case media::VideoFrame::STORAGE_SHMEM: {
base::SharedMemoryHandle remote_handle;
buffer_pool_->ShareToProcess(buffer_id, client->render_process_handle,
&remote_handle);
client->event_handler->OnBufferCreated(
client->controller_id, remote_handle, buffer->mapped_size(),
buffer_id);
break;
}
default:
NOTREACHED();
break;
}
}
VideoCaptureController::ControllerClient* VideoCaptureController::FindClient(
VideoCaptureControllerID id,
VideoCaptureControllerEventHandler* handler,
const ControllerClients& clients) {
for (auto* client : clients) {
if (client->controller_id == id && client->event_handler == handler)
return client;
}
return NULL;
}
VideoCaptureController::ControllerClient* VideoCaptureController::FindClient(
int session_id,
const ControllerClients& clients) {
for (auto client : clients) {
if (client->session_id == session_id)
return client;
}
return NULL;
}
} // namespace content