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