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/video/capture/file_video_capture_device.h"
6
7#include <string>
8
9#include "base/bind.h"
10#include "base/memory/scoped_ptr.h"
11#include "base/strings/string_number_conversions.h"
12#include "base/strings/string_piece.h"
13
14namespace media {
15static const int kY4MHeaderMaxSize = 200;
16static const char kY4MSimpleFrameDelimiter[] = "FRAME";
17static const int kY4MSimpleFrameDelimiterSize = 6;
18
19int ParseY4MInt(const base::StringPiece& token) {
20  int temp_int;
21  CHECK(base::StringToInt(token, &temp_int)) << token;
22  return temp_int;
23}
24
25// Extract numerator and denominator out of a token that must have the aspect
26// numerator:denominator, both integer numbers.
27void ParseY4MRational(const base::StringPiece& token,
28                      int* numerator,
29                      int* denominator) {
30  size_t index_divider = token.find(':');
31  CHECK_NE(index_divider, token.npos);
32  *numerator = ParseY4MInt(token.substr(0, index_divider));
33  *denominator = ParseY4MInt(token.substr(index_divider + 1, token.length()));
34  CHECK(*denominator);
35}
36
37// This function parses the ASCII string in |header| as belonging to a Y4M file,
38// returning the collected format in |video_format|. For a non authoritative
39// explanation of the header format, check
40// http://wiki.multimedia.cx/index.php?title=YUV4MPEG2
41// Restrictions: Only interlaced I420 pixel format is supported, and pixel
42// aspect ratio is ignored.
43// Implementation notes: Y4M header should end with an ASCII 0x20 (whitespace)
44// character, however all examples mentioned in the Y4M header description end
45// with a newline character instead. Also, some headers do _not_ specify pixel
46// format, in this case it means I420.
47// This code was inspired by third_party/libvpx/.../y4minput.* .
48void ParseY4MTags(const std::string& file_header,
49                  media::VideoCaptureFormat* video_format) {
50  video_format->pixel_format = media::PIXEL_FORMAT_I420;
51  video_format->frame_size.set_width(0);
52  video_format->frame_size.set_height(0);
53  size_t index = 0;
54  size_t blank_position = 0;
55  base::StringPiece token;
56  while ((blank_position = file_header.find_first_of("\n ", index)) !=
57         std::string::npos) {
58    // Every token is supposed to have an identifier letter and a bunch of
59    // information immediately after, which we extract into a |token| here.
60    token =
61        base::StringPiece(&file_header[index + 1], blank_position - index - 1);
62    CHECK(!token.empty());
63    switch (file_header[index]) {
64      case 'W':
65        video_format->frame_size.set_width(ParseY4MInt(token));
66        break;
67      case 'H':
68        video_format->frame_size.set_height(ParseY4MInt(token));
69        break;
70      case 'F': {
71        // If the token is "FRAME", it means we have finished with the header.
72        if (token[0] == 'R')
73          break;
74        int fps_numerator, fps_denominator;
75        ParseY4MRational(token, &fps_numerator, &fps_denominator);
76        video_format->frame_rate = fps_numerator / fps_denominator;
77        break;
78      }
79      case 'I':
80        // Interlacing is ignored, but we don't like mixed modes.
81        CHECK_NE(token[0], 'm');
82        break;
83      case 'A':
84        // Pixel aspect ratio ignored.
85        break;
86      case 'C':
87        CHECK(token == "420" || token == "420jpeg" || token == "420paldv")
88            << token;  // Only I420 is supported, and we fudge the variants.
89        break;
90      default:
91        break;
92    }
93    // We're done if we have found a newline character right after the token.
94    if (file_header[blank_position] == '\n')
95      break;
96    index = blank_position + 1;
97  }
98  // Last video format semantic correctness check before sending it back.
99  CHECK(video_format->IsValid());
100}
101
102// Reads and parses the header of a Y4M |file|, returning the collected pixel
103// format in |video_format|. Returns the index of the first byte of the first
104// video frame.
105// Restrictions: Only trivial per-frame headers are supported.
106// static
107int64 FileVideoCaptureDevice::ParseFileAndExtractVideoFormat(
108    base::File* file,
109    media::VideoCaptureFormat* video_format) {
110  std::string header(kY4MHeaderMaxSize, 0);
111  file->Read(0, &header[0], kY4MHeaderMaxSize - 1);
112
113  size_t header_end = header.find(kY4MSimpleFrameDelimiter);
114  CHECK_NE(header_end, header.npos);
115
116  ParseY4MTags(header, video_format);
117  return header_end + kY4MSimpleFrameDelimiterSize;
118}
119
120// Opens a given file for reading, and returns the file to the caller, who is
121// responsible for closing it.
122// static
123base::File FileVideoCaptureDevice::OpenFileForRead(
124    const base::FilePath& file_path) {
125  base::File file(file_path, base::File::FLAG_OPEN | base::File::FLAG_READ);
126  CHECK(file.IsValid()) << file_path.value();
127  return file.Pass();
128}
129
130FileVideoCaptureDevice::FileVideoCaptureDevice(const base::FilePath& file_path)
131    : capture_thread_("CaptureThread"),
132      file_path_(file_path),
133      frame_size_(0),
134      current_byte_index_(0),
135      first_frame_byte_index_(0) {}
136
137FileVideoCaptureDevice::~FileVideoCaptureDevice() {
138  DCHECK(thread_checker_.CalledOnValidThread());
139  // Check if the thread is running.
140  // This means that the device have not been DeAllocated properly.
141  CHECK(!capture_thread_.IsRunning());
142}
143
144void FileVideoCaptureDevice::AllocateAndStart(
145    const VideoCaptureParams& params,
146    scoped_ptr<VideoCaptureDevice::Client> client) {
147  DCHECK(thread_checker_.CalledOnValidThread());
148  CHECK(!capture_thread_.IsRunning());
149
150  capture_thread_.Start();
151  capture_thread_.message_loop()->PostTask(
152      FROM_HERE,
153      base::Bind(&FileVideoCaptureDevice::OnAllocateAndStart,
154                 base::Unretained(this),
155                 params,
156                 base::Passed(&client)));
157}
158
159void FileVideoCaptureDevice::StopAndDeAllocate() {
160  DCHECK(thread_checker_.CalledOnValidThread());
161  CHECK(capture_thread_.IsRunning());
162
163  capture_thread_.message_loop()->PostTask(
164      FROM_HERE,
165      base::Bind(&FileVideoCaptureDevice::OnStopAndDeAllocate,
166                 base::Unretained(this)));
167  capture_thread_.Stop();
168}
169
170int FileVideoCaptureDevice::CalculateFrameSize() {
171  DCHECK_EQ(capture_format_.pixel_format, PIXEL_FORMAT_I420);
172  DCHECK_EQ(capture_thread_.message_loop(), base::MessageLoop::current());
173  return capture_format_.frame_size.GetArea() * 12 / 8;
174}
175
176void FileVideoCaptureDevice::OnAllocateAndStart(
177    const VideoCaptureParams& params,
178    scoped_ptr<VideoCaptureDevice::Client> client) {
179  DCHECK_EQ(capture_thread_.message_loop(), base::MessageLoop::current());
180
181  client_ = client.Pass();
182
183  // Open the file and parse the header. Get frame size and format.
184  DCHECK(!file_.IsValid());
185  file_ = OpenFileForRead(file_path_);
186  first_frame_byte_index_ =
187      ParseFileAndExtractVideoFormat(&file_, &capture_format_);
188  current_byte_index_ = first_frame_byte_index_;
189  DVLOG(1) << "Opened video file " << capture_format_.frame_size.ToString()
190           << ", fps: " << capture_format_.frame_rate;
191
192  frame_size_ = CalculateFrameSize();
193  video_frame_.reset(new uint8[frame_size_]);
194
195  capture_thread_.message_loop()->PostTask(
196      FROM_HERE,
197      base::Bind(&FileVideoCaptureDevice::OnCaptureTask,
198                 base::Unretained(this)));
199}
200
201void FileVideoCaptureDevice::OnStopAndDeAllocate() {
202  DCHECK_EQ(capture_thread_.message_loop(), base::MessageLoop::current());
203  file_.Close();
204  client_.reset();
205  current_byte_index_ = 0;
206  first_frame_byte_index_ = 0;
207  frame_size_ = 0;
208  video_frame_.reset();
209}
210
211void FileVideoCaptureDevice::OnCaptureTask() {
212  DCHECK_EQ(capture_thread_.message_loop(), base::MessageLoop::current());
213  if (!client_)
214    return;
215  int result = file_.Read(current_byte_index_,
216                          reinterpret_cast<char*>(video_frame_.get()),
217                          frame_size_);
218
219  // If we passed EOF to base::File, it will return 0 read characters. In that
220  // case, reset the pointer and read again.
221  if (result != frame_size_) {
222    CHECK_EQ(result, 0);
223    current_byte_index_ = first_frame_byte_index_;
224    CHECK_EQ(file_.Read(current_byte_index_,
225                        reinterpret_cast<char*>(video_frame_.get()),
226                        frame_size_),
227             frame_size_);
228  } else {
229    current_byte_index_ += frame_size_ + kY4MSimpleFrameDelimiterSize;
230  }
231
232  // Give the captured frame to the client.
233  client_->OnIncomingCapturedData(video_frame_.get(),
234                                  frame_size_,
235                                  capture_format_,
236                                  0,
237                                  base::TimeTicks::Now());
238  // Reschedule next CaptureTask.
239  base::MessageLoop::current()->PostDelayedTask(
240      FROM_HERE,
241      base::Bind(&FileVideoCaptureDevice::OnCaptureTask,
242                 base::Unretained(this)),
243      base::TimeDelta::FromSeconds(1) / capture_format_.frame_rate);
244}
245
246}  // namespace media
247