blob: ebdadce93e3277cc5046dc08ec935471b7a56e86 [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.
//
// Notes about usage of this object by VideoCaptureImplManager.
//
// VideoCaptureImplManager access this object by using a Unretained()
// binding and tasks on the IO thread. It is then important that
// VideoCaptureImpl never post task to itself. All operations must be
// synchronous.
#include "content/renderer/media/video_capture_impl.h"
#include <stddef.h>
#include <utility>
#include "base/bind.h"
#include "base/macros.h"
#include "base/stl_util.h"
#include "base/trace_event/trace_event.h"
#include "content/child/child_process.h"
#include "content/public/child/child_thread.h"
#include "content/public/common/service_names.mojom.h"
#include "media/base/bind_to_current_loop.h"
#include "media/base/limits.h"
#include "media/base/video_frame.h"
#include "mojo/public/cpp/system/platform_handle.h"
#include "services/service_manager/public/cpp/connector.h"
namespace content {
// A holder of a memory-backed buffer and accessors to it.
class VideoCaptureImpl::ClientBuffer
: public base::RefCountedThreadSafe<ClientBuffer> {
public:
ClientBuffer(std::unique_ptr<base::SharedMemory> buffer, size_t buffer_size)
: buffer_(std::move(buffer)), buffer_size_(buffer_size) {}
base::SharedMemory* buffer() const { return buffer_.get(); }
size_t buffer_size() const { return buffer_size_; }
private:
friend class base::RefCountedThreadSafe<ClientBuffer>;
virtual ~ClientBuffer() {}
const std::unique_ptr<base::SharedMemory> buffer_;
const size_t buffer_size_;
DISALLOW_COPY_AND_ASSIGN(ClientBuffer);
};
// Information about a video capture client of ours.
struct VideoCaptureImpl::ClientInfo {
ClientInfo() = default;
ClientInfo(const ClientInfo& other) = default;
~ClientInfo() = default;
media::VideoCaptureParams params;
VideoCaptureStateUpdateCB state_update_cb;
VideoCaptureDeliverFrameCB deliver_frame_cb;
};
VideoCaptureImpl::VideoCaptureImpl(media::VideoCaptureSessionId session_id)
: device_id_(session_id),
session_id_(session_id),
video_capture_host_for_testing_(nullptr),
observer_binding_(this),
state_(VIDEO_CAPTURE_STATE_STOPPED),
weak_factory_(this) {
io_thread_checker_.DetachFromThread();
if (ChildThread::Get()) { // This will be null in unit tests.
mojom::VideoCaptureHostPtr temp_video_capture_host;
ChildThread::Get()->GetConnector()->BindInterface(
mojom::kBrowserServiceName,
mojo::MakeRequest(&temp_video_capture_host));
video_capture_host_info_ = temp_video_capture_host.PassInterface();
}
}
VideoCaptureImpl::~VideoCaptureImpl() {
DCHECK(io_thread_checker_.CalledOnValidThread());
if ((state_ == VIDEO_CAPTURE_STATE_STARTING ||
state_ == VIDEO_CAPTURE_STATE_STARTED) &&
GetVideoCaptureHost())
GetVideoCaptureHost()->Stop(device_id_);
}
void VideoCaptureImpl::SuspendCapture(bool suspend) {
DCHECK(io_thread_checker_.CalledOnValidThread());
if (suspend)
GetVideoCaptureHost()->Pause(device_id_);
else
GetVideoCaptureHost()->Resume(device_id_, session_id_, params_);
}
void VideoCaptureImpl::StartCapture(
int client_id,
const media::VideoCaptureParams& params,
const VideoCaptureStateUpdateCB& state_update_cb,
const VideoCaptureDeliverFrameCB& deliver_frame_cb) {
DVLOG(1) << __func__ << " |device_id_| = " << device_id_;
DCHECK(io_thread_checker_.CalledOnValidThread());
ClientInfo client_info;
client_info.params = params;
client_info.state_update_cb = state_update_cb;
client_info.deliver_frame_cb = deliver_frame_cb;
switch (state_) {
case VIDEO_CAPTURE_STATE_STARTING:
case VIDEO_CAPTURE_STATE_STARTED:
clients_[client_id] = client_info;
// TODO(sheu): Allowing resolution change will require that all
// outstanding clients of a capture session support resolution change.
DCHECK_EQ(params_.resolution_change_policy,
params.resolution_change_policy);
return;
case VIDEO_CAPTURE_STATE_STOPPING:
clients_pending_on_restart_[client_id] = client_info;
DVLOG(1) << __func__ << " Got new resolution while stopping: "
<< params.requested_format.frame_size.ToString();
return;
case VIDEO_CAPTURE_STATE_STOPPED:
case VIDEO_CAPTURE_STATE_ENDED:
clients_[client_id] = client_info;
params_ = params;
params_.requested_format.frame_rate =
std::min(params_.requested_format.frame_rate,
static_cast<float>(media::limits::kMaxFramesPerSecond));
DVLOG(1) << "StartCapture: starting with first resolution "
<< params_.requested_format.frame_size.ToString();
StartCaptureInternal();
return;
case VIDEO_CAPTURE_STATE_ERROR:
state_update_cb.Run(VIDEO_CAPTURE_STATE_ERROR);
return;
case VIDEO_CAPTURE_STATE_PAUSED:
case VIDEO_CAPTURE_STATE_RESUMED:
// The internal |state_| is never set to PAUSED/RESUMED since
// VideoCaptureImpl is not modified by those.
NOTREACHED();
return;
}
}
void VideoCaptureImpl::StopCapture(int client_id) {
DCHECK(io_thread_checker_.CalledOnValidThread());
// A client ID can be in only one client list.
// If this ID is in any client list, we can just remove it from
// that client list and don't have to run the other following RemoveClient().
if (!RemoveClient(client_id, &clients_pending_on_restart_)) {
RemoveClient(client_id, &clients_);
}
if (!clients_.empty())
return;
DVLOG(1) << "StopCapture: No more client, stopping ...";
StopDevice();
client_buffers_.clear();
weak_factory_.InvalidateWeakPtrs();
}
void VideoCaptureImpl::RequestRefreshFrame() {
DCHECK(io_thread_checker_.CalledOnValidThread());
GetVideoCaptureHost()->RequestRefreshFrame(device_id_);
}
void VideoCaptureImpl::GetDeviceSupportedFormats(
const VideoCaptureDeviceFormatsCB& callback) {
DCHECK(io_thread_checker_.CalledOnValidThread());
GetVideoCaptureHost()->GetDeviceSupportedFormats(
device_id_, session_id_,
base::BindOnce(&VideoCaptureImpl::OnDeviceSupportedFormats,
weak_factory_.GetWeakPtr(), callback));
}
void VideoCaptureImpl::GetDeviceFormatsInUse(
const VideoCaptureDeviceFormatsCB& callback) {
DCHECK(io_thread_checker_.CalledOnValidThread());
GetVideoCaptureHost()->GetDeviceFormatsInUse(
device_id_, session_id_,
base::BindOnce(&VideoCaptureImpl::OnDeviceFormatsInUse,
weak_factory_.GetWeakPtr(), callback));
}
void VideoCaptureImpl::OnStateChanged(mojom::VideoCaptureState state) {
DVLOG(1) << __func__ << " state: " << state;
DCHECK(io_thread_checker_.CalledOnValidThread());
switch (state) {
case mojom::VideoCaptureState::STARTED:
state_ = VIDEO_CAPTURE_STATE_STARTED;
for (const auto& client : clients_)
client.second.state_update_cb.Run(VIDEO_CAPTURE_STATE_STARTED);
// In case there is any frame dropped before STARTED, always request for
// a frame refresh to start the video call with.
// Capture device will make a decision if it should refresh a frame.
RequestRefreshFrame();
break;
case mojom::VideoCaptureState::STOPPED:
state_ = VIDEO_CAPTURE_STATE_STOPPED;
client_buffers_.clear();
weak_factory_.InvalidateWeakPtrs();
if (!clients_.empty() || !clients_pending_on_restart_.empty())
RestartCapture();
break;
case mojom::VideoCaptureState::PAUSED:
for (const auto& client : clients_)
client.second.state_update_cb.Run(VIDEO_CAPTURE_STATE_PAUSED);
break;
case mojom::VideoCaptureState::RESUMED:
for (const auto& client : clients_)
client.second.state_update_cb.Run(VIDEO_CAPTURE_STATE_RESUMED);
break;
case mojom::VideoCaptureState::FAILED:
for (const auto& client : clients_)
client.second.state_update_cb.Run(VIDEO_CAPTURE_STATE_ERROR);
clients_.clear();
state_ = VIDEO_CAPTURE_STATE_ERROR;
break;
case mojom::VideoCaptureState::ENDED:
// We'll only notify the client that the stream has stopped.
for (const auto& client : clients_)
client.second.state_update_cb.Run(VIDEO_CAPTURE_STATE_STOPPED);
clients_.clear();
state_ = VIDEO_CAPTURE_STATE_ENDED;
break;
}
}
void VideoCaptureImpl::OnBufferCreated(int32_t buffer_id,
mojo::ScopedSharedBufferHandle handle) {
DVLOG(1) << __func__ << " buffer_id: " << buffer_id;
DCHECK(io_thread_checker_.CalledOnValidThread());
DCHECK(handle.is_valid());
base::SharedMemoryHandle memory_handle;
size_t memory_size = 0;
mojo::UnwrappedSharedMemoryHandleProtection protection;
const MojoResult result = mojo::UnwrapSharedMemoryHandle(
std::move(handle), &memory_handle, &memory_size, &protection);
DCHECK_EQ(MOJO_RESULT_OK, result);
DCHECK_GT(memory_size, 0u);
// TODO(https://crbug.com/803136): We should also be able to assert that the
// unwrapped handle was shared for read-only mapping. That condition is not
// currently guaranteed to be met.
std::unique_ptr<base::SharedMemory> shm(
new base::SharedMemory(memory_handle, true /* read_only */));
if (!shm->Map(memory_size)) {
DLOG(ERROR) << "OnBufferCreated: Map failed.";
return;
}
const bool inserted =
client_buffers_
.insert(std::make_pair(buffer_id,
new ClientBuffer(std::move(shm), memory_size)))
.second;
DCHECK(inserted);
}
void VideoCaptureImpl::OnBufferReady(int32_t buffer_id,
media::mojom::VideoFrameInfoPtr info) {
DVLOG(1) << __func__ << " buffer_id: " << buffer_id;
DCHECK(io_thread_checker_.CalledOnValidThread());
bool consume_buffer = state_ == VIDEO_CAPTURE_STATE_STARTED;
if ((info->pixel_format != media::PIXEL_FORMAT_I420 &&
info->pixel_format != media::PIXEL_FORMAT_Y16) ||
info->storage_type != media::VideoPixelStorage::CPU) {
consume_buffer = false;
LOG(DFATAL) << "Wrong pixel format or storage, got pixel format:"
<< VideoPixelFormatToString(info->pixel_format)
<< ", storage:" << static_cast<int>(info->storage_type);
}
if (!consume_buffer) {
GetVideoCaptureHost()->ReleaseBuffer(device_id_, buffer_id, -1.0);
return;
}
base::TimeTicks reference_time;
media::VideoFrameMetadata frame_metadata;
frame_metadata.MergeInternalValuesFrom(*info->metadata);
const bool success = frame_metadata.GetTimeTicks(
media::VideoFrameMetadata::REFERENCE_TIME, &reference_time);
DCHECK(success);
if (first_frame_ref_time_.is_null())
first_frame_ref_time_ = reference_time;
// If the timestamp is not prepared, we use reference time to make a rough
// estimate. e.g. ThreadSafeCaptureOracle::DidCaptureFrame().
// TODO(miu): Fix upstream capturers to always set timestamp and reference
// time. See http://crbug/618407/ for tracking.
if (info->timestamp.is_zero())
info->timestamp = reference_time - first_frame_ref_time_;
// TODO(qiangchen): Change the metric name to "reference_time" and
// "timestamp", so that we have consistent naming everywhere.
// Used by chrome/browser/extension/api/cast_streaming/performance_test.cc
TRACE_EVENT_INSTANT2("cast_perf_test", "OnBufferReceived",
TRACE_EVENT_SCOPE_THREAD, "timestamp",
(reference_time - base::TimeTicks()).InMicroseconds(),
"time_delta", info->timestamp.InMicroseconds());
const auto& iter = client_buffers_.find(buffer_id);
DCHECK(iter != client_buffers_.end());
scoped_refptr<ClientBuffer> buffer = iter->second;
scoped_refptr<media::VideoFrame> frame =
media::VideoFrame::WrapExternalSharedMemory(
static_cast<media::VideoPixelFormat>(info->pixel_format),
info->coded_size, info->visible_rect, info->visible_rect.size(),
reinterpret_cast<uint8_t*>(buffer->buffer()->memory()),
buffer->buffer_size(), buffer->buffer()->handle(),
0 /* shared_memory_offset */, info->timestamp);
if (!frame) {
GetVideoCaptureHost()->ReleaseBuffer(device_id_, buffer_id, -1.0);
return;
}
frame->AddDestructionObserver(base::BindOnce(
&VideoCaptureImpl::DidFinishConsumingFrame, frame->metadata(),
media::BindToCurrentLoop(base::BindOnce(
&VideoCaptureImpl::OnClientBufferFinished, weak_factory_.GetWeakPtr(),
buffer_id, std::move(buffer)))));
frame->metadata()->MergeInternalValuesFrom(*info->metadata);
// TODO(qiangchen): Dive into the full code path to let frame metadata hold
// reference time rather than using an extra parameter.
for (const auto& client : clients_)
client.second.deliver_frame_cb.Run(frame, reference_time);
}
void VideoCaptureImpl::OnBufferDestroyed(int32_t buffer_id) {
DCHECK(io_thread_checker_.CalledOnValidThread());
const auto& cb_iter = client_buffers_.find(buffer_id);
if (cb_iter != client_buffers_.end()) {
DCHECK(!cb_iter->second.get() || cb_iter->second->HasOneRef())
<< "Instructed to delete buffer we are still using.";
client_buffers_.erase(cb_iter);
}
}
void VideoCaptureImpl::OnClientBufferFinished(
int buffer_id,
scoped_refptr<ClientBuffer> buffer,
double consumer_resource_utilization) {
DCHECK(io_thread_checker_.CalledOnValidThread());
// Subtle race note: It's important that the |buffer| argument be
// std::move()'ed to this method and never copied. This is so that the caller,
// DidFinishConsumingFrame(), does not implicitly retain a reference while it
// is running the trampoline callback on another thread. This is necessary to
// ensure the reference count on the ClientBuffer will be correct at the time
// OnBufferDestroyed() is called. http://crbug.com/797851
#if DCHECK_IS_ON()
// The ClientBuffer should have exactly two references to it at this point,
// one is this method's second argument and the other is from
// |client_buffers_|.
DCHECK(!buffer->HasOneRef());
ClientBuffer* const buffer_raw_ptr = buffer.get();
buffer = nullptr;
// Now there should be only one reference, from |client_buffers_|.
DCHECK(buffer_raw_ptr->HasOneRef());
#else
buffer = nullptr;
#endif
GetVideoCaptureHost()->ReleaseBuffer(
device_id_, buffer_id, consumer_resource_utilization);
}
void VideoCaptureImpl::StopDevice() {
DCHECK(io_thread_checker_.CalledOnValidThread());
if (state_ != VIDEO_CAPTURE_STATE_STARTING &&
state_ != VIDEO_CAPTURE_STATE_STARTED)
return;
state_ = VIDEO_CAPTURE_STATE_STOPPING;
GetVideoCaptureHost()->Stop(device_id_);
params_.requested_format.frame_size.SetSize(0, 0);
}
void VideoCaptureImpl::RestartCapture() {
DCHECK(io_thread_checker_.CalledOnValidThread());
DCHECK_EQ(state_, VIDEO_CAPTURE_STATE_STOPPED);
int width = 0;
int height = 0;
clients_.insert(clients_pending_on_restart_.begin(),
clients_pending_on_restart_.end());
clients_pending_on_restart_.clear();
for (const auto& client : clients_) {
width = std::max(width,
client.second.params.requested_format.frame_size.width());
height = std::max(
height, client.second.params.requested_format.frame_size.height());
}
params_.requested_format.frame_size.SetSize(width, height);
DVLOG(1) << __func__ << " " << params_.requested_format.frame_size.ToString();
StartCaptureInternal();
}
void VideoCaptureImpl::StartCaptureInternal() {
DCHECK(io_thread_checker_.CalledOnValidThread());
state_ = VIDEO_CAPTURE_STATE_STARTING;
mojom::VideoCaptureObserverPtr observer;
observer_binding_.Bind(mojo::MakeRequest(&observer));
GetVideoCaptureHost()->Start(device_id_, session_id_, params_,
std::move(observer));
}
void VideoCaptureImpl::OnDeviceSupportedFormats(
const VideoCaptureDeviceFormatsCB& callback,
const media::VideoCaptureFormats& supported_formats) {
DCHECK(io_thread_checker_.CalledOnValidThread());
callback.Run(supported_formats);
}
void VideoCaptureImpl::OnDeviceFormatsInUse(
const VideoCaptureDeviceFormatsCB& callback,
const media::VideoCaptureFormats& formats_in_use) {
DCHECK(io_thread_checker_.CalledOnValidThread());
callback.Run(formats_in_use);
}
bool VideoCaptureImpl::RemoveClient(int client_id, ClientInfoMap* clients) {
DCHECK(io_thread_checker_.CalledOnValidThread());
const ClientInfoMap::iterator it = clients->find(client_id);
if (it == clients->end())
return false;
it->second.state_update_cb.Run(VIDEO_CAPTURE_STATE_STOPPED);
clients->erase(it);
return true;
}
mojom::VideoCaptureHost* VideoCaptureImpl::GetVideoCaptureHost() {
DCHECK(io_thread_checker_.CalledOnValidThread());
if (video_capture_host_for_testing_)
return video_capture_host_for_testing_;
if (!video_capture_host_.get())
video_capture_host_.Bind(std::move(video_capture_host_info_));
return video_capture_host_.get();
};
// static
void VideoCaptureImpl::DidFinishConsumingFrame(
const media::VideoFrameMetadata* metadata,
BufferFinishedCallback callback_to_io_thread) {
// Note: This function may be called on any thread by the VideoFrame
// destructor. |metadata| is still valid for read-access at this point.
double consumer_resource_utilization = -1.0;
if (!metadata->GetDouble(media::VideoFrameMetadata::RESOURCE_UTILIZATION,
&consumer_resource_utilization)) {
consumer_resource_utilization = -1.0;
}
std::move(callback_to_io_thread).Run(consumer_resource_utilization);
}
} // namespace content