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