1 // Copyright 2013 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/file_video_capture_device.h"
6
7 #include <stddef.h>
8 #include <utility>
9
10 #include "base/bind.h"
11 #include "base/location.h"
12 #include "base/logging.h"
13 #include "base/macros.h"
14 #include "base/single_thread_task_runner.h"
15 #include "base/strings/string_number_conversions.h"
16 #include "base/strings/string_piece.h"
17 #include "base/strings/string_util.h"
18 #include "base/threading/thread_task_runner_handle.h"
19 #include "media/capture/mojom/image_capture_types.h"
20 #include "media/capture/video/blob_utils.h"
21 #include "media/capture/video/gpu_memory_buffer_utils.h"
22 #include "media/capture/video_capture_types.h"
23 #include "media/parsers/jpeg_parser.h"
24 #include "third_party/libyuv/include/libyuv.h"
25
26 namespace media {
27
28 static const int kY4MHeaderMaxSize = 200;
29 static const char kY4MSimpleFrameDelimiter[] = "FRAME";
30 static const int kY4MSimpleFrameDelimiterSize = 6;
31 static const float kMJpegFrameRate = 30.0f;
32
ParseY4MInt(const base::StringPiece & token)33 int ParseY4MInt(const base::StringPiece& token) {
34 int temp_int;
35 CHECK(base::StringToInt(token, &temp_int)) << token;
36 return temp_int;
37 }
38
39 // Extract numerator and denominator out of a token that must have the aspect
40 // numerator:denominator, both integer numbers.
ParseY4MRational(const base::StringPiece & token,int * numerator,int * denominator)41 void ParseY4MRational(const base::StringPiece& token,
42 int* numerator, int* denominator) {
43 size_t index_divider = token.find(':');
44 CHECK_NE(index_divider, token.npos);
45 *numerator = ParseY4MInt(token.substr(0, index_divider));
46 *denominator = ParseY4MInt(token.substr(index_divider + 1, token.length()));
47 CHECK(*denominator);
48 }
49
50 // This function parses the ASCII string in |header| as belonging to a Y4M file,
51 // returning the collected format in |video_format|. For a non authoritative
52 // explanation of the header format, check
53 // http://wiki.multimedia.cx/index.php?title=YUV4MPEG2
54 // Restrictions: Only interlaced I420 pixel format is supported, and pixel
55 // aspect ratio is ignored.
56 // Implementation notes: Y4M header should end with an ASCII 0x20 (whitespace)
57 // character, however all examples mentioned in the Y4M header description end
58 // with a newline character instead. Also, some headers do _not_ specify pixel
59 // format, in this case it means I420.
60 // This code was inspired by third_party/libvpx/.../y4minput.* .
ParseY4MTags(const std::string & file_header,VideoCaptureFormat * video_format)61 void ParseY4MTags(const std::string& file_header,
62 VideoCaptureFormat* video_format) {
63 VideoCaptureFormat format;
64 format.pixel_format = PIXEL_FORMAT_I420;
65 size_t index = 0;
66 size_t blank_position = 0;
67 base::StringPiece token;
68 while ((blank_position = file_header.find_first_of("\n ", index)) !=
69 std::string::npos) {
70 // Every token is supposed to have an identifier letter and a bunch of
71 // information immediately after, which we extract into a |token| here.
72 token =
73 base::StringPiece(&file_header[index + 1], blank_position - index - 1);
74 CHECK(!token.empty());
75 switch (file_header[index]) {
76 case 'W':
77 format.frame_size.set_width(ParseY4MInt(token));
78 break;
79 case 'H':
80 format.frame_size.set_height(ParseY4MInt(token));
81 break;
82 case 'F': {
83 // If the token is "FRAME", it means we have finished with the header.
84 if (token[0] == 'R')
85 break;
86 int fps_numerator, fps_denominator;
87 ParseY4MRational(token, &fps_numerator, &fps_denominator);
88 format.frame_rate = fps_numerator / fps_denominator;
89 break;
90 }
91 case 'I':
92 // Interlacing is ignored, but we don't like mixed modes.
93 CHECK_NE(token[0], 'm');
94 break;
95 case 'A':
96 // Pixel aspect ratio ignored.
97 break;
98 case 'C':
99 CHECK(token == "420" || token == "420jpeg" || token == "420mpeg2" ||
100 token == "420paldv")
101 << token; // Only I420 is supported, and we fudge the variants.
102 break;
103 default:
104 break;
105 }
106 // We're done if we have found a newline character right after the token.
107 if (file_header[blank_position] == '\n')
108 break;
109 index = blank_position + 1;
110 }
111 // Last video format semantic correctness check before sending it back.
112 CHECK(format.IsValid());
113 *video_format = format;
114 }
115
116 class VideoFileParser {
117 public:
118 explicit VideoFileParser(const base::FilePath& file_path);
119 virtual ~VideoFileParser();
120
121 // Parses file header and collects format information in |capture_format|.
122 virtual bool Initialize(VideoCaptureFormat* capture_format) = 0;
123
124 // Gets the start pointer of next frame and stores current frame size in
125 // |frame_size|.
126 virtual const uint8_t* GetNextFrame(int* frame_size) = 0;
127
128 protected:
129 const base::FilePath file_path_;
130 int frame_size_;
131 size_t current_byte_index_;
132 size_t first_frame_byte_index_;
133 };
134
135 class Y4mFileParser final : public VideoFileParser {
136 public:
137 explicit Y4mFileParser(const base::FilePath& file_path);
138
139 // VideoFileParser implementation, class methods.
140 ~Y4mFileParser() override;
141 bool Initialize(VideoCaptureFormat* capture_format) override;
142 const uint8_t* GetNextFrame(int* frame_size) override;
143
144 private:
145 std::unique_ptr<base::File> file_;
146 std::unique_ptr<uint8_t[]> video_frame_;
147
148 DISALLOW_COPY_AND_ASSIGN(Y4mFileParser);
149 };
150
151 class MjpegFileParser final : public VideoFileParser {
152 public:
153 explicit MjpegFileParser(const base::FilePath& file_path);
154
155 // VideoFileParser implementation, class methods.
156 ~MjpegFileParser() override;
157 bool Initialize(VideoCaptureFormat* capture_format) override;
158 const uint8_t* GetNextFrame(int* frame_size) override;
159
160 private:
161 std::unique_ptr<base::MemoryMappedFile> mapped_file_;
162
163 DISALLOW_COPY_AND_ASSIGN(MjpegFileParser);
164 };
165
VideoFileParser(const base::FilePath & file_path)166 VideoFileParser::VideoFileParser(const base::FilePath& file_path)
167 : file_path_(file_path),
168 frame_size_(0),
169 current_byte_index_(0),
170 first_frame_byte_index_(0) {}
171
172 VideoFileParser::~VideoFileParser() = default;
173
Y4mFileParser(const base::FilePath & file_path)174 Y4mFileParser::Y4mFileParser(const base::FilePath& file_path)
175 : VideoFileParser(file_path) {}
176
177 Y4mFileParser::~Y4mFileParser() = default;
178
Initialize(VideoCaptureFormat * capture_format)179 bool Y4mFileParser::Initialize(VideoCaptureFormat* capture_format) {
180 file_.reset(new base::File(file_path_,
181 base::File::FLAG_OPEN | base::File::FLAG_READ));
182 if (!file_->IsValid()) {
183 DLOG(ERROR) << file_path_.value() << ", error: "
184 << base::File::ErrorToString(file_->error_details());
185 return false;
186 }
187
188 std::string header(kY4MHeaderMaxSize, '\0');
189 file_->Read(0, &header[0], header.size());
190 const size_t header_end = header.find(kY4MSimpleFrameDelimiter);
191 CHECK_NE(header_end, header.npos);
192
193 ParseY4MTags(header, capture_format);
194 first_frame_byte_index_ = header_end + kY4MSimpleFrameDelimiterSize;
195 current_byte_index_ = first_frame_byte_index_;
196 frame_size_ = capture_format->ImageAllocationSize();
197 return true;
198 }
199
GetNextFrame(int * frame_size)200 const uint8_t* Y4mFileParser::GetNextFrame(int* frame_size) {
201 if (!video_frame_)
202 video_frame_.reset(new uint8_t[frame_size_]);
203 int result =
204 file_->Read(current_byte_index_,
205 reinterpret_cast<char*>(video_frame_.get()), frame_size_);
206
207 // If we passed EOF to base::File, it will return 0 read characters. In that
208 // case, reset the pointer and read again.
209 if (result != frame_size_) {
210 CHECK_EQ(result, 0);
211 current_byte_index_ = first_frame_byte_index_;
212 CHECK_EQ(
213 file_->Read(current_byte_index_,
214 reinterpret_cast<char*>(video_frame_.get()), frame_size_),
215 frame_size_);
216 } else {
217 current_byte_index_ += frame_size_ + kY4MSimpleFrameDelimiterSize;
218 }
219 *frame_size = frame_size_;
220 return video_frame_.get();
221 }
222
MjpegFileParser(const base::FilePath & file_path)223 MjpegFileParser::MjpegFileParser(const base::FilePath& file_path)
224 : VideoFileParser(file_path) {}
225
226 MjpegFileParser::~MjpegFileParser() = default;
227
Initialize(VideoCaptureFormat * capture_format)228 bool MjpegFileParser::Initialize(VideoCaptureFormat* capture_format) {
229 mapped_file_.reset(new base::MemoryMappedFile());
230
231 if (!mapped_file_->Initialize(file_path_) || !mapped_file_->IsValid()) {
232 LOG(ERROR) << "File memory map error: " << file_path_.value();
233 return false;
234 }
235
236 JpegParseResult result;
237 if (!ParseJpegStream(mapped_file_->data(), mapped_file_->length(), &result))
238 return false;
239
240 frame_size_ = result.image_size;
241 if (frame_size_ > static_cast<int>(mapped_file_->length())) {
242 LOG(ERROR) << "File is incomplete";
243 return false;
244 }
245
246 VideoCaptureFormat format;
247 format.pixel_format = PIXEL_FORMAT_MJPEG;
248 format.frame_size.set_width(result.frame_header.visible_width);
249 format.frame_size.set_height(result.frame_header.visible_height);
250 format.frame_rate = kMJpegFrameRate;
251 if (!format.IsValid())
252 return false;
253 *capture_format = format;
254 return true;
255 }
256
GetNextFrame(int * frame_size)257 const uint8_t* MjpegFileParser::GetNextFrame(int* frame_size) {
258 const uint8_t* buf_ptr = mapped_file_->data() + current_byte_index_;
259
260 JpegParseResult result;
261 if (!ParseJpegStream(buf_ptr, mapped_file_->length() - current_byte_index_,
262 &result)) {
263 return nullptr;
264 }
265 *frame_size = frame_size_ = result.image_size;
266 current_byte_index_ += frame_size_;
267 // Reset the pointer to play repeatedly.
268 if (current_byte_index_ >= mapped_file_->length())
269 current_byte_index_ = first_frame_byte_index_;
270 return buf_ptr;
271 }
272
273 // static
GetVideoCaptureFormat(const base::FilePath & file_path,VideoCaptureFormat * video_format)274 bool FileVideoCaptureDevice::GetVideoCaptureFormat(
275 const base::FilePath& file_path,
276 VideoCaptureFormat* video_format) {
277 std::unique_ptr<VideoFileParser> file_parser =
278 GetVideoFileParser(file_path, video_format);
279 return file_parser != nullptr;
280 }
281
282 // static
GetVideoFileParser(const base::FilePath & file_path,VideoCaptureFormat * video_format)283 std::unique_ptr<VideoFileParser> FileVideoCaptureDevice::GetVideoFileParser(
284 const base::FilePath& file_path,
285 VideoCaptureFormat* video_format) {
286 std::unique_ptr<VideoFileParser> file_parser;
287 std::string file_name(file_path.value().begin(), file_path.value().end());
288
289 if (base::EndsWith(file_name, "y4m",
290 base::CompareCase::INSENSITIVE_ASCII)) {
291 file_parser.reset(new Y4mFileParser(file_path));
292 } else if (base::EndsWith(file_name, "mjpeg",
293 base::CompareCase::INSENSITIVE_ASCII)) {
294 file_parser.reset(new MjpegFileParser(file_path));
295 } else {
296 LOG(ERROR) << "Unsupported file format.";
297 return file_parser;
298 }
299
300 if (!file_parser->Initialize(video_format)) {
301 file_parser.reset();
302 }
303 return file_parser;
304 }
305
FileVideoCaptureDevice(const base::FilePath & file_path,std::unique_ptr<gpu::GpuMemoryBufferSupport> gmb_support)306 FileVideoCaptureDevice::FileVideoCaptureDevice(
307 const base::FilePath& file_path,
308 std::unique_ptr<gpu::GpuMemoryBufferSupport> gmb_support)
309 : capture_thread_("CaptureThread"),
310 file_path_(file_path),
311 gmb_support_(gmb_support
312 ? std::move(gmb_support)
313 : std::make_unique<gpu::GpuMemoryBufferSupport>()) {}
314
~FileVideoCaptureDevice()315 FileVideoCaptureDevice::~FileVideoCaptureDevice() {
316 DCHECK(thread_checker_.CalledOnValidThread());
317 // Check if the thread is running.
318 // This means that the device have not been DeAllocated properly.
319 CHECK(!capture_thread_.IsRunning());
320 }
321
AllocateAndStart(const VideoCaptureParams & params,std::unique_ptr<VideoCaptureDevice::Client> client)322 void FileVideoCaptureDevice::AllocateAndStart(
323 const VideoCaptureParams& params,
324 std::unique_ptr<VideoCaptureDevice::Client> client) {
325 DCHECK(thread_checker_.CalledOnValidThread());
326 CHECK(!capture_thread_.IsRunning());
327
328 capture_thread_.Start();
329 capture_thread_.task_runner()->PostTask(
330 FROM_HERE,
331 base::BindOnce(&FileVideoCaptureDevice::OnAllocateAndStart,
332 base::Unretained(this), params, std::move(client)));
333 }
334
StopAndDeAllocate()335 void FileVideoCaptureDevice::StopAndDeAllocate() {
336 DCHECK(thread_checker_.CalledOnValidThread());
337 CHECK(capture_thread_.IsRunning());
338
339 capture_thread_.task_runner()->PostTask(
340 FROM_HERE, base::BindOnce(&FileVideoCaptureDevice::OnStopAndDeAllocate,
341 base::Unretained(this)));
342 capture_thread_.Stop();
343 }
344
GetPhotoState(GetPhotoStateCallback callback)345 void FileVideoCaptureDevice::GetPhotoState(GetPhotoStateCallback callback) {
346 DCHECK(thread_checker_.CalledOnValidThread());
347
348 auto photo_capabilities = mojo::CreateEmptyPhotoState();
349
350 int height = capture_format_.frame_size.height();
351 photo_capabilities->height = mojom::Range::New(height, height, height, 0);
352 int width = capture_format_.frame_size.width();
353 photo_capabilities->width = mojom::Range::New(width, width, width, 0);
354
355 std::move(callback).Run(std::move(photo_capabilities));
356 }
357
SetPhotoOptions(mojom::PhotoSettingsPtr settings,SetPhotoOptionsCallback callback)358 void FileVideoCaptureDevice::SetPhotoOptions(mojom::PhotoSettingsPtr settings,
359 SetPhotoOptionsCallback callback) {
360 DCHECK(thread_checker_.CalledOnValidThread());
361
362 if (settings->has_height &&
363 settings->height != capture_format_.frame_size.height()) {
364 return;
365 }
366
367 if (settings->has_width &&
368 settings->width != capture_format_.frame_size.width()) {
369 return;
370 }
371
372 if (settings->has_torch && settings->torch)
373 return;
374
375 if (settings->has_red_eye_reduction && settings->red_eye_reduction)
376 return;
377
378 if (settings->has_exposure_compensation || settings->has_exposure_time ||
379 settings->has_color_temperature || settings->has_iso ||
380 settings->has_brightness || settings->has_contrast ||
381 settings->has_saturation || settings->has_sharpness ||
382 settings->has_focus_distance || settings->has_pan || settings->has_tilt ||
383 settings->has_zoom || settings->has_fill_light_mode) {
384 return;
385 }
386
387 std::move(callback).Run(true);
388 }
389
TakePhoto(TakePhotoCallback callback)390 void FileVideoCaptureDevice::TakePhoto(TakePhotoCallback callback) {
391 DCHECK(thread_checker_.CalledOnValidThread());
392 base::AutoLock lock(lock_);
393
394 take_photo_callbacks_.push(std::move(callback));
395 }
396
OnAllocateAndStart(const VideoCaptureParams & params,std::unique_ptr<VideoCaptureDevice::Client> client)397 void FileVideoCaptureDevice::OnAllocateAndStart(
398 const VideoCaptureParams& params,
399 std::unique_ptr<VideoCaptureDevice::Client> client) {
400 DCHECK(capture_thread_.task_runner()->BelongsToCurrentThread());
401
402 client_ = std::move(client);
403
404 if (params.buffer_type == VideoCaptureBufferType::kGpuMemoryBuffer)
405 video_capture_use_gmb_ = true;
406
407 DCHECK(!file_parser_);
408 file_parser_ = GetVideoFileParser(file_path_, &capture_format_);
409 if (!file_parser_) {
410 client_->OnError(
411 VideoCaptureError::kFileVideoCaptureDeviceCouldNotOpenVideoFile,
412 FROM_HERE, "Could not open Video file");
413 return;
414 }
415
416 DVLOG(1) << "Opened video file " << capture_format_.frame_size.ToString()
417 << ", fps: " << capture_format_.frame_rate;
418 client_->OnStarted();
419
420 capture_thread_.task_runner()->PostTask(
421 FROM_HERE, base::BindOnce(&FileVideoCaptureDevice::OnCaptureTask,
422 base::Unretained(this)));
423 }
424
OnStopAndDeAllocate()425 void FileVideoCaptureDevice::OnStopAndDeAllocate() {
426 DCHECK(capture_thread_.task_runner()->BelongsToCurrentThread());
427 file_parser_.reset();
428 client_.reset();
429 next_frame_time_ = base::TimeTicks();
430 }
431
OnCaptureTask()432 void FileVideoCaptureDevice::OnCaptureTask() {
433 DCHECK(capture_thread_.task_runner()->BelongsToCurrentThread());
434 if (!client_)
435 return;
436 base::AutoLock lock(lock_);
437
438 // Give the captured frame to the client.
439 int frame_size = 0;
440 const uint8_t* frame_ptr = file_parser_->GetNextFrame(&frame_size);
441 DCHECK(frame_size);
442 CHECK(frame_ptr);
443 const base::TimeTicks current_time = base::TimeTicks::Now();
444 if (first_ref_time_.is_null())
445 first_ref_time_ = current_time;
446
447 if (video_capture_use_gmb_) {
448 const gfx::Size& buffer_size = capture_format_.frame_size;
449 std::unique_ptr<gfx::GpuMemoryBuffer> gmb;
450 VideoCaptureDevice::Client::Buffer capture_buffer;
451 auto reserve_result = AllocateNV12GpuMemoryBuffer(
452 client_.get(), buffer_size, gmb_support_.get(), &gmb, &capture_buffer);
453 if (reserve_result !=
454 VideoCaptureDevice::Client::ReserveResult::kSucceeded) {
455 client_->OnFrameDropped(
456 ConvertReservationFailureToFrameDropReason(reserve_result));
457 DVLOG(2) << __func__ << " frame was dropped.";
458 return;
459 }
460 ScopedNV12GpuMemoryBufferMapping scoped_mapping(std::move(gmb));
461 const uint8_t* src_y_plane = frame_ptr;
462 const uint8_t* src_u_plane =
463 frame_ptr +
464 VideoFrame::PlaneSize(PIXEL_FORMAT_I420, 0, buffer_size).GetArea();
465 const uint8_t* src_v_plane =
466 frame_ptr +
467 VideoFrame::PlaneSize(PIXEL_FORMAT_I420, 0, buffer_size).GetArea() +
468 VideoFrame::PlaneSize(PIXEL_FORMAT_I420, 1, buffer_size).GetArea();
469 libyuv::I420ToNV12(
470 src_y_plane, buffer_size.width(), src_u_plane, buffer_size.width() / 2,
471 src_v_plane, buffer_size.width() / 2, scoped_mapping.y_plane(),
472 scoped_mapping.y_stride(), scoped_mapping.uv_plane(),
473 scoped_mapping.uv_stride(), buffer_size.width(), buffer_size.height());
474 VideoCaptureFormat modified_format = capture_format_;
475 // When GpuMemoryBuffer is used, the frame data is opaque to the CPU for
476 // most of the time. Currently the only supported underlying format is
477 // NV12.
478 modified_format.pixel_format = PIXEL_FORMAT_NV12;
479 client_->OnIncomingCapturedBuffer(std::move(capture_buffer),
480 modified_format, current_time,
481 current_time - first_ref_time_);
482 } else {
483 // Leave the color space unset for compatibility purposes but this
484 // information should be retrieved from the container when possible.
485 client_->OnIncomingCapturedData(
486 frame_ptr, frame_size, capture_format_, gfx::ColorSpace(),
487 0 /* clockwise_rotation */, false /* flip_y */, current_time,
488 current_time - first_ref_time_);
489 }
490
491 // Process waiting photo callbacks
492 while (!take_photo_callbacks_.empty()) {
493 auto cb = std::move(take_photo_callbacks_.front());
494 take_photo_callbacks_.pop();
495
496 mojom::BlobPtr blob =
497 RotateAndBlobify(frame_ptr, frame_size, capture_format_, 0);
498 if (!blob)
499 continue;
500
501 std::move(cb).Run(std::move(blob));
502 }
503
504 // Reschedule next CaptureTask.
505 const base::TimeDelta frame_interval =
506 base::TimeDelta::FromMicroseconds(1E6 / capture_format_.frame_rate);
507 if (next_frame_time_.is_null()) {
508 next_frame_time_ = current_time + frame_interval;
509 } else {
510 next_frame_time_ += frame_interval;
511 // Don't accumulate any debt if we are lagging behind - just post next frame
512 // immediately and continue as normal.
513 if (next_frame_time_ < current_time)
514 next_frame_time_ = current_time;
515 }
516 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
517 FROM_HERE,
518 base::BindOnce(&FileVideoCaptureDevice::OnCaptureTask,
519 base::Unretained(this)),
520 next_frame_time_ - current_time);
521 }
522
523 } // namespace media
524