pepper_media_stream_video_track_host.cc revision 03b57e008b61dfcb1fbad3aea950ae0e001748b0
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.
4
5#include "content/renderer/pepper/pepper_media_stream_video_track_host.h"
6
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"
21
22// IS_ALIGNED is also defined in
23// third_party/webrtc/overrides/webrtc/base/basictypes.h
24// TODO(ronghuawu): Avoid undef.
25#undef IS_ALIGNED
26#include "third_party/libyuv/include/libyuv.h"
27
28using media::VideoFrame;
29using ppapi::host::HostMessageContext;
30using ppapi::MediaStreamVideoTrackShared;
31
32namespace {
33
34const int32_t kDefaultNumberOfBuffers = 4;
35const int32_t kMaxNumberOfBuffers = 8;
36// Filter mode for scaling frames.
37const libyuv::FilterMode kFilterMode = libyuv::kFilterBox;
38
39const char kPepperVideoSourceName[] = "PepperVideoSourceName";
40
41// Default config for output mode.
42const int kDefaultOutputFrameRate = 30;
43
44media::VideoPixelFormat ToPixelFormat(PP_VideoFrame_Format format) {
45  switch (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;
50    default:
51      DVLOG(1) << "Unsupported pixel format " << format;
52      return media::PIXEL_FORMAT_UNKNOWN;
53  }
54}
55
56PP_VideoFrame_Format ToPpapiFormat(VideoFrame::Format format) {
57  switch (format) {
58    case VideoFrame::YV12:
59      return PP_VIDEOFRAME_FORMAT_YV12;
60    case VideoFrame::I420:
61      return PP_VIDEOFRAME_FORMAT_I420;
62    default:
63      DVLOG(1) << "Unsupported pixel format " << format;
64      return PP_VIDEOFRAME_FORMAT_UNKNOWN;
65  }
66}
67
68VideoFrame::Format FromPpapiFormat(PP_VideoFrame_Format format) {
69  switch (format) {
70    case PP_VIDEOFRAME_FORMAT_YV12:
71      return VideoFrame::YV12;
72    case PP_VIDEOFRAME_FORMAT_I420:
73      return VideoFrame::I420;
74    default:
75      DVLOG(1) << "Unsupported pixel format " << format;
76      return VideoFrame::UNKNOWN;
77  }
78}
79
80// Compute size base on the size of frame received from MediaStreamVideoSink
81// and size specified by plugin.
82gfx::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());
85}
86
87// Compute format base on the format of frame received from MediaStreamVideoSink
88// and format specified by plugin.
89PP_VideoFrame_Format GetTargetFormat(PP_VideoFrame_Format source,
90                                     PP_VideoFrame_Format plugin) {
91  return plugin != PP_VIDEOFRAME_FORMAT_UNKNOWN ? plugin : source;
92}
93
94void ConvertFromMediaVideoFrame(const scoped_refptr<media::VideoFrame>& src,
95                                PP_VideoFrame_Format dst_format,
96                                const gfx::Size& dst_size,
97                                uint8_t* dst) {
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),
107                         dst,
108                         dst_size.width() * 4,
109                         dst_size.width(),
110                         dst_size.height());
111    } else {
112      media::ScaleYUVToRGB32(src->data(VideoFrame::kYPlane),
113                             src->data(VideoFrame::kUPlane),
114                             src->data(VideoFrame::kVPlane),
115                             dst,
116                             src->coded_size().width(),
117                             src->coded_size().height(),
118                             dst_size.width(),
119                             dst_size.height(),
120                             src->stride(VideoFrame::kYPlane),
121                             src->stride(VideoFrame::kUPlane),
122                             dst_size.width() * 4,
123                             media::YV12,
124                             media::ROTATE_0,
125                             media::FILTER_BILINEAR);
126    }
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
134    };
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(),
142                       dst,
143                       dst_width,
144                       dst_width,
145                       dst_height,
146                       kFilterMode);
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]),
154                       src_halfwidth,
155                       src_halfheight,
156                       dst,
157                       dst_halfwidth,
158                       dst_halfwidth,
159                       dst_halfheight,
160                       kFilterMode);
161    dst += dst_halfwidth * dst_halfheight;
162    libyuv::ScalePlane(src->data(kPlanesOrder[plane_order][2]),
163                       src->stride(kPlanesOrder[plane_order][2]),
164                       src_halfwidth,
165                       src_halfheight,
166                       dst,
167                       dst_halfwidth,
168                       dst_halfwidth,
169                       dst_halfheight,
170                       kFilterMode);
171  } else {
172    NOTREACHED();
173  }
174}
175
176}  // namespace
177
178namespace content {
179
180// Internal class used for delivering video frames on the IO-thread to
181// the MediaStreamVideoSource implementation.
182class PepperMediaStreamVideoTrackHost::FrameDeliverer
183    : public base::RefCountedThreadSafe<FrameDeliverer> {
184 public:
185  FrameDeliverer(
186      const scoped_refptr<base::MessageLoopProxy>& io_message_loop_proxy,
187      const VideoCaptureDeliverFrameCB& new_frame_callback);
188
189  void DeliverVideoFrame(const scoped_refptr<media::VideoFrame>& frame,
190                         const media::VideoCaptureFormat& format);
191
192 private:
193  friend class base::RefCountedThreadSafe<FrameDeliverer>;
194  virtual ~FrameDeliverer();
195
196  void DeliverFrameOnIO(const scoped_refptr<media::VideoFrame>& frame,
197                        const media::VideoCaptureFormat& format);
198
199  scoped_refptr<base::MessageLoopProxy> io_message_loop_;
200  VideoCaptureDeliverFrameCB new_frame_callback_;
201
202  DISALLOW_COPY_AND_ASSIGN(FrameDeliverer);
203};
204
205PepperMediaStreamVideoTrackHost::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) {
210}
211
212PepperMediaStreamVideoTrackHost::FrameDeliverer::~FrameDeliverer() {
213}
214
215void PepperMediaStreamVideoTrackHost::FrameDeliverer::DeliverVideoFrame(
216    const scoped_refptr<media::VideoFrame>& frame,
217    const media::VideoCaptureFormat& format) {
218  io_message_loop_->PostTask(
219      FROM_HERE,
220      base::Bind(&FrameDeliverer::DeliverFrameOnIO,
221                 this, frame, format));
222}
223
224void PepperMediaStreamVideoTrackHost::FrameDeliverer::DeliverFrameOnIO(
225     const scoped_refptr<media::VideoFrame>& frame,
226     const media::VideoCaptureFormat& format) {
227  DCHECK(io_message_loop_->BelongsToCurrentThread());
228  // The time when this frame is generated is unknown so give a null value to
229  // |estimated_capture_time|.
230  new_frame_callback_.Run(frame, format, base::TimeTicks());
231}
232
233PepperMediaStreamVideoTrackHost::PepperMediaStreamVideoTrackHost(
234    RendererPpapiHost* host,
235    PP_Instance instance,
236    PP_Resource resource,
237    const blink::WebMediaStreamTrack& track)
238    : PepperMediaStreamTrackHostBase(host, instance, resource),
239      track_(track),
240      connected_(false),
241      number_of_buffers_(kDefaultNumberOfBuffers),
242      source_frame_format_(PP_VIDEOFRAME_FORMAT_UNKNOWN),
243      plugin_frame_format_(PP_VIDEOFRAME_FORMAT_UNKNOWN),
244      frame_data_size_(0),
245      type_(kRead),
246      output_started_(false),
247      weak_factory_(this) {
248  DCHECK(!track_.isNull());
249}
250
251PepperMediaStreamVideoTrackHost::PepperMediaStreamVideoTrackHost(
252    RendererPpapiHost* host,
253    PP_Instance instance,
254    PP_Resource resource)
255    : PepperMediaStreamTrackHostBase(host, instance, resource),
256      connected_(false),
257      number_of_buffers_(kDefaultNumberOfBuffers),
258      source_frame_format_(PP_VIDEOFRAME_FORMAT_UNKNOWN),
259      plugin_frame_format_(PP_VIDEOFRAME_FORMAT_UNKNOWN),
260      frame_data_size_(0),
261      type_(kWrite),
262      output_started_(false),
263      weak_factory_(this) {
264  InitBlinkTrack();
265  DCHECK(!track_.isNull());
266}
267
268bool PepperMediaStreamVideoTrackHost::IsMediaStreamVideoTrackHost() {
269  return true;
270}
271
272PepperMediaStreamVideoTrackHost::~PepperMediaStreamVideoTrackHost() {
273  OnClose();
274}
275
276void PepperMediaStreamVideoTrackHost::InitBuffers() {
277  gfx::Size size = GetTargetSize(source_frame_size_, plugin_frame_size_);
278  DCHECK(!size.IsEmpty());
279
280  PP_VideoFrame_Format format =
281      GetTargetFormat(source_frame_format_, plugin_frame_format_);
282  DCHECK_NE(format, PP_VIDEOFRAME_FORMAT_UNKNOWN);
283
284  if (format == PP_VIDEOFRAME_FORMAT_BGRA) {
285    frame_data_size_ = size.width() * size.height() * 4;
286  } else {
287    frame_data_size_ =
288        VideoFrame::AllocationSize(FromPpapiFormat(format), size);
289  }
290
291  DCHECK_GT(frame_data_size_, 0U);
292  int32_t buffer_size =
293      sizeof(ppapi::MediaStreamBuffer::Video) + frame_data_size_;
294  bool result = PepperMediaStreamTrackHostBase::InitBuffers(number_of_buffers_,
295                                                            buffer_size,
296                                                            type_);
297  CHECK(result);
298
299  if (type_ == kWrite) {
300    for (int32_t i = 0; i < buffer_manager()->number_of_buffers(); ++i) {
301      ppapi::MediaStreamBuffer::Video* buffer =
302          &(buffer_manager()->GetBufferPointer(i)->video);
303      buffer->header.size = buffer_manager()->buffer_size();
304      buffer->header.type = ppapi::MediaStreamBuffer::TYPE_VIDEO;
305      buffer->format = format;
306      buffer->size.width = size.width();
307      buffer->size.height = size.height();
308      buffer->data_size = frame_data_size_;
309    }
310
311    // Make all the frames avaiable to the plugin.
312    std::vector<int32_t> indices = buffer_manager()->DequeueBuffers();
313    SendEnqueueBuffersMessageToPlugin(indices);
314  }
315}
316
317void PepperMediaStreamVideoTrackHost::OnClose() {
318  if (connected_) {
319    MediaStreamVideoSink::RemoveFromVideoTrack(this, track_);
320    weak_factory_.InvalidateWeakPtrs();
321    connected_ = false;
322  }
323}
324
325int32_t PepperMediaStreamVideoTrackHost::OnHostMsgEnqueueBuffer(
326    ppapi::host::HostMessageContext* context, int32_t index) {
327  if (type_ == kRead) {
328    return PepperMediaStreamTrackHostBase::OnHostMsgEnqueueBuffer(context,
329                                                                  index);
330  } else {
331    return SendFrameToTrack(index);
332  }
333}
334
335int32_t PepperMediaStreamVideoTrackHost::SendFrameToTrack(int32_t index) {
336  DCHECK_EQ(type_, kWrite);
337
338  if (output_started_) {
339    // Sends the frame to blink video track.
340    ppapi::MediaStreamBuffer::Video* pp_frame =
341        &(buffer_manager()->GetBufferPointer(index)->video);
342
343    int32 y_stride = plugin_frame_size_.width();
344    int32 uv_stride = (plugin_frame_size_.width() + 1) / 2;
345    uint8* y_data = static_cast<uint8*>(pp_frame->data);
346    // Default to I420
347    uint8* u_data = y_data + plugin_frame_size_.GetArea();
348    uint8* v_data = y_data + (plugin_frame_size_.GetArea() * 5 / 4);
349    if (plugin_frame_format_ == PP_VIDEOFRAME_FORMAT_YV12) {
350      // Swap u and v for YV12.
351      uint8* tmp = u_data;
352      u_data = v_data;
353      v_data = tmp;
354    }
355
356    int64 ts_ms = static_cast<int64>(pp_frame->timestamp *
357                                     base::Time::kMillisecondsPerSecond);
358    scoped_refptr<VideoFrame> frame = media::VideoFrame::WrapExternalYuvData(
359        FromPpapiFormat(plugin_frame_format_),
360        plugin_frame_size_,
361        gfx::Rect(plugin_frame_size_),
362        plugin_frame_size_,
363        y_stride,
364        uv_stride,
365        uv_stride,
366        y_data,
367        u_data,
368        v_data,
369        base::TimeDelta::FromMilliseconds(ts_ms),
370        base::Closure());
371
372    frame_deliverer_->DeliverVideoFrame(
373        frame,
374        media::VideoCaptureFormat(plugin_frame_size_,
375                                  kDefaultOutputFrameRate,
376                                  ToPixelFormat(plugin_frame_format_)));
377  }
378
379  // Makes the frame available again for plugin.
380  SendEnqueueBufferMessageToPlugin(index);
381  return PP_OK;
382}
383
384void PepperMediaStreamVideoTrackHost::OnVideoFrame(
385    const scoped_refptr<VideoFrame>& frame,
386    const media::VideoCaptureFormat& format,
387    const base::TimeTicks& estimated_capture_time) {
388  DCHECK(frame);
389  // TODO(penghuang): Check |frame->end_of_stream()| and close the track.
390  PP_VideoFrame_Format ppformat = ToPpapiFormat(frame->format());
391  if (ppformat == PP_VIDEOFRAME_FORMAT_UNKNOWN)
392    return;
393
394  if (source_frame_size_.IsEmpty()) {
395    source_frame_size_ = frame->coded_size();
396    source_frame_format_ = ppformat;
397    InitBuffers();
398  }
399
400  int32_t index = buffer_manager()->DequeueBuffer();
401  // Drop frames if the underlying buffer is full.
402  if (index < 0) {
403    DVLOG(1) << "A frame is dropped.";
404    return;
405  }
406
407  CHECK_EQ(ppformat, source_frame_format_) << "Frame format is changed.";
408
409  gfx::Size size = GetTargetSize(source_frame_size_, plugin_frame_size_);
410  ppformat =
411      GetTargetFormat(source_frame_format_, plugin_frame_format_);
412  ppapi::MediaStreamBuffer::Video* buffer =
413      &(buffer_manager()->GetBufferPointer(index)->video);
414  buffer->header.size = buffer_manager()->buffer_size();
415  buffer->header.type = ppapi::MediaStreamBuffer::TYPE_VIDEO;
416  buffer->timestamp = frame->timestamp().InSecondsF();
417  buffer->format = ppformat;
418  buffer->size.width = size.width();
419  buffer->size.height = size.height();
420  buffer->data_size = frame_data_size_;
421  ConvertFromMediaVideoFrame(frame, ppformat, size, buffer->data);
422
423  SendEnqueueBufferMessageToPlugin(index);
424}
425
426void PepperMediaStreamVideoTrackHost::GetCurrentSupportedFormats(
427    int max_requested_width, int max_requested_height,
428    double max_requested_frame_rate,
429    const VideoCaptureDeviceFormatsCB& callback) {
430  if (type_ != kWrite) {
431    DVLOG(1) << "GetCurrentSupportedFormats is only supported in output mode.";
432    callback.Run(media::VideoCaptureFormats());
433    return;
434  }
435
436  media::VideoCaptureFormats formats;
437  formats.push_back(
438      media::VideoCaptureFormat(plugin_frame_size_,
439                                kDefaultOutputFrameRate,
440                                ToPixelFormat(plugin_frame_format_)));
441  callback.Run(formats);
442}
443
444void PepperMediaStreamVideoTrackHost::StartSourceImpl(
445    const media::VideoCaptureParams& params,
446    const VideoCaptureDeliverFrameCB& frame_callback) {
447  output_started_ = true;
448  frame_deliverer_ = new FrameDeliverer(io_message_loop(), frame_callback);
449}
450
451void PepperMediaStreamVideoTrackHost::StopSourceImpl() {
452  output_started_ = false;
453  frame_deliverer_ = NULL;
454}
455
456void PepperMediaStreamVideoTrackHost::DidConnectPendingHostToResource() {
457  if (!connected_) {
458    MediaStreamVideoSink::AddToVideoTrack(
459        this,
460        media::BindToCurrentLoop(
461            base::Bind(
462                &PepperMediaStreamVideoTrackHost::OnVideoFrame,
463                weak_factory_.GetWeakPtr())),
464        track_);
465    connected_ = true;
466  }
467}
468
469int32_t PepperMediaStreamVideoTrackHost::OnResourceMessageReceived(
470    const IPC::Message& msg,
471    HostMessageContext* context) {
472  PPAPI_BEGIN_MESSAGE_MAP(PepperMediaStreamVideoTrackHost, msg)
473    PPAPI_DISPATCH_HOST_RESOURCE_CALL(
474        PpapiHostMsg_MediaStreamVideoTrack_Configure, OnHostMsgConfigure)
475  PPAPI_END_MESSAGE_MAP()
476  return PepperMediaStreamTrackHostBase::OnResourceMessageReceived(msg,
477                                                                   context);
478}
479
480int32_t PepperMediaStreamVideoTrackHost::OnHostMsgConfigure(
481    HostMessageContext* context,
482    const MediaStreamVideoTrackShared::Attributes& attributes) {
483  CHECK(MediaStreamVideoTrackShared::VerifyAttributes(attributes));
484
485  bool changed = false;
486  gfx::Size new_size(attributes.width, attributes.height);
487  if (GetTargetSize(source_frame_size_, plugin_frame_size_) !=
488      GetTargetSize(source_frame_size_, new_size)) {
489    changed = true;
490  }
491  plugin_frame_size_ = new_size;
492
493  int32_t buffers = attributes.buffers
494                        ? std::min(kMaxNumberOfBuffers, attributes.buffers)
495                        : kDefaultNumberOfBuffers;
496  if (buffers != number_of_buffers_)
497    changed = true;
498  number_of_buffers_ = buffers;
499
500  if (GetTargetFormat(source_frame_format_, plugin_frame_format_) !=
501      GetTargetFormat(source_frame_format_, attributes.format)) {
502    changed = true;
503  }
504  plugin_frame_format_ = attributes.format;
505
506  // If the first frame has been received, we will re-initialize buffers with
507  // new settings. Otherwise, we will initialize buffer when we receive
508  // the first frame, because plugin can only provide part of attributes
509  // which are not enough to initialize buffers.
510  if (changed && (type_ == kWrite || !source_frame_size_.IsEmpty()))
511    InitBuffers();
512
513  // TODO(ronghuawu): Ask the owner of DOMMediaStreamTrackToResource why
514  // source id instead of track id is used there.
515  const std::string id = track_.source().id().utf8();
516  context->reply_msg = PpapiPluginMsg_MediaStreamVideoTrack_ConfigureReply(id);
517  return PP_OK;
518}
519
520void PepperMediaStreamVideoTrackHost::InitBlinkTrack() {
521  std::string source_id;
522  base::Base64Encode(base::RandBytesAsString(64), &source_id);
523  blink::WebMediaStreamSource webkit_source;
524  webkit_source.initialize(base::UTF8ToUTF16(source_id),
525                           blink::WebMediaStreamSource::TypeVideo,
526                           base::UTF8ToUTF16(kPepperVideoSourceName));
527  webkit_source.setExtraData(this);
528
529  const bool enabled = true;
530  blink::WebMediaConstraints constraints;
531  constraints.initialize();
532  track_ = MediaStreamVideoTrack::CreateVideoTrack(
533       this, constraints,
534       base::Bind(
535           &PepperMediaStreamVideoTrackHost::OnTrackStarted,
536           base::Unretained(this)),
537       enabled);
538}
539
540void PepperMediaStreamVideoTrackHost::OnTrackStarted(
541    MediaStreamSource* source,
542    MediaStreamRequestResult result,
543    const blink::WebString& result_name) {
544  DVLOG(3) << "OnTrackStarted result: " << result;
545}
546
547}  // namespace content
548