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/libjingle/overrides/talk/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(VideoFrame::Format format) {
58 case VideoFrame::YV12:
59 return PP_VIDEOFRAME_FORMAT_YV12;
60 case VideoFrame::I420:
61 return PP_VIDEOFRAME_FORMAT_I420;
63 DVLOG(1) << "Unsupported pixel format " << format;
64 return PP_VIDEOFRAME_FORMAT_UNKNOWN;
68 VideoFrame::Format FromPpapiFormat(PP_VideoFrame_Format format) {
70 case PP_VIDEOFRAME_FORMAT_YV12:
71 return VideoFrame::YV12;
72 case PP_VIDEOFRAME_FORMAT_I420:
73 return VideoFrame::I420;
75 DVLOG(1) << "Unsupported pixel format " << format;
76 return VideoFrame::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() == VideoFrame::YV12 || src->format() == VideoFrame::I420);
99 if (dst_format == PP_VIDEOFRAME_FORMAT_BGRA) {
100 if (src->coded_size() == dst_size) {
101 libyuv::I420ToARGB(src->data(VideoFrame::kYPlane),
102 src->stride(VideoFrame::kYPlane),
103 src->data(VideoFrame::kUPlane),
104 src->stride(VideoFrame::kUPlane),
105 src->data(VideoFrame::kVPlane),
106 src->stride(VideoFrame::kVPlane),
108 dst_size.width() * 4,
112 media::ScaleYUVToRGB32(src->data(VideoFrame::kYPlane),
113 src->data(VideoFrame::kUPlane),
114 src->data(VideoFrame::kVPlane),
116 src->coded_size().width(),
117 src->coded_size().height(),
120 src->stride(VideoFrame::kYPlane),
121 src->stride(VideoFrame::kUPlane),
122 dst_size.width() * 4,
125 media::FILTER_BILINEAR);
127 } else if (dst_format == PP_VIDEOFRAME_FORMAT_YV12 ||
128 dst_format == PP_VIDEOFRAME_FORMAT_I420) {
129 static const size_t kPlanesOrder[][3] = {
130 {VideoFrame::kYPlane, VideoFrame::kVPlane,
131 VideoFrame::kUPlane}, // YV12
132 {VideoFrame::kYPlane, VideoFrame::kUPlane,
133 VideoFrame::kVPlane}, // I420
135 const int plane_order = (dst_format == PP_VIDEOFRAME_FORMAT_YV12) ? 0 : 1;
136 int dst_width = dst_size.width();
137 int dst_height = dst_size.height();
138 libyuv::ScalePlane(src->data(kPlanesOrder[plane_order][0]),
139 src->stride(kPlanesOrder[plane_order][0]),
140 src->coded_size().width(),
141 src->coded_size().height(),
147 dst += dst_width * dst_height;
148 const int src_halfwidth = (src->coded_size().width() + 1) >> 1;
149 const int src_halfheight = (src->coded_size().height() + 1) >> 1;
150 const int dst_halfwidth = (dst_width + 1) >> 1;
151 const int dst_halfheight = (dst_height + 1) >> 1;
152 libyuv::ScalePlane(src->data(kPlanesOrder[plane_order][1]),
153 src->stride(kPlanesOrder[plane_order][1]),
161 dst += dst_halfwidth * dst_halfheight;
162 libyuv::ScalePlane(src->data(kPlanesOrder[plane_order][2]),
163 src->stride(kPlanesOrder[plane_order][2]),
180 // Internal class used for delivering video frames on the IO-thread to
181 // the MediaStreamVideoSource implementation.
182 class PepperMediaStreamVideoTrackHost::FrameDeliverer
183 : public base::RefCountedThreadSafe<FrameDeliverer> {
186 const scoped_refptr<base::MessageLoopProxy>& io_message_loop_proxy,
187 const VideoCaptureDeliverFrameCB& new_frame_callback);
189 void DeliverVideoFrame(const scoped_refptr<media::VideoFrame>& frame,
190 const media::VideoCaptureFormat& format);
193 friend class base::RefCountedThreadSafe<FrameDeliverer>;
194 virtual ~FrameDeliverer();
196 void DeliverFrameOnIO(const scoped_refptr<media::VideoFrame>& frame,
197 const media::VideoCaptureFormat& format);
199 scoped_refptr<base::MessageLoopProxy> io_message_loop_;
200 VideoCaptureDeliverFrameCB new_frame_callback_;
202 DISALLOW_COPY_AND_ASSIGN(FrameDeliverer);
205 PepperMediaStreamVideoTrackHost::FrameDeliverer::FrameDeliverer(
206 const scoped_refptr<base::MessageLoopProxy>& io_message_loop_proxy,
207 const VideoCaptureDeliverFrameCB& new_frame_callback)
208 : io_message_loop_(io_message_loop_proxy),
209 new_frame_callback_(new_frame_callback) {
212 PepperMediaStreamVideoTrackHost::FrameDeliverer::~FrameDeliverer() {
215 void PepperMediaStreamVideoTrackHost::FrameDeliverer::DeliverVideoFrame(
216 const scoped_refptr<media::VideoFrame>& frame,
217 const media::VideoCaptureFormat& format) {
218 io_message_loop_->PostTask(
220 base::Bind(&FrameDeliverer::DeliverFrameOnIO,
221 this, frame, format));
224 void PepperMediaStreamVideoTrackHost::FrameDeliverer::DeliverFrameOnIO(
225 const scoped_refptr<media::VideoFrame>& frame,
226 const media::VideoCaptureFormat& format) {
227 DCHECK(io_message_loop_->BelongsToCurrentThread());
228 new_frame_callback_.Run(frame, format);
231 PepperMediaStreamVideoTrackHost::PepperMediaStreamVideoTrackHost(
232 RendererPpapiHost* host,
233 PP_Instance instance,
234 PP_Resource resource,
235 const blink::WebMediaStreamTrack& track)
236 : PepperMediaStreamTrackHostBase(host, instance, resource),
239 number_of_buffers_(kDefaultNumberOfBuffers),
240 source_frame_format_(PP_VIDEOFRAME_FORMAT_UNKNOWN),
241 plugin_frame_format_(PP_VIDEOFRAME_FORMAT_UNKNOWN),
244 output_started_(false),
245 weak_factory_(this) {
246 DCHECK(!track_.isNull());
249 PepperMediaStreamVideoTrackHost::PepperMediaStreamVideoTrackHost(
250 RendererPpapiHost* host,
251 PP_Instance instance,
252 PP_Resource resource)
253 : PepperMediaStreamTrackHostBase(host, instance, resource),
255 number_of_buffers_(kDefaultNumberOfBuffers),
256 source_frame_format_(PP_VIDEOFRAME_FORMAT_UNKNOWN),
257 plugin_frame_format_(PP_VIDEOFRAME_FORMAT_UNKNOWN),
260 output_started_(false),
261 weak_factory_(this) {
263 DCHECK(!track_.isNull());
266 bool PepperMediaStreamVideoTrackHost::IsMediaStreamVideoTrackHost() {
270 PepperMediaStreamVideoTrackHost::~PepperMediaStreamVideoTrackHost() {
274 void PepperMediaStreamVideoTrackHost::InitBuffers() {
275 gfx::Size size = GetTargetSize(source_frame_size_, plugin_frame_size_);
276 DCHECK(!size.IsEmpty());
278 PP_VideoFrame_Format format =
279 GetTargetFormat(source_frame_format_, plugin_frame_format_);
280 DCHECK_NE(format, PP_VIDEOFRAME_FORMAT_UNKNOWN);
282 if (format == PP_VIDEOFRAME_FORMAT_BGRA) {
283 frame_data_size_ = size.width() * size.height() * 4;
286 VideoFrame::AllocationSize(FromPpapiFormat(format), size);
289 DCHECK_GT(frame_data_size_, 0U);
290 int32_t buffer_size =
291 sizeof(ppapi::MediaStreamBuffer::Video) + frame_data_size_;
292 bool result = PepperMediaStreamTrackHostBase::InitBuffers(number_of_buffers_,
297 if (type_ == kWrite) {
298 for (int32_t i = 0; i < buffer_manager()->number_of_buffers(); ++i) {
299 ppapi::MediaStreamBuffer::Video* buffer =
300 &(buffer_manager()->GetBufferPointer(i)->video);
301 buffer->header.size = buffer_manager()->buffer_size();
302 buffer->header.type = ppapi::MediaStreamBuffer::TYPE_VIDEO;
303 buffer->format = format;
304 buffer->size.width = size.width();
305 buffer->size.height = size.height();
306 buffer->data_size = frame_data_size_;
309 // Make all the frames avaiable to the plugin.
310 std::vector<int32_t> indices = buffer_manager()->DequeueBuffers();
311 SendEnqueueBuffersMessageToPlugin(indices);
315 void PepperMediaStreamVideoTrackHost::OnClose() {
317 MediaStreamVideoSink::RemoveFromVideoTrack(this, track_);
318 weak_factory_.InvalidateWeakPtrs();
323 int32_t PepperMediaStreamVideoTrackHost::OnHostMsgEnqueueBuffer(
324 ppapi::host::HostMessageContext* context, int32_t index) {
325 if (type_ == kRead) {
326 return PepperMediaStreamTrackHostBase::OnHostMsgEnqueueBuffer(context,
329 return SendFrameToTrack(index);
333 int32_t PepperMediaStreamVideoTrackHost::SendFrameToTrack(int32_t index) {
334 DCHECK_EQ(type_, kWrite);
336 if (output_started_) {
337 // Sends the frame to blink video track.
338 ppapi::MediaStreamBuffer::Video* pp_frame =
339 &(buffer_manager()->GetBufferPointer(index)->video);
341 int32 y_stride = plugin_frame_size_.width();
342 int32 uv_stride = (plugin_frame_size_.width() + 1) / 2;
343 uint8* y_data = static_cast<uint8*>(pp_frame->data);
345 uint8* u_data = y_data + plugin_frame_size_.GetArea();
346 uint8* v_data = y_data + (plugin_frame_size_.GetArea() * 5 / 4);
347 if (plugin_frame_format_ == PP_VIDEOFRAME_FORMAT_YV12) {
348 // Swap u and v for YV12.
354 int64 ts_ms = static_cast<int64>(pp_frame->timestamp *
355 base::Time::kMillisecondsPerSecond);
356 scoped_refptr<VideoFrame> frame = media::VideoFrame::WrapExternalYuvData(
357 FromPpapiFormat(plugin_frame_format_),
359 gfx::Rect(plugin_frame_size_),
367 base::TimeDelta::FromMilliseconds(ts_ms),
370 frame_deliverer_->DeliverVideoFrame(
372 media::VideoCaptureFormat(plugin_frame_size_,
373 kDefaultOutputFrameRate,
374 ToPixelFormat(plugin_frame_format_)));
377 // Makes the frame available again for plugin.
378 SendEnqueueBufferMessageToPlugin(index);
382 void PepperMediaStreamVideoTrackHost::OnVideoFrame(
383 const scoped_refptr<VideoFrame>& frame,
384 const media::VideoCaptureFormat& format) {
386 // TODO(penghuang): Check |frame->end_of_stream()| and close the track.
387 PP_VideoFrame_Format ppformat = ToPpapiFormat(frame->format());
388 if (ppformat == PP_VIDEOFRAME_FORMAT_UNKNOWN)
391 if (source_frame_size_.IsEmpty()) {
392 source_frame_size_ = frame->coded_size();
393 source_frame_format_ = ppformat;
397 int32_t index = buffer_manager()->DequeueBuffer();
398 // Drop frames if the underlying buffer is full.
400 DVLOG(1) << "A frame is dropped.";
404 CHECK(frame->coded_size() == source_frame_size_) << "Frame size is changed";
405 CHECK_EQ(ppformat, source_frame_format_) << "Frame format is changed.";
407 gfx::Size size = GetTargetSize(source_frame_size_, plugin_frame_size_);
409 GetTargetFormat(source_frame_format_, plugin_frame_format_);
410 ppapi::MediaStreamBuffer::Video* buffer =
411 &(buffer_manager()->GetBufferPointer(index)->video);
412 buffer->header.size = buffer_manager()->buffer_size();
413 buffer->header.type = ppapi::MediaStreamBuffer::TYPE_VIDEO;
414 buffer->timestamp = frame->timestamp().InSecondsF();
415 buffer->format = ppformat;
416 buffer->size.width = size.width();
417 buffer->size.height = size.height();
418 buffer->data_size = frame_data_size_;
419 ConvertFromMediaVideoFrame(frame, ppformat, size, buffer->data);
421 SendEnqueueBufferMessageToPlugin(index);
424 void PepperMediaStreamVideoTrackHost::GetCurrentSupportedFormats(
425 int max_requested_width, int max_requested_height,
426 const VideoCaptureDeviceFormatsCB& callback) {
427 if (type_ != kWrite) {
428 DVLOG(1) << "GetCurrentSupportedFormats is only supported in output mode.";
429 callback.Run(media::VideoCaptureFormats());
433 media::VideoCaptureFormats formats;
435 media::VideoCaptureFormat(plugin_frame_size_,
436 kDefaultOutputFrameRate,
437 ToPixelFormat(plugin_frame_format_)));
438 callback.Run(formats);
441 void PepperMediaStreamVideoTrackHost::StartSourceImpl(
442 const media::VideoCaptureParams& params,
443 const VideoCaptureDeliverFrameCB& frame_callback) {
444 output_started_ = true;
445 frame_deliverer_ = new FrameDeliverer(io_message_loop(), frame_callback);
448 void PepperMediaStreamVideoTrackHost::StopSourceImpl() {
449 output_started_ = false;
450 frame_deliverer_ = NULL;
453 void PepperMediaStreamVideoTrackHost::DidConnectPendingHostToResource() {
455 MediaStreamVideoSink::AddToVideoTrack(
457 media::BindToCurrentLoop(
459 &PepperMediaStreamVideoTrackHost::OnVideoFrame,
460 weak_factory_.GetWeakPtr())),
466 int32_t PepperMediaStreamVideoTrackHost::OnResourceMessageReceived(
467 const IPC::Message& msg,
468 HostMessageContext* context) {
469 IPC_BEGIN_MESSAGE_MAP(PepperMediaStreamVideoTrackHost, msg)
470 PPAPI_DISPATCH_HOST_RESOURCE_CALL(
471 PpapiHostMsg_MediaStreamVideoTrack_Configure, OnHostMsgConfigure)
472 IPC_END_MESSAGE_MAP()
473 return PepperMediaStreamTrackHostBase::OnResourceMessageReceived(msg,
477 int32_t PepperMediaStreamVideoTrackHost::OnHostMsgConfigure(
478 HostMessageContext* context,
479 const MediaStreamVideoTrackShared::Attributes& attributes) {
480 CHECK(MediaStreamVideoTrackShared::VerifyAttributes(attributes));
482 bool changed = false;
483 gfx::Size new_size(attributes.width, attributes.height);
484 if (GetTargetSize(source_frame_size_, plugin_frame_size_) !=
485 GetTargetSize(source_frame_size_, new_size)) {
488 plugin_frame_size_ = new_size;
490 int32_t buffers = attributes.buffers
491 ? std::min(kMaxNumberOfBuffers, attributes.buffers)
492 : kDefaultNumberOfBuffers;
493 if (buffers != number_of_buffers_)
495 number_of_buffers_ = buffers;
497 if (GetTargetFormat(source_frame_format_, plugin_frame_format_) !=
498 GetTargetFormat(source_frame_format_, attributes.format)) {
501 plugin_frame_format_ = attributes.format;
503 // If the first frame has been received, we will re-initialize buffers with
504 // new settings. Otherwise, we will initialize buffer when we receive
505 // the first frame, because plugin can only provide part of attributes
506 // which are not enough to initialize buffers.
507 if (changed && (type_ == kWrite || !source_frame_size_.IsEmpty()))
510 // TODO(ronghuawu): Ask the owner of DOMMediaStreamTrackToResource why
511 // source id instead of track id is used there.
512 const std::string id = track_.source().id().utf8();
513 context->reply_msg = PpapiPluginMsg_MediaStreamVideoTrack_ConfigureReply(id);
517 void PepperMediaStreamVideoTrackHost::InitBlinkTrack() {
518 std::string source_id;
519 base::Base64Encode(base::RandBytesAsString(64), &source_id);
520 blink::WebMediaStreamSource webkit_source;
521 webkit_source.initialize(base::UTF8ToUTF16(source_id),
522 blink::WebMediaStreamSource::TypeVideo,
523 base::UTF8ToUTF16(kPepperVideoSourceName));
524 webkit_source.setExtraData(this);
526 const bool enabled = true;
527 blink::WebMediaConstraints constraints;
528 constraints.initialize();
529 track_ = MediaStreamVideoTrack::CreateVideoTrack(
532 &PepperMediaStreamVideoTrackHost::OnTrackStarted,
533 base::Unretained(this)),
537 void PepperMediaStreamVideoTrackHost::OnTrackStarted(
538 MediaStreamSource* source, bool success) {
539 DVLOG(3) << "OnTrackStarted result: " << success;
542 } // namespace content