Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright 2017 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "media/capture/video/chromeos/camera_hal_delegate.h" | |
| 6 | |
| 7 #include <fcntl.h> | |
| 8 #include <sys/uio.h> | |
| 9 | |
| 10 #include "base/bind.h" | |
| 11 #include "base/bind_helpers.h" | |
| 12 #include "base/strings/string_piece.h" | |
| 13 #include "media/capture/video/chromeos/camera_metadata_utils.h" | |
| 14 #include "media/capture/video/chromeos/video_capture_device_arc_chromeos.h" | |
| 15 #include "mojo/edk/embedder/embedder.h" | |
| 16 #include "mojo/edk/embedder/named_platform_handle.h" | |
| 17 #include "mojo/edk/embedder/named_platform_handle_utils.h" | |
| 18 #include "mojo/edk/embedder/pending_process_connection.h" | |
| 19 #include "mojo/edk/embedder/platform_channel_pair.h" | |
| 20 #include "mojo/edk/embedder/platform_channel_utils_posix.h" | |
| 21 #include "mojo/edk/embedder/platform_handle_vector.h" | |
| 22 | |
| 23 namespace media { | |
| 24 | |
| 25 namespace { | |
| 26 | |
| 27 const base::StringPiece kArcCamera3SocketPath("/var/run/camera/camera3.sock"); | |
| 28 | |
| 29 const base::TimeDelta kEventWaitTimeoutMs = | |
| 30 base::TimeDelta::FromMilliseconds(3000); | |
| 31 | |
| 32 } // namespace | |
| 33 | |
| 34 CameraHalDelegate::CameraHalDelegate( | |
| 35 const scoped_refptr<base::SingleThreadTaskRunner> module_task_runner) | |
| 36 : camera_info_updated_(base::WaitableEvent::ResetPolicy::MANUAL, | |
| 37 base::WaitableEvent::InitialState::NOT_SIGNALED), | |
| 38 num_cameras_(0), | |
| 39 module_callbacks_registered_(false), | |
| 40 module_task_runner_(module_task_runner), | |
| 41 camera_module_callbacks_(this) { | |
|
wuchengli
2017/05/02 16:11:42
note to myself: this is run on browser UI thread.
| |
| 42 thread_checker_.DetachFromThread(); | |
| 43 } | |
| 44 | |
| 45 CameraHalDelegate::~CameraHalDelegate() { | |
|
wuchengli
2017/05/02 16:11:41
note to myself: CameraHalDelegate is singleton, cr
| |
| 46 base::WaitableEvent interface_closed( | |
| 47 base::WaitableEvent::ResetPolicy::MANUAL, | |
| 48 base::WaitableEvent::InitialState::NOT_SIGNALED); | |
| 49 module_task_runner_->PostTask( | |
| 50 FROM_HERE, | |
| 51 base::Bind(&CameraHalDelegate::ResetMojoInterfaceOnModuleThread, | |
| 52 base::Unretained(this), base::Unretained(&interface_closed))); | |
| 53 interface_closed.Wait(); | |
| 54 } | |
| 55 | |
| 56 bool CameraHalDelegate::StartCameraModuleIpc() { | |
|
wuchengli
2017/05/02 16:11:42
note to myself: this is called on browser UI threa
| |
| 57 // Non-blocking socket handle. | |
| 58 mojo::edk::ScopedPlatformHandle socket_handle = mojo::edk::CreateClientHandle( | |
| 59 mojo::edk::NamedPlatformHandle(kArcCamera3SocketPath)); | |
| 60 | |
| 61 // Set socket to blocking | |
| 62 int flags = HANDLE_EINTR(fcntl(socket_handle.get().handle, F_GETFL)); | |
| 63 if (flags == -1) { | |
| 64 LOG(ERROR) << "fcntl(F_GETFL) failed"; | |
|
wuchengli
2017/05/02 16:11:42
PLOG. Same for other places in this file.
jcliang
2017/05/03 04:44:56
Done.
| |
| 65 return false; | |
| 66 } | |
| 67 if (HANDLE_EINTR(fcntl(socket_handle.get().handle, F_SETFL, | |
| 68 flags & ~O_NONBLOCK)) == -1) { | |
| 69 LOG(ERROR) << "fcntl(F_SETFL) failed"; | |
| 70 return false; | |
| 71 } | |
| 72 | |
| 73 char token[32] = {}; | |
| 74 std::deque<mojo::edk::PlatformHandle> platform_handles; | |
| 75 ssize_t ret = mojo::edk::PlatformChannelRecvmsg( | |
| 76 socket_handle.get(), token, sizeof(token), &platform_handles, true); | |
| 77 if (ret == -1) { | |
| 78 LOG(ERROR) << "PlatformChannelRecvmsg failed: " << strerror(errno); | |
| 79 } | |
| 80 if (platform_handles.size() != 1) { | |
| 81 LOG(ERROR) << "Unexpected number of handles received, expected 1: " | |
| 82 << platform_handles.size(); | |
| 83 return false; | |
| 84 } | |
| 85 mojo::edk::ScopedPlatformHandle parent_pipe(platform_handles.back()); | |
| 86 platform_handles.pop_back(); | |
| 87 if (!parent_pipe.is_valid()) { | |
| 88 LOG(ERROR) << "Invalid parent pipe"; | |
| 89 return false; | |
| 90 } | |
| 91 mojo::edk::SetParentPipeHandle(std::move(parent_pipe)); | |
| 92 | |
| 93 mojo::ScopedMessagePipeHandle child_pipe = | |
| 94 mojo::edk::CreateChildMessagePipe(std::string(token, 32)); | |
| 95 | |
| 96 camera_module_ = | |
| 97 mojo::MakeProxy(mojo::InterfacePtrInfo<arc::mojom::CameraModule>( | |
| 98 std::move(child_pipe), 0u), | |
| 99 module_task_runner_); | |
| 100 | |
| 101 VLOG(1) << "Camera module IPC connection established"; | |
| 102 | |
| 103 return true; | |
| 104 } | |
| 105 | |
| 106 std::unique_ptr<VideoCaptureDevice> CameraHalDelegate::CreateDevice( | |
| 107 const scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner, | |
| 108 const VideoCaptureDeviceDescriptor& device_descriptor) { | |
| 109 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 110 std::unique_ptr<VideoCaptureDevice> capture_device( | |
| 111 new VideoCaptureDeviceArcChromeOS(ui_task_runner, device_descriptor, | |
| 112 this)); | |
| 113 return capture_device; | |
| 114 } | |
| 115 | |
| 116 void CameraHalDelegate::GetSupportedFormats( | |
| 117 const VideoCaptureDeviceDescriptor& device_descriptor, | |
| 118 VideoCaptureFormats* supported_formats) { | |
| 119 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 120 | |
| 121 if (!UpdateCameraInfo()) { | |
| 122 return; | |
| 123 } | |
| 124 int camera_id = std::stoi(device_descriptor.device_id); | |
| 125 if (camera_info_.find(camera_id) == camera_info_.end() || | |
| 126 camera_info_[camera_id].is_null()) { | |
| 127 // The camera may be removed already or is not ready yet. | |
|
wuchengli
2017/05/02 16:11:42
How does the client handle the case that camera is
jcliang
2017/05/03 04:44:56
I think Chrome would find the camera unusable, and
| |
| 128 VLOG(1) << "Invalid camera_id: " << camera_id; | |
|
wuchengli
2017/05/02 16:11:42
How can the camera be removed already? If UpdateCa
jcliang
2017/05/03 04:44:55
If the camera is not ready then camera_info_[camer
| |
| 129 return; | |
| 130 } | |
| 131 const arc::mojom::CameraInfoPtr& camera_info = camera_info_[camera_id]; | |
| 132 | |
| 133 const arc::mojom::CameraMetadataEntryPtr* fps_settings = | |
| 134 GetMetadataEntry(camera_info->static_camera_characteristics, | |
| 135 arc::mojom::CameraMetadataTag:: | |
| 136 ANDROID_CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES); | |
| 137 if (!fps_settings) { | |
| 138 LOG(ERROR) << "Failed to get available target FPS ranges from camera info"; | |
| 139 return; | |
| 140 } | |
| 141 // The available target FPS ranges are stored as pairs of two int32s: | |
| 142 // (min_fps, max_fps). | |
| 143 float max_fps = 0.0; | |
| 144 int32_t* iter = reinterpret_cast<int32_t*>((*fps_settings)->data.data()); | |
| 145 // There may be multiple FPS ranges. We simply use the maximum FPS of all the | |
|
wuchengli
2017/05/02 16:11:42
Do we want to use the maximum FPS of all FPS range
jcliang
2017/05/03 04:44:56
The VideoCaptureDeviceFactoryLinux asks the kernel
| |
| 146 // FPS ranges here. | |
| 147 for (size_t i = 0; i < (*fps_settings)->count / sizeof(int32_t); ++i) { | |
| 148 float fps = static_cast<float>(*(iter + 1)); | |
| 149 if (fps > max_fps) { | |
| 150 max_fps = fps; | |
| 151 } | |
| 152 iter += 2; | |
| 153 } | |
| 154 | |
| 155 const arc::mojom::CameraMetadataEntryPtr* configurations = | |
| 156 GetMetadataEntry(camera_info->static_camera_characteristics, | |
| 157 arc::mojom::CameraMetadataTag:: | |
| 158 ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS); | |
| 159 if (!configurations) { | |
| 160 LOG(ERROR) | |
| 161 << "Failed to get available stream configurations from camera info"; | |
| 162 return; | |
| 163 } | |
| 164 // The available stream configurations are stored as tuples of four int32s: | |
| 165 // (hal_pixel_format, width, height, input_or_output_type). | |
| 166 iter = reinterpret_cast<int32_t*>((*configurations)->data.data()); | |
| 167 for (size_t i = 0; i < (*configurations)->count / sizeof(int32_t); ++i) { | |
| 168 arc::mojom::HalPixelFormat format = | |
| 169 static_cast<arc::mojom::HalPixelFormat>(*iter); | |
| 170 int32_t width = *(iter + 1); | |
| 171 int32_t height = *(iter + 2); | |
| 172 int32_t type = *(iter + 3); | |
| 173 iter += 4; | |
| 174 VLOG(1) << "[" << std::hex << format << " " << std::dec << width << " " | |
| 175 << height << " " << type << "]"; | |
| 176 VideoPixelFormat cr_format = | |
| 177 VideoCaptureDeviceArcChromeOS::PixFormatHalToChromium(format); | |
| 178 if (cr_format == PIXEL_FORMAT_UNKNOWN) { | |
| 179 continue; | |
| 180 } | |
| 181 VLOG(1) << "Supported format: " << width << "x" << height | |
| 182 << " fps=" << max_fps << " format=" << cr_format; | |
| 183 supported_formats->emplace_back(gfx::Size(width, height), max_fps, | |
| 184 cr_format); | |
| 185 } | |
| 186 } | |
| 187 | |
| 188 void CameraHalDelegate::GetDeviceDescriptors( | |
| 189 VideoCaptureDeviceDescriptors* device_descriptors) { | |
| 190 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 191 | |
| 192 if (!UpdateCameraInfo()) { | |
| 193 return; | |
| 194 } | |
|
wuchengli
2017/05/02 16:11:42
Explain if a device is unplugged at this time, the
jcliang
2017/05/03 04:44:55
Done.
| |
| 195 for (size_t camera_id = 0; camera_id < num_cameras_; ++camera_id) { | |
| 196 VideoCaptureDeviceDescriptor desc; | |
| 197 arc::mojom::CameraInfoPtr& camera_info = camera_info_[camera_id]; | |
| 198 if (!camera_info) { | |
| 199 continue; | |
| 200 } | |
| 201 desc.device_id = std::to_string(camera_id); | |
| 202 desc.capture_api = VideoCaptureApi::ANDROID_API2_LIMITED; | |
| 203 desc.transport_type = VideoCaptureTransportType::OTHER_TRANSPORT; | |
| 204 switch (camera_info->facing) { | |
| 205 case arc::mojom::CameraFacing::CAMERA_FACING_BACK: | |
| 206 desc.facing = VideoFacingMode::MEDIA_VIDEO_FACING_ENVIRONMENT; | |
| 207 desc.display_name = std::string("Back Camera"); | |
| 208 break; | |
| 209 case arc::mojom::CameraFacing::CAMERA_FACING_FRONT: | |
| 210 desc.facing = VideoFacingMode::MEDIA_VIDEO_FACING_USER; | |
| 211 desc.display_name = std::string("Front Camera"); | |
| 212 break; | |
| 213 case arc::mojom::CameraFacing::CAMERA_FACING_EXTERNAL: | |
| 214 desc.facing = VideoFacingMode::MEDIA_VIDEO_FACING_NONE; | |
| 215 desc.display_name = std::string("External Camera"); | |
| 216 break; | |
| 217 } | |
| 218 device_descriptors->push_back(desc); | |
| 219 } | |
| 220 } | |
| 221 | |
| 222 void CameraHalDelegate::GetCameraInfo(int32_t camera_id, | |
| 223 const GetCameraInfoCallback& callback) { | |
| 224 // This method may be called on any thread. Currently this method is used by | |
| 225 // VideoCaptureDeviceArcChromeOS to query camera info. | |
| 226 module_task_runner_->PostTask( | |
| 227 FROM_HERE, base::Bind(&CameraHalDelegate::GetCameraInfoOnModuleThread, | |
| 228 this, camera_id, callback)); | |
| 229 } | |
| 230 | |
| 231 void CameraHalDelegate::OpenDevice(int32_t camera_id, | |
| 232 const OpenDeviceCallback& callback) { | |
| 233 // This method may be called on any thread. Currently this method is used by | |
| 234 // VideoCaptureDeviceArcChromeOS to open a camera device. | |
| 235 module_task_runner_->PostTask( | |
| 236 FROM_HERE, base::Bind(&CameraHalDelegate::OpenDeviceOnModuleThread, this, | |
| 237 camera_id, callback)); | |
| 238 } | |
| 239 | |
| 240 void CameraHalDelegate::ResetMojoInterfaceOnModuleThread( | |
| 241 base::WaitableEvent* interface_closed) { | |
| 242 DCHECK(module_task_runner_->BelongsToCurrentThread()); | |
| 243 camera_module_.reset(); | |
| 244 if (camera_module_callbacks_.is_bound()) { | |
| 245 camera_module_callbacks_.Unbind(); | |
| 246 } | |
| 247 interface_closed->Signal(); | |
| 248 } | |
| 249 | |
| 250 bool CameraHalDelegate::UpdateCameraInfo() { | |
| 251 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 252 if (camera_info_updated_.IsSignaled()) { | |
| 253 return true; | |
| 254 } | |
| 255 module_task_runner_->PostTask( | |
| 256 FROM_HERE, | |
| 257 base::Bind(&CameraHalDelegate::UpdateCameraInfoOnModuleThread, this)); | |
| 258 if (!camera_info_updated_.TimedWait(kEventWaitTimeoutMs)) { | |
| 259 LOG(ERROR) << "Timed out getting camera info"; | |
| 260 return false; | |
| 261 } | |
| 262 return true; | |
| 263 } | |
| 264 | |
| 265 void CameraHalDelegate::UpdateCameraInfoOnModuleThread() { | |
| 266 DCHECK(module_task_runner_->BelongsToCurrentThread()); | |
| 267 // We clear all existing camera info here and ask again. The camera info | |
| 268 // should remain unchanged for internal cameras, but for external cameras | |
| 269 // the info may change. | |
| 270 camera_info_.clear(); | |
|
wuchengli
2017/05/02 16:11:42
Should we set num_cameras_ to 0 here? It's more co
jcliang
2017/05/03 04:44:56
Yes we should set |num_cameras_| to 0 here to be c
| |
| 271 camera_module_->GetNumberOfCameras( | |
| 272 base::Bind(&CameraHalDelegate::OnGotNumberOfCamerasOnModuleThread, this)); | |
| 273 } | |
| 274 | |
| 275 void CameraHalDelegate::OnGotNumberOfCamerasOnModuleThread( | |
| 276 int32_t num_cameras) { | |
| 277 DCHECK(module_task_runner_->BelongsToCurrentThread()); | |
| 278 num_cameras_ = num_cameras; | |
| 279 if (!module_callbacks_registered_) { | |
| 280 // Per camera HAL v3 specification SetCallbacks() should be called after the | |
| 281 // first time GetNumberOfCameras() is called, and before other CameraModule | |
| 282 // functions are called. | |
| 283 SetCallbacksOnModuleThread(); | |
| 284 } else { | |
| 285 for (ssize_t camera_id = 0; camera_id < num_cameras; ++camera_id) { | |
| 286 GetCameraInfoOnModuleThread( | |
| 287 camera_id, | |
| 288 base::Bind(&CameraHalDelegate::OnGotCameraInfoOnModuleThread, this, | |
| 289 camera_id)); | |
| 290 } | |
| 291 } | |
| 292 } | |
| 293 | |
| 294 void CameraHalDelegate::GetCameraInfoOnModuleThread( | |
| 295 int32_t camera_id, | |
| 296 const GetCameraInfoCallback& callback) { | |
| 297 DCHECK(module_task_runner_->BelongsToCurrentThread()); | |
| 298 camera_module_->GetCameraInfo(camera_id, callback); | |
| 299 } | |
| 300 | |
| 301 void CameraHalDelegate::OnGotCameraInfoOnModuleThread( | |
| 302 int32_t camera_id, | |
| 303 int32_t result, | |
| 304 arc::mojom::CameraInfoPtr camera_info) { | |
| 305 DCHECK(module_task_runner_->BelongsToCurrentThread()); | |
| 306 DCHECK(!camera_info_updated_.IsSignaled()); | |
|
wuchengli
2017/05/02 16:11:41
Should we move DCHECK after line 311? I'm thinking
jcliang
2017/05/03 04:44:55
This won't happen for now as we only deal with bui
| |
| 307 if (result) { | |
| 308 // The call to get_camera_info may fail if the device is still in the | |
| 309 // enumerating state or is no longer present. | |
|
wuchengli
2017/05/02 16:11:41
If the device is no longer present, shouldn't we u
jcliang
2017/05/03 04:44:56
Currently I'm following the HAL API spec: https://
| |
| 310 VLOG(1) << "Failed to get camera info. Device id: " << camera_id; | |
|
wuchengli
2017/05/02 16:11:42
If get_camera_info fails, should we return here?
jcliang
2017/05/03 04:44:55
We continue here because of the reason above.
| |
| 311 } | |
| 312 // In case of failure |camera_info| is empty. | |
| 313 camera_info_[camera_id] = std::move(camera_info); | |
|
wuchengli
2017/05/02 16:11:42
VideoCaptureDeviceArcChromeOS can call GetCameraIn
jcliang
2017/05/03 04:44:55
|camera_info_| is only used internally by CameraHa
| |
| 314 if (camera_info_.size() == num_cameras_) { | |
| 315 camera_info_updated_.Signal(); | |
|
wuchengli
2017/05/02 16:11:42
Is it possible we get CameraDeviceStatusChange bef
jcliang
2017/05/03 04:44:56
The code is refactored to only handle built-in cam
| |
| 316 } | |
| 317 } | |
| 318 | |
| 319 void CameraHalDelegate::OpenDeviceOnModuleThread( | |
| 320 int32_t camera_id, | |
| 321 const OpenDeviceCallback& callback) { | |
| 322 DCHECK(module_task_runner_->BelongsToCurrentThread()); | |
| 323 camera_module_->OpenDevice(camera_id, callback); | |
| 324 } | |
| 325 | |
| 326 void CameraHalDelegate::SetCallbacksOnModuleThread() { | |
| 327 DCHECK(module_task_runner_->BelongsToCurrentThread()); | |
| 328 camera_module_->SetCallbacks( | |
| 329 camera_module_callbacks_.CreateInterfacePtrAndBind(), | |
| 330 base::Bind(&CameraHalDelegate::OnSetCallbacksOnModuleThread, this)); | |
| 331 } | |
| 332 | |
| 333 void CameraHalDelegate::OnSetCallbacksOnModuleThread(int32_t result) { | |
|
wuchengli
2017/05/02 16:11:42
What thread runs this function?
jcliang
2017/05/03 04:44:56
Added a DCHECK to make sure this runs on |module_t
| |
| 334 if (result) { | |
| 335 LOG(ERROR) << "Failed to set camera module callbacks"; | |
|
wuchengli
2017/05/02 16:11:42
I think we should set num_camera_ to 0, clear came
jcliang
2017/05/03 04:44:56
|camera_info_| is empty here. I've set |num_camera
| |
| 336 return; | |
| 337 } | |
| 338 module_callbacks_registered_ = true; | |
| 339 for (size_t camera_id = 0; camera_id < num_cameras_; ++camera_id) { | |
| 340 GetCameraInfoOnModuleThread( | |
| 341 camera_id, base::Bind(&CameraHalDelegate::OnGotCameraInfoOnModuleThread, | |
| 342 this, camera_id)); | |
| 343 } | |
| 344 } | |
| 345 | |
| 346 // CameraModuleCallbacks implementations. | |
| 347 void CameraHalDelegate::CameraDeviceStatusChange( | |
| 348 int32_t camera_id, | |
| 349 arc::mojom::CameraDeviceStatus new_status) { | |
| 350 DCHECK(module_task_runner_->BelongsToCurrentThread()); | |
| 351 // Here we just reset |camera_info_updated_| instead of trigger an update of | |
| 352 // camera info. This is to make sure that changes to |num_cameras_| and | |
| 353 // |camera_info_| are only triggered by UpdateCameraInfo to avoid race | |
| 354 // conditions. | |
| 355 if (new_status == | |
| 356 arc::mojom::CameraDeviceStatus::CAMERA_DEVICE_STATUS_NOT_PRESENT) { | |
|
wuchengli
2017/05/02 16:11:41
Probably we won't be able to enable this for USB a
jcliang
2017/05/03 04:44:56
Done.
| |
| 357 if (camera_info_.find(camera_id) != camera_info_.end()) { | |
| 358 camera_info_updated_.Reset(); | |
| 359 } | |
| 360 } else if (new_status == | |
| 361 arc::mojom::CameraDeviceStatus::CAMERA_DEVICE_STATUS_PRESENT) { | |
| 362 if (camera_info_.find(camera_id) == camera_info_.end() || | |
| 363 camera_info_[camera_id].is_null()) { | |
| 364 camera_info_updated_.Reset(); | |
|
wuchengli
2017/05/02 16:11:41
As discussed, how do we fix the issue the client m
| |
| 365 } | |
| 366 } | |
|
wuchengli
2017/05/02 16:11:42
What about CAMERA_DEVICE_STATUS_ENUMERATING? I thi
jcliang
2017/05/03 04:44:56
We can simply ignore the ENUMERATING state, since
| |
| 367 } | |
| 368 | |
| 369 } // namespace media | |
| OLD | NEW |