Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / media / capture / video / file_video_capture_device.cc
blob073c0a13254c68dfec20c82ad454af3481395f68
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/capture/video/file_video_capture_device.h"
7 #include "base/bind.h"
8 #include "base/strings/string_number_conversions.h"
9 #include "base/strings/string_piece.h"
10 #include "media/base/video_capture_types.h"
11 #include "media/filters/jpeg_parser.h"
13 namespace media {
15 static const int kY4MHeaderMaxSize = 200;
16 static const char kY4MSimpleFrameDelimiter[] = "FRAME";
17 static const int kY4MSimpleFrameDelimiterSize = 6;
18 static const float kMJpegFrameRate = 30.0f;
20 int ParseY4MInt(const base::StringPiece& token) {
21 int temp_int;
22 CHECK(base::StringToInt(token, &temp_int)) << token;
23 return temp_int;
26 // Extract numerator and denominator out of a token that must have the aspect
27 // numerator:denominator, both integer numbers.
28 void ParseY4MRational(const base::StringPiece& token,
29 int* numerator, 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);
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_new/.../y4minput.* .
48 void ParseY4MTags(const std::string& file_header,
49 media::VideoCaptureFormat* video_format) {
50 media::VideoCaptureFormat format;
51 format.pixel_format = media::PIXEL_FORMAT_I420;
52 size_t index = 0;
53 size_t blank_position = 0;
54 base::StringPiece token;
55 while ((blank_position = file_header.find_first_of("\n ", index)) !=
56 std::string::npos) {
57 // Every token is supposed to have an identifier letter and a bunch of
58 // information immediately after, which we extract into a |token| here.
59 token =
60 base::StringPiece(&file_header[index + 1], blank_position - index - 1);
61 CHECK(!token.empty());
62 switch (file_header[index]) {
63 case 'W':
64 format.frame_size.set_width(ParseY4MInt(token));
65 break;
66 case 'H':
67 format.frame_size.set_height(ParseY4MInt(token));
68 break;
69 case 'F': {
70 // If the token is "FRAME", it means we have finished with the header.
71 if (token[0] == 'R')
72 break;
73 int fps_numerator, fps_denominator;
74 ParseY4MRational(token, &fps_numerator, &fps_denominator);
75 format.frame_rate = fps_numerator / fps_denominator;
76 break;
78 case 'I':
79 // Interlacing is ignored, but we don't like mixed modes.
80 CHECK_NE(token[0], 'm');
81 break;
82 case 'A':
83 // Pixel aspect ratio ignored.
84 break;
85 case 'C':
86 CHECK(token == "420" || token == "420jpeg" || token == "420paldv")
87 << token; // Only I420 is supported, and we fudge the variants.
88 break;
89 default:
90 break;
92 // We're done if we have found a newline character right after the token.
93 if (file_header[blank_position] == '\n')
94 break;
95 index = blank_position + 1;
97 // Last video format semantic correctness check before sending it back.
98 CHECK(format.IsValid());
99 *video_format = format;
102 class VideoFileParser {
103 public:
104 explicit VideoFileParser(const base::FilePath& file_path);
105 virtual ~VideoFileParser();
107 // Parses file header and collects format information in |capture_format|.
108 virtual bool Initialize(media::VideoCaptureFormat* capture_format) = 0;
110 // Gets the start pointer of next frame and stores current frame size in
111 // |frame_size|.
112 virtual const uint8_t* GetNextFrame(int* frame_size) = 0;
114 protected:
115 const base::FilePath file_path_;
116 int frame_size_;
117 size_t current_byte_index_;
118 size_t first_frame_byte_index_;
121 class Y4mFileParser final : public VideoFileParser {
122 public:
123 explicit Y4mFileParser(const base::FilePath& file_path);
125 // VideoFileParser implementation, class methods.
126 ~Y4mFileParser() override;
127 bool Initialize(media::VideoCaptureFormat* capture_format) override;
128 const uint8_t* GetNextFrame(int* frame_size) override;
130 private:
131 scoped_ptr<base::File> file_;
132 scoped_ptr<uint8_t[]> video_frame_;
134 DISALLOW_COPY_AND_ASSIGN(Y4mFileParser);
137 class MjpegFileParser final : public VideoFileParser {
138 public:
139 explicit MjpegFileParser(const base::FilePath& file_path);
141 // VideoFileParser implementation, class methods.
142 ~MjpegFileParser() override;
143 bool Initialize(media::VideoCaptureFormat* capture_format) override;
144 const uint8_t* GetNextFrame(int* frame_size) override;
146 private:
147 scoped_ptr<base::MemoryMappedFile> mapped_file_;
149 DISALLOW_COPY_AND_ASSIGN(MjpegFileParser);
152 VideoFileParser::VideoFileParser(const base::FilePath& file_path)
153 : file_path_(file_path),
154 frame_size_(0),
155 current_byte_index_(0),
156 first_frame_byte_index_(0) {}
158 VideoFileParser::~VideoFileParser() {}
160 Y4mFileParser::Y4mFileParser(const base::FilePath& file_path)
161 : VideoFileParser(file_path) {}
163 Y4mFileParser::~Y4mFileParser() {}
165 bool Y4mFileParser::Initialize(media::VideoCaptureFormat* capture_format) {
166 file_.reset(new base::File(file_path_,
167 base::File::FLAG_OPEN | base::File::FLAG_READ));
168 if (!file_->IsValid()) {
169 DLOG(ERROR) << file_path_.value() << ", error: "
170 << base::File::ErrorToString(file_->error_details());
171 return false;
174 std::string header(kY4MHeaderMaxSize, '\0');
175 file_->Read(0, &header[0], header.size());
176 const size_t header_end = header.find(kY4MSimpleFrameDelimiter);
177 CHECK_NE(header_end, header.npos);
179 ParseY4MTags(header, capture_format);
180 first_frame_byte_index_ = header_end + kY4MSimpleFrameDelimiterSize;
181 current_byte_index_ = first_frame_byte_index_;
182 frame_size_ = capture_format->ImageAllocationSize();
183 return true;
186 const uint8_t* Y4mFileParser::GetNextFrame(int* frame_size) {
187 if (!video_frame_)
188 video_frame_.reset(new uint8_t[frame_size_]);
189 int result =
190 file_->Read(current_byte_index_,
191 reinterpret_cast<char*>(video_frame_.get()), frame_size_);
193 // If we passed EOF to base::File, it will return 0 read characters. In that
194 // case, reset the pointer and read again.
195 if (result != frame_size_) {
196 CHECK_EQ(result, 0);
197 current_byte_index_ = first_frame_byte_index_;
198 CHECK_EQ(
199 file_->Read(current_byte_index_,
200 reinterpret_cast<char*>(video_frame_.get()), frame_size_),
201 frame_size_);
202 } else {
203 current_byte_index_ += frame_size_ + kY4MSimpleFrameDelimiterSize;
205 *frame_size = frame_size_;
206 return video_frame_.get();
209 MjpegFileParser::MjpegFileParser(const base::FilePath& file_path)
210 : VideoFileParser(file_path) {}
212 MjpegFileParser::~MjpegFileParser() {}
214 bool MjpegFileParser::Initialize(media::VideoCaptureFormat* capture_format) {
215 mapped_file_.reset(new base::MemoryMappedFile());
217 if (!mapped_file_->Initialize(file_path_) || !mapped_file_->IsValid()) {
218 LOG(ERROR) << "File memory map error: " << file_path_.value();
219 return false;
222 JpegParseResult result;
223 if (!ParseJpegStream(mapped_file_->data(), mapped_file_->length(), &result))
224 return false;
226 frame_size_ = result.image_size;
227 if (frame_size_ > static_cast<int>(mapped_file_->length())) {
228 LOG(ERROR) << "File is incomplete";
229 return false;
232 VideoCaptureFormat format;
233 format.pixel_format = media::PIXEL_FORMAT_MJPEG;
234 format.frame_size.set_width(result.frame_header.visible_width);
235 format.frame_size.set_height(result.frame_header.visible_height);
236 format.frame_rate = kMJpegFrameRate;
237 if (!format.IsValid())
238 return false;
239 *capture_format = format;
240 return true;
243 const uint8_t* MjpegFileParser::GetNextFrame(int* frame_size) {
244 const uint8_t* buf_ptr = mapped_file_->data() + current_byte_index_;
246 JpegParseResult result;
247 if (!ParseJpegStream(buf_ptr, mapped_file_->length() - current_byte_index_,
248 &result)) {
249 return nullptr;
251 *frame_size = frame_size_ = result.image_size;
252 current_byte_index_ += frame_size_;
253 // Reset the pointer to play repeatedly.
254 if (current_byte_index_ >= mapped_file_->length())
255 current_byte_index_ = first_frame_byte_index_;
256 return buf_ptr;
259 // static
260 bool FileVideoCaptureDevice::GetVideoCaptureFormat(
261 const base::FilePath& file_path,
262 media::VideoCaptureFormat* video_format) {
263 scoped_ptr<VideoFileParser> file_parser =
264 GetVideoFileParser(file_path, video_format);
265 return file_parser != nullptr;
268 // static
269 scoped_ptr<VideoFileParser>
270 FileVideoCaptureDevice::GetVideoFileParser(
271 const base::FilePath& file_path,
272 media::VideoCaptureFormat* video_format) {
273 scoped_ptr<VideoFileParser> file_parser;
274 std::string file_name(file_path.value().begin(), file_path.value().end());
276 if (base::EndsWith(file_name, "y4m",
277 base::CompareCase::INSENSITIVE_ASCII)) {
278 file_parser.reset(new Y4mFileParser(file_path));
279 } else if (base::EndsWith(file_name, "mjpeg",
280 base::CompareCase::INSENSITIVE_ASCII)) {
281 file_parser.reset(new MjpegFileParser(file_path));
282 } else {
283 LOG(ERROR) << "Unsupported file format.";
284 return file_parser.Pass();
287 if (!file_parser->Initialize(video_format)) {
288 file_parser.reset();
290 return file_parser.Pass();
293 FileVideoCaptureDevice::FileVideoCaptureDevice(const base::FilePath& file_path)
294 : capture_thread_("CaptureThread"), file_path_(file_path) {}
296 FileVideoCaptureDevice::~FileVideoCaptureDevice() {
297 DCHECK(thread_checker_.CalledOnValidThread());
298 // Check if the thread is running.
299 // This means that the device have not been DeAllocated properly.
300 CHECK(!capture_thread_.IsRunning());
303 void FileVideoCaptureDevice::AllocateAndStart(
304 const VideoCaptureParams& params,
305 scoped_ptr<VideoCaptureDevice::Client> client) {
306 DCHECK(thread_checker_.CalledOnValidThread());
307 CHECK(!capture_thread_.IsRunning());
309 capture_thread_.Start();
310 capture_thread_.message_loop()->PostTask(
311 FROM_HERE,
312 base::Bind(&FileVideoCaptureDevice::OnAllocateAndStart,
313 base::Unretained(this), params, base::Passed(&client)));
316 void FileVideoCaptureDevice::StopAndDeAllocate() {
317 DCHECK(thread_checker_.CalledOnValidThread());
318 CHECK(capture_thread_.IsRunning());
320 capture_thread_.message_loop()->PostTask(
321 FROM_HERE, base::Bind(&FileVideoCaptureDevice::OnStopAndDeAllocate,
322 base::Unretained(this)));
323 capture_thread_.Stop();
326 void FileVideoCaptureDevice::OnAllocateAndStart(
327 const VideoCaptureParams& params,
328 scoped_ptr<VideoCaptureDevice::Client> client) {
329 DCHECK_EQ(capture_thread_.message_loop(), base::MessageLoop::current());
331 client_ = client.Pass();
333 DCHECK(!file_parser_);
334 file_parser_ = GetVideoFileParser(file_path_, &capture_format_);
335 if (!file_parser_) {
336 client_->OnError("Could not open Video file");
337 return;
340 DVLOG(1) << "Opened video file " << capture_format_.frame_size.ToString()
341 << ", fps: " << capture_format_.frame_rate;
343 capture_thread_.message_loop()->PostTask(
344 FROM_HERE, base::Bind(&FileVideoCaptureDevice::OnCaptureTask,
345 base::Unretained(this)));
348 void FileVideoCaptureDevice::OnStopAndDeAllocate() {
349 DCHECK_EQ(capture_thread_.message_loop(), base::MessageLoop::current());
350 file_parser_.reset();
351 client_.reset();
352 next_frame_time_ = base::TimeTicks();
355 void FileVideoCaptureDevice::OnCaptureTask() {
356 DCHECK_EQ(capture_thread_.message_loop(), base::MessageLoop::current());
357 if (!client_)
358 return;
360 // Give the captured frame to the client.
361 int frame_size = 0;
362 const uint8_t* frame_ptr = file_parser_->GetNextFrame(&frame_size);
363 DCHECK(frame_size);
364 CHECK(frame_ptr);
365 const base::TimeTicks current_time = base::TimeTicks::Now();
366 client_->OnIncomingCapturedData(frame_ptr, frame_size, capture_format_, 0,
367 current_time);
368 // Reschedule next CaptureTask.
369 const base::TimeDelta frame_interval =
370 base::TimeDelta::FromMicroseconds(1E6 / capture_format_.frame_rate);
371 if (next_frame_time_.is_null()) {
372 next_frame_time_ = current_time + frame_interval;
373 } else {
374 next_frame_time_ += frame_interval;
375 // Don't accumulate any debt if we are lagging behind - just post next frame
376 // immediately and continue as normal.
377 if (next_frame_time_ < current_time)
378 next_frame_time_ = current_time;
380 base::MessageLoop::current()->PostDelayedTask(
381 FROM_HERE, base::Bind(&FileVideoCaptureDevice::OnCaptureTask,
382 base::Unretained(this)),
383 next_frame_time_ - current_time);
386 } // namespace media