| // 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_functions.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "build/build_config.h" |
| #include "content/browser/renderer_host/media/media_stream_manager.h" |
| #include "content/browser/renderer_host/media/video_capture_manager.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/common/content_switches.h" |
| #include "media/base/video_frame.h" |
| #include "media/capture/mojom/video_capture_types.mojom.h" |
| #include "media/capture/video/video_capture_buffer_pool.h" |
| #include "media/capture/video/video_capture_buffer_tracker_factory_impl.h" |
| #include "media/capture/video/video_capture_device_client.h" |
| #include "mojo/public/cpp/system/platform_handle.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 { |
| |
| // Counter used for identifying a DeviceRequest to start a capture device. |
| static int g_device_start_id = 0; |
| |
| static const int kInfiniteRatio = 99999; |
| |
| #define UMA_HISTOGRAM_ASPECT_RATIO(name, width, height) \ |
| base::UmaHistogramSparse( \ |
| name, (height) ? ((width)*100) / (height) : kInfiniteRatio); |
| |
| void LogVideoFrameDrop(media::VideoCaptureFrameDropReason reason, |
| blink::MediaStreamType stream_type) { |
| const int kEnumCount = |
| static_cast<int>(media::VideoCaptureFrameDropReason::kMaxValue) + 1; |
| UMA_HISTOGRAM_ENUMERATION("Media.VideoCapture.FrameDrop", reason, kEnumCount); |
| switch (stream_type) { |
| case blink::MEDIA_DEVICE_VIDEO_CAPTURE: |
| UMA_HISTOGRAM_ENUMERATION("Media.VideoCapture.FrameDrop.DeviceCapture", |
| reason, kEnumCount); |
| break; |
| case blink::MEDIA_GUM_TAB_VIDEO_CAPTURE: |
| UMA_HISTOGRAM_ENUMERATION("Media.VideoCapture.FrameDrop.GumTabCapture", |
| reason, kEnumCount); |
| break; |
| case blink::MEDIA_GUM_DESKTOP_VIDEO_CAPTURE: |
| UMA_HISTOGRAM_ENUMERATION( |
| "Media.VideoCapture.FrameDrop.GumDesktopCapture", reason, kEnumCount); |
| break; |
| case blink::MEDIA_DISPLAY_VIDEO_CAPTURE: |
| UMA_HISTOGRAM_ENUMERATION("Media.VideoCapture.FrameDrop.DisplayCapture", |
| reason, kEnumCount); |
| break; |
| default: |
| // Do nothing |
| return; |
| } |
| } |
| |
| void LogMaxConsecutiveVideoFrameDropCountExceeded( |
| media::VideoCaptureFrameDropReason reason, |
| blink::MediaStreamType stream_type) { |
| const int kEnumCount = |
| static_cast<int>(media::VideoCaptureFrameDropReason::kMaxValue) + 1; |
| UMA_HISTOGRAM_ENUMERATION("Media.VideoCapture.MaxFrameDropExceeded", reason, |
| kEnumCount); |
| switch (stream_type) { |
| case blink::MEDIA_DEVICE_VIDEO_CAPTURE: |
| UMA_HISTOGRAM_ENUMERATION( |
| "Media.VideoCapture.MaxFrameDropExceeded.DeviceCapture", reason, |
| kEnumCount); |
| break; |
| case blink::MEDIA_GUM_TAB_VIDEO_CAPTURE: |
| UMA_HISTOGRAM_ENUMERATION( |
| "Media.VideoCapture.MaxFrameDropExceeded.GumTabCapture", reason, |
| kEnumCount); |
| break; |
| case blink::MEDIA_GUM_DESKTOP_VIDEO_CAPTURE: |
| UMA_HISTOGRAM_ENUMERATION( |
| "Media.VideoCapture.MaxFrameDropExceeded.GumDesktopCapture", reason, |
| kEnumCount); |
| break; |
| case blink::MEDIA_DISPLAY_VIDEO_CAPTURE: |
| UMA_HISTOGRAM_ENUMERATION( |
| "Media.VideoCapture.MaxFrameDropExceeded.DisplayCapture", reason, |
| kEnumCount); |
| break; |
| default: |
| // Do nothing |
| return; |
| } |
| } |
| |
| void CallOnError(media::VideoCaptureError error, |
| VideoCaptureControllerEventHandler* client, |
| VideoCaptureControllerID id) { |
| client->OnError(id, error); |
| } |
| |
| void CallOnStarted(VideoCaptureControllerEventHandler* client, |
| VideoCaptureControllerID id) { |
| client->OnStarted(id); |
| } |
| |
| void CallOnStartedUsingGpuDecode(VideoCaptureControllerEventHandler* client, |
| VideoCaptureControllerID id) { |
| client->OnStartedUsingGpuDecode(id); |
| } |
| |
| } // anonymous namespace |
| |
| struct VideoCaptureController::ControllerClient { |
| ControllerClient(VideoCaptureControllerID id, |
| VideoCaptureControllerEventHandler* handler, |
| media::VideoCaptureSessionId session_id, |
| const media::VideoCaptureParams& params) |
| : controller_id(id), |
| event_handler(handler), |
| 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; |
| |
| const media::VideoCaptureSessionId session_id; |
| const media::VideoCaptureParams parameters; |
| |
| std::vector<int> known_buffer_context_ids; |
| // |buffer_context_id|s of buffers currently being consumed by this client. |
| std::vector<int> buffers_in_use; |
| |
| // 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::BufferContext::BufferContext( |
| int buffer_context_id, |
| int buffer_id, |
| media::VideoFrameConsumerFeedbackObserver* consumer_feedback_observer, |
| media::mojom::VideoBufferHandlePtr buffer_handle) |
| : buffer_context_id_(buffer_context_id), |
| buffer_id_(buffer_id), |
| is_retired_(false), |
| frame_feedback_id_(0), |
| consumer_feedback_observer_(consumer_feedback_observer), |
| buffer_handle_(std::move(buffer_handle)), |
| max_consumer_utilization_( |
| media::VideoFrameConsumerFeedbackObserver::kNoUtilizationRecorded), |
| consumer_hold_count_(0) {} |
| |
| VideoCaptureController::BufferContext::~BufferContext() = default; |
| |
| VideoCaptureController::BufferContext::BufferContext( |
| VideoCaptureController::BufferContext&& other) = default; |
| |
| VideoCaptureController::BufferContext& VideoCaptureController::BufferContext:: |
| operator=(BufferContext&& other) = default; |
| |
| void VideoCaptureController::BufferContext::RecordConsumerUtilization( |
| double utilization) { |
| if (std::isfinite(utilization) && utilization >= 0.0) { |
| max_consumer_utilization_ = |
| std::max(max_consumer_utilization_, utilization); |
| } |
| } |
| |
| void VideoCaptureController::BufferContext::IncreaseConsumerCount() { |
| consumer_hold_count_++; |
| } |
| |
| void VideoCaptureController::BufferContext::DecreaseConsumerCount() { |
| consumer_hold_count_--; |
| if (consumer_hold_count_ == 0) { |
| if (consumer_feedback_observer_ != nullptr && |
| max_consumer_utilization_ != |
| media::VideoFrameConsumerFeedbackObserver::kNoUtilizationRecorded) { |
| consumer_feedback_observer_->OnUtilizationReport( |
| frame_feedback_id_, max_consumer_utilization_); |
| } |
| buffer_read_permission_.reset(); |
| max_consumer_utilization_ = |
| media::VideoFrameConsumerFeedbackObserver::kNoUtilizationRecorded; |
| } |
| } |
| |
| media::mojom::VideoBufferHandlePtr |
| VideoCaptureController::BufferContext::CloneBufferHandle() { |
| // Unable to use buffer_handle_->Clone(), because shared_buffer does not |
| // support the copy constructor. |
| media::mojom::VideoBufferHandlePtr result = |
| media::mojom::VideoBufferHandle::New(); |
| if (buffer_handle_->is_shared_buffer_handle()) { |
| // Special behavior here: If the handle was already read-only, the Clone() |
| // call here will maintain that read-only permission. If it was read-write, |
| // the cloned handle will have read-write permission. |
| // |
| // TODO(crbug.com/797470): We should be able to demote read-write to |
| // read-only permissions when Clone()'ing handles. Currently, this causes a |
| // crash. |
| result->set_shared_buffer_handle( |
| buffer_handle_->get_shared_buffer_handle()->Clone( |
| mojo::SharedBufferHandle::AccessMode::READ_WRITE)); |
| } else if (buffer_handle_->is_read_only_shmem_region()) { |
| result->set_read_only_shmem_region( |
| buffer_handle_->get_read_only_shmem_region().Duplicate()); |
| } else if (buffer_handle_->is_mailbox_handles()) { |
| result->set_mailbox_handles(buffer_handle_->get_mailbox_handles()->Clone()); |
| } else { |
| NOTREACHED() << "Unexpected video buffer handle type"; |
| } |
| return result; |
| } |
| |
| VideoCaptureController::FrameDropLogState::FrameDropLogState( |
| media::VideoCaptureFrameDropReason reason) |
| : drop_count((reason == media::VideoCaptureFrameDropReason::kNone) ? 0 : 1), |
| drop_reason(reason), |
| max_log_count_exceeded(false) {} |
| |
| VideoCaptureController::VideoCaptureController( |
| const std::string& device_id, |
| blink::MediaStreamType stream_type, |
| const media::VideoCaptureParams& params, |
| std::unique_ptr<VideoCaptureDeviceLauncher> device_launcher, |
| base::RepeatingCallback<void(const std::string&)> emit_log_message_cb) |
| : serial_id_(g_device_start_id++), |
| device_id_(device_id), |
| stream_type_(stream_type), |
| parameters_(params), |
| device_launcher_(std::move(device_launcher)), |
| emit_log_message_cb_(std::move(emit_log_message_cb)), |
| device_launch_observer_(nullptr), |
| state_(blink::VIDEO_CAPTURE_STATE_STARTING), |
| has_received_frames_(false), |
| weak_ptr_factory_(this) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| } |
| |
| VideoCaptureController::~VideoCaptureController() = default; |
| |
| base::WeakPtr<VideoCaptureController> |
| VideoCaptureController::GetWeakPtrForIOThread() { |
| return weak_ptr_factory_.GetWeakPtr(); |
| } |
| |
| void VideoCaptureController::AddClient( |
| VideoCaptureControllerID id, |
| VideoCaptureControllerEventHandler* event_handler, |
| media::VideoCaptureSessionId session_id, |
| const media::VideoCaptureParams& params) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| std::ostringstream string_stream; |
| string_stream << "VideoCaptureController::AddClient(): id = " << id |
| << ", session_id = " << session_id |
| << ", params.requested_format = " |
| << media::VideoCaptureFormat::ToString(params.requested_format); |
| EmitLogMessage(string_stream.str(), 1); |
| |
| // 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_format == media::PIXEL_FORMAT_Y16 || |
| params.requested_format.pixel_format == media::PIXEL_FORMAT_ARGB)) { |
| // 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, |
| media::VideoCaptureError:: |
| kVideoCaptureControllerInvalidOrUnsupportedVideoCaptureParametersRequested); |
| return; |
| } |
| |
| // If this is the first client added to the controller, cache the parameters. |
| if (controller_clients_.empty()) |
| video_capture_format_ = params.requested_format; |
| |
| // Signal error in case device is already in error state. |
| if (state_ == blink::VIDEO_CAPTURE_STATE_ERROR) { |
| event_handler->OnError( |
| id, |
| media::VideoCaptureError::kVideoCaptureControllerIsAlreadyInErrorState); |
| return; |
| } |
| |
| // Do nothing if this client has called AddClient before. |
| if (FindClient(id, event_handler, controller_clients_)) |
| return; |
| |
| // If the device has reported OnStarted event, report it to this client here. |
| if (state_ == blink::VIDEO_CAPTURE_STATE_STARTED) |
| event_handler->OnStarted(id); |
| |
| std::unique_ptr<ControllerClient> client = |
| std::make_unique<ControllerClient>(id, event_handler, session_id, params); |
| // If we already have gotten frame_info from the device, repeat it to the new |
| // client. |
| if (state_ != blink::VIDEO_CAPTURE_STATE_ERROR) { |
| controller_clients_.push_back(std::move(client)); |
| } |
| } |
| |
| int VideoCaptureController::RemoveClient( |
| VideoCaptureControllerID id, |
| VideoCaptureControllerEventHandler* event_handler) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| std::ostringstream string_stream; |
| string_stream << "VideoCaptureController::RemoveClient: id = " << id; |
| EmitLogMessage(string_stream.str(), 1); |
| |
| ControllerClient* client = FindClient(id, event_handler, controller_clients_); |
| if (!client) |
| return kInvalidMediaCaptureSessionId; |
| |
| for (const auto& buffer_id : client->buffers_in_use) { |
| OnClientFinishedConsumingBuffer( |
| client, buffer_id, |
| media::VideoFrameConsumerFeedbackObserver::kNoUtilizationRecorded); |
| } |
| client->buffers_in_use.clear(); |
| |
| int session_id = client->session_id; |
| controller_clients_.remove_if( |
| [client](const std::unique_ptr<ControllerClient>& ptr) { |
| return ptr.get() == 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; |
| } |
| |
| size_t VideoCaptureController::GetClientCount() const { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| return controller_clients_.size(); |
| } |
| |
| bool VideoCaptureController::HasActiveClient() const { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| for (const auto& client : controller_clients_) { |
| if (!client->paused) |
| return true; |
| } |
| return false; |
| } |
| |
| bool VideoCaptureController::HasPausedClient() const { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| for (const auto& client : controller_clients_) { |
| if (client->paused) |
| return true; |
| } |
| return false; |
| } |
| |
| void VideoCaptureController::StopSession(int session_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| std::ostringstream string_stream; |
| string_stream << "VideoCaptureController::StopSession: session_id = " |
| << session_id; |
| EmitLogMessage(string_stream.str(), 1); |
| |
| 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, |
| 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. |
| if (!client) { |
| NOTREACHED(); |
| return; |
| } |
| auto buffers_in_use_entry_iter = |
| std::find(std::begin(client->buffers_in_use), |
| std::end(client->buffers_in_use), buffer_id); |
| if (buffers_in_use_entry_iter == std::end(client->buffers_in_use)) { |
| NOTREACHED(); |
| return; |
| } |
| client->buffers_in_use.erase(buffers_in_use_entry_iter); |
| |
| OnClientFinishedConsumingBuffer(client, buffer_id, |
| consumer_resource_utilization); |
| } |
| |
| const base::Optional<media::VideoCaptureFormat> |
| VideoCaptureController::GetVideoCaptureFormat() const { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| return video_capture_format_; |
| } |
| |
| void VideoCaptureController::OnNewBuffer( |
| int32_t buffer_id, |
| media::mojom::VideoBufferHandlePtr buffer_handle) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK(FindUnretiredBufferContextFromBufferId(buffer_id) == |
| buffer_contexts_.end()); |
| buffer_contexts_.emplace_back(next_buffer_context_id_++, buffer_id, |
| launched_device_.get(), |
| std::move(buffer_handle)); |
| } |
| |
| void VideoCaptureController::OnFrameReadyInBuffer( |
| int buffer_id, |
| int frame_feedback_id, |
| std::unique_ptr< |
| media::VideoCaptureDevice::Client::Buffer::ScopedAccessPermission> |
| buffer_read_permission, |
| media::mojom::VideoFrameInfoPtr frame_info) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK_NE(buffer_id, media::VideoCaptureBufferPool::kInvalidId); |
| TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("video_and_image_capture"), |
| "VideoCaptureController::OnFrameReadyInBuffer"); |
| |
| frame_drop_log_state_ = FrameDropLogState(); |
| |
| auto buffer_context_iter = FindUnretiredBufferContextFromBufferId(buffer_id); |
| DCHECK(buffer_context_iter != buffer_contexts_.end()); |
| buffer_context_iter->set_frame_feedback_id(frame_feedback_id); |
| DCHECK(!buffer_context_iter->HasConsumers()); |
| |
| if (state_ != blink::VIDEO_CAPTURE_STATE_ERROR) { |
| const int buffer_context_id = buffer_context_iter->buffer_context_id(); |
| for (const auto& client : controller_clients_) { |
| if (client->session_closed || client->paused) |
| continue; |
| |
| // On the first use of a BufferContext for a particular client, call |
| // OnBufferCreated(). |
| if (!base::ContainsValue(client->known_buffer_context_ids, |
| buffer_context_id)) { |
| client->known_buffer_context_ids.push_back(buffer_context_id); |
| client->event_handler->OnNewBuffer( |
| client->controller_id, buffer_context_iter->CloneBufferHandle(), |
| buffer_context_id); |
| } |
| |
| if (!base::ContainsValue(client->buffers_in_use, buffer_context_id)) |
| client->buffers_in_use.push_back(buffer_context_id); |
| else |
| NOTREACHED() << "Unexpected duplicate buffer: " << buffer_context_id; |
| |
| buffer_context_iter->IncreaseConsumerCount(); |
| client->event_handler->OnBufferReady(client->controller_id, |
| buffer_context_id, frame_info); |
| } |
| if (buffer_context_iter->HasConsumers()) { |
| buffer_context_iter->set_read_permission( |
| std::move(buffer_read_permission)); |
| } |
| } |
| |
| if (!has_received_frames_) { |
| UMA_HISTOGRAM_COUNTS_1M("Media.VideoCapture.Width", |
| frame_info->coded_size.width()); |
| UMA_HISTOGRAM_COUNTS_1M("Media.VideoCapture.Height", |
| frame_info->coded_size.height()); |
| UMA_HISTOGRAM_ASPECT_RATIO("Media.VideoCapture.AspectRatio", |
| frame_info->coded_size.width(), |
| frame_info->coded_size.height()); |
| double frame_rate = 0.0f; |
| if (video_capture_format_) { |
| media::VideoFrameMetadata metadata; |
| metadata.MergeInternalValuesFrom(frame_info->metadata); |
| if (!metadata.GetDouble(VideoFrameMetadata::FRAME_RATE, &frame_rate)) { |
| frame_rate = video_capture_format_->frame_rate; |
| } |
| } |
| UMA_HISTOGRAM_COUNTS_1M("Media.VideoCapture.FrameRate", frame_rate); |
| UMA_HISTOGRAM_TIMES("Media.VideoCapture.DelayUntilFirstFrame", |
| base::TimeTicks::Now() - time_of_start_request_); |
| OnLog("First frame received at VideoCaptureController"); |
| has_received_frames_ = true; |
| } |
| } |
| |
| void VideoCaptureController::OnBufferRetired(int buffer_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| auto buffer_context_iter = FindUnretiredBufferContextFromBufferId(buffer_id); |
| DCHECK(buffer_context_iter != buffer_contexts_.end()); |
| |
| // If there are any clients still using the buffer, we need to allow them |
| // to finish up. We need to hold on to the BufferContext entry until then, |
| // because it contains the consumer hold. |
| if (!buffer_context_iter->HasConsumers()) |
| ReleaseBufferContext(buffer_context_iter); |
| else |
| buffer_context_iter->set_is_retired(); |
| } |
| |
| void VideoCaptureController::OnError(media::VideoCaptureError error) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| state_ = blink::VIDEO_CAPTURE_STATE_ERROR; |
| PerformForClientsWithOpenSession(base::BindRepeating(&CallOnError, error)); |
| } |
| |
| void VideoCaptureController::OnFrameDropped( |
| media::VideoCaptureFrameDropReason reason) { |
| TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("video_and_image_capture"), |
| "VideoCaptureController::OnFrameDropped"); |
| if (reason == frame_drop_log_state_.drop_reason) { |
| if (frame_drop_log_state_.max_log_count_exceeded) |
| return; |
| |
| if (++frame_drop_log_state_.drop_count > |
| kMaxConsecutiveFrameDropForSameReasonCount) { |
| frame_drop_log_state_.max_log_count_exceeded = true; |
| LogMaxConsecutiveVideoFrameDropCountExceeded(reason, stream_type_); |
| std::ostringstream string_stream; |
| string_stream << "Too many consecutive frames dropped with reason code " |
| << static_cast<int>(reason) |
| << ". Stopping to log dropped frames for this reason in " |
| "order to avoid log spam."; |
| EmitLogMessage(string_stream.str(), 1); |
| return; |
| } |
| } else { |
| frame_drop_log_state_ = FrameDropLogState(reason); |
| } |
| |
| LogVideoFrameDrop(reason, stream_type_); |
| std::ostringstream string_stream; |
| string_stream << "Frame dropped with reason code " |
| << static_cast<int>(reason); |
| EmitLogMessage(string_stream.str(), 1); |
| } |
| |
| void VideoCaptureController::OnLog(const std::string& message) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| EmitLogMessage(message, 3); |
| } |
| |
| void VideoCaptureController::OnStarted() { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| EmitLogMessage(__func__, 3); |
| state_ = blink::VIDEO_CAPTURE_STATE_STARTED; |
| PerformForClientsWithOpenSession(base::BindRepeating(&CallOnStarted)); |
| } |
| |
| void VideoCaptureController::OnStartedUsingGpuDecode() { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| EmitLogMessage(__func__, 3); |
| PerformForClientsWithOpenSession( |
| base::BindRepeating(&CallOnStartedUsingGpuDecode)); |
| } |
| |
| void VideoCaptureController::OnStopped() { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| EmitLogMessage(__func__, 3); |
| // Clients of VideoCaptureController are currently not interested in |
| // OnStopped events, so we simply swallow the event here. Note that, if we |
| // wanted to forward it to clients in the future, care would have to be taken |
| // for the case of there being outstanding OnBufferRetired() events that have |
| // been deferred because a client was still consuming a retired buffer. |
| } |
| |
| void VideoCaptureController::OnDeviceLaunched( |
| std::unique_ptr<LaunchedVideoCaptureDevice> device) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| EmitLogMessage(__func__, 3); |
| launched_device_ = std::move(device); |
| for (auto& entry : buffer_contexts_) |
| entry.set_consumer_feedback_observer(launched_device_.get()); |
| if (device_launch_observer_) { |
| device_launch_observer_->OnDeviceLaunched(this); |
| } |
| } |
| |
| void VideoCaptureController::OnDeviceLaunchFailed( |
| media::VideoCaptureError error) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| EmitLogMessage(__func__, 3); |
| if (device_launch_observer_) { |
| device_launch_observer_->OnDeviceLaunchFailed(this, error); |
| device_launch_observer_ = nullptr; |
| } |
| } |
| |
| void VideoCaptureController::OnDeviceLaunchAborted() { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| EmitLogMessage(__func__, 3); |
| if (device_launch_observer_) { |
| device_launch_observer_->OnDeviceLaunchAborted(); |
| device_launch_observer_ = nullptr; |
| } |
| } |
| |
| void VideoCaptureController::OnDeviceConnectionLost() { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| EmitLogMessage(__func__, 3); |
| if (device_launch_observer_) { |
| device_launch_observer_->OnDeviceConnectionLost(this); |
| device_launch_observer_ = nullptr; |
| } |
| } |
| |
| void VideoCaptureController::CreateAndStartDeviceAsync( |
| const media::VideoCaptureParams& params, |
| VideoCaptureDeviceLaunchObserver* observer, |
| base::OnceClosure done_cb) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("video_and_image_capture"), |
| "VideoCaptureController::CreateAndStartDeviceAsync"); |
| std::ostringstream string_stream; |
| string_stream |
| << "VideoCaptureController::CreateAndStartDeviceAsync: serial_id = " |
| << serial_id() << ", device_id = " << device_id(); |
| EmitLogMessage(string_stream.str(), 1); |
| time_of_start_request_ = base::TimeTicks::Now(); |
| device_launch_observer_ = observer; |
| device_launcher_->LaunchDeviceAsync( |
| device_id_, stream_type_, params, GetWeakPtrForIOThread(), |
| base::BindOnce(&VideoCaptureController::OnDeviceConnectionLost, |
| GetWeakPtrForIOThread()), |
| this, std::move(done_cb)); |
| } |
| |
| void VideoCaptureController::ReleaseDeviceAsync(base::OnceClosure done_cb) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("video_and_image_capture"), |
| "VideoCaptureController::ReleaseDeviceAsync"); |
| std::ostringstream string_stream; |
| string_stream << "VideoCaptureController::ReleaseDeviceAsync: serial_id = " |
| << serial_id() << ", device_id = " << device_id(); |
| EmitLogMessage(string_stream.str(), 1); |
| if (!launched_device_) { |
| device_launcher_->AbortLaunch(); |
| return; |
| } |
| launched_device_.reset(); |
| } |
| |
| bool VideoCaptureController::IsDeviceAlive() const { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| return launched_device_ != nullptr; |
| } |
| |
| void VideoCaptureController::GetPhotoState( |
| media::VideoCaptureDevice::GetPhotoStateCallback callback) const { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK(launched_device_); |
| launched_device_->GetPhotoState(std::move(callback)); |
| } |
| |
| void VideoCaptureController::SetPhotoOptions( |
| media::mojom::PhotoSettingsPtr settings, |
| media::VideoCaptureDevice::SetPhotoOptionsCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK(launched_device_); |
| launched_device_->SetPhotoOptions(std::move(settings), std::move(callback)); |
| } |
| |
| void VideoCaptureController::TakePhoto( |
| media::VideoCaptureDevice::TakePhotoCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK(launched_device_); |
| TRACE_EVENT_INSTANT0(TRACE_DISABLED_BY_DEFAULT("video_and_image_capture"), |
| "VideoCaptureController::TakePhoto", |
| TRACE_EVENT_SCOPE_PROCESS); |
| launched_device_->TakePhoto(std::move(callback)); |
| } |
| |
| void VideoCaptureController::MaybeSuspend() { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK(launched_device_); |
| EmitLogMessage(__func__, 3); |
| launched_device_->MaybeSuspendDevice(); |
| } |
| |
| void VideoCaptureController::Resume() { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK(launched_device_); |
| EmitLogMessage(__func__, 3); |
| launched_device_->ResumeDevice(); |
| } |
| |
| void VideoCaptureController::RequestRefreshFrame() { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK(launched_device_); |
| launched_device_->RequestRefreshFrame(); |
| } |
| |
| void VideoCaptureController::SetDesktopCaptureWindowIdAsync( |
| gfx::NativeViewId window_id, |
| base::OnceClosure done_cb) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK(launched_device_); |
| launched_device_->SetDesktopCaptureWindowIdAsync(window_id, |
| std::move(done_cb)); |
| } |
| |
| VideoCaptureController::ControllerClient* VideoCaptureController::FindClient( |
| VideoCaptureControllerID id, |
| VideoCaptureControllerEventHandler* handler, |
| const ControllerClients& clients) { |
| for (const auto& client : clients) { |
| if (client->controller_id == id && client->event_handler == handler) |
| return client.get(); |
| } |
| return nullptr; |
| } |
| |
| VideoCaptureController::ControllerClient* VideoCaptureController::FindClient( |
| int session_id, |
| const ControllerClients& clients) { |
| for (const auto& client : clients) { |
| if (client->session_id == session_id) |
| return client.get(); |
| } |
| return nullptr; |
| } |
| |
| std::vector<VideoCaptureController::BufferContext>::iterator |
| VideoCaptureController::FindBufferContextFromBufferContextId( |
| int buffer_context_id) { |
| return std::find_if(buffer_contexts_.begin(), buffer_contexts_.end(), |
| [buffer_context_id](const BufferContext& entry) { |
| return entry.buffer_context_id() == buffer_context_id; |
| }); |
| } |
| |
| std::vector<VideoCaptureController::BufferContext>::iterator |
| VideoCaptureController::FindUnretiredBufferContextFromBufferId(int buffer_id) { |
| return std::find_if(buffer_contexts_.begin(), buffer_contexts_.end(), |
| [buffer_id](const BufferContext& entry) { |
| return (entry.buffer_id() == buffer_id) && |
| (entry.is_retired() == false); |
| }); |
| } |
| |
| void VideoCaptureController::OnClientFinishedConsumingBuffer( |
| ControllerClient* client, |
| int buffer_context_id, |
| double consumer_resource_utilization) { |
| auto buffer_context_iter = |
| FindBufferContextFromBufferContextId(buffer_context_id); |
| DCHECK(buffer_context_iter != buffer_contexts_.end()); |
| |
| buffer_context_iter->RecordConsumerUtilization(consumer_resource_utilization); |
| buffer_context_iter->DecreaseConsumerCount(); |
| if (!buffer_context_iter->HasConsumers() && |
| buffer_context_iter->is_retired()) { |
| ReleaseBufferContext(buffer_context_iter); |
| } |
| } |
| |
| void VideoCaptureController::ReleaseBufferContext( |
| const std::vector<BufferContext>::iterator& buffer_context_iter) { |
| for (const auto& client : controller_clients_) { |
| if (client->session_closed) |
| continue; |
| auto entry_iter = std::find(std::begin(client->known_buffer_context_ids), |
| std::end(client->known_buffer_context_ids), |
| buffer_context_iter->buffer_context_id()); |
| if (entry_iter != std::end(client->known_buffer_context_ids)) { |
| client->known_buffer_context_ids.erase(entry_iter); |
| client->event_handler->OnBufferDestroyed( |
| client->controller_id, buffer_context_iter->buffer_context_id()); |
| } |
| } |
| buffer_contexts_.erase(buffer_context_iter); |
| } |
| |
| void VideoCaptureController::PerformForClientsWithOpenSession( |
| EventHandlerAction action) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| for (const auto& client : controller_clients_) { |
| if (client->session_closed) |
| continue; |
| action.Run(client->event_handler, client->controller_id); |
| } |
| } |
| |
| void VideoCaptureController::EmitLogMessage(const std::string& message, |
| int verbose_log_level) { |
| DVLOG(verbose_log_level) << message; |
| emit_log_message_cb_.Run(message); |
| } |
| |
| } // namespace content |