1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
|
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/video/capture/file_video_capture_device.h"
#include <string>
#include "base/bind.h"
#include "base/command_line.h"
#include "base/memory/scoped_ptr.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_piece.h"
#include "base/strings/sys_string_conversions.h"
#include "media/base/media_switches.h"
namespace media {
static const char kFileVideoCaptureDeviceName[] =
"/dev/placeholder-for-file-backed-fake-capture-device";
static const int kY4MHeaderMaxSize = 200;
static const char kY4MSimpleFrameDelimiter[] = "FRAME";
static const int kY4MSimpleFrameDelimiterSize = 6;
int ParseY4MInt(const base::StringPiece& token) {
int temp_int;
CHECK(base::StringToInt(token, &temp_int));
return temp_int;
}
// Extract numerator and denominator out of a token that must have the aspect
// numerator:denominator, both integer numbers.
void ParseY4MRational(const base::StringPiece& token,
int* numerator,
int* denominator) {
size_t index_divider = token.find(':');
CHECK_NE(index_divider, token.npos);
*numerator = ParseY4MInt(token.substr(0, index_divider));
*denominator = ParseY4MInt(token.substr(index_divider + 1, token.length()));
CHECK(*denominator);
}
// This function parses the ASCII string in |header| as belonging to a Y4M file,
// returning the collected format in |video_format|. For a non authoritative
// explanation of the header format, check
// http://wiki.multimedia.cx/index.php?title=YUV4MPEG2
// Restrictions: Only interlaced I420 pixel format is supported, and pixel
// aspect ratio is ignored.
// Implementation notes: Y4M header should end with an ASCII 0x20 (whitespace)
// character, however all examples mentioned in the Y4M header description end
// with a newline character instead. Also, some headers do _not_ specify pixel
// format, in this case it means I420.
// This code was inspired by third_party/libvpx/.../y4minput.* .
void ParseY4MTags(const std::string& file_header,
media::VideoCaptureFormat* video_format) {
video_format->pixel_format = media::PIXEL_FORMAT_I420;
video_format->frame_size.set_width(0);
video_format->frame_size.set_height(0);
size_t index = 0;
size_t blank_position = 0;
base::StringPiece token;
while ((blank_position = file_header.find_first_of("\n ", index)) !=
std::string::npos) {
// Every token is supposed to have an identifier letter and a bunch of
// information immediately after, which we extract into a |token| here.
token =
base::StringPiece(&file_header[index + 1], blank_position - index - 1);
CHECK(!token.empty());
switch (file_header[index]) {
case 'W':
video_format->frame_size.set_width(ParseY4MInt(token));
break;
case 'H':
video_format->frame_size.set_height(ParseY4MInt(token));
break;
case 'F': {
// If the token is "FRAME", it means we have finished with the header.
if (token[0] == 'R')
break;
int fps_numerator, fps_denominator;
ParseY4MRational(token, &fps_numerator, &fps_denominator);
video_format->frame_rate = fps_numerator / fps_denominator;
break;
}
case 'I':
// Interlacing is ignored, but we don't like mixed modes.
CHECK_NE(token[0], 'm');
break;
case 'A':
// Pixel aspect ratio ignored.
break;
case 'C':
CHECK_EQ(ParseY4MInt(token), 420); // Only I420 supported.
break;
default:
break;
}
// We're done if we have found a newline character right after the token.
if (file_header[blank_position] == '\n')
break;
index = blank_position + 1;
}
// Last video format semantic correctness check before sending it back.
CHECK(video_format->IsValid());
}
// Reads and parses the header of a Y4M |file|, returning the collected pixel
// format in |video_format|. Returns the index of the first byte of the first
// video frame.
// Restrictions: Only trivial per-frame headers are supported.
int64 ParseFileAndExtractVideoFormat(
const base::PlatformFile& file,
media::VideoCaptureFormat* video_format) {
std::string header(kY4MHeaderMaxSize, 0);
base::ReadPlatformFile(file, 0, &header[0], kY4MHeaderMaxSize - 1);
size_t header_end = header.find(kY4MSimpleFrameDelimiter);
CHECK_NE(header_end, header.npos);
ParseY4MTags(header, video_format);
return header_end + kY4MSimpleFrameDelimiterSize;
}
// Opens a given file for reading, and returns the file to the caller, who is
// responsible for closing it.
base::PlatformFile OpenFileForRead(const base::FilePath& file_path) {
base::PlatformFileError file_error;
base::PlatformFile file = base::CreatePlatformFile(
file_path,
base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ,
NULL,
&file_error);
CHECK_EQ(file_error, base::PLATFORM_FILE_OK);
return file;
}
// Inspects the command line and retrieves the file path parameter.
base::FilePath GetFilePathFromCommandLine() {
base::FilePath command_line_file_path =
CommandLine::ForCurrentProcess()->GetSwitchValuePath(
switches::kUseFileForFakeVideoCapture);
CHECK(!command_line_file_path.empty());
return command_line_file_path;
}
void FileVideoCaptureDevice::GetDeviceNames(Names* const device_names) {
DCHECK(device_names->empty());
base::FilePath command_line_file_path = GetFilePathFromCommandLine();
#if defined(OS_WIN)
device_names->push_back(
Name(base::SysWideToUTF8(command_line_file_path.value()),
kFileVideoCaptureDeviceName));
#else
device_names->push_back(Name(command_line_file_path.value(),
kFileVideoCaptureDeviceName));
#endif // OS_WIN
}
void FileVideoCaptureDevice::GetDeviceSupportedFormats(
const Name& device,
VideoCaptureCapabilities* formats) {
base::PlatformFile file = OpenFileForRead(GetFilePathFromCommandLine());
VideoCaptureCapability capture_capability;
ParseFileAndExtractVideoFormat(file, &capture_capability.supported_format);
formats->push_back(capture_capability);
CHECK(base::ClosePlatformFile(file));
}
VideoCaptureDevice* FileVideoCaptureDevice::Create(const Name& device_name) {
#if defined(OS_WIN)
return new FileVideoCaptureDevice(
base::FilePath(base::SysUTF8ToWide(device_name.name())));
#else
return new FileVideoCaptureDevice(base::FilePath(device_name.name()));
#endif // OS_WIN
}
FileVideoCaptureDevice::FileVideoCaptureDevice(const base::FilePath& file_path)
: capture_thread_("CaptureThread"),
file_path_(file_path),
file_(base::kInvalidPlatformFileValue),
frame_size_(0),
current_byte_index_(0),
first_frame_byte_index_(0) {}
FileVideoCaptureDevice::~FileVideoCaptureDevice() {
DCHECK(thread_checker_.CalledOnValidThread());
// Check if the thread is running.
// This means that the device have not been DeAllocated properly.
CHECK(!capture_thread_.IsRunning());
}
void FileVideoCaptureDevice::AllocateAndStart(
const VideoCaptureParams& params,
scoped_ptr<VideoCaptureDevice::Client> client) {
DCHECK(thread_checker_.CalledOnValidThread());
CHECK(!capture_thread_.IsRunning());
capture_thread_.Start();
capture_thread_.message_loop()->PostTask(
FROM_HERE,
base::Bind(&FileVideoCaptureDevice::OnAllocateAndStart,
base::Unretained(this),
params,
base::Passed(&client)));
}
void FileVideoCaptureDevice::StopAndDeAllocate() {
DCHECK(thread_checker_.CalledOnValidThread());
CHECK(capture_thread_.IsRunning());
capture_thread_.message_loop()->PostTask(
FROM_HERE,
base::Bind(&FileVideoCaptureDevice::OnStopAndDeAllocate,
base::Unretained(this)));
capture_thread_.Stop();
}
int FileVideoCaptureDevice::CalculateFrameSize() {
DCHECK_EQ(capture_format_.pixel_format, PIXEL_FORMAT_I420);
DCHECK_EQ(capture_thread_.message_loop(), base::MessageLoop::current());
return capture_format_.frame_size.GetArea() * 12 / 8;
}
void FileVideoCaptureDevice::OnAllocateAndStart(
const VideoCaptureParams& params,
scoped_ptr<VideoCaptureDevice::Client> client) {
DCHECK_EQ(capture_thread_.message_loop(), base::MessageLoop::current());
client_ = client.Pass();
// Open the file and parse the header. Get frame size and format.
DCHECK_EQ(file_, base::kInvalidPlatformFileValue);
file_ = OpenFileForRead(file_path_);
first_frame_byte_index_ =
ParseFileAndExtractVideoFormat(file_, &capture_format_);
current_byte_index_ = first_frame_byte_index_;
DVLOG(1) << "Opened video file " << capture_format_.frame_size.ToString()
<< ", fps: " << capture_format_.frame_rate;
frame_size_ = CalculateFrameSize();
video_frame_.reset(new uint8[frame_size_]);
capture_thread_.message_loop()->PostTask(
FROM_HERE,
base::Bind(&FileVideoCaptureDevice::OnCaptureTask,
base::Unretained(this)));
}
void FileVideoCaptureDevice::OnStopAndDeAllocate() {
DCHECK_EQ(capture_thread_.message_loop(), base::MessageLoop::current());
CHECK(base::ClosePlatformFile(file_));
client_.reset();
current_byte_index_ = 0;
first_frame_byte_index_ = 0;
frame_size_ = 0;
video_frame_.reset();
}
void FileVideoCaptureDevice::OnCaptureTask() {
DCHECK_EQ(capture_thread_.message_loop(), base::MessageLoop::current());
if (!client_)
return;
int result =
base::ReadPlatformFile(file_,
current_byte_index_,
reinterpret_cast<char*>(video_frame_.get()),
frame_size_);
// If we passed EOF to PlatformFile, it will return 0 read characters. In that
// case, reset the pointer and read again.
if (result != frame_size_) {
CHECK_EQ(result, 0);
current_byte_index_ = first_frame_byte_index_;
CHECK_EQ(base::ReadPlatformFile(file_,
current_byte_index_,
reinterpret_cast<char*>(video_frame_.get()),
frame_size_),
frame_size_);
} else {
current_byte_index_ += frame_size_ + kY4MSimpleFrameDelimiterSize;
}
// Give the captured frame to the client.
client_->OnIncomingCapturedFrame(video_frame_.get(),
frame_size_,
base::Time::Now(),
0,
capture_format_);
// Reschedule next CaptureTask.
base::MessageLoop::current()->PostDelayedTask(
FROM_HERE,
base::Bind(&FileVideoCaptureDevice::OnCaptureTask,
base::Unretained(this)),
base::TimeDelta::FromSeconds(1) / capture_format_.frame_rate);
}
} // namespace media
|