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