1 /*
2 * Copyright (c) 2017 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 <stdio.h>
12
13 #include <string>
14
15 #include "api/scoped_refptr.h"
16 #include "api/video/i420_buffer.h"
17 #include "rtc_base/logging.h"
18 #include "test/frame_utils.h"
19 #include "test/testsupport/file_utils.h"
20 #include "test/testsupport/frame_reader.h"
21
22 namespace webrtc {
23 namespace test {
FrameSizeBytes(int width,int height)24 size_t FrameSizeBytes(int width, int height) {
25 int half_width = (width + 1) / 2;
26 size_t size_y = static_cast<size_t>(width) * height;
27 size_t size_uv = static_cast<size_t>(half_width) * ((height + 1) / 2);
28 return size_y + 2 * size_uv;
29 }
30
DropperUtil(int source_fps,int target_fps)31 YuvFrameReaderImpl::DropperUtil::DropperUtil(int source_fps, int target_fps)
32 : frame_size_buckets_(
33 std::max(1.0, static_cast<double>(source_fps) / target_fps)),
34 bucket_level_(0.0) {}
35
36 YuvFrameReaderImpl::DropperUtil::DropDecision
UpdateLevel()37 YuvFrameReaderImpl::DropperUtil::UpdateLevel() {
38 DropDecision decision;
39 if (bucket_level_ <= 0.0) {
40 decision = DropDecision::kKeepFrame;
41 bucket_level_ += frame_size_buckets_;
42 } else {
43 decision = DropDecision::kDropframe;
44 }
45 bucket_level_ -= 1.0;
46 return decision;
47 }
48
YuvFrameReaderImpl(std::string input_filename,int width,int height)49 YuvFrameReaderImpl::YuvFrameReaderImpl(std::string input_filename,
50 int width,
51 int height)
52 : YuvFrameReaderImpl(input_filename,
53 width,
54 height,
55 width,
56 height,
57 RepeatMode::kSingle,
58 30,
59 30) {}
YuvFrameReaderImpl(std::string input_filename,int input_width,int input_height,int desired_width,int desired_height,RepeatMode repeat_mode,absl::optional<int> clip_fps,int target_fps)60 YuvFrameReaderImpl::YuvFrameReaderImpl(std::string input_filename,
61 int input_width,
62 int input_height,
63 int desired_width,
64 int desired_height,
65 RepeatMode repeat_mode,
66 absl::optional<int> clip_fps,
67 int target_fps)
68 : input_filename_(input_filename),
69 frame_length_in_bytes_(input_width * input_height +
70 2 * ((input_width + 1) / 2) *
71 ((input_height + 1) / 2)),
72 input_width_(input_width),
73 input_height_(input_height),
74 desired_width_(desired_width),
75 desired_height_(desired_height),
76 frame_size_bytes_(FrameSizeBytes(input_width, input_height)),
77 repeat_mode_(repeat_mode),
78 number_of_frames_(-1),
79 current_frame_index_(-1),
80 dropper_(clip_fps.has_value() ? new DropperUtil(*clip_fps, target_fps)
81 : nullptr),
82 input_file_(nullptr) {}
83
~YuvFrameReaderImpl()84 YuvFrameReaderImpl::~YuvFrameReaderImpl() {
85 Close();
86 }
87
Init()88 bool YuvFrameReaderImpl::Init() {
89 if (input_width_ <= 0 || input_height_ <= 0) {
90 RTC_LOG(LS_ERROR) << "Frame width and height must be positive. Was: "
91 << input_width_ << "x" << input_height_;
92 return false;
93 }
94 input_file_ = fopen(input_filename_.c_str(), "rb");
95 if (input_file_ == nullptr) {
96 RTC_LOG(LS_ERROR) << "Couldn't open input file: "
97 << input_filename_.c_str();
98 return false;
99 }
100 // Calculate total number of frames.
101 size_t source_file_size = GetFileSize(input_filename_);
102 if (source_file_size <= 0u) {
103 RTC_LOG(LS_ERROR) << "Input file " << input_filename_.c_str()
104 << " is empty.";
105 return false;
106 }
107 number_of_frames_ =
108 static_cast<int>(source_file_size / frame_length_in_bytes_);
109
110 if (number_of_frames_ == 0) {
111 RTC_LOG(LS_ERROR) << "Input file " << input_filename_.c_str()
112 << " is too small.";
113 }
114
115 current_frame_index_ = 0;
116 return true;
117 }
118
ReadFrame()119 rtc::scoped_refptr<I420Buffer> YuvFrameReaderImpl::ReadFrame() {
120 if (input_file_ == nullptr) {
121 RTC_LOG(LS_ERROR) << "YuvFrameReaderImpl is not initialized.";
122 return nullptr;
123 }
124
125 rtc::scoped_refptr<I420Buffer> buffer;
126
127 do {
128 if (current_frame_index_ >= number_of_frames_) {
129 switch (repeat_mode_) {
130 case RepeatMode::kSingle:
131 return nullptr;
132 case RepeatMode::kRepeat:
133 fseek(input_file_, 0, SEEK_SET);
134 current_frame_index_ = 0;
135 break;
136 case RepeatMode::kPingPong:
137 if (current_frame_index_ == number_of_frames_ * 2) {
138 fseek(input_file_, 0, SEEK_SET);
139 current_frame_index_ = 0;
140 } else {
141 int reverse_frame_index = current_frame_index_ - number_of_frames_;
142 int seek_frame_pos = (number_of_frames_ - reverse_frame_index - 1);
143 fseek(input_file_, seek_frame_pos * frame_size_bytes_, SEEK_SET);
144 }
145 break;
146 }
147 }
148 ++current_frame_index_;
149
150 buffer = ReadI420Buffer(input_width_, input_height_, input_file_);
151 if (!buffer && ferror(input_file_)) {
152 RTC_LOG(LS_ERROR) << "Couldn't read frame from file: "
153 << input_filename_.c_str();
154 }
155 } while (dropper_ &&
156 dropper_->UpdateLevel() == DropperUtil::DropDecision::kDropframe);
157
158 if (input_width_ == desired_width_ && input_height_ == desired_height_) {
159 return buffer;
160 }
161
162 rtc::scoped_refptr<I420Buffer> rescaled_buffer(
163 I420Buffer::Create(desired_width_, desired_height_));
164 rescaled_buffer->ScaleFrom(*buffer.get());
165
166 return rescaled_buffer;
167 }
168
Close()169 void YuvFrameReaderImpl::Close() {
170 if (input_file_ != nullptr) {
171 fclose(input_file_);
172 input_file_ = nullptr;
173 }
174 }
175
FrameLength()176 size_t YuvFrameReaderImpl::FrameLength() {
177 return frame_length_in_bytes_;
178 }
179
NumberOfFrames()180 int YuvFrameReaderImpl::NumberOfFrames() {
181 return number_of_frames_;
182 }
183
184 } // namespace test
185 } // namespace webrtc
186