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.
5 #include "media/video/capture/file_video_capture_device.h"
10 #include "base/command_line.h"
11 #include "base/memory/scoped_ptr.h"
12 #include "base/strings/string_number_conversions.h"
13 #include "base/strings/string_piece.h"
14 #include "base/strings/sys_string_conversions.h"
15 #include "media/base/media_switches.h"
19 static const char kFileVideoCaptureDeviceName[] =
20 "/dev/placeholder-for-file-backed-fake-capture-device";
22 static const int kY4MHeaderMaxSize = 200;
23 static const char kY4MSimpleFrameDelimiter[] = "FRAME";
24 static const int kY4MSimpleFrameDelimiterSize = 6;
26 int ParseY4MInt(const base::StringPiece& token) {
28 CHECK(base::StringToInt(token, &temp_int)) << token;
32 // Extract numerator and denominator out of a token that must have the aspect
33 // numerator:denominator, both integer numbers.
34 void ParseY4MRational(const base::StringPiece& token,
37 size_t index_divider = token.find(':');
38 CHECK_NE(index_divider, token.npos);
39 *numerator = ParseY4MInt(token.substr(0, index_divider));
40 *denominator = ParseY4MInt(token.substr(index_divider + 1, token.length()));
44 // This function parses the ASCII string in |header| as belonging to a Y4M file,
45 // returning the collected format in |video_format|. For a non authoritative
46 // explanation of the header format, check
47 // http://wiki.multimedia.cx/index.php?title=YUV4MPEG2
48 // Restrictions: Only interlaced I420 pixel format is supported, and pixel
49 // aspect ratio is ignored.
50 // Implementation notes: Y4M header should end with an ASCII 0x20 (whitespace)
51 // character, however all examples mentioned in the Y4M header description end
52 // with a newline character instead. Also, some headers do _not_ specify pixel
53 // format, in this case it means I420.
54 // This code was inspired by third_party/libvpx/.../y4minput.* .
55 void ParseY4MTags(const std::string& file_header,
56 media::VideoCaptureFormat* video_format) {
57 video_format->pixel_format = media::PIXEL_FORMAT_I420;
58 video_format->frame_size.set_width(0);
59 video_format->frame_size.set_height(0);
61 size_t blank_position = 0;
62 base::StringPiece token;
63 while ((blank_position = file_header.find_first_of("\n ", index)) !=
65 // Every token is supposed to have an identifier letter and a bunch of
66 // information immediately after, which we extract into a |token| here.
68 base::StringPiece(&file_header[index + 1], blank_position - index - 1);
69 CHECK(!token.empty());
70 switch (file_header[index]) {
72 video_format->frame_size.set_width(ParseY4MInt(token));
75 video_format->frame_size.set_height(ParseY4MInt(token));
78 // If the token is "FRAME", it means we have finished with the header.
81 int fps_numerator, fps_denominator;
82 ParseY4MRational(token, &fps_numerator, &fps_denominator);
83 video_format->frame_rate = fps_numerator / fps_denominator;
87 // Interlacing is ignored, but we don't like mixed modes.
88 CHECK_NE(token[0], 'm');
91 // Pixel aspect ratio ignored.
94 CHECK(token == "420" || token == "420jpeg" || token == "420paldv")
95 << token; // Only I420 is supported, and we fudge the variants.
100 // We're done if we have found a newline character right after the token.
101 if (file_header[blank_position] == '\n')
103 index = blank_position + 1;
105 // Last video format semantic correctness check before sending it back.
106 CHECK(video_format->IsValid());
109 // Reads and parses the header of a Y4M |file|, returning the collected pixel
110 // format in |video_format|. Returns the index of the first byte of the first
112 // Restrictions: Only trivial per-frame headers are supported.
113 int64 ParseFileAndExtractVideoFormat(
115 media::VideoCaptureFormat* video_format) {
116 std::string header(kY4MHeaderMaxSize, 0);
117 file->Read(0, &header[0], kY4MHeaderMaxSize - 1);
119 size_t header_end = header.find(kY4MSimpleFrameDelimiter);
120 CHECK_NE(header_end, header.npos);
122 ParseY4MTags(header, video_format);
123 return header_end + kY4MSimpleFrameDelimiterSize;
126 // Opens a given file for reading, and returns the file to the caller, who is
127 // responsible for closing it.
128 base::File OpenFileForRead(const base::FilePath& file_path) {
129 base::File file(file_path, base::File::FLAG_OPEN | base::File::FLAG_READ);
130 CHECK(file.IsValid()) << file_path.value();
134 // Inspects the command line and retrieves the file path parameter.
135 base::FilePath GetFilePathFromCommandLine() {
136 base::FilePath command_line_file_path =
137 CommandLine::ForCurrentProcess()->GetSwitchValuePath(
138 switches::kUseFileForFakeVideoCapture);
139 CHECK(!command_line_file_path.empty());
140 return command_line_file_path;
143 void FileVideoCaptureDevice::GetDeviceNames(Names* const device_names) {
144 DCHECK(device_names->empty());
145 base::FilePath command_line_file_path = GetFilePathFromCommandLine();
147 device_names->push_back(
148 Name(base::SysWideToUTF8(command_line_file_path.value()),
149 kFileVideoCaptureDeviceName));
151 device_names->push_back(Name(command_line_file_path.value(),
152 kFileVideoCaptureDeviceName));
156 void FileVideoCaptureDevice::GetDeviceSupportedFormats(
158 VideoCaptureFormats* supported_formats) {
159 base::File file = OpenFileForRead(GetFilePathFromCommandLine());
160 VideoCaptureFormat capture_format;
161 ParseFileAndExtractVideoFormat(&file, &capture_format);
162 supported_formats->push_back(capture_format);
165 VideoCaptureDevice* FileVideoCaptureDevice::Create(const Name& device_name) {
167 return new FileVideoCaptureDevice(
168 base::FilePath(base::SysUTF8ToWide(device_name.name())));
170 return new FileVideoCaptureDevice(base::FilePath(device_name.name()));
174 FileVideoCaptureDevice::FileVideoCaptureDevice(const base::FilePath& file_path)
175 : capture_thread_("CaptureThread"),
176 file_path_(file_path),
178 current_byte_index_(0),
179 first_frame_byte_index_(0) {}
181 FileVideoCaptureDevice::~FileVideoCaptureDevice() {
182 DCHECK(thread_checker_.CalledOnValidThread());
183 // Check if the thread is running.
184 // This means that the device have not been DeAllocated properly.
185 CHECK(!capture_thread_.IsRunning());
188 void FileVideoCaptureDevice::AllocateAndStart(
189 const VideoCaptureParams& params,
190 scoped_ptr<VideoCaptureDevice::Client> client) {
191 DCHECK(thread_checker_.CalledOnValidThread());
192 CHECK(!capture_thread_.IsRunning());
194 capture_thread_.Start();
195 capture_thread_.message_loop()->PostTask(
197 base::Bind(&FileVideoCaptureDevice::OnAllocateAndStart,
198 base::Unretained(this),
200 base::Passed(&client)));
203 void FileVideoCaptureDevice::StopAndDeAllocate() {
204 DCHECK(thread_checker_.CalledOnValidThread());
205 CHECK(capture_thread_.IsRunning());
207 capture_thread_.message_loop()->PostTask(
209 base::Bind(&FileVideoCaptureDevice::OnStopAndDeAllocate,
210 base::Unretained(this)));
211 capture_thread_.Stop();
214 int FileVideoCaptureDevice::CalculateFrameSize() {
215 DCHECK_EQ(capture_format_.pixel_format, PIXEL_FORMAT_I420);
216 DCHECK_EQ(capture_thread_.message_loop(), base::MessageLoop::current());
217 return capture_format_.frame_size.GetArea() * 12 / 8;
220 void FileVideoCaptureDevice::OnAllocateAndStart(
221 const VideoCaptureParams& params,
222 scoped_ptr<VideoCaptureDevice::Client> client) {
223 DCHECK_EQ(capture_thread_.message_loop(), base::MessageLoop::current());
225 client_ = client.Pass();
227 // Open the file and parse the header. Get frame size and format.
228 DCHECK(!file_.IsValid());
229 file_ = OpenFileForRead(file_path_);
230 first_frame_byte_index_ =
231 ParseFileAndExtractVideoFormat(&file_, &capture_format_);
232 current_byte_index_ = first_frame_byte_index_;
233 DVLOG(1) << "Opened video file " << capture_format_.frame_size.ToString()
234 << ", fps: " << capture_format_.frame_rate;
236 frame_size_ = CalculateFrameSize();
237 video_frame_.reset(new uint8[frame_size_]);
239 capture_thread_.message_loop()->PostTask(
241 base::Bind(&FileVideoCaptureDevice::OnCaptureTask,
242 base::Unretained(this)));
245 void FileVideoCaptureDevice::OnStopAndDeAllocate() {
246 DCHECK_EQ(capture_thread_.message_loop(), base::MessageLoop::current());
249 current_byte_index_ = 0;
250 first_frame_byte_index_ = 0;
252 video_frame_.reset();
255 void FileVideoCaptureDevice::OnCaptureTask() {
256 DCHECK_EQ(capture_thread_.message_loop(), base::MessageLoop::current());
259 int result = file_.Read(current_byte_index_,
260 reinterpret_cast<char*>(video_frame_.get()),
263 // If we passed EOF to base::File, it will return 0 read characters. In that
264 // case, reset the pointer and read again.
265 if (result != frame_size_) {
267 current_byte_index_ = first_frame_byte_index_;
268 CHECK_EQ(file_.Read(current_byte_index_,
269 reinterpret_cast<char*>(video_frame_.get()),
273 current_byte_index_ += frame_size_ + kY4MSimpleFrameDelimiterSize;
276 // Give the captured frame to the client.
277 client_->OnIncomingCapturedData(video_frame_.get(),
281 base::TimeTicks::Now());
282 // Reschedule next CaptureTask.
283 base::MessageLoop::current()->PostDelayedTask(
285 base::Bind(&FileVideoCaptureDevice::OnCaptureTask,
286 base::Unretained(this)),
287 base::TimeDelta::FromSeconds(1) / capture_format_.frame_rate);