1 // Copyright (c) 2012 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.
5 // Notes about usage of this object by VideoCaptureImplManager.
7 // VideoCaptureImplManager access this object by using a Unretained()
8 // binding and tasks on the IO thread. It is then important that
9 // VideoCaptureImpl never post task to itself. All operations must be
12 #include "content/renderer/media/video_capture_impl.h"
14 #include "base/bind.h"
15 #include "base/stl_util.h"
16 #include "base/thread_task_runner_handle.h"
17 #include "content/child/child_process.h"
18 #include "content/common/gpu/client/gpu_memory_buffer_impl.h"
19 #include "content/common/media/video_capture_messages.h"
20 #include "media/base/bind_to_current_loop.h"
21 #include "media/base/limits.h"
22 #include "media/base/video_frame.h"
28 const int kUndefinedDeviceId
= 0;
30 // This is called on an unknown thread when the VideoFrame destructor executes.
31 // As of this writing, this callback mechanism is the only interface in
32 // VideoFrame to provide the final value for |release_sync_point|.
33 // VideoCaptureImpl::DidFinishConsumingFrame() will read the value saved here,
34 // and pass it back to the IO thread to pass back to the host via the
36 void SaveReleaseSyncPoint(uint32
* storage
, uint32 release_sync_point
) {
37 *storage
= release_sync_point
;
42 // A holder of a memory-backed buffer and accessors to it.
43 class VideoCaptureImpl::ClientBuffer
44 : public base::RefCountedThreadSafe
<ClientBuffer
> {
46 ClientBuffer(scoped_ptr
<base::SharedMemory
> buffer
, size_t buffer_size
)
47 : buffer_(buffer
.Pass()), buffer_size_(buffer_size
) {}
49 base::SharedMemory
* buffer() const { return buffer_
.get(); }
50 size_t buffer_size() const { return buffer_size_
; }
53 friend class base::RefCountedThreadSafe
<ClientBuffer
>;
55 virtual ~ClientBuffer() {}
57 const scoped_ptr
<base::SharedMemory
> buffer_
;
58 const size_t buffer_size_
;
60 DISALLOW_COPY_AND_ASSIGN(ClientBuffer
);
63 // A holder of a GpuMemoryBuffer-backed buffer, Map()ed on ctor and Unmap()ed on
64 // dtor. Creates and owns GpuMemoryBuffer instances.
65 class VideoCaptureImpl::ClientBuffer2
66 : public base::RefCountedThreadSafe
<ClientBuffer2
> {
69 const std::vector
<gfx::GpuMemoryBufferHandle
>& client_handles
,
70 const gfx::Size
& size
)
71 : handles_(client_handles
),
73 const media::VideoPixelFormat format
= media::PIXEL_FORMAT_I420
;
74 for (size_t i
= 0; i
< handles_
.size(); ++i
) {
75 const size_t width
= media::VideoFrame::Columns(i
, format
, size_
.width());
76 const size_t height
= media::VideoFrame::Rows(i
, format
, size_
.height());
77 buffers_
.push_back(GpuMemoryBufferImpl::CreateFromHandle(
79 gfx::Size(width
, height
),
80 gfx::BufferFormat::R_8
,
81 gfx::BufferUsage::MAP
,
82 base::Bind(&ClientBuffer2::DestroyGpuMemoryBuffer
,
83 base::Unretained(this))));
84 void* data_ptr
= nullptr;
85 buffers_
[i
]->Map(&data_ptr
);
86 data_
[i
] = reinterpret_cast<uint8
*>(data_ptr
);
91 uint8
* data(int plane
) const { return data_
[plane
]; }
92 int32
stride(int plane
) const { return strides_
[plane
]; }
93 std::vector
<gfx::GpuMemoryBufferHandle
> gpu_memory_buffer_handles() {
98 friend class base::RefCountedThreadSafe
<ClientBuffer2
>;
100 virtual ~ClientBuffer2() {
101 for (auto& buffer
: buffers_
)
105 void DestroyGpuMemoryBuffer(uint32 sync_point
) {}
107 const std::vector
<gfx::GpuMemoryBufferHandle
> handles_
;
108 const gfx::Size size_
;
109 ScopedVector
<gfx::GpuMemoryBuffer
> buffers_
;
110 uint8
* data_
[media::VideoFrame::kMaxPlanes
];
111 int32 strides_
[media::VideoFrame::kMaxPlanes
];
113 DISALLOW_COPY_AND_ASSIGN(ClientBuffer2
);
116 VideoCaptureImpl::VideoCaptureImpl(
117 const media::VideoCaptureSessionId session_id
,
118 VideoCaptureMessageFilter
* filter
)
119 : message_filter_(filter
),
120 device_id_(kUndefinedDeviceId
),
121 session_id_(session_id
),
122 state_(VIDEO_CAPTURE_STATE_STOPPED
),
123 weak_factory_(this) {
127 VideoCaptureImpl::~VideoCaptureImpl() {
128 DCHECK(io_task_runner_
->BelongsToCurrentThread());
131 void VideoCaptureImpl::Init() {
132 // For creating callbacks in unittest, this class may be constructed from a
133 // different thread than the IO thread, e.g. wherever unittest runs on.
134 // Therefore, this function should define the thread ownership.
136 io_task_runner_
= base::ThreadTaskRunnerHandle::Get();
138 message_filter_
->AddDelegate(this);
141 void VideoCaptureImpl::DeInit() {
142 DCHECK(io_task_runner_
->BelongsToCurrentThread());
143 if (state_
== VIDEO_CAPTURE_STATE_STARTED
)
144 Send(new VideoCaptureHostMsg_Stop(device_id_
));
145 message_filter_
->RemoveDelegate(this);
148 void VideoCaptureImpl::SuspendCapture(bool suspend
) {
149 DCHECK(io_task_runner_
->BelongsToCurrentThread());
150 Send(suspend
? static_cast<IPC::Message
*>(
151 new VideoCaptureHostMsg_Pause(device_id_
))
152 : static_cast<IPC::Message
*>(new VideoCaptureHostMsg_Resume(
153 device_id_
, session_id_
, client_params_
)));
156 void VideoCaptureImpl::StartCapture(
157 const media::VideoCaptureParams
& params
,
158 const VideoCaptureStateUpdateCB
& state_update_cb
,
159 const VideoCaptureDeliverFrameCB
& deliver_frame_cb
) {
160 DCHECK(io_task_runner_
->BelongsToCurrentThread());
161 if (state_update_cb
.is_null() || deliver_frame_cb
.is_null()) {
162 DVLOG(1) << "StartCapture: Passed null callbacks.";
165 if (state_
== VIDEO_CAPTURE_STATE_ERROR
) {
166 state_update_cb
.Run(VIDEO_CAPTURE_STATE_ERROR
);
170 // Save client info regardless of whether we're actually starting capture
171 // now or not, in case a delegate hasn't yet been added.
172 client_params_
= params
;
173 state_update_cb_
= state_update_cb
;
174 deliver_frame_cb_
= deliver_frame_cb
;
175 client_params_
.requested_format
.frame_rate
=
176 std::min(client_params_
.requested_format
.frame_rate
,
177 static_cast<float>(media::limits::kMaxFramesPerSecond
));
179 // Postpone starting capture until OnDelegateAdded(). Do this after saving
180 // |client_params_|, etc. as StartCapture will be called with these saved
181 // values as soon as a delegate has been added
182 if (device_id_
== kUndefinedDeviceId
) {
183 DVLOG(1) << "StartCapture: Not starting, device_id_ is undefined.";
187 // Finally, we can start (assuming we haven't started already).
188 // Notify the client that we have started regardless of |state_|.
189 state_update_cb
.Run(VIDEO_CAPTURE_STATE_STARTED
);
190 if (state_
== VIDEO_CAPTURE_STATE_STARTED
)
192 DVLOG(1) << "StartCapture: starting with resolution "
193 << client_params_
.requested_format
.frame_size
.ToString();
194 StartCaptureInternal();
197 void VideoCaptureImpl::StopCapture() {
198 DCHECK(io_task_runner_
->BelongsToCurrentThread());
199 if (state_
== VIDEO_CAPTURE_STATE_STOPPED
|| !IsInitialized())
202 DVLOG(1) << "StopCapture: Stopping capture.";
203 state_update_cb_
.Run(VIDEO_CAPTURE_STATE_STOPPED
);
205 client_buffers_
.clear();
206 client_buffer2s_
.clear();
208 weak_factory_
.InvalidateWeakPtrs();
211 void VideoCaptureImpl::GetDeviceSupportedFormats(
212 const VideoCaptureDeviceFormatsCB
& callback
) {
213 DCHECK(io_task_runner_
->BelongsToCurrentThread());
214 device_formats_cb_queue_
.push_back(callback
);
215 if (device_formats_cb_queue_
.size() == 1)
216 Send(new VideoCaptureHostMsg_GetDeviceSupportedFormats(device_id_
,
220 void VideoCaptureImpl::GetDeviceFormatsInUse(
221 const VideoCaptureDeviceFormatsCB
& callback
) {
222 DCHECK(io_task_runner_
->BelongsToCurrentThread());
223 device_formats_in_use_cb_queue_
.push_back(callback
);
224 if (device_formats_in_use_cb_queue_
.size() == 1)
226 new VideoCaptureHostMsg_GetDeviceFormatsInUse(device_id_
, session_id_
));
229 void VideoCaptureImpl::OnBufferCreated(base::SharedMemoryHandle handle
,
232 DCHECK(io_task_runner_
->BelongsToCurrentThread());
234 // In case client calls StopCapture before the arrival of created buffer,
235 // just close this buffer and return.
236 if (state_
!= VIDEO_CAPTURE_STATE_STARTED
) {
237 base::SharedMemory::CloseHandle(handle
);
241 scoped_ptr
<base::SharedMemory
> shm(new base::SharedMemory(handle
, false));
242 if (!shm
->Map(length
)) {
243 DLOG(ERROR
) << "OnBufferCreated: Map failed.";
246 const bool inserted
=
247 client_buffers_
.insert(std::make_pair(buffer_id
, new ClientBuffer(
248 shm
.Pass(), length
)))
253 void VideoCaptureImpl::OnBufferCreated2(
254 const std::vector
<gfx::GpuMemoryBufferHandle
>& handles
,
255 const gfx::Size
& size
,
257 DCHECK(io_task_runner_
->BelongsToCurrentThread());
259 // In case client calls StopCapture before the arrival of created buffer,
260 // just close this buffer and return.
261 if (state_
!= VIDEO_CAPTURE_STATE_STARTED
)
264 const bool inserted
=
265 client_buffer2s_
.insert(std::make_pair(buffer_id
,
266 new ClientBuffer2(handles
, size
)))
271 void VideoCaptureImpl::OnBufferDestroyed(int buffer_id
) {
272 DCHECK(io_task_runner_
->BelongsToCurrentThread());
274 const auto& cb_iter
= client_buffers_
.find(buffer_id
);
275 if (cb_iter
!= client_buffers_
.end()) {
276 DCHECK(!cb_iter
->second
.get() || cb_iter
->second
->HasOneRef())
277 << "Instructed to delete buffer we are still using.";
278 client_buffers_
.erase(cb_iter
);
280 const auto& cb2_iter
= client_buffer2s_
.find(buffer_id
);
281 if (cb2_iter
!= client_buffer2s_
.end()) {
282 DCHECK(!cb2_iter
->second
.get() || cb2_iter
->second
->HasOneRef())
283 << "Instructed to delete buffer we are still using.";
284 client_buffer2s_
.erase(cb2_iter
);
289 void VideoCaptureImpl::OnBufferReceived(
291 base::TimeTicks timestamp
,
292 const base::DictionaryValue
& metadata
,
293 media::VideoPixelFormat pixel_format
,
294 media::VideoFrame::StorageType storage_type
,
295 const gfx::Size
& coded_size
,
296 const gfx::Rect
& visible_rect
,
297 const std::vector
<gpu::MailboxHolder
>& mailbox_holders
) {
298 DCHECK(io_task_runner_
->BelongsToCurrentThread());
299 if (state_
!= VIDEO_CAPTURE_STATE_STARTED
) {
300 Send(new VideoCaptureHostMsg_BufferReady(device_id_
, buffer_id
, 0, -1.0));
303 if (first_frame_timestamp_
.is_null())
304 first_frame_timestamp_
= timestamp
;
306 // Used by chrome/browser/extension/api/cast_streaming/performance_test.cc
307 TRACE_EVENT_INSTANT2("cast_perf_test", "OnBufferReceived",
308 TRACE_EVENT_SCOPE_THREAD
, "timestamp",
309 timestamp
.ToInternalValue(), "time_delta",
310 (timestamp
- first_frame_timestamp_
).ToInternalValue());
312 scoped_refptr
<media::VideoFrame
> frame
;
313 base::Callback
<void(uint32
, double)> buffer_finished_callback
;
314 uint32
* release_sync_point_storage
= new uint32(0);
315 if (mailbox_holders
.empty()) {
316 DCHECK_EQ(media::PIXEL_FORMAT_I420
, pixel_format
);
317 const auto& iter
= client_buffers_
.find(buffer_id
);
318 DCHECK(iter
!= client_buffers_
.end());
319 const scoped_refptr
<ClientBuffer
> buffer
= iter
->second
;
320 frame
= media::VideoFrame::WrapExternalSharedMemory(
324 gfx::Size(visible_rect
.width(), visible_rect
.height()),
325 reinterpret_cast<uint8
*>(buffer
->buffer()->memory()),
326 buffer
->buffer_size(),
327 buffer
->buffer()->handle(),
328 0 /* shared_memory_offset */,
329 timestamp
- first_frame_timestamp_
);
330 buffer_finished_callback
= media::BindToCurrentLoop(
331 base::Bind(&VideoCaptureImpl::OnClientBufferFinished
,
332 weak_factory_
.GetWeakPtr(), buffer_id
, buffer
));
335 for (const auto& mailbox_holder
: mailbox_holders
)
336 DCHECK(mailbox_holder
.mailbox
.Verify());
337 DCHECK(mailbox_holders
.size() == 1u || mailbox_holders
.size() == 3u);
340 scoped_refptr
<ClientBuffer2
> buffer
;
341 if (mailbox_holders
.size() ==
342 media::VideoFrame::NumPlanes(media::PIXEL_FORMAT_ARGB
)) {
343 DCHECK_EQ(media::PIXEL_FORMAT_ARGB
, pixel_format
);
344 frame
= media::VideoFrame::WrapNativeTexture(
347 base::Bind(&SaveReleaseSyncPoint
, release_sync_point_storage
),
349 gfx::Rect(coded_size
),
351 timestamp
- first_frame_timestamp_
);
352 } else if (mailbox_holders
.size() ==
353 media::VideoFrame::NumPlanes(media::PIXEL_FORMAT_I420
)) {
354 DCHECK_EQ(media::PIXEL_FORMAT_I420
, pixel_format
);
355 const auto& iter
= client_buffer2s_
.find(buffer_id
);
356 DCHECK(iter
!= client_buffer2s_
.end());
357 buffer
= iter
->second
;
358 frame
= media::VideoFrame::WrapYUV420NativeTextures(
359 mailbox_holders
[media::VideoFrame::kYPlane
],
360 mailbox_holders
[media::VideoFrame::kUPlane
],
361 mailbox_holders
[media::VideoFrame::kVPlane
],
362 base::Bind(&SaveReleaseSyncPoint
, release_sync_point_storage
),
364 gfx::Rect(coded_size
),
366 timestamp
- first_frame_timestamp_
);
368 buffer_finished_callback
= media::BindToCurrentLoop(
369 base::Bind(&VideoCaptureImpl::OnClientBufferFinished2
,
370 weak_factory_
.GetWeakPtr(), buffer_id
, buffer
));
372 frame
->metadata()->SetTimeTicks(media::VideoFrameMetadata::REFERENCE_TIME
,
374 frame
->AddDestructionObserver(
375 base::Bind(&VideoCaptureImpl::DidFinishConsumingFrame
,
376 frame
->metadata(), release_sync_point_storage
,
377 buffer_finished_callback
));
378 frame
->metadata()->MergeInternalValuesFrom(metadata
);
379 deliver_frame_cb_
.Run(frame
, timestamp
);
382 void VideoCaptureImpl::OnClientBufferFinished(
384 const scoped_refptr
<ClientBuffer
>& /* ignored_buffer */,
385 uint32 release_sync_point
,
386 double consumer_resource_utilization
) {
387 DCHECK(io_task_runner_
->BelongsToCurrentThread());
388 Send(new VideoCaptureHostMsg_BufferReady(device_id_
, buffer_id
,
390 consumer_resource_utilization
));
392 void VideoCaptureImpl::OnClientBufferFinished2(
394 const scoped_refptr
<ClientBuffer2
>& gpu_memory_buffer
/* ignored_buffer */,
395 uint32 release_sync_point
,
396 double consumer_resource_utilization
) {
397 OnClientBufferFinished(buffer_id
, scoped_refptr
<ClientBuffer
>(),
398 release_sync_point
, consumer_resource_utilization
);
401 void VideoCaptureImpl::OnStateChanged(VideoCaptureState state
) {
402 // TODO(ajose): http://crbug.com/522155 improve this state machine.
403 DCHECK(io_task_runner_
->BelongsToCurrentThread());
406 if (state
== VIDEO_CAPTURE_STATE_STOPPED
) {
407 DVLOG(1) << "OnStateChanged: stopped!, device_id = " << device_id_
;
408 client_buffers_
.clear();
409 client_buffer2s_
.clear();
410 weak_factory_
.InvalidateWeakPtrs();
413 if (state
== VIDEO_CAPTURE_STATE_ERROR
) {
414 DVLOG(1) << "OnStateChanged: error!, device_id = " << device_id_
;
415 if (!state_update_cb_
.is_null())
416 state_update_cb_
.Run(VIDEO_CAPTURE_STATE_ERROR
);
420 if (state
== VIDEO_CAPTURE_STATE_ENDED
) {
421 DVLOG(1) << "OnStateChanged: ended!, device_id = " << device_id_
;
422 // We'll only notify the client that the stream has stopped.
423 if (!state_update_cb_
.is_null())
424 state_update_cb_
.Run(VIDEO_CAPTURE_STATE_STOPPED
);
431 void VideoCaptureImpl::OnDeviceSupportedFormatsEnumerated(
432 const media::VideoCaptureFormats
& supported_formats
) {
433 DCHECK(io_task_runner_
->BelongsToCurrentThread());
434 for (size_t i
= 0; i
< device_formats_cb_queue_
.size(); ++i
)
435 device_formats_cb_queue_
[i
].Run(supported_formats
);
436 device_formats_cb_queue_
.clear();
439 void VideoCaptureImpl::OnDeviceFormatsInUseReceived(
440 const media::VideoCaptureFormats
& formats_in_use
) {
441 DCHECK(io_task_runner_
->BelongsToCurrentThread());
442 for (size_t i
= 0; i
< device_formats_in_use_cb_queue_
.size(); ++i
)
443 device_formats_in_use_cb_queue_
[i
].Run(formats_in_use
);
444 device_formats_in_use_cb_queue_
.clear();
447 void VideoCaptureImpl::OnDelegateAdded(int32 device_id
) {
448 DCHECK(io_task_runner_
->BelongsToCurrentThread());
449 DVLOG(1) << "OnDelegateAdded: device_id " << device_id
;
451 device_id_
= device_id
;
452 StartCapture(client_params_
, state_update_cb_
, deliver_frame_cb_
);
455 void VideoCaptureImpl::StopDevice() {
456 DCHECK(io_task_runner_
->BelongsToCurrentThread());
458 if (state_
== VIDEO_CAPTURE_STATE_STARTED
) {
459 Send(new VideoCaptureHostMsg_Stop(device_id_
));
460 client_params_
.requested_format
.frame_size
.SetSize(0, 0);
464 void VideoCaptureImpl::RestartCapture() {
465 DCHECK(io_task_runner_
->BelongsToCurrentThread());
466 DCHECK_EQ(state_
, VIDEO_CAPTURE_STATE_STOPPED
);
468 DVLOG(1) << "RestartCapture, restarting capture.";
469 StartCaptureInternal();
472 void VideoCaptureImpl::StartCaptureInternal() {
473 DCHECK(io_task_runner_
->BelongsToCurrentThread());
476 Send(new VideoCaptureHostMsg_Start(device_id_
, session_id_
, client_params_
));
477 state_
= VIDEO_CAPTURE_STATE_STARTED
;
480 void VideoCaptureImpl::Send(IPC::Message
* message
) {
481 DCHECK(io_task_runner_
->BelongsToCurrentThread());
482 message_filter_
->Send(message
);
486 void VideoCaptureImpl::DidFinishConsumingFrame(
487 const media::VideoFrameMetadata
* metadata
,
488 uint32
* release_sync_point_storage
,
489 const base::Callback
<void(uint32
, double)>& callback_to_io_thread
) {
490 // Note: This function may be called on any thread by the VideoFrame
491 // destructor. |metadata| is still valid for read-access at this point.
493 uint32 release_sync_point
= 0u;
494 if (release_sync_point_storage
) {
495 release_sync_point
= *release_sync_point_storage
;
496 delete release_sync_point_storage
;
499 double consumer_resource_utilization
= -1.0;
500 if (!metadata
->GetDouble(media::VideoFrameMetadata::RESOURCE_UTILIZATION
,
501 &consumer_resource_utilization
)) {
502 consumer_resource_utilization
= -1.0;
505 callback_to_io_thread
.Run(release_sync_point
, consumer_resource_utilization
);
508 bool VideoCaptureImpl::IsInitialized() const {
509 return !(state_update_cb_
.is_null() || deliver_frame_cb_
.is_null()) &&
510 device_id_
!= kUndefinedDeviceId
;
513 void VideoCaptureImpl::ResetClient() {
514 client_params_
= media::VideoCaptureParams();
515 state_update_cb_
.Reset();
516 deliver_frame_cb_
.Reset();
517 first_frame_timestamp_
= base::TimeTicks();
518 device_id_
= kUndefinedDeviceId
;
519 state_
= VIDEO_CAPTURE_STATE_STOPPED
;
522 } // namespace content