blob: 5da9e7fd601e81ef94b3842fe853e5bd735c8448 [file] [log] [blame]
// Copyright 2018 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 "components/mirroring/service/video_capture_client.h"
#include "base/bind.h"
#include "media/base/bind_to_current_loop.h"
#include "media/base/video_frame.h"
#include "media/capture/mojom/video_capture_types.mojom.h"
namespace mirroring {
namespace {
// Required by mojom::VideoCaptureHost interface. Can be any number.
constexpr int kDeviceId = 0;
} // namespace
VideoCaptureClient::VideoCaptureClient(const media::VideoCaptureParams& params,
media::mojom::VideoCaptureHostPtr host)
: params_(params),
video_capture_host_(std::move(host)),
binding_(this),
weak_factory_(this) {
DCHECK(video_capture_host_);
}
VideoCaptureClient::~VideoCaptureClient() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
Stop();
}
void VideoCaptureClient::Start(FrameDeliverCallback deliver_callback,
base::OnceClosure error_callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DVLOG(1) << __func__;
DCHECK(!deliver_callback.is_null());
frame_deliver_callback_ = std::move(deliver_callback);
error_callback_ = std::move(error_callback);
media::mojom::VideoCaptureObserverPtr observer;
binding_.Bind(mojo::MakeRequest(&observer));
video_capture_host_->Start(kDeviceId, 0, params_, std::move(observer));
}
void VideoCaptureClient::Stop() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DVLOG(1) << __func__;
video_capture_host_->Stop(kDeviceId);
}
void VideoCaptureClient::Pause() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DVLOG(2) << __func__;
if (frame_deliver_callback_.is_null())
return;
frame_deliver_callback_.Reset();
video_capture_host_->Pause(kDeviceId);
}
void VideoCaptureClient::Resume(FrameDeliverCallback deliver_callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DVLOG(2) << __func__;
DCHECK(!deliver_callback.is_null());
if (!frame_deliver_callback_.is_null()) {
return;
}
frame_deliver_callback_ = std::move(deliver_callback);
video_capture_host_->Resume(kDeviceId, 0, params_);
}
void VideoCaptureClient::RequestRefreshFrame() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (frame_deliver_callback_.is_null())
return;
video_capture_host_->RequestRefreshFrame(kDeviceId);
}
void VideoCaptureClient::OnStateChanged(media::mojom::VideoCaptureState state) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DVLOG(2) << __func__ << " state: " << state;
switch (state) {
case media::mojom::VideoCaptureState::STARTED:
RequestRefreshFrame();
break;
case media::mojom::VideoCaptureState::FAILED:
if (!error_callback_.is_null())
std::move(error_callback_).Run();
break;
case media::mojom::VideoCaptureState::PAUSED:
case media::mojom::VideoCaptureState::RESUMED:
break;
case media::mojom::VideoCaptureState::STOPPED:
case media::mojom::VideoCaptureState::ENDED:
client_buffers_.clear();
mapped_buffers_.clear();
weak_factory_.InvalidateWeakPtrs();
error_callback_.Reset();
frame_deliver_callback_.Reset();
binding_.Close();
break;
}
}
void VideoCaptureClient::OnNewBuffer(
int32_t buffer_id,
media::mojom::VideoBufferHandlePtr buffer_handle) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DVLOG(3) << __func__ << ": buffer_id=" << buffer_id;
if (!buffer_handle->is_read_only_shmem_region() &&
!buffer_handle->is_shared_buffer_handle()) {
NOTIMPLEMENTED();
return;
}
const auto insert_result = client_buffers_.emplace(
std::make_pair(buffer_id, std::move(buffer_handle)));
DCHECK(insert_result.second);
}
void VideoCaptureClient::OnBufferReady(int32_t buffer_id,
media::mojom::VideoFrameInfoPtr info) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DVLOG(3) << __func__ << ": buffer_id=" << buffer_id;
bool consume_buffer = !frame_deliver_callback_.is_null();
if (info->pixel_format != media::PIXEL_FORMAT_I420 &&
info->pixel_format != media::PIXEL_FORMAT_Y16) {
consume_buffer = false;
LOG(DFATAL) << "Wrong pixel format, got pixel format:"
<< VideoPixelFormatToString(info->pixel_format);
}
if (!consume_buffer) {
video_capture_host_->ReleaseBuffer(kDeviceId, 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(crbug/618407): Fix upstream capturers to always set timestamp and
// reference time.
if (info->timestamp.is_zero())
info->timestamp = reference_time - first_frame_ref_time_;
// 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& buffer_iter = client_buffers_.find(buffer_id);
if (buffer_iter == client_buffers_.end()) {
LOG(DFATAL) << "Ignoring OnBufferReady() for unknown buffer.";
return;
}
scoped_refptr<media::VideoFrame> frame;
BufferFinishedCallback buffer_finished_callback;
if (buffer_iter->second->is_shared_buffer_handle()) {
// TODO(https://crbug.com/843117): Remove this case after migrating
// media::VideoCaptureDeviceClient to the new shared memory API.
auto mapping_iter = mapped_buffers_.find(buffer_id);
const size_t buffer_size =
media::VideoFrame::AllocationSize(info->pixel_format, info->coded_size);
if (mapping_iter != mapped_buffers_.end() &&
buffer_size > mapping_iter->second.second) {
// Unmaps shared memory for too-small region.
mapped_buffers_.erase(mapping_iter);
mapping_iter = mapped_buffers_.end();
}
if (mapping_iter == mapped_buffers_.end()) {
mojo::ScopedSharedBufferMapping mapping =
buffer_iter->second->get_shared_buffer_handle()->Map(buffer_size);
if (!mapping) {
video_capture_host_->ReleaseBuffer(kDeviceId, buffer_id, -1.0);
return;
}
mapping_iter =
mapped_buffers_
.emplace(std::make_pair(
buffer_id, MappingAndSize(std::move(mapping), buffer_size)))
.first;
}
const auto& buffer = mapping_iter->second;
frame = media::VideoFrame::WrapExternalData(
info->pixel_format, info->coded_size, info->visible_rect,
info->visible_rect.size(),
reinterpret_cast<uint8_t*>(buffer.first.get()), buffer.second,
info->timestamp);
buffer_finished_callback = media::BindToCurrentLoop(base::BindOnce(
&VideoCaptureClient::OnClientBufferFinished, weak_factory_.GetWeakPtr(),
buffer_id, base::ReadOnlySharedMemoryMapping()));
} else {
base::ReadOnlySharedMemoryMapping mapping =
buffer_iter->second->get_read_only_shmem_region().Map();
const size_t frame_allocation_size =
media::VideoFrame::AllocationSize(info->pixel_format, info->coded_size);
if (mapping.IsValid() && mapping.size() >= frame_allocation_size) {
frame = media::VideoFrame::WrapExternalData(
info->pixel_format, info->coded_size, info->visible_rect,
info->visible_rect.size(),
const_cast<uint8_t*>(static_cast<const uint8_t*>(mapping.memory())),
frame_allocation_size, info->timestamp);
}
buffer_finished_callback = media::BindToCurrentLoop(base::BindOnce(
&VideoCaptureClient::OnClientBufferFinished, weak_factory_.GetWeakPtr(),
buffer_id, std::move(mapping)));
}
if (!frame) {
LOG(DFATAL) << "Unable to wrap shared memory mapping.";
video_capture_host_->ReleaseBuffer(kDeviceId, buffer_id, -1.0);
OnStateChanged(media::mojom::VideoCaptureState::FAILED);
return;
}
frame->AddDestructionObserver(
base::BindOnce(&VideoCaptureClient::DidFinishConsumingFrame,
frame->metadata(), std::move(buffer_finished_callback)));
frame->metadata()->MergeInternalValuesFrom(info->metadata);
if (info->color_space.has_value())
frame->set_color_space(info->color_space.value());
frame_deliver_callback_.Run(frame);
}
void VideoCaptureClient::OnBufferDestroyed(int32_t buffer_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DVLOG(3) << __func__ << ": buffer_id=" << buffer_id;
const auto& buffer_iter = client_buffers_.find(buffer_id);
if (buffer_iter != client_buffers_.end())
client_buffers_.erase(buffer_iter);
const auto& mapping_iter = mapped_buffers_.find(buffer_id);
if (mapping_iter != mapped_buffers_.end())
mapped_buffers_.erase(mapping_iter);
}
void VideoCaptureClient::OnClientBufferFinished(
int buffer_id,
base::ReadOnlySharedMemoryMapping mapping,
double consumer_resource_utilization) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DVLOG(3) << __func__ << ": buffer_id=" << buffer_id;
// Buffer was already destroyed.
if (client_buffers_.find(buffer_id) == client_buffers_.end()) {
DCHECK(mapped_buffers_.find(buffer_id) == mapped_buffers_.end());
return;
}
video_capture_host_->ReleaseBuffer(kDeviceId, buffer_id,
consumer_resource_utilization);
}
// static
void VideoCaptureClient::DidFinishConsumingFrame(
const media::VideoFrameMetadata* metadata,
BufferFinishedCallback callback) {
// 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;
}
DCHECK(!callback.is_null());
std::move(callback).Run(consumer_resource_utilization);
}
} // namespace mirroring