• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  *  Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.
3  *
4  *  Use of this source code is governed by a BSD-style license
5  *  that can be found in the LICENSE file in the root of the source
6  *  tree. An additional intellectual property rights grant can be found
7  *  in the file PATENTS.  All contributing project authors may
8  *  be found in the AUTHORS file in the root of the source tree.
9  */
10 
11 #include "rtc_tools/video_file_reader.h"
12 
13 #include <cstdio>
14 #include <string>
15 #include <vector>
16 
17 #include "absl/strings/match.h"
18 #include "absl/types/optional.h"
19 #include "api/video/i420_buffer.h"
20 #include "rtc_base/checks.h"
21 #include "rtc_base/logging.h"
22 #include "rtc_base/ref_counted_object.h"
23 #include "rtc_base/string_encode.h"
24 #include "rtc_base/string_to_number.h"
25 
26 namespace webrtc {
27 namespace test {
28 
29 namespace {
30 
ReadBytes(uint8_t * dst,size_t n,FILE * file)31 bool ReadBytes(uint8_t* dst, size_t n, FILE* file) {
32   return fread(reinterpret_cast<char*>(dst), /* size= */ 1, n, file) == n;
33 }
34 
35 // Common base class for .yuv and .y4m files.
36 class VideoFile : public Video {
37  public:
VideoFile(int width,int height,const std::vector<fpos_t> & frame_positions,FILE * file)38   VideoFile(int width,
39             int height,
40             const std::vector<fpos_t>& frame_positions,
41             FILE* file)
42       : width_(width),
43         height_(height),
44         frame_positions_(frame_positions),
45         file_(file) {}
46 
~VideoFile()47   ~VideoFile() override { fclose(file_); }
48 
number_of_frames() const49   size_t number_of_frames() const override { return frame_positions_.size(); }
width() const50   int width() const override { return width_; }
height() const51   int height() const override { return height_; }
52 
GetFrame(size_t frame_index) const53   rtc::scoped_refptr<I420BufferInterface> GetFrame(
54       size_t frame_index) const override {
55     RTC_CHECK_LT(frame_index, frame_positions_.size());
56 
57     fsetpos(file_, &frame_positions_[frame_index]);
58     rtc::scoped_refptr<I420Buffer> buffer = I420Buffer::Create(width_, height_);
59 
60     if (!ReadBytes(buffer->MutableDataY(), width_ * height_, file_) ||
61         !ReadBytes(buffer->MutableDataU(),
62                    buffer->ChromaWidth() * buffer->ChromaHeight(), file_) ||
63         !ReadBytes(buffer->MutableDataV(),
64                    buffer->ChromaWidth() * buffer->ChromaHeight(), file_)) {
65       RTC_LOG(LS_ERROR) << "Could not read YUV data for frame " << frame_index;
66       return nullptr;
67     }
68 
69     return buffer;
70   }
71 
72  private:
73   const int width_;
74   const int height_;
75   const std::vector<fpos_t> frame_positions_;
76   FILE* const file_;
77 };
78 
79 }  // namespace
80 
Iterator(const rtc::scoped_refptr<const Video> & video,size_t index)81 Video::Iterator::Iterator(const rtc::scoped_refptr<const Video>& video,
82                           size_t index)
83     : video_(video), index_(index) {}
84 
85 Video::Iterator::Iterator(const Video::Iterator& other) = default;
86 Video::Iterator::Iterator(Video::Iterator&& other) = default;
87 Video::Iterator& Video::Iterator::operator=(Video::Iterator&&) = default;
88 Video::Iterator& Video::Iterator::operator=(const Video::Iterator&) = default;
89 Video::Iterator::~Iterator() = default;
90 
operator *() const91 rtc::scoped_refptr<I420BufferInterface> Video::Iterator::operator*() const {
92   return video_->GetFrame(index_);
93 }
operator ==(const Video::Iterator & other) const94 bool Video::Iterator::operator==(const Video::Iterator& other) const {
95   return index_ == other.index_;
96 }
operator !=(const Video::Iterator & other) const97 bool Video::Iterator::operator!=(const Video::Iterator& other) const {
98   return !(*this == other);
99 }
100 
operator ++(int)101 Video::Iterator Video::Iterator::operator++(int) {
102   const Iterator copy = *this;
103   ++*this;
104   return copy;
105 }
106 
operator ++()107 Video::Iterator& Video::Iterator::operator++() {
108   ++index_;
109   return *this;
110 }
111 
begin() const112 Video::Iterator Video::begin() const {
113   return Iterator(this, 0);
114 }
115 
end() const116 Video::Iterator Video::end() const {
117   return Iterator(this, number_of_frames());
118 }
119 
OpenY4mFile(const std::string & file_name)120 rtc::scoped_refptr<Video> OpenY4mFile(const std::string& file_name) {
121   FILE* file = fopen(file_name.c_str(), "rb");
122   if (file == nullptr) {
123     RTC_LOG(LS_ERROR) << "Could not open input file for reading: " << file_name;
124     return nullptr;
125   }
126 
127   int parse_file_header_result = -1;
128   if (fscanf(file, "YUV4MPEG2 %n", &parse_file_header_result) != 0 ||
129       parse_file_header_result == -1) {
130     RTC_LOG(LS_ERROR) << "File " << file_name
131                       << " does not start with YUV4MPEG2 header";
132     return nullptr;
133   }
134 
135   std::string header_line;
136   while (true) {
137     const int c = fgetc(file);
138     if (c == EOF) {
139       RTC_LOG(LS_ERROR) << "Could not read header line";
140       return nullptr;
141     }
142     if (c == '\n')
143       break;
144     header_line.push_back(static_cast<char>(c));
145   }
146 
147   absl::optional<int> width;
148   absl::optional<int> height;
149   absl::optional<float> fps;
150 
151   std::vector<std::string> fields;
152   rtc::tokenize(header_line, ' ', &fields);
153   for (const std::string& field : fields) {
154     const char prefix = field.front();
155     const std::string suffix = field.substr(1);
156     switch (prefix) {
157       case 'W':
158         width = rtc::StringToNumber<int>(suffix);
159         break;
160       case 'H':
161         height = rtc::StringToNumber<int>(suffix);
162         break;
163       case 'C':
164         if (suffix != "420" && suffix != "420mpeg2") {
165           RTC_LOG(LS_ERROR)
166               << "Does not support any other color space than I420 or "
167                  "420mpeg2, but was: "
168               << suffix;
169           return nullptr;
170         }
171         break;
172       case 'F': {
173         std::vector<std::string> fraction;
174         rtc::tokenize(suffix, ':', &fraction);
175         if (fraction.size() == 2) {
176           const absl::optional<int> numerator =
177               rtc::StringToNumber<int>(fraction[0]);
178           const absl::optional<int> denominator =
179               rtc::StringToNumber<int>(fraction[1]);
180           if (numerator && denominator && *denominator != 0)
181             fps = *numerator / static_cast<float>(*denominator);
182           break;
183         }
184       }
185     }
186   }
187   if (!width || !height) {
188     RTC_LOG(LS_ERROR) << "Could not find width and height in file header";
189     return nullptr;
190   }
191   if (!fps) {
192     RTC_LOG(LS_ERROR) << "Could not find fps in file header";
193     return nullptr;
194   }
195   RTC_LOG(LS_INFO) << "Video has resolution: " << *width << "x" << *height
196                    << " " << *fps << " fps";
197   if (*width % 2 != 0 || *height % 2 != 0) {
198     RTC_LOG(LS_ERROR)
199         << "Only supports even width/height so that chroma size is a "
200            "whole number.";
201     return nullptr;
202   }
203 
204   const int i420_frame_size = 3 * *width * *height / 2;
205   std::vector<fpos_t> frame_positions;
206   while (true) {
207     int parse_frame_header_result = -1;
208     if (fscanf(file, "FRAME\n%n", &parse_frame_header_result) != 0 ||
209         parse_frame_header_result == -1) {
210       if (!feof(file)) {
211         RTC_LOG(LS_ERROR) << "Did not find FRAME header, ignoring rest of file";
212       }
213       break;
214     }
215     fpos_t pos;
216     fgetpos(file, &pos);
217     frame_positions.push_back(pos);
218     // Skip over YUV pixel data.
219     fseek(file, i420_frame_size, SEEK_CUR);
220   }
221   if (frame_positions.empty()) {
222     RTC_LOG(LS_ERROR) << "Could not find any frames in the file";
223     return nullptr;
224   }
225   RTC_LOG(LS_INFO) << "Video has " << frame_positions.size() << " frames";
226 
227   return new rtc::RefCountedObject<VideoFile>(*width, *height, frame_positions,
228                                               file);
229 }
230 
OpenYuvFile(const std::string & file_name,int width,int height)231 rtc::scoped_refptr<Video> OpenYuvFile(const std::string& file_name,
232                                       int width,
233                                       int height) {
234   FILE* file = fopen(file_name.c_str(), "rb");
235   if (file == nullptr) {
236     RTC_LOG(LS_ERROR) << "Could not open input file for reading: " << file_name;
237     return nullptr;
238   }
239 
240   if (width % 2 != 0 || height % 2 != 0) {
241     RTC_LOG(LS_ERROR)
242         << "Only supports even width/height so that chroma size is a "
243            "whole number.";
244     return nullptr;
245   }
246 
247   // Seek to end of file.
248   fseek(file, 0, SEEK_END);
249   const size_t file_size = ftell(file);
250   // Seek back to beginning of file.
251   fseek(file, 0, SEEK_SET);
252 
253   const int i420_frame_size = 3 * width * height / 2;
254   const size_t number_of_frames = file_size / i420_frame_size;
255 
256   std::vector<fpos_t> frame_positions;
257   for (size_t i = 0; i < number_of_frames; ++i) {
258     fpos_t pos;
259     fgetpos(file, &pos);
260     frame_positions.push_back(pos);
261     fseek(file, i420_frame_size, SEEK_CUR);
262   }
263   if (frame_positions.empty()) {
264     RTC_LOG(LS_ERROR) << "Could not find any frames in the file";
265     return nullptr;
266   }
267   RTC_LOG(LS_INFO) << "Video has " << frame_positions.size() << " frames";
268 
269   return new rtc::RefCountedObject<VideoFile>(width, height, frame_positions,
270                                               file);
271 }
272 
OpenYuvOrY4mFile(const std::string & file_name,int width,int height)273 rtc::scoped_refptr<Video> OpenYuvOrY4mFile(const std::string& file_name,
274                                            int width,
275                                            int height) {
276   if (absl::EndsWith(file_name, ".yuv"))
277     return OpenYuvFile(file_name, width, height);
278   if (absl::EndsWith(file_name, ".y4m"))
279     return OpenY4mFile(file_name);
280 
281   RTC_LOG(LS_ERROR) << "Video file does not end in either .yuv or .y4m: "
282                     << file_name;
283 
284   return nullptr;
285 }
286 
287 }  // namespace test
288 }  // namespace webrtc
289