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