1 /*
2 * Copyright (c) 2016 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 "modules/video_coding/utility/ivf_file_writer.h"
12
13 #include <utility>
14
15 #include "api/video_codecs/video_codec.h"
16 #include "modules/rtp_rtcp/source/byte_io.h"
17 #include "modules/video_coding/utility/ivf_defines.h"
18 #include "rtc_base/checks.h"
19 #include "rtc_base/logging.h"
20
21 // TODO(palmkvist): make logging more informative in the absence of a file name
22 // (or get one)
23
24 namespace webrtc {
25
26 namespace {
27
28 constexpr int kDefaultWidth = 1280;
29 constexpr int kDefaultHeight = 720;
30 } // namespace
31
IvfFileWriter(FileWrapper file,size_t byte_limit)32 IvfFileWriter::IvfFileWriter(FileWrapper file, size_t byte_limit)
33 : codec_type_(kVideoCodecGeneric),
34 bytes_written_(0),
35 byte_limit_(byte_limit),
36 num_frames_(0),
37 width_(0),
38 height_(0),
39 last_timestamp_(-1),
40 using_capture_timestamps_(false),
41 file_(std::move(file)) {
42 RTC_DCHECK(byte_limit == 0 || kIvfHeaderSize <= byte_limit)
43 << "The byte_limit is too low, not even the header will fit.";
44 }
45
~IvfFileWriter()46 IvfFileWriter::~IvfFileWriter() {
47 Close();
48 }
49
Wrap(FileWrapper file,size_t byte_limit)50 std::unique_ptr<IvfFileWriter> IvfFileWriter::Wrap(FileWrapper file,
51 size_t byte_limit) {
52 return std::unique_ptr<IvfFileWriter>(
53 new IvfFileWriter(std::move(file), byte_limit));
54 }
55
WriteHeader()56 bool IvfFileWriter::WriteHeader() {
57 if (!file_.Rewind()) {
58 RTC_LOG(LS_WARNING) << "Unable to rewind ivf output file.";
59 return false;
60 }
61
62 uint8_t ivf_header[kIvfHeaderSize] = {0};
63 ivf_header[0] = 'D';
64 ivf_header[1] = 'K';
65 ivf_header[2] = 'I';
66 ivf_header[3] = 'F';
67 ByteWriter<uint16_t>::WriteLittleEndian(&ivf_header[4], 0); // Version.
68 ByteWriter<uint16_t>::WriteLittleEndian(&ivf_header[6], 32); // Header size.
69
70 switch (codec_type_) {
71 case kVideoCodecVP8:
72 ivf_header[8] = 'V';
73 ivf_header[9] = 'P';
74 ivf_header[10] = '8';
75 ivf_header[11] = '0';
76 break;
77 case kVideoCodecVP9:
78 ivf_header[8] = 'V';
79 ivf_header[9] = 'P';
80 ivf_header[10] = '9';
81 ivf_header[11] = '0';
82 break;
83 case kVideoCodecAV1:
84 ivf_header[8] = 'A';
85 ivf_header[9] = 'V';
86 ivf_header[10] = '0';
87 ivf_header[11] = '1';
88 break;
89 case kVideoCodecH264:
90 ivf_header[8] = 'H';
91 ivf_header[9] = '2';
92 ivf_header[10] = '6';
93 ivf_header[11] = '4';
94 break;
95 default:
96 // For unknown codec type use **** code. You can specify actual payload
97 // format when playing the video with ffplay: ffplay -f H263 file.ivf
98 ivf_header[8] = '*';
99 ivf_header[9] = '*';
100 ivf_header[10] = '*';
101 ivf_header[11] = '*';
102 break;
103 }
104
105 ByteWriter<uint16_t>::WriteLittleEndian(&ivf_header[12], width_);
106 ByteWriter<uint16_t>::WriteLittleEndian(&ivf_header[14], height_);
107 // Render timestamps are in ms (1/1000 scale), while RTP timestamps use a
108 // 90kHz clock.
109 ByteWriter<uint32_t>::WriteLittleEndian(
110 &ivf_header[16], using_capture_timestamps_ ? 1000 : 90000);
111 ByteWriter<uint32_t>::WriteLittleEndian(&ivf_header[20], 1);
112 ByteWriter<uint32_t>::WriteLittleEndian(&ivf_header[24],
113 static_cast<uint32_t>(num_frames_));
114 ByteWriter<uint32_t>::WriteLittleEndian(&ivf_header[28], 0); // Reserved.
115
116 if (!file_.Write(ivf_header, kIvfHeaderSize)) {
117 RTC_LOG(LS_ERROR) << "Unable to write IVF header for ivf output file.";
118 return false;
119 }
120
121 if (bytes_written_ < kIvfHeaderSize) {
122 bytes_written_ = kIvfHeaderSize;
123 }
124
125 return true;
126 }
127
InitFromFirstFrame(const EncodedImage & encoded_image,VideoCodecType codec_type)128 bool IvfFileWriter::InitFromFirstFrame(const EncodedImage& encoded_image,
129 VideoCodecType codec_type) {
130 if (encoded_image._encodedWidth == 0 || encoded_image._encodedHeight == 0) {
131 width_ = kDefaultWidth;
132 height_ = kDefaultHeight;
133 } else {
134 width_ = encoded_image._encodedWidth;
135 height_ = encoded_image._encodedHeight;
136 }
137
138 using_capture_timestamps_ = encoded_image.Timestamp() == 0;
139
140 codec_type_ = codec_type;
141
142 if (!WriteHeader())
143 return false;
144
145 const char* codec_name = CodecTypeToPayloadString(codec_type_);
146 RTC_LOG(LS_WARNING) << "Created IVF file for codec data of type "
147 << codec_name << " at resolution " << width_ << " x "
148 << height_ << ", using "
149 << (using_capture_timestamps_ ? "1" : "90")
150 << "kHz clock resolution.";
151 return true;
152 }
153
WriteFrame(const EncodedImage & encoded_image,VideoCodecType codec_type)154 bool IvfFileWriter::WriteFrame(const EncodedImage& encoded_image,
155 VideoCodecType codec_type) {
156 if (!file_.is_open())
157 return false;
158
159 if (num_frames_ == 0 && !InitFromFirstFrame(encoded_image, codec_type))
160 return false;
161 RTC_DCHECK_EQ(codec_type_, codec_type);
162
163 if ((encoded_image._encodedWidth > 0 || encoded_image._encodedHeight > 0) &&
164 (encoded_image._encodedHeight != height_ ||
165 encoded_image._encodedWidth != width_)) {
166 RTC_LOG(LS_WARNING)
167 << "Incoming frame has resolution different from previous: (" << width_
168 << "x" << height_ << ") -> (" << encoded_image._encodedWidth << "x"
169 << encoded_image._encodedHeight << ")";
170 }
171
172 int64_t timestamp = using_capture_timestamps_
173 ? encoded_image.capture_time_ms_
174 : wrap_handler_.Unwrap(encoded_image.Timestamp());
175 if (last_timestamp_ != -1 && timestamp <= last_timestamp_) {
176 RTC_LOG(LS_WARNING) << "Timestamp no increasing: " << last_timestamp_
177 << " -> " << timestamp;
178 }
179 last_timestamp_ = timestamp;
180
181 bool written_frames = false;
182 size_t max_sl_index = encoded_image.SpatialIndex().value_or(0);
183 const uint8_t* data = encoded_image.data();
184 for (size_t sl_idx = 0; sl_idx <= max_sl_index; ++sl_idx) {
185 size_t cur_size = encoded_image.SpatialLayerFrameSize(sl_idx).value_or(0);
186 if (cur_size > 0) {
187 written_frames = true;
188 if (!WriteOneSpatialLayer(timestamp, data, cur_size)) {
189 return false;
190 }
191 data += cur_size;
192 }
193 }
194
195 // If frame has only one spatial layer it won't have any spatial layers'
196 // sizes. Therefore this case should be addressed separately.
197 if (!written_frames) {
198 return WriteOneSpatialLayer(timestamp, data, encoded_image.size());
199 } else {
200 return true;
201 }
202 }
203
WriteOneSpatialLayer(int64_t timestamp,const uint8_t * data,size_t size)204 bool IvfFileWriter::WriteOneSpatialLayer(int64_t timestamp,
205 const uint8_t* data,
206 size_t size) {
207 const size_t kFrameHeaderSize = 12;
208 if (byte_limit_ != 0 &&
209 bytes_written_ + kFrameHeaderSize + size > byte_limit_) {
210 RTC_LOG(LS_WARNING) << "Closing IVF file due to reaching size limit: "
211 << byte_limit_ << " bytes.";
212 Close();
213 return false;
214 }
215 uint8_t frame_header[kFrameHeaderSize] = {};
216 ByteWriter<uint32_t>::WriteLittleEndian(&frame_header[0],
217 static_cast<uint32_t>(size));
218 ByteWriter<uint64_t>::WriteLittleEndian(&frame_header[4], timestamp);
219 if (!file_.Write(frame_header, kFrameHeaderSize) ||
220 !file_.Write(data, size)) {
221 RTC_LOG(LS_ERROR) << "Unable to write frame to file.";
222 return false;
223 }
224
225 bytes_written_ += kFrameHeaderSize + size;
226
227 ++num_frames_;
228 return true;
229 }
230
Close()231 bool IvfFileWriter::Close() {
232 if (!file_.is_open())
233 return false;
234
235 if (num_frames_ == 0) {
236 file_.Close();
237 return true;
238 }
239
240 bool ret = WriteHeader();
241 file_.Close();
242 return ret;
243 }
244
245 } // namespace webrtc
246