blob: 66fed4249e63596422b732dd09486f0c2bf0d4a2 [file] [log] [blame]
Magnus Jedvert10e829a2018-09-05 08:46:181/*
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 08:46:1813#include <cstdio>
14#include <string>
Yves Gerey3e707812018-11-28 15:47:4915#include <vector>
Magnus Jedvert10e829a2018-09-05 08:46:1816
Steve Anton68586e82018-12-14 01:41:2517#include "absl/strings/match.h"
Magnus Jedvert10e829a2018-09-05 08:46:1818#include "absl/types/optional.h"
Niels Möller105711e2022-06-14 13:48:2619#include "api/make_ref_counted.h"
Magnus Jedvert10e829a2018-09-05 08:46:1820#include "api/video/i420_buffer.h"
Yves Gerey3e707812018-11-28 15:47:4921#include "rtc_base/checks.h"
Magnus Jedvert10e829a2018-09-05 08:46:1822#include "rtc_base/logging.h"
Steve Anton10542f22019-01-11 17:11:0023#include "rtc_base/string_encode.h"
Magnus Jedvert10e829a2018-09-05 08:46:1824#include "rtc_base/string_to_number.h"
Magnus Jedvert10e829a2018-09-05 08:46:1825
26namespace webrtc {
27namespace test {
28
29namespace {
30
Hans Wennborgb4f7ab12019-02-13 08:13:2331bool ReadBytes(uint8_t* dst, size_t n, FILE* file) {
32 return fread(reinterpret_cast<char*>(dst), /* size= */ 1, n, file) == n;
33}
34
Magnus Jedvert10e829a2018-09-05 08:46:1835// Common base class for .yuv and .y4m files.
36class VideoFile : public Video {
37 public:
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
47 ~VideoFile() override { fclose(file_); }
48
49 size_t number_of_frames() const override { return frame_positions_.size(); }
50 int width() const override { return width_; }
51 int height() const override { return height_; }
52
53 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_);
Magnus Jedvert10e829a2018-09-05 08:46:1859
Hans Wennborgb4f7ab12019-02-13 08:13:2360 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_)) {
Magnus Jedvert10e829a2018-09-05 08:46:1865 RTC_LOG(LS_ERROR) << "Could not read YUV data for frame " << frame_index;
66 return nullptr;
67 }
Hans Wennborgb4f7ab12019-02-13 08:13:2368
Magnus Jedvert10e829a2018-09-05 08:46:1869 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
81Video::Iterator::Iterator(const rtc::scoped_refptr<const Video>& video,
82 size_t index)
83 : video_(video), index_(index) {}
84
85Video::Iterator::Iterator(const Video::Iterator& other) = default;
86Video::Iterator::Iterator(Video::Iterator&& other) = default;
87Video::Iterator& Video::Iterator::operator=(Video::Iterator&&) = default;
88Video::Iterator& Video::Iterator::operator=(const Video::Iterator&) = default;
89Video::Iterator::~Iterator() = default;
90
91rtc::scoped_refptr<I420BufferInterface> Video::Iterator::operator*() const {
92 return video_->GetFrame(index_);
93}
94bool Video::Iterator::operator==(const Video::Iterator& other) const {
95 return index_ == other.index_;
96}
97bool Video::Iterator::operator!=(const Video::Iterator& other) const {
98 return !(*this == other);
99}
100
101Video::Iterator Video::Iterator::operator++(int) {
102 const Iterator copy = *this;
103 ++*this;
104 return copy;
105}
106
107Video::Iterator& Video::Iterator::operator++() {
108 ++index_;
109 return *this;
110}
111
112Video::Iterator Video::begin() const {
Niels Möller4024f642022-01-13 08:56:55113 return Iterator(rtc::scoped_refptr<const Video>(this), 0);
Magnus Jedvert10e829a2018-09-05 08:46:18114}
115
116Video::Iterator Video::end() const {
Niels Möller4024f642022-01-13 08:56:55117 return Iterator(rtc::scoped_refptr<const Video>(this), number_of_frames());
Magnus Jedvert10e829a2018-09-05 08:46:18118}
119
120rtc::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;
Hans Wennborgb4f7ab12019-02-13 08:13:23128 if (fscanf(file, "YUV4MPEG2 %n", &parse_file_header_result) != 0 ||
129 parse_file_header_result == -1) {
Magnus Jedvert10e829a2018-09-05 08:46:18130 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) {
Byoungchan Lee0c288202022-03-24 08:40:03207 std::array<char, 6> read_buffer;
208 if (fread(read_buffer.data(), 1, read_buffer.size(), file) <
209 read_buffer.size() ||
210 memcmp(read_buffer.data(), "FRAME\n", read_buffer.size()) != 0) {
Magnus Jedvert10e829a2018-09-05 08:46:18211 if (!feof(file)) {
212 RTC_LOG(LS_ERROR) << "Did not find FRAME header, ignoring rest of file";
213 }
214 break;
215 }
216 fpos_t pos;
217 fgetpos(file, &pos);
218 frame_positions.push_back(pos);
219 // Skip over YUV pixel data.
220 fseek(file, i420_frame_size, SEEK_CUR);
221 }
222 if (frame_positions.empty()) {
223 RTC_LOG(LS_ERROR) << "Could not find any frames in the file";
224 return nullptr;
225 }
226 RTC_LOG(LS_INFO) << "Video has " << frame_positions.size() << " frames";
227
Tommi87f70902021-04-27 12:43:08228 return rtc::make_ref_counted<VideoFile>(*width, *height, frame_positions,
229 file);
Magnus Jedvert10e829a2018-09-05 08:46:18230}
231
232rtc::scoped_refptr<Video> OpenYuvFile(const std::string& file_name,
233 int width,
234 int height) {
235 FILE* file = fopen(file_name.c_str(), "rb");
236 if (file == nullptr) {
237 RTC_LOG(LS_ERROR) << "Could not open input file for reading: " << file_name;
238 return nullptr;
239 }
240
241 if (width % 2 != 0 || height % 2 != 0) {
242 RTC_LOG(LS_ERROR)
243 << "Only supports even width/height so that chroma size is a "
244 "whole number.";
245 return nullptr;
246 }
247
248 // Seek to end of file.
249 fseek(file, 0, SEEK_END);
250 const size_t file_size = ftell(file);
251 // Seek back to beginning of file.
252 fseek(file, 0, SEEK_SET);
253
254 const int i420_frame_size = 3 * width * height / 2;
255 const size_t number_of_frames = file_size / i420_frame_size;
256
257 std::vector<fpos_t> frame_positions;
258 for (size_t i = 0; i < number_of_frames; ++i) {
259 fpos_t pos;
260 fgetpos(file, &pos);
261 frame_positions.push_back(pos);
262 fseek(file, i420_frame_size, SEEK_CUR);
263 }
264 if (frame_positions.empty()) {
265 RTC_LOG(LS_ERROR) << "Could not find any frames in the file";
266 return nullptr;
267 }
268 RTC_LOG(LS_INFO) << "Video has " << frame_positions.size() << " frames";
269
Tommi87f70902021-04-27 12:43:08270 return rtc::make_ref_counted<VideoFile>(width, height, frame_positions, file);
Magnus Jedvert10e829a2018-09-05 08:46:18271}
272
273rtc::scoped_refptr<Video> OpenYuvOrY4mFile(const std::string& file_name,
274 int width,
275 int height) {
Steve Anton68586e82018-12-14 01:41:25276 if (absl::EndsWith(file_name, ".yuv"))
Magnus Jedvert10e829a2018-09-05 08:46:18277 return OpenYuvFile(file_name, width, height);
Steve Anton68586e82018-12-14 01:41:25278 if (absl::EndsWith(file_name, ".y4m"))
Magnus Jedvertb468ace2018-09-05 14:11:48279 return OpenY4mFile(file_name);
Magnus Jedvert10e829a2018-09-05 08:46:18280
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