| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "content/renderer/pepper/pepper_media_stream_video_track_host.h" |
| |
| #include <stddef.h> |
| |
| #include "base/base64.h" |
| #include "base/logging.h" |
| #include "base/macros.h" |
| #include "base/rand_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "content/renderer/media/media_stream_video_source.h" |
| #include "content/renderer/media/media_stream_video_track.h" |
| #include "media/base/bind_to_current_loop.h" |
| #include "media/base/video_util.h" |
| #include "ppapi/c/pp_errors.h" |
| #include "ppapi/c/ppb_media_stream_video_track.h" |
| #include "ppapi/c/ppb_video_frame.h" |
| #include "ppapi/host/dispatch_host_message.h" |
| #include "ppapi/host/host_message_context.h" |
| #include "ppapi/proxy/ppapi_messages.h" |
| #include "ppapi/shared_impl/media_stream_buffer.h" |
| #include "third_party/libyuv/include/libyuv.h" |
| |
| using media::VideoFrame; |
| using ppapi::host::HostMessageContext; |
| using ppapi::MediaStreamVideoTrackShared; |
| |
| namespace { |
| |
| const int32_t kDefaultNumberOfBuffers = 4; |
| const int32_t kMaxNumberOfBuffers = 8; |
| // Filter mode for scaling frames. |
| const libyuv::FilterMode kFilterMode = libyuv::kFilterBox; |
| |
| const char kPepperVideoSourceName[] = "PepperVideoSourceName"; |
| |
| // Default config for output mode. |
| const int kDefaultOutputFrameRate = 30; |
| |
| media::VideoPixelFormat ToPixelFormat(PP_VideoFrame_Format format) { |
| switch (format) { |
| case PP_VIDEOFRAME_FORMAT_YV12: |
| return media::PIXEL_FORMAT_YV12; |
| case PP_VIDEOFRAME_FORMAT_I420: |
| return media::PIXEL_FORMAT_I420; |
| default: |
| DVLOG(1) << "Unsupported pixel format " << format; |
| return media::PIXEL_FORMAT_UNKNOWN; |
| } |
| } |
| |
| PP_VideoFrame_Format ToPpapiFormat(media::VideoPixelFormat format) { |
| switch (format) { |
| case media::PIXEL_FORMAT_YV12: |
| return PP_VIDEOFRAME_FORMAT_YV12; |
| case media::PIXEL_FORMAT_I420: |
| return PP_VIDEOFRAME_FORMAT_I420; |
| default: |
| DVLOG(1) << "Unsupported pixel format " << format; |
| return PP_VIDEOFRAME_FORMAT_UNKNOWN; |
| } |
| } |
| |
| media::VideoPixelFormat FromPpapiFormat(PP_VideoFrame_Format format) { |
| switch (format) { |
| case PP_VIDEOFRAME_FORMAT_YV12: |
| return media::PIXEL_FORMAT_YV12; |
| case PP_VIDEOFRAME_FORMAT_I420: |
| return media::PIXEL_FORMAT_I420; |
| default: |
| DVLOG(1) << "Unsupported pixel format " << format; |
| return media::PIXEL_FORMAT_UNKNOWN; |
| } |
| } |
| |
| // Compute size base on the size of frame received from MediaStreamVideoSink |
| // and size specified by plugin. |
| gfx::Size GetTargetSize(const gfx::Size& source, const gfx::Size& plugin) { |
| return gfx::Size(plugin.width() ? plugin.width() : source.width(), |
| plugin.height() ? plugin.height() : source.height()); |
| } |
| |
| // Compute format base on the format of frame received from MediaStreamVideoSink |
| // and format specified by plugin. |
| PP_VideoFrame_Format GetTargetFormat(PP_VideoFrame_Format source, |
| PP_VideoFrame_Format plugin) { |
| return plugin != PP_VIDEOFRAME_FORMAT_UNKNOWN ? plugin : source; |
| } |
| |
| void ConvertFromMediaVideoFrame(const scoped_refptr<media::VideoFrame>& src, |
| PP_VideoFrame_Format dst_format, |
| const gfx::Size& dst_size, |
| uint8_t* dst) { |
| CHECK(src->format() == media::PIXEL_FORMAT_YV12 || |
| src->format() == media::PIXEL_FORMAT_I420); |
| if (dst_format == PP_VIDEOFRAME_FORMAT_BGRA) { |
| if (src->visible_rect().size() == dst_size) { |
| libyuv::I420ToARGB(src->visible_data(VideoFrame::kYPlane), |
| src->stride(VideoFrame::kYPlane), |
| src->visible_data(VideoFrame::kUPlane), |
| src->stride(VideoFrame::kUPlane), |
| src->visible_data(VideoFrame::kVPlane), |
| src->stride(VideoFrame::kVPlane), |
| dst, |
| dst_size.width() * 4, |
| dst_size.width(), |
| dst_size.height()); |
| } else { |
| libyuv::YUVToARGBScaleClip( |
| src->visible_data(VideoFrame::kYPlane), |
| src->stride(VideoFrame::kYPlane), |
| src->visible_data(VideoFrame::kUPlane), |
| src->stride(VideoFrame::kUPlane), |
| src->visible_data(VideoFrame::kVPlane), |
| src->stride(VideoFrame::kVPlane), libyuv::FOURCC_YV12, |
| src->visible_rect().width(), src->visible_rect().height(), dst, |
| dst_size.width() * 4, libyuv::FOURCC_ARGB, dst_size.width(), |
| dst_size.height(), 0, 0, dst_size.width(), dst_size.height(), |
| kFilterMode); |
| } |
| } else if (dst_format == PP_VIDEOFRAME_FORMAT_YV12 || |
| dst_format == PP_VIDEOFRAME_FORMAT_I420) { |
| static const size_t kPlanesOrder[][3] = { |
| {VideoFrame::kYPlane, VideoFrame::kVPlane, |
| VideoFrame::kUPlane}, // YV12 |
| {VideoFrame::kYPlane, VideoFrame::kUPlane, |
| VideoFrame::kVPlane}, // I420 |
| }; |
| const int plane_order = (dst_format == PP_VIDEOFRAME_FORMAT_YV12) ? 0 : 1; |
| int dst_width = dst_size.width(); |
| int dst_height = dst_size.height(); |
| libyuv::ScalePlane(src->visible_data(kPlanesOrder[plane_order][0]), |
| src->stride(kPlanesOrder[plane_order][0]), |
| src->visible_rect().width(), |
| src->visible_rect().height(), |
| dst, |
| dst_width, |
| dst_width, |
| dst_height, |
| kFilterMode); |
| dst += dst_width * dst_height; |
| const int src_halfwidth = (src->visible_rect().width() + 1) >> 1; |
| const int src_halfheight = (src->visible_rect().height() + 1) >> 1; |
| const int dst_halfwidth = (dst_width + 1) >> 1; |
| const int dst_halfheight = (dst_height + 1) >> 1; |
| libyuv::ScalePlane(src->visible_data(kPlanesOrder[plane_order][1]), |
| src->stride(kPlanesOrder[plane_order][1]), |
| src_halfwidth, |
| src_halfheight, |
| dst, |
| dst_halfwidth, |
| dst_halfwidth, |
| dst_halfheight, |
| kFilterMode); |
| dst += dst_halfwidth * dst_halfheight; |
| libyuv::ScalePlane(src->visible_data(kPlanesOrder[plane_order][2]), |
| src->stride(kPlanesOrder[plane_order][2]), |
| src_halfwidth, |
| src_halfheight, |
| dst, |
| dst_halfwidth, |
| dst_halfwidth, |
| dst_halfheight, |
| kFilterMode); |
| } else { |
| NOTREACHED(); |
| } |
| } |
| |
| } // namespace |
| |
| namespace content { |
| |
| // Internal class used for delivering video frames on the IO-thread to |
| // the MediaStreamVideoSource implementation. |
| class PepperMediaStreamVideoTrackHost::FrameDeliverer |
| : public base::RefCountedThreadSafe<FrameDeliverer> { |
| public: |
| FrameDeliverer(scoped_refptr<base::SingleThreadTaskRunner> io_task_runner, |
| const VideoCaptureDeliverFrameCB& new_frame_callback); |
| |
| void DeliverVideoFrame(const scoped_refptr<media::VideoFrame>& frame); |
| |
| private: |
| friend class base::RefCountedThreadSafe<FrameDeliverer>; |
| virtual ~FrameDeliverer(); |
| |
| void DeliverFrameOnIO(const scoped_refptr<media::VideoFrame>& frame); |
| |
| scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_; |
| VideoCaptureDeliverFrameCB new_frame_callback_; |
| |
| DISALLOW_COPY_AND_ASSIGN(FrameDeliverer); |
| }; |
| |
| PepperMediaStreamVideoTrackHost::FrameDeliverer::FrameDeliverer( |
| scoped_refptr<base::SingleThreadTaskRunner> io_task_runner, |
| const VideoCaptureDeliverFrameCB& new_frame_callback) |
| : io_task_runner_(io_task_runner), new_frame_callback_(new_frame_callback) { |
| } |
| |
| PepperMediaStreamVideoTrackHost::FrameDeliverer::~FrameDeliverer() { |
| } |
| |
| void PepperMediaStreamVideoTrackHost::FrameDeliverer::DeliverVideoFrame( |
| const scoped_refptr<media::VideoFrame>& frame) { |
| io_task_runner_->PostTask( |
| FROM_HERE, base::Bind(&FrameDeliverer::DeliverFrameOnIO, this, frame)); |
| } |
| |
| void PepperMediaStreamVideoTrackHost::FrameDeliverer::DeliverFrameOnIO( |
| const scoped_refptr<media::VideoFrame>& frame) { |
| DCHECK(io_task_runner_->BelongsToCurrentThread()); |
| // The time when this frame is generated is unknown so give a null value to |
| // |estimated_capture_time|. |
| new_frame_callback_.Run(frame, base::TimeTicks()); |
| } |
| |
| PepperMediaStreamVideoTrackHost::PepperMediaStreamVideoTrackHost( |
| RendererPpapiHost* host, |
| PP_Instance instance, |
| PP_Resource resource, |
| const blink::WebMediaStreamTrack& track) |
| : PepperMediaStreamTrackHostBase(host, instance, resource), |
| track_(track), |
| number_of_buffers_(kDefaultNumberOfBuffers), |
| source_frame_format_(PP_VIDEOFRAME_FORMAT_UNKNOWN), |
| plugin_frame_format_(PP_VIDEOFRAME_FORMAT_UNKNOWN), |
| frame_data_size_(0), |
| type_(kRead), |
| weak_factory_(this) { |
| DCHECK(!track_.isNull()); |
| } |
| |
| PepperMediaStreamVideoTrackHost::PepperMediaStreamVideoTrackHost( |
| RendererPpapiHost* host, |
| PP_Instance instance, |
| PP_Resource resource) |
| : PepperMediaStreamTrackHostBase(host, instance, resource), |
| number_of_buffers_(kDefaultNumberOfBuffers), |
| source_frame_format_(PP_VIDEOFRAME_FORMAT_UNKNOWN), |
| plugin_frame_format_(PP_VIDEOFRAME_FORMAT_UNKNOWN), |
| frame_data_size_(0), |
| type_(kWrite), |
| weak_factory_(this) { |
| InitBlinkTrack(); |
| DCHECK(!track_.isNull()); |
| } |
| |
| PepperMediaStreamVideoTrackHost::~PepperMediaStreamVideoTrackHost() { |
| OnClose(); |
| } |
| |
| bool PepperMediaStreamVideoTrackHost::IsMediaStreamVideoTrackHost() { |
| return true; |
| } |
| |
| void PepperMediaStreamVideoTrackHost::InitBuffers() { |
| gfx::Size size = GetTargetSize(source_frame_size_, plugin_frame_size_); |
| DCHECK(!size.IsEmpty()); |
| |
| PP_VideoFrame_Format format = |
| GetTargetFormat(source_frame_format_, plugin_frame_format_); |
| DCHECK_NE(format, PP_VIDEOFRAME_FORMAT_UNKNOWN); |
| |
| if (format == PP_VIDEOFRAME_FORMAT_BGRA) { |
| frame_data_size_ = size.width() * size.height() * 4; |
| } else { |
| frame_data_size_ = |
| VideoFrame::AllocationSize(FromPpapiFormat(format), size); |
| } |
| |
| DCHECK_GT(frame_data_size_, 0U); |
| int32_t buffer_size = |
| sizeof(ppapi::MediaStreamBuffer::Video) + frame_data_size_; |
| bool result = PepperMediaStreamTrackHostBase::InitBuffers(number_of_buffers_, |
| buffer_size, |
| type_); |
| CHECK(result); |
| |
| if (type_ == kWrite) { |
| for (int32_t i = 0; i < buffer_manager()->number_of_buffers(); ++i) { |
| ppapi::MediaStreamBuffer::Video* buffer = |
| &(buffer_manager()->GetBufferPointer(i)->video); |
| buffer->header.size = buffer_manager()->buffer_size(); |
| buffer->header.type = ppapi::MediaStreamBuffer::TYPE_VIDEO; |
| buffer->format = format; |
| buffer->size.width = size.width(); |
| buffer->size.height = size.height(); |
| buffer->data_size = frame_data_size_; |
| } |
| |
| // Make all the frames avaiable to the plugin. |
| std::vector<int32_t> indices = buffer_manager()->DequeueBuffers(); |
| SendEnqueueBuffersMessageToPlugin(indices); |
| } |
| } |
| |
| void PepperMediaStreamVideoTrackHost::OnClose() { |
| MediaStreamVideoSink::DisconnectFromTrack(); |
| weak_factory_.InvalidateWeakPtrs(); |
| } |
| |
| int32_t PepperMediaStreamVideoTrackHost::OnHostMsgEnqueueBuffer( |
| ppapi::host::HostMessageContext* context, int32_t index) { |
| if (type_ == kRead) { |
| return PepperMediaStreamTrackHostBase::OnHostMsgEnqueueBuffer(context, |
| index); |
| } else { |
| return SendFrameToTrack(index); |
| } |
| } |
| |
| int32_t PepperMediaStreamVideoTrackHost::SendFrameToTrack(int32_t index) { |
| DCHECK_EQ(type_, kWrite); |
| |
| if (frame_deliverer_) { |
| // Sends the frame to blink video track. |
| ppapi::MediaStreamBuffer::Video* pp_frame = |
| &(buffer_manager()->GetBufferPointer(index)->video); |
| |
| int32_t y_stride = plugin_frame_size_.width(); |
| int32_t uv_stride = (plugin_frame_size_.width() + 1) / 2; |
| uint8_t* y_data = static_cast<uint8_t*>(pp_frame->data); |
| // Default to I420 |
| uint8_t* u_data = y_data + plugin_frame_size_.GetArea(); |
| uint8_t* v_data = y_data + (plugin_frame_size_.GetArea() * 5 / 4); |
| if (plugin_frame_format_ == PP_VIDEOFRAME_FORMAT_YV12) { |
| // Swap u and v for YV12. |
| uint8_t* tmp = u_data; |
| u_data = v_data; |
| v_data = tmp; |
| } |
| |
| int64_t ts_ms = static_cast<int64_t>(pp_frame->timestamp * |
| base::Time::kMillisecondsPerSecond); |
| scoped_refptr<VideoFrame> frame = media::VideoFrame::WrapExternalYuvData( |
| FromPpapiFormat(plugin_frame_format_), |
| plugin_frame_size_, |
| gfx::Rect(plugin_frame_size_), |
| plugin_frame_size_, |
| y_stride, |
| uv_stride, |
| uv_stride, |
| y_data, |
| u_data, |
| v_data, |
| base::TimeDelta::FromMilliseconds(ts_ms)); |
| if (!frame) |
| return PP_ERROR_FAILED; |
| |
| frame_deliverer_->DeliverVideoFrame(frame); |
| } |
| |
| // Makes the frame available again for plugin. |
| SendEnqueueBufferMessageToPlugin(index); |
| return PP_OK; |
| } |
| |
| void PepperMediaStreamVideoTrackHost::OnVideoFrame( |
| const scoped_refptr<VideoFrame>& video_frame, |
| base::TimeTicks estimated_capture_time) { |
| DCHECK(video_frame.get()); |
| // TODO(penghuang): Check |frame->end_of_stream()| and close the track. |
| scoped_refptr<media::VideoFrame> frame = video_frame; |
| // Drop alpha channel since we do not support it yet. |
| if (frame->format() == media::PIXEL_FORMAT_YV12A) |
| frame = media::WrapAsI420VideoFrame(video_frame); |
| PP_VideoFrame_Format ppformat = ToPpapiFormat(frame->format()); |
| if (ppformat == PP_VIDEOFRAME_FORMAT_UNKNOWN) |
| return; |
| |
| if (source_frame_size_.IsEmpty()) { |
| source_frame_size_ = frame->visible_rect().size(); |
| source_frame_format_ = ppformat; |
| InitBuffers(); |
| } |
| |
| int32_t index = buffer_manager()->DequeueBuffer(); |
| // Drop frames if the underlying buffer is full. |
| if (index < 0) { |
| DVLOG(1) << "A frame is dropped."; |
| return; |
| } |
| |
| CHECK_EQ(ppformat, source_frame_format_) << "Frame format is changed."; |
| |
| gfx::Size size = GetTargetSize(source_frame_size_, plugin_frame_size_); |
| ppformat = |
| GetTargetFormat(source_frame_format_, plugin_frame_format_); |
| ppapi::MediaStreamBuffer::Video* buffer = |
| &(buffer_manager()->GetBufferPointer(index)->video); |
| buffer->header.size = buffer_manager()->buffer_size(); |
| buffer->header.type = ppapi::MediaStreamBuffer::TYPE_VIDEO; |
| buffer->timestamp = frame->timestamp().InSecondsF(); |
| buffer->format = ppformat; |
| buffer->size.width = size.width(); |
| buffer->size.height = size.height(); |
| buffer->data_size = frame_data_size_; |
| ConvertFromMediaVideoFrame(frame, ppformat, size, buffer->data); |
| |
| SendEnqueueBufferMessageToPlugin(index); |
| } |
| |
| class PepperMediaStreamVideoTrackHost::VideoSource final |
| : public MediaStreamVideoSource { |
| public: |
| explicit VideoSource(base::WeakPtr<PepperMediaStreamVideoTrackHost> host) |
| : host_(std::move(host)) {} |
| |
| ~VideoSource() final { StopSourceImpl(); } |
| |
| void GetCurrentSupportedFormats( |
| int max_requested_width, int max_requested_height, |
| double max_requested_frame_rate, |
| const VideoCaptureDeviceFormatsCB& callback) final { |
| media::VideoCaptureFormats formats; |
| if (host_) { |
| formats.push_back(media::VideoCaptureFormat( |
| host_->plugin_frame_size_, |
| kDefaultOutputFrameRate, |
| ToPixelFormat(host_->plugin_frame_format_))); |
| } |
| callback.Run(formats); |
| } |
| |
| void StartSourceImpl( |
| const media::VideoCaptureFormat& format, |
| const blink::WebMediaConstraints& constraints, |
| const VideoCaptureDeliverFrameCB& frame_callback) final { |
| if (host_) { |
| host_->frame_deliverer_ = |
| new FrameDeliverer(io_task_runner(), frame_callback); |
| } |
| } |
| |
| void StopSourceImpl() final { |
| if (host_) |
| host_->frame_deliverer_ = nullptr; |
| } |
| |
| private: |
| const base::WeakPtr<PepperMediaStreamVideoTrackHost> host_; |
| |
| DISALLOW_COPY_AND_ASSIGN(VideoSource); |
| }; |
| |
| void PepperMediaStreamVideoTrackHost::DidConnectPendingHostToResource() { |
| if (!MediaStreamVideoSink::connected_track().isNull()) |
| return; |
| MediaStreamVideoSink::ConnectToTrack( |
| track_, media::BindToCurrentLoop( |
| base::Bind(&PepperMediaStreamVideoTrackHost::OnVideoFrame, |
| weak_factory_.GetWeakPtr())), |
| false); |
| } |
| |
| int32_t PepperMediaStreamVideoTrackHost::OnResourceMessageReceived( |
| const IPC::Message& msg, |
| HostMessageContext* context) { |
| PPAPI_BEGIN_MESSAGE_MAP(PepperMediaStreamVideoTrackHost, msg) |
| PPAPI_DISPATCH_HOST_RESOURCE_CALL( |
| PpapiHostMsg_MediaStreamVideoTrack_Configure, OnHostMsgConfigure) |
| PPAPI_END_MESSAGE_MAP() |
| return PepperMediaStreamTrackHostBase::OnResourceMessageReceived(msg, |
| context); |
| } |
| |
| int32_t PepperMediaStreamVideoTrackHost::OnHostMsgConfigure( |
| HostMessageContext* context, |
| const MediaStreamVideoTrackShared::Attributes& attributes) { |
| CHECK(MediaStreamVideoTrackShared::VerifyAttributes(attributes)); |
| |
| bool changed = false; |
| gfx::Size new_size(attributes.width, attributes.height); |
| if (GetTargetSize(source_frame_size_, plugin_frame_size_) != |
| GetTargetSize(source_frame_size_, new_size)) { |
| changed = true; |
| } |
| plugin_frame_size_ = new_size; |
| |
| int32_t buffers = attributes.buffers |
| ? std::min(kMaxNumberOfBuffers, attributes.buffers) |
| : kDefaultNumberOfBuffers; |
| if (buffers != number_of_buffers_) |
| changed = true; |
| number_of_buffers_ = buffers; |
| |
| if (GetTargetFormat(source_frame_format_, plugin_frame_format_) != |
| GetTargetFormat(source_frame_format_, attributes.format)) { |
| changed = true; |
| } |
| plugin_frame_format_ = attributes.format; |
| |
| // If the first frame has been received, we will re-initialize buffers with |
| // new settings. Otherwise, we will initialize buffer when we receive |
| // the first frame, because plugin can only provide part of attributes |
| // which are not enough to initialize buffers. |
| if (changed && (type_ == kWrite || !source_frame_size_.IsEmpty())) |
| InitBuffers(); |
| |
| // TODO(ronghuawu): Ask the owner of DOMMediaStreamTrackToResource why |
| // source id instead of track id is used there. |
| const std::string id = track_.source().id().utf8(); |
| context->reply_msg = PpapiPluginMsg_MediaStreamVideoTrack_ConfigureReply(id); |
| return PP_OK; |
| } |
| |
| void PepperMediaStreamVideoTrackHost::InitBlinkTrack() { |
| std::string source_id; |
| base::Base64Encode(base::RandBytesAsString(64), &source_id); |
| blink::WebMediaStreamSource webkit_source; |
| webkit_source.initialize(blink::WebString::fromASCII(source_id), |
| blink::WebMediaStreamSource::TypeVideo, |
| blink::WebString::fromASCII(kPepperVideoSourceName)); |
| MediaStreamVideoSource* const source = |
| new VideoSource(weak_factory_.GetWeakPtr()); |
| webkit_source.setExtraData(source); // Takes ownership of |source|. |
| |
| const bool enabled = true; |
| blink::WebMediaConstraints constraints; |
| constraints.initialize(); |
| track_ = MediaStreamVideoTrack::CreateVideoTrack( |
| source, constraints, |
| base::Bind( |
| &PepperMediaStreamVideoTrackHost::OnTrackStarted, |
| base::Unretained(this)), |
| enabled); |
| // Note: The call to CreateVideoTrack() returned a track that holds a |
| // ref-counted reference to |webkit_source| (and, implicitly, |source|). |
| } |
| |
| void PepperMediaStreamVideoTrackHost::OnTrackStarted( |
| MediaStreamSource* source, |
| MediaStreamRequestResult result, |
| const blink::WebString& result_name) { |
| DVLOG(3) << "OnTrackStarted result: " << result; |
| } |
| |
| } // namespace content |