Chromium Code Reviews| Index: content/renderer/pepper/pepper_media_stream_video_track_host.cc |
| diff --git a/content/renderer/pepper/pepper_media_stream_video_track_host.cc b/content/renderer/pepper/pepper_media_stream_video_track_host.cc |
| index 7512c5a84ca8b2de5974e5662cc59d02a2bf218e..8984f84cfd972810daf9ab456d6e51c3a6ba50e7 100644 |
| --- a/content/renderer/pepper/pepper_media_stream_video_track_host.cc |
| +++ b/content/renderer/pepper/pepper_media_stream_video_track_host.cc |
| @@ -5,35 +5,133 @@ |
| #include "content/renderer/pepper/pepper_media_stream_video_track_host.h" |
| #include "base/logging.h" |
| +#include "media/base/yuv_convert.h" |
| #include "ppapi/c/pp_errors.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/scale.h" |
| using media::VideoFrame; |
| +using ppapi::host::HostMessageContext; |
| +using ppapi::MediaStreamVideoTrackShared; |
| namespace { |
| -// TODO(penghuang): make it configurable. |
| -const int32_t kNumberOfFrames = 4; |
| +const int32_t kDefaultNumberOfBuffers = 4; |
| +const int32_t kMaxNumberOfBuffers = 8; |
| PP_VideoFrame_Format ToPpapiFormat(VideoFrame::Format format) { |
| switch (format) { |
| case VideoFrame::YV12: |
| return PP_VIDEOFRAME_FORMAT_YV12; |
| - case VideoFrame::YV16: |
| - return PP_VIDEOFRAME_FORMAT_YV16; |
| case VideoFrame::I420: |
| return PP_VIDEOFRAME_FORMAT_I420; |
| - case VideoFrame::YV12A: |
| - return PP_VIDEOFRAME_FORMAT_YV12A; |
| - case VideoFrame::YV12J: |
| - return PP_VIDEOFRAME_FORMAT_YV12J; |
| default: |
| DVLOG(1) << "Unsupported pixel format " << format; |
| return PP_VIDEOFRAME_FORMAT_UNKNOWN; |
| } |
| } |
| +VideoFrame::Format FromPpapiFormat(PP_VideoFrame_Format format) { |
| + switch (format) { |
| + case PP_VIDEOFRAME_FORMAT_YV12: |
| + return VideoFrame::YV12; |
| + case PP_VIDEOFRAME_FORMAT_I420: |
| + return VideoFrame::I420; |
| + default: |
| + DVLOG(1) << "Unsupported pixel format " << format; |
| + return VideoFrame::UNKNOWN; |
| + } |
| +} |
| + |
| +// Compute size base on the size of frame received from MediaStreamVideoSink |
| +// and size specified by plugin. |
| +gfx::Size ComputeSize(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 ComputeFormat(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() == VideoFrame::YV12 || |
| + src->format() == VideoFrame::I420); |
| + if (dst_format == PP_VIDEOFRAME_FORMAT_BGRA) { |
| + if (src->coded_size() == dst_size) { |
| + media::ConvertYUVToRGB32(src->data(VideoFrame::kYPlane), |
| + src->data(VideoFrame::kUPlane), |
| + src->data(VideoFrame::kVPlane), |
| + dst, |
| + dst_size.width(), |
| + dst_size.height(), |
| + src->stride(VideoFrame::kYPlane), |
| + src->stride(VideoFrame::kUPlane), |
| + dst_size.width() * 4, |
| + media::YV12); |
| + } else { |
| + media::ScaleYUVToRGB32(src->data(VideoFrame::kYPlane), |
| + src->data(VideoFrame::kUPlane), |
| + src->data(VideoFrame::kVPlane), |
| + dst, |
| + src->coded_size().width(), |
| + src->coded_size().height(), |
| + dst_size.width(), |
| + dst_size.height(), |
| + src->stride(VideoFrame::kYPlane), |
| + src->stride(VideoFrame::kUPlane), |
| + dst_size.width() * 4, |
| + media::YV12, |
| + media::ROTATE_0, |
| + media::FILTER_BILINEAR); |
| + } |
| + } 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->data(kPlanesOrder[plane_order][0]), |
| + src->stride(kPlanesOrder[plane_order][0]), |
| + src->coded_size().width(), |
| + src->coded_size().height(), |
| + dst, dst_width, dst_width, dst_height, |
| + libyuv::kFilterBox); |
|
Ronghua Wu (Left Chromium)
2014/02/19 17:09:35
define libyuv::kFilterBox as a const variable
Peng
2014/02/19 19:22:36
Done.
|
| + dst += dst_width * dst_height; |
| + const int src_halfwidth = (src->coded_size().width() + 1) >> 1; |
| + const int src_halfheight = (src->coded_size().height() + 1) >> 1; |
| + const int dst_halfwidth = (dst_width + 1) >> 1; |
| + const int dst_halfheight = (dst_height + 1) >> 1; |
| + libyuv::ScalePlane(src->data(kPlanesOrder[plane_order][1]), |
| + src->stride(kPlanesOrder[plane_order][1]), |
| + src_halfwidth, src_halfheight, |
| + dst, dst_halfwidth, dst_halfwidth, dst_halfheight, |
| + libyuv::kFilterBox); |
| + dst += dst_halfwidth * dst_halfheight; |
| + libyuv::ScalePlane(src->data(kPlanesOrder[plane_order][2]), |
| + src->stride(kPlanesOrder[plane_order][2]), |
| + src_halfwidth, src_halfheight, |
| + dst, dst_halfwidth, dst_halfwidth, dst_halfheight, |
| + libyuv::kFilterBox); |
| + } else { |
| + NOTREACHED(); |
| + } |
| +} |
| + |
| } // namespace |
| namespace content { |
| @@ -46,7 +144,9 @@ PepperMediaStreamVideoTrackHost::PepperMediaStreamVideoTrackHost( |
| : PepperMediaStreamTrackHostBase(host, instance, resource), |
| track_(track), |
| connected_(false), |
| - frame_format_(VideoFrame::UNKNOWN), |
| + buffers_(kDefaultNumberOfBuffers), |
| + source_frame_format_(PP_VIDEOFRAME_FORMAT_UNKNOWN), |
| + plugin_frame_format_(PP_VIDEOFRAME_FORMAT_UNKNOWN), |
| frame_data_size_(0) { |
| DCHECK(!track_.isNull()); |
| } |
| @@ -55,6 +155,29 @@ PepperMediaStreamVideoTrackHost::~PepperMediaStreamVideoTrackHost() { |
| OnClose(); |
| } |
| +void PepperMediaStreamVideoTrackHost::InitBuffers() { |
| + gfx::Size size = ComputeSize(source_frame_size_, plugin_frame_size_); |
| + DCHECK(!size.IsEmpty()); |
| + |
| + PP_VideoFrame_Format format = |
| + ComputeFormat(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(buffers_, |
| + buffer_size); |
| + CHECK(result); |
| +} |
| + |
| void PepperMediaStreamVideoTrackHost::OnClose() { |
| if (connected_) { |
| MediaStreamVideoSink::RemoveFromVideoTrack(this, track_); |
| @@ -70,53 +193,35 @@ void PepperMediaStreamVideoTrackHost::OnVideoFrame( |
| if (ppformat == PP_VIDEOFRAME_FORMAT_UNKNOWN) |
| return; |
| - if (frame_size_ != frame->coded_size() || frame_format_ != frame->format()) { |
| - frame_size_ = frame->coded_size(); |
| - frame_format_ = frame->format(); |
| - // TODO(penghuang): Support changing |frame_size_| & |frame_format_| more |
| - // than once. |
| - DCHECK(!frame_data_size_); |
| - frame_data_size_ = VideoFrame::AllocationSize(frame_format_, frame_size_); |
| - int32_t size = sizeof(ppapi::MediaStreamBuffer::Video) + frame_data_size_; |
| - bool result = InitBuffers(kNumberOfFrames, size); |
| - // TODO(penghuang): Send PP_ERROR_NOMEMORY to plugin. |
| - CHECK(result); |
| + if (source_frame_size_.IsEmpty()) { |
| + source_frame_size_ = frame->coded_size(); |
| + source_frame_format_ = ppformat; |
| + InitBuffers(); |
| } |
| int32_t index = buffer_manager()->DequeueBuffer(); |
| // Drop frames if the underlying buffer is full. |
| - if (index < 0) |
| + if (index < 0) { |
| + DVLOG(1) << "A frame is dropped."; |
| return; |
| + } |
| + |
| + CHECK(frame->coded_size() == source_frame_size_) << "Frame size is changed"; |
| + CHECK_EQ(ppformat, source_frame_format_) << "Frame format is changed."; |
| - // TODO(penghuang): support format conversion and size scaling. |
| + gfx::Size size = ComputeSize(source_frame_size_, plugin_frame_size_); |
| + PP_VideoFrame_Format format = ComputeFormat(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->GetTimestamp().InSecondsF(); |
| - buffer->format = ppformat; |
| - buffer->size.width = frame->coded_size().width(); |
| - buffer->size.height = frame->coded_size().height(); |
| + buffer->format = format; |
| + buffer->size.width = size.width(); |
| + buffer->size.height = size.height(); |
| buffer->data_size = frame_data_size_; |
| - |
| - COMPILE_ASSERT(VideoFrame::kYPlane == 0, y_plane_should_be_0); |
| - COMPILE_ASSERT(VideoFrame::kUPlane == 1, u_plane_should_be_1); |
| - COMPILE_ASSERT(VideoFrame::kVPlane == 2, v_plane_should_be_2); |
| - |
| - uint8_t* dst = buffer->data; |
| - size_t num_planes = VideoFrame::NumPlanes(frame->format()); |
| - for (size_t i = 0; i < num_planes; ++i) { |
| - const uint8_t* src = frame->data(i); |
| - const size_t row_bytes = frame->row_bytes(i); |
| - const size_t src_stride = frame->stride(i); |
| - int rows = frame->rows(i); |
| - for (int j = 0; j < rows; ++j) { |
| - memcpy(dst, src, row_bytes); |
| - dst += row_bytes; |
| - src += src_stride; |
| - } |
| - } |
| - |
| + ConvertFromMediaVideoFrame(frame, format, size, buffer->data); |
| SendEnqueueBufferMessageToPlugin(index); |
| } |
| @@ -127,4 +232,59 @@ void PepperMediaStreamVideoTrackHost::DidConnectPendingHostToResource() { |
| } |
| } |
| +int32_t PepperMediaStreamVideoTrackHost::OnResourceMessageReceived( |
| + const IPC::Message& msg, |
| + HostMessageContext* context) { |
| + IPC_BEGIN_MESSAGE_MAP(PepperMediaStreamVideoTrackHost, msg) |
| + PPAPI_DISPATCH_HOST_RESOURCE_CALL( |
| + PpapiHostMsg_MediaStreamVideoTrack_Configure, |
| + OnHostMsgConfigure) |
| + IPC_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 = plugin_frame_size_; |
|
Ronghua Wu (Left Chromium)
2014/02/19 17:09:35
Do we need to init this with plugin_frame_size_? W
Peng
2014/02/19 19:22:36
Done.
|
| + new_size.set_width(attributes.width); |
| + new_size.set_height(attributes.height); |
| + if (ComputeSize(source_frame_size_, plugin_frame_size_) != |
| + ComputeSize(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 != buffers_) |
| + changed = true; |
| + buffers_ = buffers; |
|
Ronghua Wu (Left Chromium)
2014/02/19 17:09:35
nit, name this buffer_num_
Peng
2014/02/19 19:22:36
Done.
|
| + |
| + if (plugin_frame_format_ != attributes.format) { |
| + PP_VideoFrame_Format original_format = ComputeFormat( |
| + source_frame_format_, plugin_frame_format_); |
| + PP_VideoFrame_Format new_format = ComputeFormat( |
| + source_frame_format_, attributes.format); |
| + if (new_format != original_format) |
|
Ronghua Wu (Left Chromium)
2014/02/19 17:09:35
prefer to keep consistent with what you did for si
Peng
2014/02/19 19:22:36
Done.
|
| + 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. |
|
Ronghua Wu (Left Chromium)
2014/02/19 17:09:35
wonder how should we handle the output case, i.e.
Peng
2014/02/19 19:22:36
Using an output_mode flag could be a solution. If
Ronghua Wu (Left Chromium)
2014/02/19 21:16:24
SG
|
| + if (changed && !source_frame_size_.IsEmpty()) |
| + InitBuffers(); |
| + |
| + context->reply_msg = PpapiPluginMsg_MediaStreamVideoTrack_ConfigureReply(); |
| + return PP_OK; |
| +} |
| + |
| } // namespace content |