| // Copyright 2018 The Chromium OS 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 "media_perception/mojo_connector.h" |
| |
| #include <map> |
| #include <utility> |
| #include <vector> |
| |
| #include <mojo/core/embedder/embedder.h> |
| #include <mojo/public/cpp/system/invitation.h> |
| |
| #include "media_perception/serialized_proto.h" |
| |
| namespace mri { |
| |
| namespace { |
| |
| CreatePushSubscriptionResultCode GetCreatePushSubscriptionResultCode( |
| video_capture::mojom::CreatePushSubscriptionResultCode code) { |
| switch (code) { |
| case video_capture::mojom::CreatePushSubscriptionResultCode::kFailed: |
| return CreatePushSubscriptionResultCode::FAILED; |
| case video_capture::mojom:: |
| CreatePushSubscriptionResultCode::kCreatedWithDifferentSettings: |
| return CreatePushSubscriptionResultCode:: |
| CREATED_WITH_DIFFERENT_SETTINGS; |
| case video_capture::mojom:: |
| CreatePushSubscriptionResultCode::kCreatedWithRequestedSettings: |
| return CreatePushSubscriptionResultCode:: |
| CREATED_WITH_REQUESTED_SETTINGS; |
| } |
| return CreatePushSubscriptionResultCode::RESULT_UNKNOWN; |
| } |
| |
| PixelFormat GetPixelFormatFromVideoCapturePixelFormat( |
| media::mojom::VideoCapturePixelFormat format) { |
| switch (format) { |
| case media::mojom::VideoCapturePixelFormat::I420: |
| return PixelFormat::I420; |
| case media::mojom::VideoCapturePixelFormat::MJPEG: |
| return PixelFormat::MJPEG; |
| default: |
| return PixelFormat::FORMAT_UNKNOWN; |
| } |
| return PixelFormat::FORMAT_UNKNOWN; |
| } |
| |
| media::mojom::VideoCapturePixelFormat GetVideoCapturePixelFormatFromPixelFormat( |
| PixelFormat pixel_format) { |
| switch (pixel_format) { |
| case PixelFormat::I420: |
| return media::mojom::VideoCapturePixelFormat::I420; |
| case PixelFormat::MJPEG: |
| return media::mojom::VideoCapturePixelFormat::MJPEG; |
| default: |
| return media::mojom::VideoCapturePixelFormat::UNKNOWN; |
| } |
| return media::mojom::VideoCapturePixelFormat::UNKNOWN; |
| } |
| |
| constexpr char kConnectorPipe[] = "mpp-connector-pipe"; |
| |
| } // namespace |
| |
| MojoConnector::MojoConnector(): ipc_thread_("IpcThread") { |
| mojo::core::Init(); |
| LOG(INFO) << "Starting IPC thread."; |
| if (!ipc_thread_.StartWithOptions( |
| base::Thread::Options(base::MessageLoop::TYPE_IO, 0))) { |
| LOG(ERROR) << "Failed to start IPC Thread"; |
| } |
| ipc_support_ = std::make_unique<mojo::core::ScopedIPCSupport>( |
| ipc_thread_.task_runner(), |
| mojo::core::ScopedIPCSupport::ShutdownPolicy::FAST); |
| |
| unique_device_counter_ = 1; |
| } |
| |
| void MojoConnector::SetVideoCaptureServiceClient( |
| std::shared_ptr<VideoCaptureServiceClient> video_capture_service_client) { |
| video_capture_service_client_ = video_capture_service_client; |
| } |
| |
| void MojoConnector::SetChromeAudioServiceClient( |
| std::shared_ptr<ChromeAudioServiceClient> chrome_audio_service_client) { |
| chrome_audio_service_client_ = chrome_audio_service_client; |
| } |
| |
| void MojoConnector::SetRtanalytics( |
| std::shared_ptr<Rtanalytics> rtanalytics) { |
| rtanalytics_ = rtanalytics; |
| } |
| |
| void MojoConnector::ReceiveMojoInvitationFileDescriptor(int fd_int) { |
| base::ScopedFD fd(fd_int); |
| if (!fd.is_valid()) { |
| LOG(ERROR) << "FD is not valid."; |
| return; |
| } |
| ipc_thread_.task_runner()->PostTask( |
| FROM_HERE, base::Bind(&MojoConnector::AcceptConnectionOnIpcThread, |
| base::Unretained(this), base::Passed(&fd))); |
| } |
| |
| void MojoConnector::OnConnectionErrorOrClosed() { |
| LOG(ERROR) << "Connection error/closed received"; |
| } |
| |
| void MojoConnector::OnVideoSourceProviderConnectionErrorOrClosed() { |
| std::lock_guard<std::mutex> lock(vcs_connection_state_mutex_); |
| is_connected_to_vcs_ = false; |
| } |
| |
| void MojoConnector::AcceptConnectionOnIpcThread(base::ScopedFD fd) { |
| CHECK(ipc_thread_.task_runner()->BelongsToCurrentThread()); |
| mojo::IncomingInvitation invitation = mojo::IncomingInvitation::Accept( |
| mojo::PlatformChannelEndpoint(mojo::PlatformHandle(std::move(fd)))); |
| mojo::ScopedMessagePipeHandle child_pipe = |
| invitation.ExtractMessagePipe(kConnectorPipe); |
| if (!child_pipe.is_valid()) { |
| LOG(ERROR) << "child_pipe is not valid"; |
| } |
| media_perception_service_impl_ = std::make_unique<MediaPerceptionServiceImpl>( |
| std::move(child_pipe), |
| base::Bind(&MojoConnector::OnConnectionErrorOrClosed, |
| base::Unretained(this)), |
| video_capture_service_client_, |
| chrome_audio_service_client_, |
| rtanalytics_); |
| } |
| |
| void MojoConnector::ConnectToVideoCaptureService() { |
| std::lock_guard<std::mutex> lock(vcs_connection_state_mutex_); |
| if (!is_connected_to_vcs_) { |
| ipc_thread_.task_runner()->PostTask( |
| FROM_HERE, |
| base::Bind(&MojoConnector::ConnectToVideoCaptureServiceOnIpcThread, |
| base::Unretained(this))); |
| is_connected_to_vcs_ = true; |
| } |
| } |
| |
| void MojoConnector::ConnectToVideoCaptureServiceOnIpcThread() { |
| unique_device_counter_ = 1; |
| |
| media_perception_service_impl_->ConnectToVideoCaptureService( |
| mojo::MakeRequest(&video_source_provider_)); |
| video_source_provider_.set_connection_error_handler( |
| base::Bind(&MojoConnector::OnVideoSourceProviderConnectionErrorOrClosed, |
| base::Unretained(this))); |
| } |
| |
| bool MojoConnector::IsConnectedToVideoCaptureService() { |
| std::lock_guard<std::mutex> lock(vcs_connection_state_mutex_); |
| return is_connected_to_vcs_; |
| } |
| |
| void MojoConnector::GetDevices( |
| const VideoCaptureServiceClient::GetDevicesCallback& callback) { |
| ipc_thread_.task_runner()->PostTask( |
| FROM_HERE, base::Bind(&MojoConnector::GetDevicesOnIpcThread, |
| base::Unretained(this), callback)); |
| } |
| |
| void MojoConnector::GetDevicesOnIpcThread( |
| const VideoCaptureServiceClient::GetDevicesCallback& callback) { |
| video_source_provider_->GetSourceInfos(base::Bind( |
| &MojoConnector::OnDeviceInfosReceived, base::Unretained(this), callback)); |
| } |
| |
| std::string MojoConnector::GetObfuscatedDeviceId( |
| const std::string& device_id, |
| const std::string& display_name) { |
| const std::string unique_id = device_id + display_name; |
| std::map<std::string, std::string>::iterator it = |
| unique_id_map_.find(unique_id); |
| if (it == unique_id_map_.end()) { |
| std::string obfuscated_id = std::to_string(unique_device_counter_); |
| unique_id_map_.insert( |
| std::make_pair(unique_id, obfuscated_id)); |
| // Increment the counter so the next obfuscated id is different. |
| unique_device_counter_++; |
| return obfuscated_id; |
| } |
| |
| // Obfuscated id already created for this device. |
| return it->second; |
| } |
| |
| void MojoConnector::OnDeviceInfosReceived( |
| const VideoCaptureServiceClient::GetDevicesCallback& callback, |
| std::vector<media::mojom::VideoCaptureDeviceInfoPtr> infos) { |
| LOG(INFO) << "Got callback for device infos."; |
| std::vector<SerializedVideoDevice> devices; |
| for (const auto& capture_device : infos) { |
| VideoDevice device; |
| const std::string device_id = capture_device->descriptor->device_id; |
| const std::string obfuscated_device_id = GetObfuscatedDeviceId( |
| device_id, capture_device->descriptor->display_name); |
| obfuscated_device_id_map_.insert( |
| std::make_pair(obfuscated_device_id, device_id)); |
| device.set_id(obfuscated_device_id); |
| device.set_display_name(capture_device->descriptor->display_name); |
| device.set_model_id(capture_device->descriptor->model_id); |
| LOG(INFO) << "Device: " << device.display_name(); |
| for (const auto& capture_format : capture_device->supported_formats) { |
| VideoStreamParams supported_format; |
| supported_format.set_width_in_pixels(capture_format->frame_size->width); |
| supported_format.set_height_in_pixels(capture_format->frame_size->height); |
| supported_format.set_frame_rate_in_frames_per_second( |
| capture_format->frame_rate); |
| supported_format.set_pixel_format( |
| GetPixelFormatFromVideoCapturePixelFormat( |
| capture_format->pixel_format)); |
| *device.add_supported_configurations() = supported_format; |
| } |
| devices.push_back(Serialized<VideoDevice>(device).GetBytes()); |
| } |
| callback(devices); |
| } |
| |
| void MojoConnector::OpenDevice( |
| const std::string& device_id, |
| bool force_reopen_with_settings, |
| std::shared_ptr<VideoFrameHandlerImpl> video_frame_handler_impl, |
| const VideoStreamParams& capture_format, |
| const VideoCaptureServiceClient::OpenDeviceCallback& callback) { |
| ipc_thread_.task_runner()->PostTask( |
| FROM_HERE, base::Bind(&MojoConnector::OpenDeviceOnIpcThread, |
| base::Unretained(this), |
| device_id, force_reopen_with_settings, |
| video_frame_handler_impl, capture_format, callback)); |
| } |
| |
| void MojoConnector::OpenDeviceOnIpcThread( |
| const std::string& device_id, |
| bool force_reopen_with_settings, |
| std::shared_ptr<VideoFrameHandlerImpl> video_frame_handler_impl, |
| const VideoStreamParams& capture_format, |
| const VideoCaptureServiceClient::OpenDeviceCallback& callback) { |
| std::map<std::string, std::string>::iterator it = |
| obfuscated_device_id_map_.find(device_id); |
| if (it == obfuscated_device_id_map_.end()) { |
| LOG(ERROR) << "Device id not found in obfuscated_device_id map."; |
| callback(device_id, |
| CreatePushSubscriptionResultCode::FAILED, |
| Serialized<VideoStreamParams>(capture_format).GetBytes()); |
| } |
| |
| std::map<std::string, VideoSourceAndPushSubscription>::iterator |
| device_it = device_id_to_active_device_map_.find(device_id); |
| // Check to see if the device is already opened. |
| if (device_it != device_id_to_active_device_map_.end()) { |
| callback(device_id, |
| CreatePushSubscriptionResultCode::ALREADY_OPEN, |
| Serialized<VideoStreamParams>( |
| video_frame_handler_impl->GetCaptureFormat()).GetBytes()); |
| return; |
| } |
| |
| // Create a new struct and move it into the member variable map. |
| VideoSourceAndPushSubscription new_device; |
| device_id_to_active_device_map_.insert( |
| std::make_pair(device_id, std::move(new_device))); |
| device_it = device_id_to_active_device_map_.find(device_id); |
| |
| video_source_provider_->GetVideoSource( |
| it->second, mojo::MakeRequest(&device_it->second.video_source)); |
| |
| auto requested_settings = media::mojom::VideoCaptureParams::New(); |
| requested_settings->requested_format = |
| media::mojom::VideoCaptureFormat::New(); |
| |
| requested_settings->requested_format->frame_rate = |
| capture_format.frame_rate_in_frames_per_second(); |
| |
| requested_settings->requested_format->pixel_format = |
| GetVideoCapturePixelFormatFromPixelFormat(capture_format.pixel_format()); |
| |
| requested_settings->requested_format->frame_size = gfx::mojom::Size::New(); |
| requested_settings->requested_format->frame_size->width = |
| capture_format.width_in_pixels(); |
| requested_settings->requested_format->frame_size->height = |
| capture_format.height_in_pixels(); |
| |
| requested_settings->buffer_type = |
| media::mojom::VideoCaptureBufferType::kSharedMemoryViaRawFileDescriptor; |
| |
| device_it->second.video_source->CreatePushSubscription( |
| video_frame_handler_impl->CreateInterfacePtr(), |
| std::move(requested_settings), |
| force_reopen_with_settings, |
| mojo::MakeRequest(&device_it->second.push_video_stream_subscription), |
| base::Bind(&MojoConnector::OnCreatePushSubscriptionCallback, |
| base::Unretained(this), device_id, callback)); |
| } |
| |
| void MojoConnector::OnCreatePushSubscriptionCallback( |
| const std::string& device_id, |
| const VideoCaptureServiceClient::OpenDeviceCallback& callback, |
| video_capture::mojom::CreatePushSubscriptionResultCode code, |
| media::mojom::VideoCaptureParamsPtr settings_opened_with) { |
| VideoStreamParams params; |
| params.set_frame_rate_in_frames_per_second( |
| settings_opened_with->requested_format->frame_rate); |
| params.set_pixel_format(GetPixelFormatFromVideoCapturePixelFormat( |
| settings_opened_with->requested_format->pixel_format)); |
| params.set_width_in_pixels( |
| settings_opened_with->requested_format->frame_size->width); |
| params.set_height_in_pixels( |
| settings_opened_with->requested_format->frame_size->height); |
| callback(device_id, |
| GetCreatePushSubscriptionResultCode(code), |
| Serialized<VideoStreamParams>(params).GetBytes()); |
| } |
| |
| bool MojoConnector::ActivateDevice(const std::string& device_id) { |
| std::map<std::string, VideoSourceAndPushSubscription>::iterator |
| it = device_id_to_active_device_map_.find(device_id); |
| if (it == device_id_to_active_device_map_.end()) { |
| LOG(ERROR) << "Device id not found in active device map."; |
| return false; |
| } |
| it->second.push_video_stream_subscription->Activate(); |
| return true; |
| } |
| |
| void MojoConnector::StopVideoCapture(const std::string& device_id) { |
| ipc_thread_.task_runner()->PostTask( |
| FROM_HERE, base::Bind(&MojoConnector::StopVideoCaptureOnIpcThread, |
| base::Unretained(this), device_id)); |
| } |
| |
| void MojoConnector::StopVideoCaptureOnIpcThread(const std::string& device_id) { |
| device_id_to_active_device_map_.erase(device_id); |
| } |
| |
| void MojoConnector::CreateVirtualDevice( |
| const VideoDevice& video_device, |
| std::shared_ptr<ProducerImpl> producer_impl, |
| const VideoCaptureServiceClient::VirtualDeviceCallback& callback) { |
| ipc_thread_.task_runner()->PostTask( |
| FROM_HERE, base::Bind(&MojoConnector::CreateVirtualDeviceOnIpcThread, |
| base::Unretained(this), video_device, producer_impl, |
| callback)); |
| } |
| |
| void MojoConnector::CreateVirtualDeviceOnIpcThread( |
| const VideoDevice& video_device, |
| std::shared_ptr<ProducerImpl> producer_impl, |
| const VideoCaptureServiceClient::VirtualDeviceCallback& callback) { |
| media::mojom::VideoCaptureDeviceInfoPtr info = |
| media::mojom::VideoCaptureDeviceInfo::New(); |
| // TODO(b/3743548): After libchrome uprev, assigning to std::vector<> is |
| // just redundant, so should be removed. |
| info->supported_formats = std::vector<media::mojom::VideoCaptureFormatPtr>(); |
| info->descriptor = media::mojom::VideoCaptureDeviceDescriptor::New(); |
| info->descriptor->model_id = video_device.model_id(); |
| info->descriptor->device_id = video_device.id(); |
| info->descriptor->display_name = video_device.display_name(); |
| info->descriptor->capture_api = media::mojom::VideoCaptureApi::VIRTUAL_DEVICE; |
| producer_impl->RegisterVirtualDevice(&video_source_provider_, |
| std::move(info)); |
| |
| callback(Serialized<VideoDevice>(video_device).GetBytes()); |
| } |
| |
| void MojoConnector::PushFrameToVirtualDevice( |
| std::shared_ptr<ProducerImpl> producer_impl, base::TimeDelta timestamp, |
| std::unique_ptr<const uint8_t[]> data, int data_size, |
| PixelFormat pixel_format, int frame_width, int frame_height) { |
| ipc_thread_.task_runner()->PostTask( |
| FROM_HERE, base::Bind(&MojoConnector::PushFrameToVirtualDeviceOnIpcThread, |
| base::Unretained(this), producer_impl, timestamp, |
| base::Passed(&data), data_size, pixel_format, |
| frame_width, frame_height)); |
| } |
| |
| void MojoConnector::PushFrameToVirtualDeviceOnIpcThread( |
| std::shared_ptr<ProducerImpl> producer_impl, base::TimeDelta timestamp, |
| std::unique_ptr<const uint8_t[]> data, int data_size, |
| PixelFormat pixel_format, int frame_width, int frame_height) { |
| producer_impl->PushNextFrame( |
| producer_impl, timestamp, std::move(data), data_size, |
| GetVideoCapturePixelFormatFromPixelFormat(pixel_format), frame_width, |
| frame_height); |
| } |
| |
| } // namespace mri |
| |