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"
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"
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
) {
22 CHECK(base::StringToInt(token
, &temp_int
)) << token
;
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()));
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
;
53 size_t blank_position
= 0;
54 base::StringPiece token
;
55 while ((blank_position
= file_header
.find_first_of("\n ", index
)) !=
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.
60 base::StringPiece(&file_header
[index
+ 1], blank_position
- index
- 1);
61 CHECK(!token
.empty());
62 switch (file_header
[index
]) {
64 format
.frame_size
.set_width(ParseY4MInt(token
));
67 format
.frame_size
.set_height(ParseY4MInt(token
));
70 // If the token is "FRAME", it means we have finished with the header.
73 int fps_numerator
, fps_denominator
;
74 ParseY4MRational(token
, &fps_numerator
, &fps_denominator
);
75 format
.frame_rate
= fps_numerator
/ fps_denominator
;
79 // Interlacing is ignored, but we don't like mixed modes.
80 CHECK_NE(token
[0], 'm');
83 // Pixel aspect ratio ignored.
86 CHECK(token
== "420" || token
== "420jpeg" || token
== "420paldv")
87 << token
; // Only I420 is supported, and we fudge the variants.
92 // We're done if we have found a newline character right after the token.
93 if (file_header
[blank_position
] == '\n')
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
{
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
112 virtual const uint8_t* GetNextFrame(int* frame_size
) = 0;
115 const base::FilePath file_path_
;
117 size_t current_byte_index_
;
118 size_t first_frame_byte_index_
;
121 class Y4mFileParser final
: public VideoFileParser
{
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
;
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
{
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
;
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
),
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());
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();
186 const uint8_t* Y4mFileParser::GetNextFrame(int* frame_size
) {
188 video_frame_
.reset(new uint8_t[frame_size_
]);
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_
) {
197 current_byte_index_
= first_frame_byte_index_
;
199 file_
->Read(current_byte_index_
,
200 reinterpret_cast<char*>(video_frame_
.get()), frame_size_
),
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();
222 JpegParseResult result
;
223 if (!ParseJpegStream(mapped_file_
->data(), mapped_file_
->length(), &result
))
226 frame_size_
= result
.image_size
;
227 if (frame_size_
> static_cast<int>(mapped_file_
->length())) {
228 LOG(ERROR
) << "File is incomplete";
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())
239 *capture_format
= format
;
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_
,
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_
;
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;
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
));
283 LOG(ERROR
) << "Unsupported file format.";
284 return file_parser
.Pass();
287 if (!file_parser
->Initialize(video_format
)) {
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(
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_
);
336 client_
->OnError("Could not open Video file");
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();
352 next_frame_time_
= base::TimeTicks();
355 void FileVideoCaptureDevice::OnCaptureTask() {
356 DCHECK_EQ(capture_thread_
.message_loop(), base::MessageLoop::current());
360 // Give the captured frame to the client.
362 const uint8_t* frame_ptr
= file_parser_
->GetNextFrame(&frame_size
);
365 const base::TimeTicks current_time
= base::TimeTicks::Now();
366 client_
->OnIncomingCapturedData(frame_ptr
, frame_size
, capture_format_
, 0,
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
;
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
);