Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright 2016 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 "media/capture/service/video_capture_device_client_impl.h" | |
| 6 | |
| 7 #include "base/time/time.h" | |
| 8 #include "media/base/bind_to_current_loop.h" | |
| 9 #include "media/capture/service/mojo_video_frame.h" | |
| 10 #include "third_party/libyuv/include/libyuv.h" | |
| 11 #include "ui/gfx/geometry/rect.h" | |
| 12 #include "ui/gfx/geometry/size.h" | |
| 13 | |
| 14 namespace media { | |
| 15 | |
| 16 namespace { | |
| 17 | |
| 18 // This implements a VideoCaptureDevice::Client::Buffer as a lightweight wrapper | |
| 19 // around a MojoVideoFrame. This Buffer is not meant to be used beyond the | |
| 20 // VideoCaptureDevice and its Client implementation. | |
| 21 class BufferImpl : public media::VideoCaptureDevice::Client::Buffer { | |
| 22 public: | |
| 23 BufferImpl(scoped_refptr<MojoVideoFrame> video_frame) | |
| 24 : video_frame_(video_frame) {} | |
| 25 | |
| 26 ~BufferImpl() override {} | |
| 27 | |
| 28 // media::VideoCaptureDevice::Client::Buffer: | |
| 29 int id() const override { // Not needed. | |
| 30 NOTREACHED(); | |
| 31 return 0; | |
| 32 } | |
| 33 gfx::Size dimensions() const override { return video_frame_->coded_size(); } | |
| 34 size_t mapped_size() const override { return video_frame_->mapped_size(); } | |
| 35 void* data(int plane) override { return video_frame_->data(plane); } | |
| 36 ClientBuffer AsClientBuffer(int plane) override { | |
| 37 NOTREACHED(); | |
| 38 return nullptr; | |
| 39 } | |
| 40 #if defined(OS_POSIX) && !(defined(OS_MACOSX) && !defined(OS_IOS)) | |
| 41 base::FileDescriptor AsPlatformFile() override { | |
| 42 NOTREACHED(); | |
| 43 return base::FileDescriptor(); | |
| 44 } | |
| 45 #endif | |
| 46 | |
| 47 const scoped_refptr<MojoVideoFrame> video_frame() { return video_frame_; } | |
| 48 | |
| 49 private: | |
| 50 const scoped_refptr<MojoVideoFrame> video_frame_; | |
| 51 | |
| 52 DISALLOW_COPY_AND_ASSIGN(BufferImpl); | |
| 53 }; | |
| 54 | |
| 55 } // namespace | |
| 56 | |
| 57 // A pool of -possibly heterogenous- MojoVideoFrames, provides MojoVideoFrames | |
| 58 // if available and of the appropriate format, otherwise allocates it. The MVF | |
| 59 // are returned via registration of DestructionObserver. This class is used from | |
| 60 // the mojo component main thread and from a number of device threads. | |
| 61 class VideoCaptureDeviceClientImpl::VideoFramePool | |
| 62 : public base::RefCountedThreadSafe< | |
| 63 VideoCaptureDeviceClientImpl::VideoFramePool> { | |
| 64 public: | |
| 65 VideoFramePool(); | |
| 66 | |
| 67 // Produces a frame matching the specified |dimensions|, either by grabbing it | |
| 68 // from the pool or creating a new frame if no suitable one is found. | |
| 69 scoped_refptr<MojoVideoFrame> CreateFrame(const gfx::Size& dimensions, | |
| 70 base::TimeDelta timestamp); | |
| 71 | |
| 72 // Once this is called frames will no longer be inserted into |frames_|. | |
| 73 void Shutdown(); | |
| 74 | |
| 75 private: | |
| 76 // Can't use a WeakPtr since this class is used by multiple threads. | |
| 77 friend class base::RefCountedThreadSafe< | |
| 78 VideoCaptureDeviceClientImpl::VideoFramePool>; | |
| 79 ~VideoFramePool(); | |
| 80 | |
| 81 // Called when the frame wrapper gets destroyed. |frame| is the actual frame | |
| 82 // that was wrapped and is re inserted in |frames_| by this function. | |
| 83 void FrameReleased(const scoped_refptr<MojoVideoFrame>& frame); | |
| 84 | |
| 85 // For mutual exclusion when accessing from multiple threads. | |
| 86 base::Lock lock_; | |
| 87 bool is_shutdown_; | |
| 88 | |
| 89 std::list<scoped_refptr<MojoVideoFrame>> frames_; | |
| 90 | |
| 91 DISALLOW_COPY_AND_ASSIGN(VideoFramePool); | |
| 92 }; | |
| 93 | |
| 94 VideoCaptureDeviceClientImpl::VideoCaptureDeviceClientImpl(Delegate* delegate) | |
| 95 : delegate_(delegate), | |
| 96 video_frame_pool_(new VideoFramePool()), | |
| 97 weak_factory_(this) { | |
| 98 DCHECK(delegate_); | |
| 99 // |this| is used exclusively by internal video capture device code which may, | |
| 100 // and ususally will, operate on a different thread from the constructor. | |
| 101 thread_checker_.DetachFromThread(); | |
| 102 } | |
| 103 | |
| 104 VideoCaptureDeviceClientImpl::~VideoCaptureDeviceClientImpl() { | |
| 105 // This happens on an OS thread. | |
| 106 video_frame_pool_->Shutdown(); | |
| 107 } | |
| 108 | |
| 109 void VideoCaptureDeviceClientImpl::OnIncomingCapturedData( | |
| 110 const uint8_t* data, | |
| 111 int length, | |
| 112 const media::VideoCaptureFormat& frame_format, | |
| 113 int clockwise_rotation, | |
| 114 const base::TimeTicks& timestamp) { | |
| 115 DVLOG(1) << __FUNCTION__; | |
| 116 // N.B.: All of the logic throughout this method is lifted directly from | |
| 117 // content::VideoCaptureDeviceClient and should generally continue to mirror | |
| 118 // that implementation until this one becomes the source of truth. | |
| 119 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 120 DCHECK_EQ(media::PIXEL_STORAGE_CPU, frame_format.pixel_storage); | |
| 121 | |
| 122 if (!frame_format.IsValid()) | |
| 123 return; | |
| 124 | |
| 125 const int width_parity = frame_format.frame_size.width() & 1; | |
| 126 const int height_parity = frame_format.frame_size.height() & 1; | |
| 127 const int even_width = frame_format.frame_size.width() & ~1; | |
| 128 const int even_height = frame_format.frame_size.height() & ~1; | |
| 129 | |
| 130 int destination_width = even_width; | |
| 131 int destination_height = even_height; | |
| 132 | |
| 133 if (clockwise_rotation == 90 || clockwise_rotation == 270) | |
| 134 std::swap(destination_width, destination_height); | |
| 135 | |
| 136 DCHECK_EQ(clockwise_rotation % 90, 0) | |
| 137 << "Rotation must be a multiple of 90, now: " << clockwise_rotation; | |
| 138 libyuv::RotationMode rotation_mode = libyuv::kRotate0; | |
| 139 if (clockwise_rotation == 90) | |
| 140 rotation_mode = libyuv::kRotate90; | |
| 141 else if (clockwise_rotation == 180) | |
| 142 rotation_mode = libyuv::kRotate180; | |
| 143 else if (clockwise_rotation == 270) | |
| 144 rotation_mode = libyuv::kRotate270; | |
| 145 | |
| 146 libyuv::FourCC colorspace = libyuv::FOURCC_ANY; | |
| 147 bool flip = false; | |
| 148 switch (frame_format.pixel_format) { | |
| 149 case media::PIXEL_FORMAT_UNKNOWN: | |
| 150 break; | |
| 151 case media::PIXEL_FORMAT_I420: | |
| 152 DCHECK(!width_parity && !height_parity); | |
| 153 colorspace = libyuv::FOURCC_I420; | |
| 154 break; | |
| 155 case media::PIXEL_FORMAT_YV12: | |
| 156 DCHECK(!width_parity && !height_parity); | |
| 157 colorspace = libyuv::FOURCC_YV12; | |
| 158 break; | |
| 159 case media::PIXEL_FORMAT_NV12: | |
| 160 DCHECK(!width_parity && !height_parity); | |
| 161 colorspace = libyuv::FOURCC_NV12; | |
| 162 break; | |
| 163 case media::PIXEL_FORMAT_NV21: | |
| 164 DCHECK(!width_parity && !height_parity); | |
| 165 colorspace = libyuv::FOURCC_NV21; | |
| 166 break; | |
| 167 case media::PIXEL_FORMAT_YUY2: | |
| 168 DCHECK(!width_parity && !height_parity); | |
| 169 colorspace = libyuv::FOURCC_YUY2; | |
| 170 break; | |
| 171 case media::PIXEL_FORMAT_UYVY: | |
| 172 DCHECK(!width_parity && !height_parity); | |
| 173 colorspace = libyuv::FOURCC_UYVY; | |
| 174 break; | |
| 175 case media::PIXEL_FORMAT_RGB24: | |
| 176 // Linux RGB24 defines red at lowest byte address, | |
| 177 // see http://linuxtv.org/downloads/v4l-dvb-apis/packed-rgb.html. | |
| 178 // Windows RGB24 defines blue at lowest byte, | |
| 179 // see https://msdn.microsoft.com/en-us/library/windows/desktop/dd407253 | |
| 180 #if defined(OS_LINUX) | |
| 181 colorspace = libyuv::FOURCC_RAW; | |
| 182 #elif defined(OS_WIN) | |
| 183 colorspace = libyuv::FOURCC_24BG; | |
| 184 #else | |
| 185 NOTREACHED() << "RGB24 is only available in Linux and Windows platforms"; | |
| 186 #endif | |
| 187 #if defined(OS_WIN) | |
| 188 // TODO(wjia): Currently, for RGB24 on WIN, capture device always | |
| 189 // passes in positive src_width and src_height. Remove this hardcoded | |
| 190 // value when nagative src_height is supported. The negative src_height | |
| 191 // indicates that vertical flipping is needed. | |
| 192 flip = true; | |
| 193 #endif | |
| 194 break; | |
| 195 case media::PIXEL_FORMAT_RGB32: | |
| 196 // Fallback to PIXEL_FORMAT_ARGB setting |flip| in Windows | |
| 197 // platforms. | |
| 198 #if defined(OS_WIN) | |
| 199 flip = true; | |
| 200 #endif | |
| 201 case media::PIXEL_FORMAT_ARGB: | |
| 202 colorspace = libyuv::FOURCC_ARGB; | |
| 203 break; | |
| 204 case media::PIXEL_FORMAT_MJPEG: | |
| 205 colorspace = libyuv::FOURCC_MJPG; | |
| 206 break; | |
| 207 default: | |
| 208 NOTREACHED() << media::VideoPixelFormatToString( | |
| 209 frame_format.pixel_format); | |
| 210 } | |
| 211 | |
| 212 const gfx::Size dimensions(destination_width, destination_height); | |
| 213 scoped_ptr<Buffer> buffer = ReserveOutputBuffer( | |
| 214 dimensions, media::PIXEL_FORMAT_I420, media::PIXEL_STORAGE_CPU); | |
|
magjed_chromium
2016/03/04 15:41:42
Would it be possible to use GPU memory buffers lik
mcasas
2016/03/04 19:59:46
GMBs are only used behind a flag and in an
experim
| |
| 215 if (!buffer) | |
| 216 return; | |
| 217 | |
| 218 int y_stride = dimensions.width(); | |
| 219 int uv_stride = y_stride / 2; | |
| 220 if (libyuv::ConvertToI420( | |
| 221 data, length, | |
| 222 static_cast<uint8_t*>(buffer->data(media::VideoFrame::kYPlane)), | |
| 223 y_stride, | |
| 224 static_cast<uint8_t*>(buffer->data(media::VideoFrame::kUPlane)), | |
| 225 uv_stride, | |
| 226 static_cast<uint8_t*>(buffer->data(media::VideoFrame::kVPlane)), | |
| 227 uv_stride, 0 /* crop_x */, 0 /* crop_y */, | |
| 228 frame_format.frame_size.width(), | |
| 229 (flip ? -1 : 1) * frame_format.frame_size.height(), even_width, | |
| 230 even_height, rotation_mode, colorspace)) { | |
| 231 DLOG(ERROR) << "Failed to convert frame pixels to I420"; | |
| 232 return; | |
| 233 } | |
| 234 | |
| 235 OnIncomingCapturedBuffer(std::move(buffer), media::VideoCaptureFormat( | |
| 236 frame_format.frame_size, 0.0f, | |
| 237 media::PIXEL_FORMAT_I420), | |
| 238 timestamp); | |
| 239 } | |
| 240 | |
| 241 scoped_ptr<media::VideoCaptureDevice::Client::Buffer> | |
| 242 VideoCaptureDeviceClientImpl::ReserveOutputBuffer( | |
| 243 const gfx::Size& dimensions, | |
| 244 media::VideoPixelFormat pixel_format, | |
| 245 media::VideoPixelStorage /* pixel_storage */) { | |
| 246 DVLOG(1) << __FUNCTION__; | |
| 247 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 248 DCHECK_EQ(media::PIXEL_FORMAT_I420, pixel_format); | |
| 249 DCHECK(!dimensions.IsEmpty()); | |
| 250 | |
| 251 return make_scoped_ptr<Buffer>(new BufferImpl(video_frame_pool_->CreateFrame( | |
| 252 dimensions, base::TimeTicks::Now() - base::TimeTicks()))); | |
| 253 } | |
| 254 | |
| 255 void VideoCaptureDeviceClientImpl::OnIncomingCapturedBuffer( | |
| 256 scoped_ptr<Buffer> buffer, | |
| 257 const media::VideoCaptureFormat& frame_format, | |
| 258 const base::TimeTicks& timestamp) { | |
| 259 DVLOG(1) << __FUNCTION__; | |
| 260 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 261 DCHECK(buffer); | |
| 262 DCHECK_EQ(frame_format.pixel_format, media::PIXEL_FORMAT_I420); | |
| 263 DCHECK_EQ(frame_format.pixel_storage, media::PIXEL_STORAGE_CPU); | |
| 264 | |
| 265 // |buffer| can get out of scope since we have the important frame. | |
| 266 delegate_->OnFrame(static_cast<BufferImpl*>(buffer.get())->video_frame(), | |
| 267 timestamp); | |
| 268 } | |
| 269 | |
| 270 void VideoCaptureDeviceClientImpl::OnIncomingCapturedVideoFrame( | |
| 271 scoped_ptr<Buffer> buffer, | |
| 272 const scoped_refptr<media::VideoFrame>& frame, | |
| 273 const base::TimeTicks& timestamp) { | |
| 274 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 275 DCHECK(buffer); | |
| 276 | |
| 277 // TODO(mcasas): Contents capture calls here. Wire this path by wrapping | |
| 278 // |frame| in a MojoVideoFrame. | |
| 279 NOTIMPLEMENTED(); | |
| 280 } | |
| 281 | |
| 282 void VideoCaptureDeviceClientImpl::OnError( | |
| 283 const tracked_objects::Location& from_here, | |
| 284 const std::string& reason) { | |
| 285 DVLOG(1) << __FUNCTION__ << " " << reason; | |
| 286 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 287 delegate_->OnError(reason); | |
| 288 } | |
| 289 | |
| 290 double VideoCaptureDeviceClientImpl::GetBufferPoolUtilization() const { | |
| 291 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 292 NOTIMPLEMENTED(); | |
| 293 // TODO(mcasas): In content/, the frame pool did have a maximum. Consider | |
| 294 // doing the same in this VideoFramePool, and return here the amount in use | |
| 295 // there. | |
| 296 return 0.0; | |
| 297 } | |
| 298 | |
| 299 VideoCaptureDeviceClientImpl::VideoFramePool::VideoFramePool() | |
| 300 : is_shutdown_(false) {} | |
| 301 | |
| 302 scoped_refptr<MojoVideoFrame> | |
| 303 VideoCaptureDeviceClientImpl::VideoFramePool::CreateFrame( | |
| 304 const gfx::Size& dimensions, | |
| 305 base::TimeDelta timestamp) { | |
| 306 DVLOG(1) << __FUNCTION__ << " " << dimensions.ToString(); | |
| 307 base::AutoLock auto_lock(lock_); | |
| 308 DCHECK(!is_shutdown_); | |
| 309 | |
| 310 std::list<scoped_refptr<MojoVideoFrame>>::iterator suitable_frame = | |
| 311 std::find_if(frames_.begin(), frames_.end(), | |
| 312 [dimensions](const scoped_refptr<MojoVideoFrame>& frame) { | |
| 313 return frame->coded_size() == dimensions; | |
| 314 }); | |
| 315 | |
| 316 scoped_refptr<MojoVideoFrame> frame; | |
| 317 if (suitable_frame != frames_.end()) { | |
| 318 frame = *suitable_frame; | |
| 319 frames_.erase(suitable_frame); | |
| 320 DCHECK(frame->handle().is_valid()); | |
| 321 frame->set_timestamp(timestamp); | |
| 322 frame->metadata()->Clear(); | |
| 323 } else { | |
| 324 frame = MojoVideoFrame::CreateMojoVideoFrame(dimensions, timestamp); | |
| 325 } | |
| 326 LOG_IF(ERROR, !frame.get()) << "Failed to get/create a video frame"; | |
| 327 | |
| 328 // Make a copy of |frame| to register a Destruction Observer. | |
| 329 scoped_refptr<MojoVideoFrame> wrapped_frame = frame; | |
| 330 wrapped_frame->AddDestructionObserver( | |
| 331 base::Bind(&VideoFramePool::FrameReleased, this, frame)); | |
| 332 return wrapped_frame; | |
| 333 } | |
| 334 | |
| 335 void VideoCaptureDeviceClientImpl::VideoFramePool::Shutdown() { | |
| 336 base::AutoLock auto_lock(lock_); | |
| 337 is_shutdown_ = true; | |
| 338 frames_.clear(); | |
| 339 } | |
| 340 | |
| 341 VideoCaptureDeviceClientImpl::VideoFramePool::~VideoFramePool() { | |
| 342 DCHECK(is_shutdown_); | |
| 343 } | |
| 344 | |
| 345 void VideoCaptureDeviceClientImpl::VideoFramePool::FrameReleased( | |
| 346 const scoped_refptr<MojoVideoFrame>& frame) { | |
| 347 DVLOG(1) << __FUNCTION__; | |
| 348 if (is_shutdown_) | |
| 349 return; | |
| 350 frames_.push_back(frame); | |
| 351 } | |
| 352 | |
| 353 } // namespace media | |
| OLD | NEW |