Unregister from GCM when the only GCM app is removed
[chromium-blink-merge.git] / media / video / capture / file_video_capture_device.cc
blob24673aa45934d824faba863e65c650c925587f19
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"
7 #include <string>
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"
14 namespace media {
15 static const int kY4MHeaderMaxSize = 200;
16 static const char kY4MSimpleFrameDelimiter[] = "FRAME";
17 static const int kY4MSimpleFrameDelimiterSize = 6;
19 int ParseY4MInt(const base::StringPiece& token) {
20 int temp_int;
21 CHECK(base::StringToInt(token, &temp_int)) << token;
22 return temp_int;
25 // Extract numerator and denominator out of a token that must have the aspect
26 // numerator:denominator, both integer numbers.
27 void 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);
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.* .
48 void 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;
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;
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;
98 // Last video format semantic correctness check before sending it back.
99 CHECK(video_format->IsValid());
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
107 int64 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);
113 size_t header_end = header.find(kY4MSimpleFrameDelimiter);
114 CHECK_NE(header_end, header.npos);
116 ParseY4MTags(header, video_format);
117 return header_end + kY4MSimpleFrameDelimiterSize;
120 // Opens a given file for reading, and returns the file to the caller, who is
121 // responsible for closing it.
122 // static
123 base::File FileVideoCaptureDevice::OpenFileForRead(
124 const base::FilePath& file_path) {
125 base::File file(file_path, base::File::FLAG_OPEN | base::File::FLAG_READ);
126 DVLOG_IF(1, file.IsValid()) << file_path.value() << ", error: "
127 << base::File::ErrorToString(file.error_details());
128 CHECK(file.IsValid());
129 return file.Pass();
132 FileVideoCaptureDevice::FileVideoCaptureDevice(const base::FilePath& file_path)
133 : capture_thread_("CaptureThread"),
134 file_path_(file_path),
135 frame_size_(0),
136 current_byte_index_(0),
137 first_frame_byte_index_(0) {}
139 FileVideoCaptureDevice::~FileVideoCaptureDevice() {
140 DCHECK(thread_checker_.CalledOnValidThread());
141 // Check if the thread is running.
142 // This means that the device have not been DeAllocated properly.
143 CHECK(!capture_thread_.IsRunning());
146 void FileVideoCaptureDevice::AllocateAndStart(
147 const VideoCaptureParams& params,
148 scoped_ptr<VideoCaptureDevice::Client> client) {
149 DCHECK(thread_checker_.CalledOnValidThread());
150 CHECK(!capture_thread_.IsRunning());
152 capture_thread_.Start();
153 capture_thread_.message_loop()->PostTask(
154 FROM_HERE,
155 base::Bind(&FileVideoCaptureDevice::OnAllocateAndStart,
156 base::Unretained(this),
157 params,
158 base::Passed(&client)));
161 void FileVideoCaptureDevice::StopAndDeAllocate() {
162 DCHECK(thread_checker_.CalledOnValidThread());
163 CHECK(capture_thread_.IsRunning());
165 capture_thread_.message_loop()->PostTask(
166 FROM_HERE,
167 base::Bind(&FileVideoCaptureDevice::OnStopAndDeAllocate,
168 base::Unretained(this)));
169 capture_thread_.Stop();
172 int FileVideoCaptureDevice::CalculateFrameSize() {
173 DCHECK_EQ(capture_format_.pixel_format, PIXEL_FORMAT_I420);
174 DCHECK_EQ(capture_thread_.message_loop(), base::MessageLoop::current());
175 return capture_format_.frame_size.GetArea() * 12 / 8;
178 void FileVideoCaptureDevice::OnAllocateAndStart(
179 const VideoCaptureParams& params,
180 scoped_ptr<VideoCaptureDevice::Client> client) {
181 DCHECK_EQ(capture_thread_.message_loop(), base::MessageLoop::current());
183 client_ = client.Pass();
185 // Open the file and parse the header. Get frame size and format.
186 DCHECK(!file_.IsValid());
187 file_ = OpenFileForRead(file_path_);
188 first_frame_byte_index_ =
189 ParseFileAndExtractVideoFormat(&file_, &capture_format_);
190 current_byte_index_ = first_frame_byte_index_;
191 DVLOG(1) << "Opened video file " << capture_format_.frame_size.ToString()
192 << ", fps: " << capture_format_.frame_rate;
194 frame_size_ = CalculateFrameSize();
195 video_frame_.reset(new uint8[frame_size_]);
197 capture_thread_.message_loop()->PostTask(
198 FROM_HERE,
199 base::Bind(&FileVideoCaptureDevice::OnCaptureTask,
200 base::Unretained(this)));
203 void FileVideoCaptureDevice::OnStopAndDeAllocate() {
204 DCHECK_EQ(capture_thread_.message_loop(), base::MessageLoop::current());
205 file_.Close();
206 client_.reset();
207 current_byte_index_ = 0;
208 first_frame_byte_index_ = 0;
209 frame_size_ = 0;
210 video_frame_.reset();
213 void FileVideoCaptureDevice::OnCaptureTask() {
214 DCHECK_EQ(capture_thread_.message_loop(), base::MessageLoop::current());
215 if (!client_)
216 return;
217 const base::TimeTicks timestamp_before_reading = base::TimeTicks::Now();
218 int result = file_.Read(current_byte_index_,
219 reinterpret_cast<char*>(video_frame_.get()),
220 frame_size_);
222 // If we passed EOF to base::File, it will return 0 read characters. In that
223 // case, reset the pointer and read again.
224 if (result != frame_size_) {
225 CHECK_EQ(result, 0);
226 current_byte_index_ = first_frame_byte_index_;
227 CHECK_EQ(file_.Read(current_byte_index_,
228 reinterpret_cast<char*>(video_frame_.get()),
229 frame_size_),
230 frame_size_);
231 } else {
232 current_byte_index_ += frame_size_ + kY4MSimpleFrameDelimiterSize;
235 // Give the captured frame to the client.
236 client_->OnIncomingCapturedData(video_frame_.get(),
237 frame_size_,
238 capture_format_,
240 base::TimeTicks::Now());
241 // Reschedule next CaptureTask.
242 const base::TimeDelta frame_interval =
243 base::TimeDelta::FromMicroseconds(1E6 / capture_format_.frame_rate);
244 base::TimeDelta next_on_capture_timedelta = frame_interval -
245 (base::TimeTicks::Now() - timestamp_before_reading);
246 if (next_on_capture_timedelta.InMilliseconds() < 0) {
247 DLOG(WARNING) << "Frame reading took longer than the frame interval.";
248 next_on_capture_timedelta = frame_interval;
250 base::MessageLoop::current()->PostDelayedTask(
251 FROM_HERE,
252 base::Bind(&FileVideoCaptureDevice::OnCaptureTask,
253 base::Unretained(this)),
254 next_on_capture_timedelta);
257 } // namespace media