| OLD | NEW |
| 1 // Copyright 2013 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "media/capture/video/file_video_capture_device.h" | 5 #include "media/capture/video/file_video_capture_device.h" |
| 6 | 6 |
| 7 #include "base/bind.h" | 7 #include "base/bind.h" |
| 8 #include "base/strings/string_number_conversions.h" | 8 #include "base/strings/string_number_conversions.h" |
| 9 #include "base/strings/string_piece.h" | 9 #include "base/strings/string_piece.h" |
| 10 #include "media/base/video_capture_types.h" | 10 #include "media/base/video_capture_types.h" |
| 11 #include "media/filters/jpeg_parser.h" |
| 11 | 12 |
| 12 namespace media { | 13 namespace media { |
| 14 |
| 13 static const int kY4MHeaderMaxSize = 200; | 15 static const int kY4MHeaderMaxSize = 200; |
| 14 static const char kY4MSimpleFrameDelimiter[] = "FRAME"; | 16 static const char kY4MSimpleFrameDelimiter[] = "FRAME"; |
| 15 static const int kY4MSimpleFrameDelimiterSize = 6; | 17 static const int kY4MSimpleFrameDelimiterSize = 6; |
| 18 static const float kMJpegFrameRate = 30.0f; |
| 16 | 19 |
| 17 int ParseY4MInt(const base::StringPiece& token) { | 20 int ParseY4MInt(const base::StringPiece& token) { |
| 18 int temp_int; | 21 int temp_int; |
| 19 CHECK(base::StringToInt(token, &temp_int)) << token; | 22 CHECK(base::StringToInt(token, &temp_int)) << token; |
| 20 return temp_int; | 23 return temp_int; |
| 21 } | 24 } |
| 22 | 25 |
| 23 // Extract numerator and denominator out of a token that must have the aspect | 26 // Extract numerator and denominator out of a token that must have the aspect |
| 24 // numerator:denominator, both integer numbers. | 27 // numerator:denominator, both integer numbers. |
| 25 void ParseY4MRational(const base::StringPiece& token, | 28 void ParseY4MRational(const base::StringPiece& token, |
| 26 int* numerator, | 29 int* numerator, int* denominator) { |
| 27 int* denominator) { | |
| 28 size_t index_divider = token.find(':'); | 30 size_t index_divider = token.find(':'); |
| 29 CHECK_NE(index_divider, token.npos); | 31 CHECK_NE(index_divider, token.npos); |
| 30 *numerator = ParseY4MInt(token.substr(0, index_divider)); | 32 *numerator = ParseY4MInt(token.substr(0, index_divider)); |
| 31 *denominator = ParseY4MInt(token.substr(index_divider + 1, token.length())); | 33 *denominator = ParseY4MInt(token.substr(index_divider + 1, token.length())); |
| 32 CHECK(*denominator); | 34 CHECK(*denominator); |
| 33 } | 35 } |
| 34 | 36 |
| 35 // This function parses the ASCII string in |header| as belonging to a Y4M file, | 37 // This function parses the ASCII string in |header| as belonging to a Y4M file, |
| 36 // returning the collected format in |video_format|. For a non authoritative | 38 // returning the collected format in |video_format|. For a non authoritative |
| 37 // explanation of the header format, check | 39 // explanation of the header format, check |
| 38 // http://wiki.multimedia.cx/index.php?title=YUV4MPEG2 | 40 // http://wiki.multimedia.cx/index.php?title=YUV4MPEG2 |
| 39 // Restrictions: Only interlaced I420 pixel format is supported, and pixel | 41 // Restrictions: Only interlaced I420 pixel format is supported, and pixel |
| 40 // aspect ratio is ignored. | 42 // aspect ratio is ignored. |
| 41 // Implementation notes: Y4M header should end with an ASCII 0x20 (whitespace) | 43 // Implementation notes: Y4M header should end with an ASCII 0x20 (whitespace) |
| 42 // character, however all examples mentioned in the Y4M header description end | 44 // character, however all examples mentioned in the Y4M header description end |
| 43 // with a newline character instead. Also, some headers do _not_ specify pixel | 45 // with a newline character instead. Also, some headers do _not_ specify pixel |
| 44 // format, in this case it means I420. | 46 // format, in this case it means I420. |
| 45 // This code was inspired by third_party/libvpx/.../y4minput.* . | 47 // This code was inspired by third_party/libvpx/.../y4minput.* . |
| 46 void ParseY4MTags(const std::string& file_header, | 48 void ParseY4MTags(const std::string& file_header, |
| 47 media::VideoCaptureFormat* video_format) { | 49 media::VideoCaptureFormat* video_format) { |
| 48 video_format->pixel_format = media::VIDEO_CAPTURE_PIXEL_FORMAT_I420; | 50 media::VideoCaptureFormat format; |
| 49 video_format->frame_size.set_width(0); | 51 format.pixel_format = media::VIDEO_CAPTURE_PIXEL_FORMAT_I420; |
| 50 video_format->frame_size.set_height(0); | |
| 51 size_t index = 0; | 52 size_t index = 0; |
| 52 size_t blank_position = 0; | 53 size_t blank_position = 0; |
| 53 base::StringPiece token; | 54 base::StringPiece token; |
| 54 while ((blank_position = file_header.find_first_of("\n ", index)) != | 55 while ((blank_position = file_header.find_first_of("\n ", index)) != |
| 55 std::string::npos) { | 56 std::string::npos) { |
| 56 // Every token is supposed to have an identifier letter and a bunch of | 57 // Every token is supposed to have an identifier letter and a bunch of |
| 57 // information immediately after, which we extract into a |token| here. | 58 // information immediately after, which we extract into a |token| here. |
| 58 token = | 59 token = |
| 59 base::StringPiece(&file_header[index + 1], blank_position - index - 1); | 60 base::StringPiece(&file_header[index + 1], blank_position - index - 1); |
| 60 CHECK(!token.empty()); | 61 CHECK(!token.empty()); |
| 61 switch (file_header[index]) { | 62 switch (file_header[index]) { |
| 62 case 'W': | 63 case 'W': |
| 63 video_format->frame_size.set_width(ParseY4MInt(token)); | 64 format.frame_size.set_width(ParseY4MInt(token)); |
| 64 break; | 65 break; |
| 65 case 'H': | 66 case 'H': |
| 66 video_format->frame_size.set_height(ParseY4MInt(token)); | 67 format.frame_size.set_height(ParseY4MInt(token)); |
| 67 break; | 68 break; |
| 68 case 'F': { | 69 case 'F': { |
| 69 // If the token is "FRAME", it means we have finished with the header. | 70 // If the token is "FRAME", it means we have finished with the header. |
| 70 if (token[0] == 'R') | 71 if (token[0] == 'R') |
| 71 break; | 72 break; |
| 72 int fps_numerator, fps_denominator; | 73 int fps_numerator, fps_denominator; |
| 73 ParseY4MRational(token, &fps_numerator, &fps_denominator); | 74 ParseY4MRational(token, &fps_numerator, &fps_denominator); |
| 74 video_format->frame_rate = fps_numerator / fps_denominator; | 75 format.frame_rate = fps_numerator / fps_denominator; |
| 75 break; | 76 break; |
| 76 } | 77 } |
| 77 case 'I': | 78 case 'I': |
| 78 // Interlacing is ignored, but we don't like mixed modes. | 79 // Interlacing is ignored, but we don't like mixed modes. |
| 79 CHECK_NE(token[0], 'm'); | 80 CHECK_NE(token[0], 'm'); |
| 80 break; | 81 break; |
| 81 case 'A': | 82 case 'A': |
| 82 // Pixel aspect ratio ignored. | 83 // Pixel aspect ratio ignored. |
| 83 break; | 84 break; |
| 84 case 'C': | 85 case 'C': |
| 85 CHECK(token == "420" || token == "420jpeg" || token == "420paldv") | 86 CHECK(token == "420" || token == "420jpeg" || token == "420paldv") |
| 86 << token; // Only I420 is supported, and we fudge the variants. | 87 << token; // Only I420 is supported, and we fudge the variants. |
| 87 break; | 88 break; |
| 88 default: | 89 default: |
| 89 break; | 90 break; |
| 90 } | 91 } |
| 91 // We're done if we have found a newline character right after the token. | 92 // We're done if we have found a newline character right after the token. |
| 92 if (file_header[blank_position] == '\n') | 93 if (file_header[blank_position] == '\n') |
| 93 break; | 94 break; |
| 94 index = blank_position + 1; | 95 index = blank_position + 1; |
| 95 } | 96 } |
| 96 // Last video format semantic correctness check before sending it back. | 97 // Last video format semantic correctness check before sending it back. |
| 97 CHECK(video_format->IsValid()); | 98 CHECK(format.IsValid()); |
| 98 } | 99 *video_format = format; |
| 99 | 100 } |
| 100 // Reads and parses the header of a Y4M |file|, returning the collected pixel | 101 |
| 101 // format in |video_format|. Returns the index of the first byte of the first | 102 class VideoFileParser { |
| 102 // video frame. | 103 public: |
| 103 // Restrictions: Only trivial per-frame headers are supported. | 104 explicit VideoFileParser(const base::FilePath& file_path); |
| 104 // static | 105 virtual ~VideoFileParser(); |
| 105 int64 FileVideoCaptureDevice::ParseFileAndExtractVideoFormat( | 106 |
| 106 base::File* file, | 107 // Parses file header and collects format information in |capture_format|. |
| 107 media::VideoCaptureFormat* video_format) { | 108 virtual bool Initialize(media::VideoCaptureFormat* capture_format) = 0; |
| 108 std::string header(kY4MHeaderMaxSize, 0); | 109 |
| 109 file->Read(0, &header[0], kY4MHeaderMaxSize - 1); | 110 // Gets the start pointer of next frame and stores current frame size in |
| 110 | 111 // |frame_size|. |
| 111 size_t header_end = header.find(kY4MSimpleFrameDelimiter); | 112 virtual const uint8_t* GetNextFrame(int* frame_size) = 0; |
| 112 CHECK_NE(header_end, header.npos); | 113 |
| 113 | 114 protected: |
| 114 ParseY4MTags(header, video_format); | 115 const base::FilePath file_path_; |
| 115 return header_end + kY4MSimpleFrameDelimiterSize; | 116 int frame_size_; |
| 116 } | 117 size_t current_byte_index_; |
| 117 | 118 size_t first_frame_byte_index_; |
| 118 // Opens a given file for reading, and returns the file to the caller, who is | 119 }; |
| 119 // responsible for closing it. | 120 |
| 120 // static | 121 class Y4mFileParser final : public VideoFileParser { |
| 121 base::File FileVideoCaptureDevice::OpenFileForRead( | 122 public: |
| 122 const base::FilePath& file_path) { | 123 explicit Y4mFileParser(const base::FilePath& file_path); |
| 123 base::File file(file_path, base::File::FLAG_OPEN | base::File::FLAG_READ); | 124 |
| 124 DLOG_IF(ERROR, file.IsValid()) | 125 // VideoFileParser implementation, class methods. |
| 125 << file_path.value() | 126 ~Y4mFileParser() override; |
| 126 << ", error: " << base::File::ErrorToString(file.error_details()); | 127 bool Initialize(media::VideoCaptureFormat* capture_format) override; |
| 127 return file.Pass(); | 128 const uint8_t* GetNextFrame(int* frame_size) override; |
| 128 } | 129 |
| 129 | 130 private: |
| 130 FileVideoCaptureDevice::FileVideoCaptureDevice(const base::FilePath& file_path) | 131 scoped_ptr<base::File> file_; |
| 131 : capture_thread_("CaptureThread"), | 132 scoped_ptr<uint8_t[]> video_frame_; |
| 132 file_path_(file_path), | 133 |
| 134 DISALLOW_COPY_AND_ASSIGN(Y4mFileParser); |
| 135 }; |
| 136 |
| 137 class MjpegFileParser final : public VideoFileParser { |
| 138 public: |
| 139 explicit MjpegFileParser(const base::FilePath& file_path); |
| 140 |
| 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; |
| 145 |
| 146 private: |
| 147 scoped_ptr<base::MemoryMappedFile> mapped_file_; |
| 148 |
| 149 DISALLOW_COPY_AND_ASSIGN(MjpegFileParser); |
| 150 }; |
| 151 |
| 152 VideoFileParser::VideoFileParser(const base::FilePath& file_path) |
| 153 : file_path_(file_path), |
| 133 frame_size_(0), | 154 frame_size_(0), |
| 134 current_byte_index_(0), | 155 current_byte_index_(0), |
| 135 first_frame_byte_index_(0) { | 156 first_frame_byte_index_(0) {} |
| 136 } | 157 |
| 158 VideoFileParser::~VideoFileParser() {} |
| 159 |
| 160 Y4mFileParser::Y4mFileParser(const base::FilePath& file_path) |
| 161 : VideoFileParser(file_path) {} |
| 162 |
| 163 Y4mFileParser::~Y4mFileParser() {} |
| 164 |
| 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; |
| 172 } |
| 173 |
| 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); |
| 178 |
| 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; |
| 184 } |
| 185 |
| 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_); |
| 192 |
| 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; |
| 204 } |
| 205 *frame_size = frame_size_; |
| 206 return video_frame_.get(); |
| 207 } |
| 208 |
| 209 MjpegFileParser::MjpegFileParser(const base::FilePath& file_path) |
| 210 : VideoFileParser(file_path) {} |
| 211 |
| 212 MjpegFileParser::~MjpegFileParser() {} |
| 213 |
| 214 bool MjpegFileParser::Initialize(media::VideoCaptureFormat* capture_format) { |
| 215 mapped_file_.reset(new base::MemoryMappedFile()); |
| 216 |
| 217 if (!mapped_file_->Initialize(file_path_) || !mapped_file_->IsValid()) { |
| 218 LOG(ERROR) << "File memory map error: " << file_path_.value(); |
| 219 return false; |
| 220 } |
| 221 |
| 222 JpegParseResult result; |
| 223 if (!ParseJpegStream(mapped_file_->data(), mapped_file_->length(), &result)) |
| 224 return false; |
| 225 |
| 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; |
| 230 } |
| 231 |
| 232 VideoCaptureFormat format; |
| 233 format.pixel_format = media::VIDEO_CAPTURE_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; |
| 241 } |
| 242 |
| 243 const uint8_t* MjpegFileParser::GetNextFrame(int* frame_size) { |
| 244 const uint8_t* buf_ptr = mapped_file_->data() + current_byte_index_; |
| 245 |
| 246 JpegParseResult result; |
| 247 if (!ParseJpegStream(buf_ptr, mapped_file_->length() - current_byte_index_, |
| 248 &result)) { |
| 249 return nullptr; |
| 250 } |
| 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; |
| 257 } |
| 258 |
| 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; |
| 266 } |
| 267 |
| 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()); |
| 275 |
| 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(); |
| 285 } |
| 286 |
| 287 if (!file_parser->Initialize(video_format)) { |
| 288 file_parser.reset(); |
| 289 } |
| 290 return file_parser.Pass(); |
| 291 } |
| 292 |
| 293 FileVideoCaptureDevice::FileVideoCaptureDevice(const base::FilePath& file_path) |
| 294 : capture_thread_("CaptureThread"), file_path_(file_path) {} |
| 137 | 295 |
| 138 FileVideoCaptureDevice::~FileVideoCaptureDevice() { | 296 FileVideoCaptureDevice::~FileVideoCaptureDevice() { |
| 139 DCHECK(thread_checker_.CalledOnValidThread()); | 297 DCHECK(thread_checker_.CalledOnValidThread()); |
| 140 // Check if the thread is running. | 298 // Check if the thread is running. |
| 141 // This means that the device have not been DeAllocated properly. | 299 // This means that the device have not been DeAllocated properly. |
| 142 CHECK(!capture_thread_.IsRunning()); | 300 CHECK(!capture_thread_.IsRunning()); |
| 143 } | 301 } |
| 144 | 302 |
| 145 void FileVideoCaptureDevice::AllocateAndStart( | 303 void FileVideoCaptureDevice::AllocateAndStart( |
| 146 const VideoCaptureParams& params, | 304 const VideoCaptureParams& params, |
| (...skipping 11 matching lines...) Expand all Loading... |
| 158 void FileVideoCaptureDevice::StopAndDeAllocate() { | 316 void FileVideoCaptureDevice::StopAndDeAllocate() { |
| 159 DCHECK(thread_checker_.CalledOnValidThread()); | 317 DCHECK(thread_checker_.CalledOnValidThread()); |
| 160 CHECK(capture_thread_.IsRunning()); | 318 CHECK(capture_thread_.IsRunning()); |
| 161 | 319 |
| 162 capture_thread_.message_loop()->PostTask( | 320 capture_thread_.message_loop()->PostTask( |
| 163 FROM_HERE, base::Bind(&FileVideoCaptureDevice::OnStopAndDeAllocate, | 321 FROM_HERE, base::Bind(&FileVideoCaptureDevice::OnStopAndDeAllocate, |
| 164 base::Unretained(this))); | 322 base::Unretained(this))); |
| 165 capture_thread_.Stop(); | 323 capture_thread_.Stop(); |
| 166 } | 324 } |
| 167 | 325 |
| 168 int FileVideoCaptureDevice::CalculateFrameSize() const { | |
| 169 DCHECK_EQ(capture_format_.pixel_format, VIDEO_CAPTURE_PIXEL_FORMAT_I420); | |
| 170 DCHECK_EQ(capture_thread_.message_loop(), base::MessageLoop::current()); | |
| 171 return capture_format_.ImageAllocationSize(); | |
| 172 } | |
| 173 | |
| 174 void FileVideoCaptureDevice::OnAllocateAndStart( | 326 void FileVideoCaptureDevice::OnAllocateAndStart( |
| 175 const VideoCaptureParams& params, | 327 const VideoCaptureParams& params, |
| 176 scoped_ptr<VideoCaptureDevice::Client> client) { | 328 scoped_ptr<VideoCaptureDevice::Client> client) { |
| 177 DCHECK_EQ(capture_thread_.message_loop(), base::MessageLoop::current()); | 329 DCHECK_EQ(capture_thread_.message_loop(), base::MessageLoop::current()); |
| 178 | 330 |
| 179 client_ = client.Pass(); | 331 client_ = client.Pass(); |
| 180 | 332 |
| 181 // Open the file and parse the header. Get frame size and format. | 333 DCHECK(!file_parser_); |
| 182 DCHECK(!file_.IsValid()); | 334 file_parser_ = GetVideoFileParser(file_path_, &capture_format_); |
| 183 file_ = OpenFileForRead(file_path_); | 335 if (!file_parser_) { |
| 184 if (!file_.IsValid()) { | |
| 185 client_->OnError("Could not open Video file"); | 336 client_->OnError("Could not open Video file"); |
| 186 return; | 337 return; |
| 187 } | 338 } |
| 188 first_frame_byte_index_ = | 339 |
| 189 ParseFileAndExtractVideoFormat(&file_, &capture_format_); | |
| 190 current_byte_index_ = first_frame_byte_index_; | |
| 191 DVLOG(1) << "Opened video file " << capture_format_.frame_size.ToString() | 340 DVLOG(1) << "Opened video file " << capture_format_.frame_size.ToString() |
| 192 << ", fps: " << capture_format_.frame_rate; | 341 << ", fps: " << capture_format_.frame_rate; |
| 193 | 342 |
| 194 frame_size_ = CalculateFrameSize(); | |
| 195 video_frame_.reset(new uint8[frame_size_]); | |
| 196 | |
| 197 capture_thread_.message_loop()->PostTask( | 343 capture_thread_.message_loop()->PostTask( |
| 198 FROM_HERE, base::Bind(&FileVideoCaptureDevice::OnCaptureTask, | 344 FROM_HERE, base::Bind(&FileVideoCaptureDevice::OnCaptureTask, |
| 199 base::Unretained(this))); | 345 base::Unretained(this))); |
| 200 } | 346 } |
| 201 | 347 |
| 202 void FileVideoCaptureDevice::OnStopAndDeAllocate() { | 348 void FileVideoCaptureDevice::OnStopAndDeAllocate() { |
| 203 DCHECK_EQ(capture_thread_.message_loop(), base::MessageLoop::current()); | 349 DCHECK_EQ(capture_thread_.message_loop(), base::MessageLoop::current()); |
| 204 file_.Close(); | 350 file_parser_.reset(); |
| 205 client_.reset(); | 351 client_.reset(); |
| 206 current_byte_index_ = 0; | |
| 207 first_frame_byte_index_ = 0; | |
| 208 frame_size_ = 0; | |
| 209 next_frame_time_ = base::TimeTicks(); | 352 next_frame_time_ = base::TimeTicks(); |
| 210 video_frame_.reset(); | |
| 211 } | 353 } |
| 212 | 354 |
| 213 void FileVideoCaptureDevice::OnCaptureTask() { | 355 void FileVideoCaptureDevice::OnCaptureTask() { |
| 214 DCHECK_EQ(capture_thread_.message_loop(), base::MessageLoop::current()); | 356 DCHECK_EQ(capture_thread_.message_loop(), base::MessageLoop::current()); |
| 215 if (!client_) | 357 if (!client_) |
| 216 return; | 358 return; |
| 217 int result = | |
| 218 file_.Read(current_byte_index_, | |
| 219 reinterpret_cast<char*>(video_frame_.get()), frame_size_); | |
| 220 | |
| 221 // If we passed EOF to base::File, it will return 0 read characters. In that | |
| 222 // case, reset the pointer and read again. | |
| 223 if (result != frame_size_) { | |
| 224 CHECK_EQ(result, 0); | |
| 225 current_byte_index_ = first_frame_byte_index_; | |
| 226 CHECK_EQ( | |
| 227 file_.Read(current_byte_index_, | |
| 228 reinterpret_cast<char*>(video_frame_.get()), frame_size_), | |
| 229 frame_size_); | |
| 230 } else { | |
| 231 current_byte_index_ += frame_size_ + kY4MSimpleFrameDelimiterSize; | |
| 232 } | |
| 233 | 359 |
| 234 // Give the captured frame to the client. | 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); |
| 235 const base::TimeTicks current_time = base::TimeTicks::Now(); | 365 const base::TimeTicks current_time = base::TimeTicks::Now(); |
| 236 client_->OnIncomingCapturedData(video_frame_.get(), frame_size_, | 366 client_->OnIncomingCapturedData(frame_ptr, frame_size, capture_format_, 0, |
| 237 capture_format_, 0, current_time); | 367 current_time); |
| 238 // Reschedule next CaptureTask. | 368 // Reschedule next CaptureTask. |
| 239 const base::TimeDelta frame_interval = | 369 const base::TimeDelta frame_interval = |
| 240 base::TimeDelta::FromMicroseconds(1E6 / capture_format_.frame_rate); | 370 base::TimeDelta::FromMicroseconds(1E6 / capture_format_.frame_rate); |
| 241 if (next_frame_time_.is_null()) { | 371 if (next_frame_time_.is_null()) { |
| 242 next_frame_time_ = current_time + frame_interval; | 372 next_frame_time_ = current_time + frame_interval; |
| 243 } else { | 373 } else { |
| 244 next_frame_time_ += frame_interval; | 374 next_frame_time_ += frame_interval; |
| 245 // Don't accumulate any debt if we are lagging behind - just post next frame | 375 // Don't accumulate any debt if we are lagging behind - just post next frame |
| 246 // immediately and continue as normal. | 376 // immediately and continue as normal. |
| 247 if (next_frame_time_ < current_time) | 377 if (next_frame_time_ < current_time) |
| 248 next_frame_time_ = current_time; | 378 next_frame_time_ = current_time; |
| 249 } | 379 } |
| 250 base::MessageLoop::current()->PostDelayedTask( | 380 base::MessageLoop::current()->PostDelayedTask( |
| 251 FROM_HERE, base::Bind(&FileVideoCaptureDevice::OnCaptureTask, | 381 FROM_HERE, base::Bind(&FileVideoCaptureDevice::OnCaptureTask, |
| 252 base::Unretained(this)), | 382 base::Unretained(this)), |
| 253 next_frame_time_ - current_time); | 383 next_frame_time_ - current_time); |
| 254 } | 384 } |
| 255 | 385 |
| 256 } // namespace media | 386 } // namespace media |
| OLD | NEW |