1 // Copyright 2014 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 #include "content/renderer/pepper/pepper_media_stream_video_track_host.h"
7 #include "base/base64.h"
8 #include "base/logging.h"
9 #include "base/rand_util.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "content/renderer/media/media_stream_video_track.h"
12 #include "media/base/bind_to_current_loop.h"
13 #include "media/base/yuv_convert.h"
14 #include "ppapi/c/pp_errors.h"
15 #include "ppapi/c/ppb_media_stream_video_track.h"
16 #include "ppapi/c/ppb_video_frame.h"
17 #include "ppapi/host/dispatch_host_message.h"
18 #include "ppapi/host/host_message_context.h"
19 #include "ppapi/proxy/ppapi_messages.h"
20 #include "ppapi/shared_impl/media_stream_buffer.h"
22 // IS_ALIGNED is also defined in
23 // third_party/webrtc/overrides/webrtc/base/basictypes.h
24 // TODO(ronghuawu): Avoid undef.
26 #include "third_party/libyuv/include/libyuv.h"
28 using media::VideoFrame
;
29 using ppapi::host::HostMessageContext
;
30 using ppapi::MediaStreamVideoTrackShared
;
34 const int32_t kDefaultNumberOfBuffers
= 4;
35 const int32_t kMaxNumberOfBuffers
= 8;
36 // Filter mode for scaling frames.
37 const libyuv::FilterMode kFilterMode
= libyuv::kFilterBox
;
39 const char kPepperVideoSourceName
[] = "PepperVideoSourceName";
41 // Default config for output mode.
42 const int kDefaultOutputFrameRate
= 30;
44 media::VideoPixelFormat
ToPixelFormat(PP_VideoFrame_Format format
) {
46 case PP_VIDEOFRAME_FORMAT_YV12
:
47 return media::PIXEL_FORMAT_YV12
;
48 case PP_VIDEOFRAME_FORMAT_I420
:
49 return media::PIXEL_FORMAT_I420
;
51 DVLOG(1) << "Unsupported pixel format " << format
;
52 return media::PIXEL_FORMAT_UNKNOWN
;
56 PP_VideoFrame_Format
ToPpapiFormat(media::VideoPixelFormat format
) {
58 case media::PIXEL_FORMAT_YV12
:
59 return PP_VIDEOFRAME_FORMAT_YV12
;
60 case media::PIXEL_FORMAT_I420
:
61 return PP_VIDEOFRAME_FORMAT_I420
;
63 DVLOG(1) << "Unsupported pixel format " << format
;
64 return PP_VIDEOFRAME_FORMAT_UNKNOWN
;
68 media::VideoPixelFormat
FromPpapiFormat(PP_VideoFrame_Format format
) {
70 case PP_VIDEOFRAME_FORMAT_YV12
:
71 return media::PIXEL_FORMAT_YV12
;
72 case PP_VIDEOFRAME_FORMAT_I420
:
73 return media::PIXEL_FORMAT_I420
;
75 DVLOG(1) << "Unsupported pixel format " << format
;
76 return media::PIXEL_FORMAT_UNKNOWN
;
80 // Compute size base on the size of frame received from MediaStreamVideoSink
81 // and size specified by plugin.
82 gfx::Size
GetTargetSize(const gfx::Size
& source
, const gfx::Size
& plugin
) {
83 return gfx::Size(plugin
.width() ? plugin
.width() : source
.width(),
84 plugin
.height() ? plugin
.height() : source
.height());
87 // Compute format base on the format of frame received from MediaStreamVideoSink
88 // and format specified by plugin.
89 PP_VideoFrame_Format
GetTargetFormat(PP_VideoFrame_Format source
,
90 PP_VideoFrame_Format plugin
) {
91 return plugin
!= PP_VIDEOFRAME_FORMAT_UNKNOWN
? plugin
: source
;
94 void ConvertFromMediaVideoFrame(const scoped_refptr
<media::VideoFrame
>& src
,
95 PP_VideoFrame_Format dst_format
,
96 const gfx::Size
& dst_size
,
98 CHECK(src
->format() == media::PIXEL_FORMAT_YV12
||
99 src
->format() == media::PIXEL_FORMAT_I420
);
100 if (dst_format
== PP_VIDEOFRAME_FORMAT_BGRA
) {
101 if (src
->visible_rect().size() == dst_size
) {
102 libyuv::I420ToARGB(src
->visible_data(VideoFrame::kYPlane
),
103 src
->stride(VideoFrame::kYPlane
),
104 src
->visible_data(VideoFrame::kUPlane
),
105 src
->stride(VideoFrame::kUPlane
),
106 src
->visible_data(VideoFrame::kVPlane
),
107 src
->stride(VideoFrame::kVPlane
),
109 dst_size
.width() * 4,
113 media::ScaleYUVToRGB32(src
->visible_data(VideoFrame::kYPlane
),
114 src
->visible_data(VideoFrame::kUPlane
),
115 src
->visible_data(VideoFrame::kVPlane
),
117 src
->visible_rect().width(),
118 src
->visible_rect().height(),
121 src
->stride(VideoFrame::kYPlane
),
122 src
->stride(VideoFrame::kUPlane
),
123 dst_size
.width() * 4,
126 media::FILTER_BILINEAR
);
128 } else if (dst_format
== PP_VIDEOFRAME_FORMAT_YV12
||
129 dst_format
== PP_VIDEOFRAME_FORMAT_I420
) {
130 static const size_t kPlanesOrder
[][3] = {
131 {VideoFrame::kYPlane
, VideoFrame::kVPlane
,
132 VideoFrame::kUPlane
}, // YV12
133 {VideoFrame::kYPlane
, VideoFrame::kUPlane
,
134 VideoFrame::kVPlane
}, // I420
136 const int plane_order
= (dst_format
== PP_VIDEOFRAME_FORMAT_YV12
) ? 0 : 1;
137 int dst_width
= dst_size
.width();
138 int dst_height
= dst_size
.height();
139 libyuv::ScalePlane(src
->visible_data(kPlanesOrder
[plane_order
][0]),
140 src
->stride(kPlanesOrder
[plane_order
][0]),
141 src
->visible_rect().width(),
142 src
->visible_rect().height(),
148 dst
+= dst_width
* dst_height
;
149 const int src_halfwidth
= (src
->visible_rect().width() + 1) >> 1;
150 const int src_halfheight
= (src
->visible_rect().height() + 1) >> 1;
151 const int dst_halfwidth
= (dst_width
+ 1) >> 1;
152 const int dst_halfheight
= (dst_height
+ 1) >> 1;
153 libyuv::ScalePlane(src
->visible_data(kPlanesOrder
[plane_order
][1]),
154 src
->stride(kPlanesOrder
[plane_order
][1]),
162 dst
+= dst_halfwidth
* dst_halfheight
;
163 libyuv::ScalePlane(src
->visible_data(kPlanesOrder
[plane_order
][2]),
164 src
->stride(kPlanesOrder
[plane_order
][2]),
181 // Internal class used for delivering video frames on the IO-thread to
182 // the MediaStreamVideoSource implementation.
183 class PepperMediaStreamVideoTrackHost::FrameDeliverer
184 : public base::RefCountedThreadSafe
<FrameDeliverer
> {
186 FrameDeliverer(scoped_refptr
<base::SingleThreadTaskRunner
> io_task_runner
,
187 const VideoCaptureDeliverFrameCB
& new_frame_callback
);
189 void DeliverVideoFrame(const scoped_refptr
<media::VideoFrame
>& frame
);
192 friend class base::RefCountedThreadSafe
<FrameDeliverer
>;
193 virtual ~FrameDeliverer();
195 void DeliverFrameOnIO(const scoped_refptr
<media::VideoFrame
>& frame
);
197 scoped_refptr
<base::SingleThreadTaskRunner
> io_task_runner_
;
198 VideoCaptureDeliverFrameCB new_frame_callback_
;
200 DISALLOW_COPY_AND_ASSIGN(FrameDeliverer
);
203 PepperMediaStreamVideoTrackHost::FrameDeliverer::FrameDeliverer(
204 scoped_refptr
<base::SingleThreadTaskRunner
> io_task_runner
,
205 const VideoCaptureDeliverFrameCB
& new_frame_callback
)
206 : io_task_runner_(io_task_runner
), new_frame_callback_(new_frame_callback
) {
209 PepperMediaStreamVideoTrackHost::FrameDeliverer::~FrameDeliverer() {
212 void PepperMediaStreamVideoTrackHost::FrameDeliverer::DeliverVideoFrame(
213 const scoped_refptr
<media::VideoFrame
>& frame
) {
214 io_task_runner_
->PostTask(
215 FROM_HERE
, base::Bind(&FrameDeliverer::DeliverFrameOnIO
, this, frame
));
218 void PepperMediaStreamVideoTrackHost::FrameDeliverer::DeliverFrameOnIO(
219 const scoped_refptr
<media::VideoFrame
>& frame
) {
220 DCHECK(io_task_runner_
->BelongsToCurrentThread());
221 // The time when this frame is generated is unknown so give a null value to
222 // |estimated_capture_time|.
223 new_frame_callback_
.Run(frame
, base::TimeTicks());
226 PepperMediaStreamVideoTrackHost::PepperMediaStreamVideoTrackHost(
227 RendererPpapiHost
* host
,
228 PP_Instance instance
,
229 PP_Resource resource
,
230 const blink::WebMediaStreamTrack
& track
)
231 : PepperMediaStreamTrackHostBase(host
, instance
, resource
),
234 number_of_buffers_(kDefaultNumberOfBuffers
),
235 source_frame_format_(PP_VIDEOFRAME_FORMAT_UNKNOWN
),
236 plugin_frame_format_(PP_VIDEOFRAME_FORMAT_UNKNOWN
),
239 output_started_(false),
240 weak_factory_(this) {
241 DCHECK(!track_
.isNull());
244 PepperMediaStreamVideoTrackHost::PepperMediaStreamVideoTrackHost(
245 RendererPpapiHost
* host
,
246 PP_Instance instance
,
247 PP_Resource resource
)
248 : PepperMediaStreamTrackHostBase(host
, instance
, resource
),
250 number_of_buffers_(kDefaultNumberOfBuffers
),
251 source_frame_format_(PP_VIDEOFRAME_FORMAT_UNKNOWN
),
252 plugin_frame_format_(PP_VIDEOFRAME_FORMAT_UNKNOWN
),
255 output_started_(false),
256 weak_factory_(this) {
258 DCHECK(!track_
.isNull());
261 bool PepperMediaStreamVideoTrackHost::IsMediaStreamVideoTrackHost() {
265 PepperMediaStreamVideoTrackHost::~PepperMediaStreamVideoTrackHost() {
269 void PepperMediaStreamVideoTrackHost::InitBuffers() {
270 gfx::Size size
= GetTargetSize(source_frame_size_
, plugin_frame_size_
);
271 DCHECK(!size
.IsEmpty());
273 PP_VideoFrame_Format format
=
274 GetTargetFormat(source_frame_format_
, plugin_frame_format_
);
275 DCHECK_NE(format
, PP_VIDEOFRAME_FORMAT_UNKNOWN
);
277 if (format
== PP_VIDEOFRAME_FORMAT_BGRA
) {
278 frame_data_size_
= size
.width() * size
.height() * 4;
281 VideoFrame::AllocationSize(FromPpapiFormat(format
), size
);
284 DCHECK_GT(frame_data_size_
, 0U);
285 int32_t buffer_size
=
286 sizeof(ppapi::MediaStreamBuffer::Video
) + frame_data_size_
;
287 bool result
= PepperMediaStreamTrackHostBase::InitBuffers(number_of_buffers_
,
292 if (type_
== kWrite
) {
293 for (int32_t i
= 0; i
< buffer_manager()->number_of_buffers(); ++i
) {
294 ppapi::MediaStreamBuffer::Video
* buffer
=
295 &(buffer_manager()->GetBufferPointer(i
)->video
);
296 buffer
->header
.size
= buffer_manager()->buffer_size();
297 buffer
->header
.type
= ppapi::MediaStreamBuffer::TYPE_VIDEO
;
298 buffer
->format
= format
;
299 buffer
->size
.width
= size
.width();
300 buffer
->size
.height
= size
.height();
301 buffer
->data_size
= frame_data_size_
;
304 // Make all the frames avaiable to the plugin.
305 std::vector
<int32_t> indices
= buffer_manager()->DequeueBuffers();
306 SendEnqueueBuffersMessageToPlugin(indices
);
310 void PepperMediaStreamVideoTrackHost::OnClose() {
312 MediaStreamVideoSink::RemoveFromVideoTrack(this, track_
);
313 weak_factory_
.InvalidateWeakPtrs();
318 int32_t PepperMediaStreamVideoTrackHost::OnHostMsgEnqueueBuffer(
319 ppapi::host::HostMessageContext
* context
, int32_t index
) {
320 if (type_
== kRead
) {
321 return PepperMediaStreamTrackHostBase::OnHostMsgEnqueueBuffer(context
,
324 return SendFrameToTrack(index
);
328 int32_t PepperMediaStreamVideoTrackHost::SendFrameToTrack(int32_t index
) {
329 DCHECK_EQ(type_
, kWrite
);
331 if (output_started_
) {
332 // Sends the frame to blink video track.
333 ppapi::MediaStreamBuffer::Video
* pp_frame
=
334 &(buffer_manager()->GetBufferPointer(index
)->video
);
336 int32 y_stride
= plugin_frame_size_
.width();
337 int32 uv_stride
= (plugin_frame_size_
.width() + 1) / 2;
338 uint8
* y_data
= static_cast<uint8
*>(pp_frame
->data
);
340 uint8
* u_data
= y_data
+ plugin_frame_size_
.GetArea();
341 uint8
* v_data
= y_data
+ (plugin_frame_size_
.GetArea() * 5 / 4);
342 if (plugin_frame_format_
== PP_VIDEOFRAME_FORMAT_YV12
) {
343 // Swap u and v for YV12.
349 int64 ts_ms
= static_cast<int64
>(pp_frame
->timestamp
*
350 base::Time::kMillisecondsPerSecond
);
351 scoped_refptr
<VideoFrame
> frame
= media::VideoFrame::WrapExternalYuvData(
352 FromPpapiFormat(plugin_frame_format_
),
354 gfx::Rect(plugin_frame_size_
),
362 base::TimeDelta::FromMilliseconds(ts_ms
));
364 frame_deliverer_
->DeliverVideoFrame(frame
);
367 // Makes the frame available again for plugin.
368 SendEnqueueBufferMessageToPlugin(index
);
372 void PepperMediaStreamVideoTrackHost::OnVideoFrame(
373 const scoped_refptr
<VideoFrame
>& frame
,
374 base::TimeTicks estimated_capture_time
) {
376 // TODO(penghuang): Check |frame->end_of_stream()| and close the track.
377 PP_VideoFrame_Format ppformat
= ToPpapiFormat(frame
->format());
378 if (ppformat
== PP_VIDEOFRAME_FORMAT_UNKNOWN
)
381 if (source_frame_size_
.IsEmpty()) {
382 source_frame_size_
= frame
->visible_rect().size();
383 source_frame_format_
= ppformat
;
387 int32_t index
= buffer_manager()->DequeueBuffer();
388 // Drop frames if the underlying buffer is full.
390 DVLOG(1) << "A frame is dropped.";
394 CHECK_EQ(ppformat
, source_frame_format_
) << "Frame format is changed.";
396 gfx::Size size
= GetTargetSize(source_frame_size_
, plugin_frame_size_
);
398 GetTargetFormat(source_frame_format_
, plugin_frame_format_
);
399 ppapi::MediaStreamBuffer::Video
* buffer
=
400 &(buffer_manager()->GetBufferPointer(index
)->video
);
401 buffer
->header
.size
= buffer_manager()->buffer_size();
402 buffer
->header
.type
= ppapi::MediaStreamBuffer::TYPE_VIDEO
;
403 buffer
->timestamp
= frame
->timestamp().InSecondsF();
404 buffer
->format
= ppformat
;
405 buffer
->size
.width
= size
.width();
406 buffer
->size
.height
= size
.height();
407 buffer
->data_size
= frame_data_size_
;
408 ConvertFromMediaVideoFrame(frame
, ppformat
, size
, buffer
->data
);
410 SendEnqueueBufferMessageToPlugin(index
);
413 void PepperMediaStreamVideoTrackHost::GetCurrentSupportedFormats(
414 int max_requested_width
, int max_requested_height
,
415 double max_requested_frame_rate
,
416 const VideoCaptureDeviceFormatsCB
& callback
) {
417 if (type_
!= kWrite
) {
418 DVLOG(1) << "GetCurrentSupportedFormats is only supported in output mode.";
419 callback
.Run(media::VideoCaptureFormats());
423 media::VideoCaptureFormats formats
;
425 media::VideoCaptureFormat(plugin_frame_size_
,
426 kDefaultOutputFrameRate
,
427 ToPixelFormat(plugin_frame_format_
)));
428 callback
.Run(formats
);
431 void PepperMediaStreamVideoTrackHost::StartSourceImpl(
432 const media::VideoCaptureFormat
& format
,
433 const blink::WebMediaConstraints
& constraints
,
434 const VideoCaptureDeliverFrameCB
& frame_callback
) {
435 output_started_
= true;
436 frame_deliverer_
= new FrameDeliverer(io_task_runner(), frame_callback
);
439 void PepperMediaStreamVideoTrackHost::StopSourceImpl() {
440 output_started_
= false;
441 frame_deliverer_
= NULL
;
444 void PepperMediaStreamVideoTrackHost::DidConnectPendingHostToResource() {
446 MediaStreamVideoSink::AddToVideoTrack(
448 media::BindToCurrentLoop(
450 &PepperMediaStreamVideoTrackHost::OnVideoFrame
,
451 weak_factory_
.GetWeakPtr())),
457 int32_t PepperMediaStreamVideoTrackHost::OnResourceMessageReceived(
458 const IPC::Message
& msg
,
459 HostMessageContext
* context
) {
460 PPAPI_BEGIN_MESSAGE_MAP(PepperMediaStreamVideoTrackHost
, msg
)
461 PPAPI_DISPATCH_HOST_RESOURCE_CALL(
462 PpapiHostMsg_MediaStreamVideoTrack_Configure
, OnHostMsgConfigure
)
463 PPAPI_END_MESSAGE_MAP()
464 return PepperMediaStreamTrackHostBase::OnResourceMessageReceived(msg
,
468 int32_t PepperMediaStreamVideoTrackHost::OnHostMsgConfigure(
469 HostMessageContext
* context
,
470 const MediaStreamVideoTrackShared::Attributes
& attributes
) {
471 CHECK(MediaStreamVideoTrackShared::VerifyAttributes(attributes
));
473 bool changed
= false;
474 gfx::Size
new_size(attributes
.width
, attributes
.height
);
475 if (GetTargetSize(source_frame_size_
, plugin_frame_size_
) !=
476 GetTargetSize(source_frame_size_
, new_size
)) {
479 plugin_frame_size_
= new_size
;
481 int32_t buffers
= attributes
.buffers
482 ? std::min(kMaxNumberOfBuffers
, attributes
.buffers
)
483 : kDefaultNumberOfBuffers
;
484 if (buffers
!= number_of_buffers_
)
486 number_of_buffers_
= buffers
;
488 if (GetTargetFormat(source_frame_format_
, plugin_frame_format_
) !=
489 GetTargetFormat(source_frame_format_
, attributes
.format
)) {
492 plugin_frame_format_
= attributes
.format
;
494 // If the first frame has been received, we will re-initialize buffers with
495 // new settings. Otherwise, we will initialize buffer when we receive
496 // the first frame, because plugin can only provide part of attributes
497 // which are not enough to initialize buffers.
498 if (changed
&& (type_
== kWrite
|| !source_frame_size_
.IsEmpty()))
501 // TODO(ronghuawu): Ask the owner of DOMMediaStreamTrackToResource why
502 // source id instead of track id is used there.
503 const std::string id
= track_
.source().id().utf8();
504 context
->reply_msg
= PpapiPluginMsg_MediaStreamVideoTrack_ConfigureReply(id
);
508 void PepperMediaStreamVideoTrackHost::InitBlinkTrack() {
509 std::string source_id
;
510 base::Base64Encode(base::RandBytesAsString(64), &source_id
);
511 blink::WebMediaStreamSource webkit_source
;
512 webkit_source
.initialize(base::UTF8ToUTF16(source_id
),
513 blink::WebMediaStreamSource::TypeVideo
,
514 base::UTF8ToUTF16(kPepperVideoSourceName
),
515 false /* remote */, true /* readonly */);
516 webkit_source
.setExtraData(this);
518 const bool enabled
= true;
519 blink::WebMediaConstraints constraints
;
520 constraints
.initialize();
521 track_
= MediaStreamVideoTrack::CreateVideoTrack(
524 &PepperMediaStreamVideoTrackHost::OnTrackStarted
,
525 base::Unretained(this)),
529 void PepperMediaStreamVideoTrackHost::OnTrackStarted(
530 MediaStreamSource
* source
,
531 MediaStreamRequestResult result
,
532 const blink::WebString
& result_name
) {
533 DVLOG(3) << "OnTrackStarted result: " << result
;
536 } // namespace content