Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "content/renderer/pepper/pepper_media_stream_video_track_host.h" | 5 #include "content/renderer/pepper/pepper_media_stream_video_track_host.h" |
| 6 | 6 |
| 7 #include "base/logging.h" | 7 #include "base/logging.h" |
| 8 #include "media/base/yuv_convert.h" | |
| 8 #include "ppapi/c/pp_errors.h" | 9 #include "ppapi/c/pp_errors.h" |
| 9 #include "ppapi/c/ppb_video_frame.h" | 10 #include "ppapi/c/ppb_video_frame.h" |
| 11 #include "ppapi/host/dispatch_host_message.h" | |
| 12 #include "ppapi/host/host_message_context.h" | |
| 13 #include "ppapi/proxy/ppapi_messages.h" | |
| 10 #include "ppapi/shared_impl/media_stream_buffer.h" | 14 #include "ppapi/shared_impl/media_stream_buffer.h" |
| 15 #include "third_party/libyuv/include/libyuv/scale.h" | |
| 11 | 16 |
| 12 using media::VideoFrame; | 17 using media::VideoFrame; |
| 18 using ppapi::host::HostMessageContext; | |
| 19 using ppapi::MediaStreamVideoTrackShared; | |
| 20 using ppapi::proxy::SerializedHandle; | |
|
Ronghua Wu (Left Chromium)
2014/02/12 19:54:15
order
Peng
2014/02/13 17:19:11
Sorry. Which one is not in order?
| |
| 13 | 21 |
| 14 namespace { | 22 namespace { |
| 15 | 23 |
| 16 // TODO(penghuang): make it configurable. | 24 const int32_t kNumberOfBuffers = 4; |
| 17 const int32_t kNumberOfFrames = 4; | 25 const int32_t kMaxNumberOfBuffers = 8; |
| 18 | 26 |
| 19 PP_VideoFrame_Format ToPpapiFormat(VideoFrame::Format format) { | 27 PP_VideoFrame_Format ToPpapiFormat(VideoFrame::Format format) { |
| 20 switch (format) { | 28 switch (format) { |
| 21 case VideoFrame::YV12: | 29 case VideoFrame::YV12: |
| 22 return PP_VIDEOFRAME_FORMAT_YV12; | 30 return PP_VIDEOFRAME_FORMAT_YV12; |
| 23 case VideoFrame::YV16: | |
| 24 return PP_VIDEOFRAME_FORMAT_YV16; | |
| 25 case VideoFrame::I420: | 31 case VideoFrame::I420: |
| 26 return PP_VIDEOFRAME_FORMAT_I420; | 32 return PP_VIDEOFRAME_FORMAT_I420; |
| 27 case VideoFrame::YV12A: | |
| 28 return PP_VIDEOFRAME_FORMAT_YV12A; | |
| 29 case VideoFrame::YV12J: | |
| 30 return PP_VIDEOFRAME_FORMAT_YV12J; | |
| 31 default: | 33 default: |
| 32 DVLOG(1) << "Unsupported pixel format " << format; | 34 DVLOG(1) << "Unsupported pixel format " << format; |
| 33 return PP_VIDEOFRAME_FORMAT_UNKNOWN; | 35 return PP_VIDEOFRAME_FORMAT_UNKNOWN; |
| 34 } | 36 } |
| 35 } | 37 } |
| 36 | 38 |
| 39 VideoFrame::Format FromPpapiFormat(PP_VideoFrame_Format format) { | |
| 40 switch (format) { | |
| 41 case PP_VIDEOFRAME_FORMAT_YV12: | |
| 42 return VideoFrame::YV12; | |
| 43 case PP_VIDEOFRAME_FORMAT_I420: | |
| 44 return VideoFrame::I420; | |
| 45 default: | |
| 46 DVLOG(1) << "Unsupported pixel format " << format; | |
| 47 return VideoFrame::UNKNOWN; | |
| 48 } | |
| 49 } | |
| 50 | |
| 51 gfx::Size ComputeSize(const gfx::Size& frame, | |
|
Ronghua Wu (Left Chromium)
2014/02/12 19:54:15
ComputeSize and ComputeFormat are not so obvious.
Peng
2014/02/13 17:19:11
Done.
| |
| 52 const gfx::Size& plugin) { | |
| 53 return gfx::Size(plugin.width() ? plugin.width() : frame.width(), | |
| 54 plugin.height() ? plugin.height() : frame.height()); | |
| 55 } | |
| 56 | |
| 57 PP_VideoFrame_Format ComputeFormat(PP_VideoFrame_Format frame, | |
| 58 PP_VideoFrame_Format plugin) { | |
| 59 return plugin != PP_VIDEOFRAME_FORMAT_UNKNOWN ? plugin : frame; | |
| 60 } | |
| 61 | |
| 62 void ConvertFromMediaVideoFrame(const scoped_refptr<media::VideoFrame>& src, | |
| 63 uint8_t* dst, | |
| 64 PP_VideoFrame_Format dst_format, | |
| 65 const gfx::Size& dst_size) { | |
|
Ronghua Wu (Left Chromium)
2014/02/12 19:54:15
nit, prefer to have read only args and then read/w
Peng
2014/02/13 17:19:11
Done.
| |
| 66 CHECK(src->format() == VideoFrame::YV12 || | |
| 67 src->format() == VideoFrame::I420); | |
| 68 if (dst_format == PP_VIDEOFRAME_FORMAT_BGRA) { | |
| 69 if (src->coded_size() == dst_size) { | |
| 70 media::ConvertYUVToRGB32(src->data(VideoFrame::kYPlane), | |
| 71 src->data(VideoFrame::kUPlane), | |
| 72 src->data(VideoFrame::kVPlane), | |
| 73 dst, | |
| 74 dst_size.width(), | |
| 75 dst_size.height(), | |
| 76 src->stride(VideoFrame::kYPlane), | |
| 77 src->stride(VideoFrame::kUPlane), | |
| 78 dst_size.width() * 4, | |
| 79 media::YV12); | |
| 80 } else { | |
| 81 media::ScaleYUVToRGB32(src->data(VideoFrame::kYPlane), | |
|
Ronghua Wu (Left Chromium)
2014/02/12 19:54:15
I think call ScaleYUVToRGB32 with the same size is
Peng
2014/02/13 17:19:11
I am not sure. Probably they are not same. I check
| |
| 82 src->data(VideoFrame::kUPlane), | |
| 83 src->data(VideoFrame::kVPlane), | |
| 84 dst, | |
| 85 src->coded_size().width(), | |
| 86 src->coded_size().height(), | |
| 87 dst_size.width(), | |
| 88 dst_size.height(), | |
| 89 src->stride(VideoFrame::kYPlane), | |
| 90 src->stride(VideoFrame::kUPlane), | |
| 91 dst_size.width() * 4, | |
| 92 media::YV12, | |
| 93 media::ROTATE_0, | |
| 94 media::FILTER_BILINEAR); | |
| 95 } | |
| 96 } else if (dst_format == PP_VIDEOFRAME_FORMAT_YV12 || | |
| 97 dst_format == PP_VIDEOFRAME_FORMAT_I420) { | |
| 98 static const size_t kPlanes[][3] = { | |
|
Ronghua Wu (Left Chromium)
2014/02/12 19:54:15
kPlanesOrder?
Peng
2014/02/13 17:19:11
Done.
| |
| 99 { VideoFrame::kYPlane, VideoFrame::kVPlane, VideoFrame::kUPlane }, // YV12 | |
| 100 { VideoFrame::kYPlane, VideoFrame::kUPlane, VideoFrame::kVPlane }, // I420 | |
| 101 }; | |
| 102 const int plane_order = (dst_format == PP_VIDEOFRAME_FORMAT_YV12) ? 0 : 1; | |
| 103 | |
| 104 if (src->coded_size() == dst_size) { | |
| 105 for (int i = 0; i < 3; i++) { | |
| 106 size_t plane = kPlanes[plane_order][i]; | |
| 107 int32_t n = src->stride(plane) * src->rows(plane); | |
| 108 memcpy(dst, src->data(plane), n); | |
| 109 dst += n; | |
| 110 } | |
| 111 } else { | |
| 112 int w = dst_size.width(); | |
| 113 int h = dst_size.height(); | |
| 114 libyuv::ScalePlane(src->data(kPlanes[plane_order][0]), | |
|
Ronghua Wu (Left Chromium)
2014/02/12 19:54:15
dito, I think ScalePlane will just do copy if the
Ronghua Wu (Left Chromium)
2014/02/12 19:54:15
And I think you should be able to just use I420Sca
Peng
2014/02/13 17:19:11
Done.
Peng
2014/02/13 17:19:11
Because we support YV12 & I420 two formats. Use I4
| |
| 115 src->stride(kPlanes[plane_order][0]), | |
| 116 src->coded_size().width(), | |
| 117 src->coded_size().height(), | |
| 118 dst, w, w, h, libyuv::kFilterBox); | |
| 119 dst += w * h; | |
| 120 w /= 2; | |
| 121 h /= 2; | |
| 122 libyuv::ScalePlane(src->data(kPlanes[plane_order][1]), | |
| 123 src->stride(kPlanes[plane_order][1]), | |
| 124 src->coded_size().width() / 2, | |
| 125 src->coded_size().height() / 2, | |
| 126 dst, w, w, h, libyuv::kFilterBox); | |
| 127 dst += w * h; | |
| 128 libyuv::ScalePlane(src->data(kPlanes[plane_order][2]), | |
| 129 src->stride(kPlanes[plane_order][2]), | |
| 130 src->coded_size().width() / 2, | |
| 131 src->coded_size().height() / 2, | |
| 132 dst, w, w, h, libyuv::kFilterBox); | |
| 133 } | |
| 134 } else { | |
| 135 NOTREACHED(); | |
| 136 } | |
| 137 } | |
| 138 | |
| 37 } // namespace | 139 } // namespace |
| 38 | 140 |
| 39 namespace content { | 141 namespace content { |
| 40 | 142 |
| 41 PepperMediaStreamVideoTrackHost::PepperMediaStreamVideoTrackHost( | 143 PepperMediaStreamVideoTrackHost::PepperMediaStreamVideoTrackHost( |
| 42 RendererPpapiHost* host, | 144 RendererPpapiHost* host, |
| 43 PP_Instance instance, | 145 PP_Instance instance, |
| 44 PP_Resource resource, | 146 PP_Resource resource, |
| 45 const blink::WebMediaStreamTrack& track) | 147 const blink::WebMediaStreamTrack& track) |
| 46 : PepperMediaStreamTrackHostBase(host, instance, resource), | 148 : PepperMediaStreamTrackHostBase(host, instance, resource), |
| 47 track_(track), | 149 track_(track), |
| 48 connected_(false), | 150 connected_(false), |
| 49 frame_format_(VideoFrame::UNKNOWN), | 151 buffers_initialized_(false), |
| 152 buffers_(kNumberOfBuffers), | |
| 153 frame_format_(PP_VIDEOFRAME_FORMAT_UNKNOWN), | |
| 154 plugin_frame_format_(PP_VIDEOFRAME_FORMAT_UNKNOWN), | |
| 50 frame_data_size_(0) { | 155 frame_data_size_(0) { |
| 51 DCHECK(!track_.isNull()); | 156 DCHECK(!track_.isNull()); |
| 52 } | 157 } |
| 53 | 158 |
| 54 PepperMediaStreamVideoTrackHost::~PepperMediaStreamVideoTrackHost() { | 159 PepperMediaStreamVideoTrackHost::~PepperMediaStreamVideoTrackHost() { |
| 55 OnClose(); | 160 OnClose(); |
| 56 } | 161 } |
| 57 | 162 |
| 163 void PepperMediaStreamVideoTrackHost::InitBuffers() { | |
| 164 gfx::Size size = ComputeSize(frame_size_, plugin_frame_size_); | |
| 165 DCHECK(!size.IsEmpty()); | |
| 166 | |
| 167 PP_VideoFrame_Format format = | |
| 168 ComputeFormat(frame_format_, plugin_frame_format_); | |
| 169 DCHECK_NE(format, PP_VIDEOFRAME_FORMAT_UNKNOWN); | |
| 170 | |
| 171 if (format == PP_VIDEOFRAME_FORMAT_BGRA) { | |
| 172 frame_data_size_ = size.width() * size.height() * 4; | |
| 173 } else { | |
| 174 frame_data_size_ = VideoFrame::AllocationSize(FromPpapiFormat(format), | |
| 175 size); | |
| 176 } | |
| 177 | |
| 178 DCHECK_GT(frame_data_size_, 0U); | |
| 179 int32_t buffer_size = | |
| 180 sizeof(ppapi::MediaStreamBuffer::Video) + frame_data_size_; | |
| 181 bool result = PepperMediaStreamTrackHostBase::InitBuffers(buffers_, | |
| 182 buffer_size); | |
| 183 // TODO(penghuang): Send PP_ERROR_NOMEMORY to plugin. | |
| 184 CHECK(result); | |
| 185 } | |
| 186 | |
| 58 void PepperMediaStreamVideoTrackHost::OnClose() { | 187 void PepperMediaStreamVideoTrackHost::OnClose() { |
| 59 if (connected_) { | 188 if (connected_) { |
| 60 MediaStreamVideoSink::RemoveFromVideoTrack(this, track_); | 189 MediaStreamVideoSink::RemoveFromVideoTrack(this, track_); |
| 61 connected_ = false; | 190 connected_ = false; |
| 62 } | 191 } |
| 63 } | 192 } |
| 64 | 193 |
| 65 void PepperMediaStreamVideoTrackHost::OnVideoFrame( | 194 void PepperMediaStreamVideoTrackHost::OnVideoFrame( |
| 66 const scoped_refptr<VideoFrame>& frame) { | 195 const scoped_refptr<VideoFrame>& frame) { |
| 67 DCHECK(frame); | 196 DCHECK(frame); |
| 68 // TODO(penghuang): Check |frame->end_of_stream()| and close the track. | 197 // TODO(penghuang): Check |frame->end_of_stream()| and close the track. |
| 69 PP_VideoFrame_Format ppformat = ToPpapiFormat(frame->format()); | 198 PP_VideoFrame_Format ppformat = ToPpapiFormat(frame->format()); |
| 70 if (ppformat == PP_VIDEOFRAME_FORMAT_UNKNOWN) | 199 if (ppformat == PP_VIDEOFRAME_FORMAT_UNKNOWN) |
| 71 return; | 200 return; |
| 72 | 201 |
| 73 if (frame_size_ != frame->coded_size() || frame_format_ != frame->format()) { | 202 if (!buffers_initialized_) { |
| 74 frame_size_ = frame->coded_size(); | 203 frame_size_ = frame->coded_size(); |
| 75 frame_format_ = frame->format(); | 204 frame_format_ = ppformat; |
| 76 // TODO(penghuang): Support changing |frame_size_| & |frame_format_| more | 205 InitBuffers(); |
| 77 // than once. | 206 buffers_initialized_ = true; |
|
Ronghua Wu (Left Chromium)
2014/02/12 19:54:15
set buffers_initialized_ inside InitBuffers
Peng
2014/02/13 17:19:11
because we will called InitBuffers() multiple time
| |
| 78 DCHECK(!frame_data_size_); | |
| 79 frame_data_size_ = VideoFrame::AllocationSize(frame_format_, frame_size_); | |
| 80 int32_t size = sizeof(ppapi::MediaStreamBuffer::Video) + frame_data_size_; | |
| 81 bool result = InitBuffers(kNumberOfFrames, size); | |
| 82 // TODO(penghuang): Send PP_ERROR_NOMEMORY to plugin. | |
| 83 CHECK(result); | |
| 84 } | 207 } |
| 85 | 208 |
| 86 int32_t index = buffer_manager()->DequeueBuffer(); | 209 int32_t index = buffer_manager()->DequeueBuffer(); |
| 87 // Drop frames if the underlying buffer is full. | 210 // Drop frames if the underlying buffer is full. |
| 88 if (index < 0) | 211 if (index < 0) { |
| 212 DVLOG(1) << "A frame is dropped."; | |
| 89 return; | 213 return; |
| 214 } | |
| 90 | 215 |
| 91 // TODO(penghuang): support format conversion and size scaling. | 216 DCHECK(frame->coded_size() == frame_size_); |
| 217 DCHECK_EQ(ppformat, frame_format_); | |
| 218 | |
| 219 gfx::Size size = ComputeSize(frame_size_, plugin_frame_size_); | |
| 220 PP_VideoFrame_Format format = ComputeFormat(frame_format_, | |
| 221 plugin_frame_format_); | |
| 92 ppapi::MediaStreamBuffer::Video* buffer = | 222 ppapi::MediaStreamBuffer::Video* buffer = |
| 93 &(buffer_manager()->GetBufferPointer(index)->video); | 223 &(buffer_manager()->GetBufferPointer(index)->video); |
| 94 buffer->header.size = buffer_manager()->buffer_size(); | 224 buffer->header.size = buffer_manager()->buffer_size(); |
| 95 buffer->header.type = ppapi::MediaStreamBuffer::TYPE_VIDEO; | 225 buffer->header.type = ppapi::MediaStreamBuffer::TYPE_VIDEO; |
| 96 buffer->timestamp = frame->GetTimestamp().InSecondsF(); | 226 buffer->timestamp = frame->GetTimestamp().InSecondsF(); |
| 97 buffer->format = ppformat; | 227 buffer->format = format; |
| 98 buffer->size.width = frame->coded_size().width(); | 228 buffer->size.width = size.width(); |
| 99 buffer->size.height = frame->coded_size().height(); | 229 buffer->size.height = size.height(); |
| 100 buffer->data_size = frame_data_size_; | 230 buffer->data_size = frame_data_size_; |
| 101 | |
| 102 COMPILE_ASSERT(VideoFrame::kYPlane == 0, y_plane_should_be_0); | |
| 103 COMPILE_ASSERT(VideoFrame::kUPlane == 1, u_plane_should_be_1); | |
| 104 COMPILE_ASSERT(VideoFrame::kVPlane == 2, v_plane_should_be_2); | |
| 105 | |
| 106 uint8_t* dst = buffer->data; | 231 uint8_t* dst = buffer->data; |
| 107 size_t num_planes = VideoFrame::NumPlanes(frame->format()); | 232 ConvertFromMediaVideoFrame(frame, dst, format, size); |
| 108 for (size_t i = 0; i < num_planes; ++i) { | |
| 109 const uint8_t* src = frame->data(i); | |
| 110 const size_t row_bytes = frame->row_bytes(i); | |
| 111 const size_t src_stride = frame->stride(i); | |
| 112 int rows = frame->rows(i); | |
| 113 for (int j = 0; j < rows; ++j) { | |
| 114 memcpy(dst, src, row_bytes); | |
| 115 dst += row_bytes; | |
| 116 src += src_stride; | |
| 117 } | |
| 118 } | |
| 119 | |
| 120 SendEnqueueBufferMessageToPlugin(index); | 233 SendEnqueueBufferMessageToPlugin(index); |
| 121 } | 234 } |
| 122 | 235 |
| 123 void PepperMediaStreamVideoTrackHost::DidConnectPendingHostToResource() { | 236 void PepperMediaStreamVideoTrackHost::DidConnectPendingHostToResource() { |
| 124 if (!connected_) { | 237 if (!connected_) { |
| 125 MediaStreamVideoSink::AddToVideoTrack(this, track_); | 238 MediaStreamVideoSink::AddToVideoTrack(this, track_); |
| 126 connected_ = true; | 239 connected_ = true; |
| 127 } | 240 } |
| 128 } | 241 } |
| 129 | 242 |
| 243 int32_t PepperMediaStreamVideoTrackHost::OnResourceMessageReceived( | |
| 244 const IPC::Message& msg, | |
| 245 HostMessageContext* context) { | |
| 246 IPC_BEGIN_MESSAGE_MAP(PepperMediaStreamVideoTrackHost, msg) | |
| 247 PPAPI_DISPATCH_HOST_RESOURCE_CALL( | |
| 248 PpapiHostMsg_MediaStreamVideoTrack_Configure, | |
| 249 OnHostMsgConfigure) | |
| 250 IPC_END_MESSAGE_MAP() | |
| 251 return PepperMediaStreamTrackHostBase::OnResourceMessageReceived(msg, | |
| 252 context); | |
| 253 } | |
| 254 | |
| 255 int32_t PepperMediaStreamVideoTrackHost::OnHostMsgConfigure( | |
| 256 HostMessageContext* context, | |
| 257 const MediaStreamVideoTrackShared::Attributes& attributes) { | |
| 258 CHECK(MediaStreamVideoTrackShared::VerifyAttributes(attributes)); | |
| 259 | |
| 260 bool changed = false; | |
| 261 const uint32_t kWHMask = MediaStreamVideoTrackShared::Attributes::MASK_WIDTH | | |
| 262 MediaStreamVideoTrackShared::Attributes::MASK_HEIGHT; | |
| 263 if (attributes.mask & kWHMask) { | |
| 264 gfx::Size new_size = plugin_frame_size_; | |
| 265 if (attributes.mask & MediaStreamVideoTrackShared::Attributes::MASK_WIDTH) | |
| 266 new_size.set_width(attributes.width); | |
| 267 if (attributes.mask & MediaStreamVideoTrackShared::Attributes::MASK_HEIGHT) | |
| 268 new_size.set_height(attributes.height); | |
| 269 if (ComputeSize(frame_size_, plugin_frame_size_) != | |
| 270 ComputeSize(frame_size_, new_size)) { | |
| 271 changed = true; | |
| 272 } | |
| 273 plugin_frame_size_ = new_size; | |
| 274 } | |
| 275 | |
| 276 if (attributes.mask & MediaStreamVideoTrackShared::Attributes::MASK_BUFFERS) { | |
| 277 int32_t buffers = attributes.buffers ? | |
| 278 std::min(kMaxNumberOfBuffers, attributes.buffers) : kNumberOfBuffers; | |
| 279 if (buffers != buffers_) { | |
| 280 buffers_ = buffers; | |
| 281 changed = true; | |
| 282 } | |
| 283 } | |
| 284 | |
| 285 if (attributes.mask & MediaStreamVideoTrackShared::Attributes::MASK_FORMAT) { | |
| 286 if (plugin_frame_format_ != attributes.format) { | |
| 287 PP_VideoFrame_Format original_format = ComputeFormat( | |
| 288 frame_format_, plugin_frame_format_); | |
| 289 PP_VideoFrame_Format new_format = ComputeFormat( | |
| 290 frame_format_, attributes.format); | |
| 291 if (new_format != original_format) | |
| 292 changed = true; | |
| 293 plugin_frame_format_ = attributes.format; | |
| 294 } | |
| 295 LOG(ERROR) << "format =" << plugin_frame_format_; | |
| 296 } | |
| 297 | |
| 298 // If buffers has been initialized, we need re-initialize it with | |
| 299 // new settings. Otherwise, we will initialize buffer when we receive | |
|
Ronghua Wu (Left Chromium)
2014/02/12 19:54:15
why do we need to "initialize buffer when receive
Peng
2014/02/13 17:19:11
Because plugin may only provide part for attribute
| |
| 300 // the first frame. | |
| 301 if (changed && buffers_initialized_) | |
| 302 InitBuffers(); | |
| 303 | |
| 304 context->reply_msg = PpapiPluginMsg_MediaStreamVideoTrack_ConfigureReply(); | |
| 305 return PP_OK; | |
| 306 } | |
| 307 | |
| 130 } // namespace content | 308 } // namespace content |
| OLD | NEW |