Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(14)

Side by Side Diff: media/capture/video/file_video_capture_device.cc

Issue 2143903003: [WIP] Move media/capture to device/capture (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 4 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
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/macros.h"
13 #include "base/single_thread_task_runner.h"
14 #include "base/strings/string_number_conversions.h"
15 #include "base/strings/string_piece.h"
16 #include "base/threading/thread_task_runner_handle.h"
17 #include "media/base/video_capture_types.h"
18 #include "media/filters/jpeg_parser.h"
19
20 namespace media {
21
22 static const int kY4MHeaderMaxSize = 200;
23 static const char kY4MSimpleFrameDelimiter[] = "FRAME";
24 static const int kY4MSimpleFrameDelimiterSize = 6;
25 static const float kMJpegFrameRate = 30.0f;
26
27 int ParseY4MInt(const base::StringPiece& token) {
28 int temp_int;
29 CHECK(base::StringToInt(token, &temp_int)) << token;
30 return temp_int;
31 }
32
33 // Extract numerator and denominator out of a token that must have the aspect
34 // numerator:denominator, both integer numbers.
35 void ParseY4MRational(const base::StringPiece& token,
36 int* numerator, int* denominator) {
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()));
41 CHECK(*denominator);
42 }
43
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 media::VideoCaptureFormat format;
58 format.pixel_format = media::PIXEL_FORMAT_I420;
59 size_t index = 0;
60 size_t blank_position = 0;
61 base::StringPiece token;
62 while ((blank_position = file_header.find_first_of("\n ", index)) !=
63 std::string::npos) {
64 // Every token is supposed to have an identifier letter and a bunch of
65 // information immediately after, which we extract into a |token| here.
66 token =
67 base::StringPiece(&file_header[index + 1], blank_position - index - 1);
68 CHECK(!token.empty());
69 switch (file_header[index]) {
70 case 'W':
71 format.frame_size.set_width(ParseY4MInt(token));
72 break;
73 case 'H':
74 format.frame_size.set_height(ParseY4MInt(token));
75 break;
76 case 'F': {
77 // If the token is "FRAME", it means we have finished with the header.
78 if (token[0] == 'R')
79 break;
80 int fps_numerator, fps_denominator;
81 ParseY4MRational(token, &fps_numerator, &fps_denominator);
82 format.frame_rate = fps_numerator / fps_denominator;
83 break;
84 }
85 case 'I':
86 // Interlacing is ignored, but we don't like mixed modes.
87 CHECK_NE(token[0], 'm');
88 break;
89 case 'A':
90 // Pixel aspect ratio ignored.
91 break;
92 case 'C':
93 CHECK(token == "420" || token == "420jpeg" || token == "420paldv")
94 << token; // Only I420 is supported, and we fudge the variants.
95 break;
96 default:
97 break;
98 }
99 // We're done if we have found a newline character right after the token.
100 if (file_header[blank_position] == '\n')
101 break;
102 index = blank_position + 1;
103 }
104 // Last video format semantic correctness check before sending it back.
105 CHECK(format.IsValid());
106 *video_format = format;
107 }
108
109 class VideoFileParser {
110 public:
111 explicit VideoFileParser(const base::FilePath& file_path);
112 virtual ~VideoFileParser();
113
114 // Parses file header and collects format information in |capture_format|.
115 virtual bool Initialize(media::VideoCaptureFormat* capture_format) = 0;
116
117 // Gets the start pointer of next frame and stores current frame size in
118 // |frame_size|.
119 virtual const uint8_t* GetNextFrame(int* frame_size) = 0;
120
121 protected:
122 const base::FilePath file_path_;
123 int frame_size_;
124 size_t current_byte_index_;
125 size_t first_frame_byte_index_;
126 };
127
128 class Y4mFileParser final : public VideoFileParser {
129 public:
130 explicit Y4mFileParser(const base::FilePath& file_path);
131
132 // VideoFileParser implementation, class methods.
133 ~Y4mFileParser() override;
134 bool Initialize(media::VideoCaptureFormat* capture_format) override;
135 const uint8_t* GetNextFrame(int* frame_size) override;
136
137 private:
138 std::unique_ptr<base::File> file_;
139 std::unique_ptr<uint8_t[]> video_frame_;
140
141 DISALLOW_COPY_AND_ASSIGN(Y4mFileParser);
142 };
143
144 class MjpegFileParser final : public VideoFileParser {
145 public:
146 explicit MjpegFileParser(const base::FilePath& file_path);
147
148 // VideoFileParser implementation, class methods.
149 ~MjpegFileParser() override;
150 bool Initialize(media::VideoCaptureFormat* capture_format) override;
151 const uint8_t* GetNextFrame(int* frame_size) override;
152
153 private:
154 std::unique_ptr<base::MemoryMappedFile> mapped_file_;
155
156 DISALLOW_COPY_AND_ASSIGN(MjpegFileParser);
157 };
158
159 VideoFileParser::VideoFileParser(const base::FilePath& file_path)
160 : file_path_(file_path),
161 frame_size_(0),
162 current_byte_index_(0),
163 first_frame_byte_index_(0) {}
164
165 VideoFileParser::~VideoFileParser() {}
166
167 Y4mFileParser::Y4mFileParser(const base::FilePath& file_path)
168 : VideoFileParser(file_path) {}
169
170 Y4mFileParser::~Y4mFileParser() {}
171
172 bool Y4mFileParser::Initialize(media::VideoCaptureFormat* capture_format) {
173 file_.reset(new base::File(file_path_,
174 base::File::FLAG_OPEN | base::File::FLAG_READ));
175 if (!file_->IsValid()) {
176 DLOG(ERROR) << file_path_.value() << ", error: "
177 << base::File::ErrorToString(file_->error_details());
178 return false;
179 }
180
181 std::string header(kY4MHeaderMaxSize, '\0');
182 file_->Read(0, &header[0], header.size());
183 const size_t header_end = header.find(kY4MSimpleFrameDelimiter);
184 CHECK_NE(header_end, header.npos);
185
186 ParseY4MTags(header, capture_format);
187 first_frame_byte_index_ = header_end + kY4MSimpleFrameDelimiterSize;
188 current_byte_index_ = first_frame_byte_index_;
189 frame_size_ = capture_format->ImageAllocationSize();
190 return true;
191 }
192
193 const uint8_t* Y4mFileParser::GetNextFrame(int* frame_size) {
194 if (!video_frame_)
195 video_frame_.reset(new uint8_t[frame_size_]);
196 int result =
197 file_->Read(current_byte_index_,
198 reinterpret_cast<char*>(video_frame_.get()), frame_size_);
199
200 // If we passed EOF to base::File, it will return 0 read characters. In that
201 // case, reset the pointer and read again.
202 if (result != frame_size_) {
203 CHECK_EQ(result, 0);
204 current_byte_index_ = first_frame_byte_index_;
205 CHECK_EQ(
206 file_->Read(current_byte_index_,
207 reinterpret_cast<char*>(video_frame_.get()), frame_size_),
208 frame_size_);
209 } else {
210 current_byte_index_ += frame_size_ + kY4MSimpleFrameDelimiterSize;
211 }
212 *frame_size = frame_size_;
213 return video_frame_.get();
214 }
215
216 MjpegFileParser::MjpegFileParser(const base::FilePath& file_path)
217 : VideoFileParser(file_path) {}
218
219 MjpegFileParser::~MjpegFileParser() {}
220
221 bool MjpegFileParser::Initialize(media::VideoCaptureFormat* capture_format) {
222 mapped_file_.reset(new base::MemoryMappedFile());
223
224 if (!mapped_file_->Initialize(file_path_) || !mapped_file_->IsValid()) {
225 LOG(ERROR) << "File memory map error: " << file_path_.value();
226 return false;
227 }
228
229 JpegParseResult result;
230 if (!ParseJpegStream(mapped_file_->data(), mapped_file_->length(), &result))
231 return false;
232
233 frame_size_ = result.image_size;
234 if (frame_size_ > static_cast<int>(mapped_file_->length())) {
235 LOG(ERROR) << "File is incomplete";
236 return false;
237 }
238
239 VideoCaptureFormat format;
240 format.pixel_format = media::PIXEL_FORMAT_MJPEG;
241 format.frame_size.set_width(result.frame_header.visible_width);
242 format.frame_size.set_height(result.frame_header.visible_height);
243 format.frame_rate = kMJpegFrameRate;
244 if (!format.IsValid())
245 return false;
246 *capture_format = format;
247 return true;
248 }
249
250 const uint8_t* MjpegFileParser::GetNextFrame(int* frame_size) {
251 const uint8_t* buf_ptr = mapped_file_->data() + current_byte_index_;
252
253 JpegParseResult result;
254 if (!ParseJpegStream(buf_ptr, mapped_file_->length() - current_byte_index_,
255 &result)) {
256 return nullptr;
257 }
258 *frame_size = frame_size_ = result.image_size;
259 current_byte_index_ += frame_size_;
260 // Reset the pointer to play repeatedly.
261 if (current_byte_index_ >= mapped_file_->length())
262 current_byte_index_ = first_frame_byte_index_;
263 return buf_ptr;
264 }
265
266 // static
267 bool FileVideoCaptureDevice::GetVideoCaptureFormat(
268 const base::FilePath& file_path,
269 media::VideoCaptureFormat* video_format) {
270 std::unique_ptr<VideoFileParser> file_parser =
271 GetVideoFileParser(file_path, video_format);
272 return file_parser != nullptr;
273 }
274
275 // static
276 std::unique_ptr<VideoFileParser> FileVideoCaptureDevice::GetVideoFileParser(
277 const base::FilePath& file_path,
278 media::VideoCaptureFormat* video_format) {
279 std::unique_ptr<VideoFileParser> file_parser;
280 std::string file_name(file_path.value().begin(), file_path.value().end());
281
282 if (base::EndsWith(file_name, "y4m",
283 base::CompareCase::INSENSITIVE_ASCII)) {
284 file_parser.reset(new Y4mFileParser(file_path));
285 } else if (base::EndsWith(file_name, "mjpeg",
286 base::CompareCase::INSENSITIVE_ASCII)) {
287 file_parser.reset(new MjpegFileParser(file_path));
288 } else {
289 LOG(ERROR) << "Unsupported file format.";
290 return file_parser;
291 }
292
293 if (!file_parser->Initialize(video_format)) {
294 file_parser.reset();
295 }
296 return file_parser;
297 }
298
299 FileVideoCaptureDevice::FileVideoCaptureDevice(const base::FilePath& file_path)
300 : capture_thread_("CaptureThread"), file_path_(file_path) {}
301
302 FileVideoCaptureDevice::~FileVideoCaptureDevice() {
303 DCHECK(thread_checker_.CalledOnValidThread());
304 // Check if the thread is running.
305 // This means that the device have not been DeAllocated properly.
306 CHECK(!capture_thread_.IsRunning());
307 }
308
309 void FileVideoCaptureDevice::AllocateAndStart(
310 const VideoCaptureParams& params,
311 std::unique_ptr<VideoCaptureDevice::Client> client) {
312 DCHECK(thread_checker_.CalledOnValidThread());
313 CHECK(!capture_thread_.IsRunning());
314
315 capture_thread_.Start();
316 capture_thread_.task_runner()->PostTask(
317 FROM_HERE,
318 base::Bind(&FileVideoCaptureDevice::OnAllocateAndStart,
319 base::Unretained(this), params, base::Passed(&client)));
320 }
321
322 void FileVideoCaptureDevice::StopAndDeAllocate() {
323 DCHECK(thread_checker_.CalledOnValidThread());
324 CHECK(capture_thread_.IsRunning());
325
326 capture_thread_.task_runner()->PostTask(
327 FROM_HERE, base::Bind(&FileVideoCaptureDevice::OnStopAndDeAllocate,
328 base::Unretained(this)));
329 capture_thread_.Stop();
330 }
331
332 void FileVideoCaptureDevice::OnAllocateAndStart(
333 const VideoCaptureParams& params,
334 std::unique_ptr<VideoCaptureDevice::Client> client) {
335 DCHECK(capture_thread_.task_runner()->BelongsToCurrentThread());
336
337 client_ = std::move(client);
338
339 DCHECK(!file_parser_);
340 file_parser_ = GetVideoFileParser(file_path_, &capture_format_);
341 if (!file_parser_) {
342 client_->OnError(FROM_HERE, "Could not open Video file");
343 return;
344 }
345
346 DVLOG(1) << "Opened video file " << capture_format_.frame_size.ToString()
347 << ", fps: " << capture_format_.frame_rate;
348
349 capture_thread_.task_runner()->PostTask(
350 FROM_HERE, base::Bind(&FileVideoCaptureDevice::OnCaptureTask,
351 base::Unretained(this)));
352 }
353
354 void FileVideoCaptureDevice::OnStopAndDeAllocate() {
355 DCHECK(capture_thread_.task_runner()->BelongsToCurrentThread());
356 file_parser_.reset();
357 client_.reset();
358 next_frame_time_ = base::TimeTicks();
359 }
360
361 void FileVideoCaptureDevice::OnCaptureTask() {
362 DCHECK(capture_thread_.task_runner()->BelongsToCurrentThread());
363 if (!client_)
364 return;
365
366 // Give the captured frame to the client.
367 int frame_size = 0;
368 const uint8_t* frame_ptr = file_parser_->GetNextFrame(&frame_size);
369 DCHECK(frame_size);
370 CHECK(frame_ptr);
371 const base::TimeTicks current_time = base::TimeTicks::Now();
372 if (first_ref_time_.is_null())
373 first_ref_time_ = current_time;
374 client_->OnIncomingCapturedData(frame_ptr, frame_size, capture_format_, 0,
375 current_time, current_time - first_ref_time_);
376 // Reschedule next CaptureTask.
377 const base::TimeDelta frame_interval =
378 base::TimeDelta::FromMicroseconds(1E6 / capture_format_.frame_rate);
379 if (next_frame_time_.is_null()) {
380 next_frame_time_ = current_time + frame_interval;
381 } else {
382 next_frame_time_ += frame_interval;
383 // Don't accumulate any debt if we are lagging behind - just post next frame
384 // immediately and continue as normal.
385 if (next_frame_time_ < current_time)
386 next_frame_time_ = current_time;
387 }
388 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
389 FROM_HERE, base::Bind(&FileVideoCaptureDevice::OnCaptureTask,
390 base::Unretained(this)),
391 next_frame_time_ - current_time);
392 }
393
394 } // namespace media
OLDNEW
« no previous file with comments | « media/capture/video/file_video_capture_device.h ('k') | media/capture/video/file_video_capture_device_factory.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698