| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2012 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/video/fake_video_capture_device.h" | |
| 6 | |
| 7 #include <stddef.h> | |
| 8 #include <algorithm> | |
| 9 #include <utility> | |
| 10 | |
| 11 #include "base/bind.h" | |
| 12 #include "base/location.h" | |
| 13 #include "base/single_thread_task_runner.h" | |
| 14 #include "base/strings/stringprintf.h" | |
| 15 #include "base/threading/thread_task_runner_handle.h" | |
| 16 #include "media/audio/fake_audio_input_stream.h" | |
| 17 #include "media/base/video_frame.h" | |
| 18 #include "mojo/public/cpp/bindings/string.h" | |
| 19 #include "third_party/skia/include/core/SkBitmap.h" | |
| 20 #include "third_party/skia/include/core/SkCanvas.h" | |
| 21 #include "third_party/skia/include/core/SkMatrix.h" | |
| 22 #include "third_party/skia/include/core/SkPaint.h" | |
| 23 #include "ui/gfx/codec/png_codec.h" | |
| 24 | |
| 25 namespace media { | |
| 26 | |
| 27 // Sweep at 600 deg/sec. | |
| 28 static const float kPacmanAngularVelocity = 600; | |
| 29 // Beep every 500 ms. | |
| 30 static const int kBeepInterval = 500; | |
| 31 | |
| 32 static const uint32_t kMinZoom = 100; | |
| 33 static const uint32_t kMaxZoom = 400; | |
| 34 | |
| 35 void DrawPacman(bool use_argb, | |
| 36 uint8_t* const data, | |
| 37 base::TimeDelta elapsed_time, | |
| 38 float frame_rate, | |
| 39 const gfx::Size& frame_size, | |
| 40 uint32_t zoom) { | |
| 41 // |kN32_SkColorType| stands for the appropriate RGBA/BGRA format. | |
| 42 const SkColorType colorspace = | |
| 43 use_argb ? kN32_SkColorType : kAlpha_8_SkColorType; | |
| 44 const SkImageInfo info = SkImageInfo::Make( | |
| 45 frame_size.width(), frame_size.height(), colorspace, kOpaque_SkAlphaType); | |
| 46 SkBitmap bitmap; | |
| 47 bitmap.setInfo(info); | |
| 48 bitmap.setPixels(data); | |
| 49 SkPaint paint; | |
| 50 paint.setStyle(SkPaint::kFill_Style); | |
| 51 SkCanvas canvas(bitmap); | |
| 52 | |
| 53 const SkScalar unscaled_zoom = zoom / 100.f; | |
| 54 SkMatrix matrix; | |
| 55 matrix.setScale(unscaled_zoom, unscaled_zoom, frame_size.width() / 2, | |
| 56 frame_size.height() / 2); | |
| 57 canvas.setMatrix(matrix); | |
| 58 | |
| 59 // Equalize Alpha_8 that has light green background while RGBA has white. | |
| 60 if (use_argb) { | |
| 61 const SkRect full_frame = | |
| 62 SkRect::MakeWH(frame_size.width(), frame_size.height()); | |
| 63 paint.setARGB(255, 0, 127, 0); | |
| 64 canvas.drawRect(full_frame, paint); | |
| 65 } | |
| 66 paint.setColor(SK_ColorGREEN); | |
| 67 | |
| 68 // Draw a sweeping circle to show an animation. | |
| 69 const float end_angle = | |
| 70 fmod(kPacmanAngularVelocity * elapsed_time.InSecondsF(), 361); | |
| 71 const int radius = std::min(frame_size.width(), frame_size.height()) / 4; | |
| 72 const SkRect rect = SkRect::MakeXYWH(frame_size.width() / 2 - radius, | |
| 73 frame_size.height() / 2 - radius, | |
| 74 2 * radius, 2 * radius); | |
| 75 canvas.drawArc(rect, 0, end_angle, true, paint); | |
| 76 | |
| 77 // Draw current time. | |
| 78 const int milliseconds = elapsed_time.InMilliseconds() % 1000; | |
| 79 const int seconds = elapsed_time.InSeconds() % 60; | |
| 80 const int minutes = elapsed_time.InMinutes() % 60; | |
| 81 const int hours = elapsed_time.InHours(); | |
| 82 const int frame_count = elapsed_time.InMilliseconds() * frame_rate / 1000; | |
| 83 | |
| 84 const std::string time_string = | |
| 85 base::StringPrintf("%d:%02d:%02d:%03d %d", hours, minutes, seconds, | |
| 86 milliseconds, frame_count); | |
| 87 canvas.scale(3, 3); | |
| 88 canvas.drawText(time_string.data(), time_string.length(), 30, 20, paint); | |
| 89 } | |
| 90 | |
| 91 // Creates a PNG-encoded frame and sends it back to |callback|. The other | |
| 92 // parameters are used to replicate the PacMan rendering. | |
| 93 void DoTakeFakePhoto(VideoCaptureDevice::TakePhotoCallback callback, | |
| 94 const VideoCaptureFormat& capture_format, | |
| 95 base::TimeDelta elapsed_time, | |
| 96 float fake_capture_rate, | |
| 97 uint32_t zoom) { | |
| 98 std::unique_ptr<uint8_t[]> buffer(new uint8_t[VideoFrame::AllocationSize( | |
| 99 PIXEL_FORMAT_ARGB, capture_format.frame_size)]); | |
| 100 | |
| 101 DrawPacman(true /* use_argb */, buffer.get(), elapsed_time, fake_capture_rate, | |
| 102 capture_format.frame_size, zoom); | |
| 103 | |
| 104 std::vector<uint8_t> encoded_data; | |
| 105 const bool result = gfx::PNGCodec::Encode( | |
| 106 buffer.get(), gfx::PNGCodec::FORMAT_RGBA, capture_format.frame_size, | |
| 107 capture_format.frame_size.width() * 4, true /* discard_transparency */, | |
| 108 std::vector<gfx::PNGCodec::Comment>(), &encoded_data); | |
| 109 DCHECK(result); | |
| 110 | |
| 111 callback.Run(mojo::String::From("image/png"), | |
| 112 mojo::Array<uint8_t>::From(encoded_data)); | |
| 113 } | |
| 114 | |
| 115 FakeVideoCaptureDevice::FakeVideoCaptureDevice(BufferOwnership buffer_ownership, | |
| 116 float fake_capture_rate) | |
| 117 : buffer_ownership_(buffer_ownership), | |
| 118 fake_capture_rate_(fake_capture_rate), | |
| 119 current_zoom_(kMinZoom), | |
| 120 weak_factory_(this) {} | |
| 121 | |
| 122 FakeVideoCaptureDevice::~FakeVideoCaptureDevice() { | |
| 123 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 124 } | |
| 125 | |
| 126 void FakeVideoCaptureDevice::AllocateAndStart( | |
| 127 const VideoCaptureParams& params, | |
| 128 std::unique_ptr<VideoCaptureDevice::Client> client) { | |
| 129 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 130 | |
| 131 client_ = std::move(client); | |
| 132 | |
| 133 // Incoming |params| can be none of the supported formats, so we get the | |
| 134 // closest thing rounded up. TODO(mcasas): Use the |params|, if they belong to | |
| 135 // the supported ones, when http://crbug.com/309554 is verified. | |
| 136 capture_format_.frame_rate = fake_capture_rate_; | |
| 137 if (params.requested_format.frame_size.width() > 1280) | |
| 138 capture_format_.frame_size.SetSize(1920, 1080); | |
| 139 else if (params.requested_format.frame_size.width() > 640) | |
| 140 capture_format_.frame_size.SetSize(1280, 720); | |
| 141 else if (params.requested_format.frame_size.width() > 320) | |
| 142 capture_format_.frame_size.SetSize(640, 480); | |
| 143 else | |
| 144 capture_format_.frame_size.SetSize(320, 240); | |
| 145 | |
| 146 if (buffer_ownership_ == BufferOwnership::CLIENT_BUFFERS) { | |
| 147 capture_format_.pixel_storage = PIXEL_STORAGE_CPU; | |
| 148 capture_format_.pixel_format = PIXEL_FORMAT_ARGB; | |
| 149 DVLOG(1) << "starting with client argb buffers"; | |
| 150 } else if (buffer_ownership_ == BufferOwnership::OWN_BUFFERS) { | |
| 151 capture_format_.pixel_storage = PIXEL_STORAGE_CPU; | |
| 152 capture_format_.pixel_format = PIXEL_FORMAT_I420; | |
| 153 DVLOG(1) << "starting with own I420 buffers"; | |
| 154 } | |
| 155 | |
| 156 if (capture_format_.pixel_format == PIXEL_FORMAT_I420) { | |
| 157 fake_frame_.reset(new uint8_t[VideoFrame::AllocationSize( | |
| 158 PIXEL_FORMAT_I420, capture_format_.frame_size)]); | |
| 159 } | |
| 160 | |
| 161 beep_time_ = base::TimeDelta(); | |
| 162 elapsed_time_ = base::TimeDelta(); | |
| 163 | |
| 164 if (buffer_ownership_ == BufferOwnership::CLIENT_BUFFERS) | |
| 165 BeepAndScheduleNextCapture( | |
| 166 base::TimeTicks::Now(), | |
| 167 base::Bind(&FakeVideoCaptureDevice::CaptureUsingClientBuffers, | |
| 168 weak_factory_.GetWeakPtr())); | |
| 169 else if (buffer_ownership_ == BufferOwnership::OWN_BUFFERS) | |
| 170 BeepAndScheduleNextCapture( | |
| 171 base::TimeTicks::Now(), | |
| 172 base::Bind(&FakeVideoCaptureDevice::CaptureUsingOwnBuffers, | |
| 173 weak_factory_.GetWeakPtr())); | |
| 174 } | |
| 175 | |
| 176 void FakeVideoCaptureDevice::StopAndDeAllocate() { | |
| 177 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 178 client_.reset(); | |
| 179 } | |
| 180 | |
| 181 void FakeVideoCaptureDevice::GetPhotoCapabilities( | |
| 182 GetPhotoCapabilitiesCallback callback) { | |
| 183 mojom::PhotoCapabilitiesPtr photo_capabilities = | |
| 184 mojom::PhotoCapabilities::New(); | |
| 185 photo_capabilities->zoom = mojom::Range::New(); | |
| 186 photo_capabilities->zoom->current = current_zoom_; | |
| 187 photo_capabilities->zoom->max = kMaxZoom; | |
| 188 photo_capabilities->zoom->min = kMinZoom; | |
| 189 callback.Run(std::move(photo_capabilities)); | |
| 190 } | |
| 191 | |
| 192 void FakeVideoCaptureDevice::SetPhotoOptions(mojom::PhotoSettingsPtr settings, | |
| 193 SetPhotoOptionsCallback callback) { | |
| 194 if (settings->has_zoom) | |
| 195 current_zoom_ = std::max(kMinZoom, std::min(settings->zoom, kMaxZoom)); | |
| 196 callback.Run(true); | |
| 197 } | |
| 198 | |
| 199 void FakeVideoCaptureDevice::TakePhoto(TakePhotoCallback callback) { | |
| 200 base::ThreadTaskRunnerHandle::Get()->PostTask( | |
| 201 FROM_HERE, | |
| 202 base::Bind(&DoTakeFakePhoto, base::Passed(&callback), capture_format_, | |
| 203 elapsed_time_, fake_capture_rate_, current_zoom_)); | |
| 204 } | |
| 205 | |
| 206 void FakeVideoCaptureDevice::CaptureUsingOwnBuffers( | |
| 207 base::TimeTicks expected_execution_time) { | |
| 208 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 209 const size_t frame_size = capture_format_.ImageAllocationSize(); | |
| 210 memset(fake_frame_.get(), 0, frame_size); | |
| 211 | |
| 212 DrawPacman(false /* use_argb */, fake_frame_.get(), elapsed_time_, | |
| 213 fake_capture_rate_, capture_format_.frame_size, current_zoom_); | |
| 214 | |
| 215 // Give the captured frame to the client. | |
| 216 base::TimeTicks now = base::TimeTicks::Now(); | |
| 217 if (first_ref_time_.is_null()) | |
| 218 first_ref_time_ = now; | |
| 219 client_->OnIncomingCapturedData(fake_frame_.get(), frame_size, | |
| 220 capture_format_, 0 /* rotation */, now, | |
| 221 now - first_ref_time_); | |
| 222 BeepAndScheduleNextCapture( | |
| 223 expected_execution_time, | |
| 224 base::Bind(&FakeVideoCaptureDevice::CaptureUsingOwnBuffers, | |
| 225 weak_factory_.GetWeakPtr())); | |
| 226 } | |
| 227 | |
| 228 void FakeVideoCaptureDevice::CaptureUsingClientBuffers( | |
| 229 base::TimeTicks expected_execution_time) { | |
| 230 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 231 | |
| 232 std::unique_ptr<VideoCaptureDevice::Client::Buffer> capture_buffer( | |
| 233 client_->ReserveOutputBuffer(capture_format_.frame_size, | |
| 234 capture_format_.pixel_format, | |
| 235 capture_format_.pixel_storage)); | |
| 236 DLOG_IF(ERROR, !capture_buffer) << "Couldn't allocate Capture Buffer"; | |
| 237 DCHECK(capture_buffer->data()) << "Buffer has NO backing memory"; | |
| 238 | |
| 239 if (capture_format_.pixel_storage == PIXEL_STORAGE_GPUMEMORYBUFFER && | |
| 240 capture_format_.pixel_format == media::PIXEL_FORMAT_I420) { | |
| 241 // Since SkBitmap expects a packed&continuous memory region for I420, we | |
| 242 // need to use |fake_frame_| to draw onto. | |
| 243 memset(fake_frame_.get(), 0, capture_format_.ImageAllocationSize()); | |
| 244 DrawPacman(false /* use_argb */, fake_frame_.get(), elapsed_time_, | |
| 245 fake_capture_rate_, capture_format_.frame_size, current_zoom_); | |
| 246 | |
| 247 // Copy data from |fake_frame_| into the reserved planes of GpuMemoryBuffer. | |
| 248 size_t offset = 0; | |
| 249 for (size_t i = 0; i < VideoFrame::NumPlanes(PIXEL_FORMAT_I420); ++i) { | |
| 250 const size_t plane_size = | |
| 251 VideoFrame::PlaneSize(PIXEL_FORMAT_I420, i, | |
| 252 capture_format_.frame_size) | |
| 253 .GetArea(); | |
| 254 memcpy(capture_buffer->data(i), fake_frame_.get() + offset, plane_size); | |
| 255 offset += plane_size; | |
| 256 } | |
| 257 } else { | |
| 258 DCHECK_EQ(capture_format_.pixel_storage, PIXEL_STORAGE_CPU); | |
| 259 DCHECK_EQ(capture_format_.pixel_format, PIXEL_FORMAT_ARGB); | |
| 260 uint8_t* data_ptr = static_cast<uint8_t*>(capture_buffer->data()); | |
| 261 memset(data_ptr, 0, capture_buffer->mapped_size()); | |
| 262 DrawPacman(true /* use_argb */, data_ptr, elapsed_time_, fake_capture_rate_, | |
| 263 capture_format_.frame_size, current_zoom_); | |
| 264 } | |
| 265 | |
| 266 // Give the captured frame to the client. | |
| 267 base::TimeTicks now = base::TimeTicks::Now(); | |
| 268 if (first_ref_time_.is_null()) | |
| 269 first_ref_time_ = now; | |
| 270 client_->OnIncomingCapturedBuffer(std::move(capture_buffer), capture_format_, | |
| 271 now, now - first_ref_time_); | |
| 272 | |
| 273 BeepAndScheduleNextCapture( | |
| 274 expected_execution_time, | |
| 275 base::Bind(&FakeVideoCaptureDevice::CaptureUsingClientBuffers, | |
| 276 weak_factory_.GetWeakPtr())); | |
| 277 } | |
| 278 | |
| 279 void FakeVideoCaptureDevice::BeepAndScheduleNextCapture( | |
| 280 base::TimeTicks expected_execution_time, | |
| 281 const base::Callback<void(base::TimeTicks)>& next_capture) { | |
| 282 const base::TimeDelta beep_interval = | |
| 283 base::TimeDelta::FromMilliseconds(kBeepInterval); | |
| 284 const base::TimeDelta frame_interval = | |
| 285 base::TimeDelta::FromMicroseconds(1e6 / fake_capture_rate_); | |
| 286 beep_time_ += frame_interval; | |
| 287 elapsed_time_ += frame_interval; | |
| 288 | |
| 289 // Generate a synchronized beep twice per second. | |
| 290 if (beep_time_ >= beep_interval) { | |
| 291 FakeAudioInputStream::BeepOnce(); | |
| 292 beep_time_ -= beep_interval; | |
| 293 } | |
| 294 | |
| 295 // Reschedule next CaptureTask. | |
| 296 const base::TimeTicks current_time = base::TimeTicks::Now(); | |
| 297 // Don't accumulate any debt if we are lagging behind - just post the next | |
| 298 // frame immediately and continue as normal. | |
| 299 const base::TimeTicks next_execution_time = | |
| 300 std::max(current_time, expected_execution_time + frame_interval); | |
| 301 const base::TimeDelta delay = next_execution_time - current_time; | |
| 302 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( | |
| 303 FROM_HERE, base::Bind(next_capture, next_execution_time), delay); | |
| 304 } | |
| 305 | |
| 306 } // namespace media | |
| OLD | NEW |