Index: media/capture/video/file_video_capture_device.cc |
diff --git a/media/capture/video/file_video_capture_device.cc b/media/capture/video/file_video_capture_device.cc |
index e7d9f064195f00c622f22724bd6eb995e7a0caec..a7d649e59dc882f971c876d8bb1275db389fa2fe 100644 |
--- a/media/capture/video/file_video_capture_device.cc |
+++ b/media/capture/video/file_video_capture_device.cc |
@@ -8,13 +8,15 @@ |
#include "base/strings/string_number_conversions.h" |
#include "base/strings/string_piece.h" |
#include "media/base/video_capture_types.h" |
+#include "media/filters/jpeg_parser.h" |
namespace media { |
static const int kY4MHeaderMaxSize = 200; |
static const char kY4MSimpleFrameDelimiter[] = "FRAME"; |
static const int kY4MSimpleFrameDelimiterSize = 6; |
+static const int kMJpegFrameRate = 30; |
-int ParseY4MInt(const base::StringPiece& token) { |
+static int ParseY4MInt(const base::StringPiece& token) { |
mcasas
2015/08/18 18:28:42
IIRC, media/ folder has this strange thing
of _not
henryhsu
2015/08/19 01:57:24
Hi xhwang, do you know this?
|
int temp_int; |
CHECK(base::StringToInt(token, &temp_int)) << token; |
return temp_int; |
@@ -22,7 +24,7 @@ int ParseY4MInt(const base::StringPiece& token) { |
// Extract numerator and denominator out of a token that must have the aspect |
// numerator:denominator, both integer numbers. |
-void ParseY4MRational(const base::StringPiece& token, |
+static void ParseY4MRational(const base::StringPiece& token, |
int* numerator, |
int* denominator) { |
size_t index_divider = token.find(':'); |
@@ -43,11 +45,10 @@ void ParseY4MRational(const base::StringPiece& token, |
// 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, |
+static bool ParseY4MTags(const std::string& file_header, |
media::VideoCaptureFormat* video_format) { |
- video_format->pixel_format = media::VIDEO_CAPTURE_PIXEL_FORMAT_I420; |
- video_format->frame_size.set_width(0); |
- video_format->frame_size.set_height(0); |
+ media::VideoCaptureFormat format; |
+ format.pixel_format = media::VIDEO_CAPTURE_PIXEL_FORMAT_I420; |
size_t index = 0; |
size_t blank_position = 0; |
base::StringPiece token; |
@@ -57,13 +58,14 @@ void ParseY4MTags(const std::string& file_header, |
// information immediately after, which we extract into a |token| here. |
token = |
base::StringPiece(&file_header[index + 1], blank_position - index - 1); |
- CHECK(!token.empty()); |
+ if (token.empty()) |
+ return false; |
switch (file_header[index]) { |
case 'W': |
- video_format->frame_size.set_width(ParseY4MInt(token)); |
+ format.frame_size.set_width(ParseY4MInt(token)); |
break; |
case 'H': |
- video_format->frame_size.set_height(ParseY4MInt(token)); |
+ format.frame_size.set_height(ParseY4MInt(token)); |
break; |
case 'F': { |
// If the token is "FRAME", it means we have finished with the header. |
@@ -71,20 +73,20 @@ void ParseY4MTags(const std::string& file_header, |
break; |
int fps_numerator, fps_denominator; |
ParseY4MRational(token, &fps_numerator, &fps_denominator); |
- video_format->frame_rate = fps_numerator / fps_denominator; |
+ 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; |
+ if (token[0] == 'm') |
+ return false; |
case 'A': |
// Pixel aspect ratio ignored. |
break; |
case 'C': |
- CHECK(token == "420" || token == "420jpeg" || token == "420paldv") |
- << token; // Only I420 is supported, and we fudge the variants. |
- break; |
+ // Only I420 is supported, and we fudge the variants. |
+ if (token != "420" && token != "420jpeg" && token != "420paldv") |
+ return false; |
default: |
break; |
} |
@@ -94,37 +96,51 @@ void ParseY4MTags(const std::string& file_header, |
index = blank_position + 1; |
} |
// Last video format semantic correctness check before sending it back. |
- CHECK(video_format->IsValid()); |
+ if (!format.IsValid()) |
mcasas
2015/08/18 18:28:42
The original CHECK() was correct (and hammered by
henryhsu
2015/08/19 01:57:24
Current implementation is to parse Y4M and MJPEG.
|
+ return false; |
+ *video_format = format; |
+ return true; |
} |
-// 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. |
// static |
-int64 FileVideoCaptureDevice::ParseFileAndExtractVideoFormat( |
- base::File* file, |
- media::VideoCaptureFormat* video_format) { |
- std::string header(kY4MHeaderMaxSize, 0); |
- file->Read(0, &header[0], kY4MHeaderMaxSize - 1); |
+size_t FileVideoCaptureDevice::ParseFileAndExtractVideoFormat( |
+ const base::MemoryMappedFile* mapped_file, |
+ media::VideoCaptureFormat* video_format, |
+ int* frame_size) { |
+ const char* buf = reinterpret_cast<const char*>(mapped_file->data()); |
+ // Try Y4M format first. |
+ std::string header(buf, kY4MHeaderMaxSize - 1); |
mcasas
2015/08/18 18:28:42
const (maybe)
|
size_t header_end = header.find(kY4MSimpleFrameDelimiter); |
mcasas
2015/08/18 18:28:41
const
henryhsu
2015/08/19 10:05:11
Done.
|
- CHECK_NE(header_end, header.npos); |
+ if (header_end != header.npos && ParseY4MTags(header, video_format)) { |
+ *frame_size = video_format->ImageAllocationSize(); |
+ return header_end + kY4MSimpleFrameDelimiterSize; |
+ } |
- ParseY4MTags(header, video_format); |
- return header_end + kY4MSimpleFrameDelimiterSize; |
+ // Try MJPEG format. |
+ JpegParseResult result; |
+ if (!ParseJpegPicture(mapped_file->data(), mapped_file->length(), &result)) |
+ return -1; |
+ video_format->pixel_format = media::VIDEO_CAPTURE_PIXEL_FORMAT_MJPEG; |
+ video_format->frame_size.set_width(result.frame_header.visible_width); |
+ video_format->frame_size.set_height(result.frame_header.visible_height); |
+ video_format->frame_rate = kMJpegFrameRate; |
+ if (!video_format->IsValid()) |
+ return -1; |
+ *frame_size = result.image_size; |
+ return 0; |
} |
-// Opens a given file for reading, and returns the file to the caller, who is |
-// responsible for closing it. |
// static |
-base::File FileVideoCaptureDevice::OpenFileForRead( |
+scoped_ptr<base::MemoryMappedFile> FileVideoCaptureDevice::OpenFileForRead( |
const base::FilePath& file_path) { |
- base::File file(file_path, base::File::FLAG_OPEN | base::File::FLAG_READ); |
- DLOG_IF(ERROR, file.IsValid()) |
- << file_path.value() |
- << ", error: " << base::File::ErrorToString(file.error_details()); |
- return file.Pass(); |
+ scoped_ptr<base::MemoryMappedFile> mapped_file(new base::MemoryMappedFile()); |
+ |
+ if (!mapped_file->Initialize(file_path) || !mapped_file->IsValid()) { |
+ LOG(ERROR) << "File memory map error: " << file_path.value(); |
+ return nullptr; |
+ } |
+ return mapped_file.Pass(); |
} |
FileVideoCaptureDevice::FileVideoCaptureDevice(const base::FilePath& file_path) |
@@ -165,12 +181,6 @@ void FileVideoCaptureDevice::StopAndDeAllocate() { |
capture_thread_.Stop(); |
} |
-int FileVideoCaptureDevice::CalculateFrameSize() const { |
- DCHECK_EQ(capture_format_.pixel_format, VIDEO_CAPTURE_PIXEL_FORMAT_I420); |
- DCHECK_EQ(capture_thread_.message_loop(), base::MessageLoop::current()); |
- return capture_format_.ImageAllocationSize(); |
-} |
- |
void FileVideoCaptureDevice::OnAllocateAndStart( |
const VideoCaptureParams& params, |
scoped_ptr<VideoCaptureDevice::Client> client) { |
@@ -179,21 +189,24 @@ void FileVideoCaptureDevice::OnAllocateAndStart( |
client_ = client.Pass(); |
// Open the file and parse the header. Get frame size and format. |
- DCHECK(!file_.IsValid()); |
- file_ = OpenFileForRead(file_path_); |
- if (!file_.IsValid()) { |
+ DCHECK(!mapped_file_); |
+ mapped_file_ = OpenFileForRead(file_path_); |
+ if (!mapped_file_) { |
client_->OnError("Could not open Video file"); |
return; |
} |
- first_frame_byte_index_ = |
- ParseFileAndExtractVideoFormat(&file_, &capture_format_); |
+ first_frame_byte_index_ = ParseFileAndExtractVideoFormat( |
+ mapped_file_.get(), &capture_format_, &frame_size_); |
+ |
+ if (first_frame_byte_index_ < 0 || |
+ first_frame_byte_index_ + frame_size_ > mapped_file_->length()) { |
+ client_->OnError("File is incomplete"); |
+ return; |
+ } |
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))); |
@@ -201,40 +214,50 @@ void FileVideoCaptureDevice::OnAllocateAndStart( |
void FileVideoCaptureDevice::OnStopAndDeAllocate() { |
DCHECK_EQ(capture_thread_.message_loop(), base::MessageLoop::current()); |
- file_.Close(); |
+ mapped_file_.reset(); |
client_.reset(); |
current_byte_index_ = 0; |
first_frame_byte_index_ = 0; |
frame_size_ = 0; |
next_frame_time_ = base::TimeTicks(); |
- video_frame_.reset(); |
} |
-void FileVideoCaptureDevice::OnCaptureTask() { |
+const uint8_t* FileVideoCaptureDevice::GetNextFrame() { |
DCHECK_EQ(capture_thread_.message_loop(), base::MessageLoop::current()); |
- if (!client_) |
- return; |
- int result = |
- file_.Read(current_byte_index_, |
- reinterpret_cast<char*>(video_frame_.get()), frame_size_); |
+ const uint8_t* buf_ptr = mapped_file_->data() + current_byte_index_; |
- // If we passed EOF to base::File, 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( |
- file_.Read(current_byte_index_, |
- reinterpret_cast<char*>(video_frame_.get()), frame_size_), |
- frame_size_); |
- } else { |
+ if (capture_format_.pixel_format == media::VIDEO_CAPTURE_PIXEL_FORMAT_MJPEG) { |
+ JpegParseResult result; |
+ if (!ParseJpegPicture(buf_ptr, mapped_file_->length() - current_byte_index_, |
+ &result)) { |
+ return nullptr; |
+ } |
+ // TODO(henryhsu): Update |capture_format_| if video has resolution change. |
+ frame_size_ = result.image_size; |
+ current_byte_index_ += frame_size_; |
+ } else if (capture_format_.pixel_format == |
+ media::VIDEO_CAPTURE_PIXEL_FORMAT_I420) { |
current_byte_index_ += frame_size_ + kY4MSimpleFrameDelimiterSize; |
+ } else { |
+ NOTREACHED() << "Not supported format: " << capture_format_.pixel_format; |
} |
+ // Play the video repeatly. |
mcasas
2015/08/18 18:28:42
s/repeatly/repeatedly/
but I'd just remove this co
henryhsu
2015/08/19 10:05:11
Done.
|
+ if (current_byte_index_ >= mapped_file_->length()) |
+ current_byte_index_ = first_frame_byte_index_; |
+ return buf_ptr; |
+} |
+ |
+void FileVideoCaptureDevice::OnCaptureTask() { |
+ DCHECK_EQ(capture_thread_.message_loop(), base::MessageLoop::current()); |
+ if (!client_) |
+ return; |
// Give the captured frame to the client. |
+ const uint8_t* frame_ptr = GetNextFrame(); |
+ CHECK(frame_ptr); |
const base::TimeTicks current_time = base::TimeTicks::Now(); |
- client_->OnIncomingCapturedData(video_frame_.get(), frame_size_, |
- capture_format_, 0, current_time); |
+ client_->OnIncomingCapturedData(frame_ptr, frame_size_, capture_format_, 0, |
+ current_time); |
// Reschedule next CaptureTask. |
const base::TimeDelta frame_interval = |
base::TimeDelta::FromMicroseconds(1E6 / capture_format_.frame_rate); |