Home | History | Annotate | Download | only in capture
      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/video/capture/file_video_capture_device.h"
      6 
      7 #include <string>
      8 
      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"
     13 
     14 namespace media {
     15 static const int kY4MHeaderMaxSize = 200;
     16 static const char kY4MSimpleFrameDelimiter[] = "FRAME";
     17 static const int kY4MSimpleFrameDelimiterSize = 6;
     18 
     19 int ParseY4MInt(const base::StringPiece& token) {
     20   int temp_int;
     21   CHECK(base::StringToInt(token, &temp_int)) << token;
     22   return temp_int;
     23 }
     24 
     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);
     35 }
     36 
     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;
     78       }
     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;
     92     }
     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;
     97   }
     98   // Last video format semantic correctness check before sending it back.
     99   CHECK(video_format->IsValid());
    100 }
    101 
    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);
    112 
    113   size_t header_end = header.find(kY4MSimpleFrameDelimiter);
    114   CHECK_NE(header_end, header.npos);
    115 
    116   ParseY4MTags(header, video_format);
    117   return header_end + kY4MSimpleFrameDelimiterSize;
    118 }
    119 
    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   CHECK(file.IsValid()) << file_path.value();
    127   return file.Pass();
    128 }
    129 
    130 FileVideoCaptureDevice::FileVideoCaptureDevice(const base::FilePath& file_path)
    131     : capture_thread_("CaptureThread"),
    132       file_path_(file_path),
    133       frame_size_(0),
    134       current_byte_index_(0),
    135       first_frame_byte_index_(0) {}
    136 
    137 FileVideoCaptureDevice::~FileVideoCaptureDevice() {
    138   DCHECK(thread_checker_.CalledOnValidThread());
    139   // Check if the thread is running.
    140   // This means that the device have not been DeAllocated properly.
    141   CHECK(!capture_thread_.IsRunning());
    142 }
    143 
    144 void FileVideoCaptureDevice::AllocateAndStart(
    145     const VideoCaptureParams& params,
    146     scoped_ptr<VideoCaptureDevice::Client> client) {
    147   DCHECK(thread_checker_.CalledOnValidThread());
    148   CHECK(!capture_thread_.IsRunning());
    149 
    150   capture_thread_.Start();
    151   capture_thread_.message_loop()->PostTask(
    152       FROM_HERE,
    153       base::Bind(&FileVideoCaptureDevice::OnAllocateAndStart,
    154                  base::Unretained(this),
    155                  params,
    156                  base::Passed(&client)));
    157 }
    158 
    159 void FileVideoCaptureDevice::StopAndDeAllocate() {
    160   DCHECK(thread_checker_.CalledOnValidThread());
    161   CHECK(capture_thread_.IsRunning());
    162 
    163   capture_thread_.message_loop()->PostTask(
    164       FROM_HERE,
    165       base::Bind(&FileVideoCaptureDevice::OnStopAndDeAllocate,
    166                  base::Unretained(this)));
    167   capture_thread_.Stop();
    168 }
    169 
    170 int FileVideoCaptureDevice::CalculateFrameSize() {
    171   DCHECK_EQ(capture_format_.pixel_format, PIXEL_FORMAT_I420);
    172   DCHECK_EQ(capture_thread_.message_loop(), base::MessageLoop::current());
    173   return capture_format_.frame_size.GetArea() * 12 / 8;
    174 }
    175 
    176 void FileVideoCaptureDevice::OnAllocateAndStart(
    177     const VideoCaptureParams& params,
    178     scoped_ptr<VideoCaptureDevice::Client> client) {
    179   DCHECK_EQ(capture_thread_.message_loop(), base::MessageLoop::current());
    180 
    181   client_ = client.Pass();
    182 
    183   // Open the file and parse the header. Get frame size and format.
    184   DCHECK(!file_.IsValid());
    185   file_ = OpenFileForRead(file_path_);
    186   first_frame_byte_index_ =
    187       ParseFileAndExtractVideoFormat(&file_, &capture_format_);
    188   current_byte_index_ = first_frame_byte_index_;
    189   DVLOG(1) << "Opened video file " << capture_format_.frame_size.ToString()
    190            << ", fps: " << capture_format_.frame_rate;
    191 
    192   frame_size_ = CalculateFrameSize();
    193   video_frame_.reset(new uint8[frame_size_]);
    194 
    195   capture_thread_.message_loop()->PostTask(
    196       FROM_HERE,
    197       base::Bind(&FileVideoCaptureDevice::OnCaptureTask,
    198                  base::Unretained(this)));
    199 }
    200 
    201 void FileVideoCaptureDevice::OnStopAndDeAllocate() {
    202   DCHECK_EQ(capture_thread_.message_loop(), base::MessageLoop::current());
    203   file_.Close();
    204   client_.reset();
    205   current_byte_index_ = 0;
    206   first_frame_byte_index_ = 0;
    207   frame_size_ = 0;
    208   video_frame_.reset();
    209 }
    210 
    211 void FileVideoCaptureDevice::OnCaptureTask() {
    212   DCHECK_EQ(capture_thread_.message_loop(), base::MessageLoop::current());
    213   if (!client_)
    214     return;
    215   int result = file_.Read(current_byte_index_,
    216                           reinterpret_cast<char*>(video_frame_.get()),
    217                           frame_size_);
    218 
    219   // If we passed EOF to base::File, it will return 0 read characters. In that
    220   // case, reset the pointer and read again.
    221   if (result != frame_size_) {
    222     CHECK_EQ(result, 0);
    223     current_byte_index_ = first_frame_byte_index_;
    224     CHECK_EQ(file_.Read(current_byte_index_,
    225                         reinterpret_cast<char*>(video_frame_.get()),
    226                         frame_size_),
    227              frame_size_);
    228   } else {
    229     current_byte_index_ += frame_size_ + kY4MSimpleFrameDelimiterSize;
    230   }
    231 
    232   // Give the captured frame to the client.
    233   client_->OnIncomingCapturedData(video_frame_.get(),
    234                                   frame_size_,
    235                                   capture_format_,
    236                                   0,
    237                                   base::TimeTicks::Now());
    238   // Reschedule next CaptureTask.
    239   base::MessageLoop::current()->PostDelayedTask(
    240       FROM_HERE,
    241       base::Bind(&FileVideoCaptureDevice::OnCaptureTask,
    242                  base::Unretained(this)),
    243       base::TimeDelta::FromSeconds(1) / capture_format_.frame_rate);
    244 }
    245 
    246 }  // namespace media
    247