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