blob: 668390a78c3a28256b10102134394f4b43464627 [file] [log] [blame]
sprang3911c262016-04-15 01:24:14 -07001/*
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
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020011#include "modules/video_coding/utility/ivf_file_writer.h"
sprang3911c262016-04-15 01:24:14 -070012
palmkviste75f2042016-09-28 06:19:48 -070013#include <utility>
14
Mirko Bonadei04255172018-09-10 16:48:02 +020015#include "api/video_codecs/video_codec.h"
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020016#include "modules/rtp_rtcp/source/byte_io.h"
Nico Grunbaumd525e2d2021-12-08 12:07:09 -080017#include "modules/video_coding/utility/ivf_defines.h"
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020018#include "rtc_base/checks.h"
19#include "rtc_base/logging.h"
sprang3911c262016-04-15 01:24:14 -070020
palmkviste75f2042016-09-28 06:19:48 -070021// TODO(palmkvist): make logging more informative in the absence of a file name
22// (or get one)
23
sprang3911c262016-04-15 01:24:14 -070024namespace webrtc {
25
Shuhai Peng9753fbc2021-12-21 22:04:22 +080026namespace {
27
28constexpr int kDefaultWidth = 1280;
29constexpr int kDefaultHeight = 720;
30} // namespace
31
Niels Möllerb7edf692019-02-08 16:40:53 +010032IvfFileWriter::IvfFileWriter(FileWrapper file, size_t byte_limit)
Kári Tristan Helgason84ccb2d2018-08-16 14:35:26 +020033 : codec_type_(kVideoCodecGeneric),
palmkviste75f2042016-09-28 06:19:48 -070034 bytes_written_(0),
35 byte_limit_(byte_limit),
sprang3911c262016-04-15 01:24:14 -070036 num_frames_(0),
37 width_(0),
38 height_(0),
39 last_timestamp_(-1),
40 using_capture_timestamps_(false),
palmkviste75f2042016-09-28 06:19:48 -070041 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}
sprang3911c262016-04-15 01:24:14 -070045
46IvfFileWriter::~IvfFileWriter() {
47 Close();
48}
49
Niels Möllerb7edf692019-02-08 16:40:53 +010050std::unique_ptr<IvfFileWriter> IvfFileWriter::Wrap(FileWrapper file,
palmkviste75f2042016-09-28 06:19:48 -070051 size_t byte_limit) {
52 return std::unique_ptr<IvfFileWriter>(
53 new IvfFileWriter(std::move(file), byte_limit));
sprang3911c262016-04-15 01:24:14 -070054}
55
56bool IvfFileWriter::WriteHeader() {
Niels Möllerb7edf692019-02-08 16:40:53 +010057 if (!file_.Rewind()) {
Mirko Bonadei675513b2017-11-09 11:09:25 +010058 RTC_LOG(LS_WARNING) << "Unable to rewind ivf output file.";
sprang3911c262016-04-15 01:24:14 -070059 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_) {
kjellander02b3d272016-04-20 05:05:54 -070071 case kVideoCodecVP8:
sprang3911c262016-04-15 01:24:14 -070072 ivf_header[8] = 'V';
73 ivf_header[9] = 'P';
74 ivf_header[10] = '8';
75 ivf_header[11] = '0';
76 break;
kjellander02b3d272016-04-20 05:05:54 -070077 case kVideoCodecVP9:
sprang3911c262016-04-15 01:24:14 -070078 ivf_header[8] = 'V';
79 ivf_header[9] = 'P';
80 ivf_header[10] = '9';
81 ivf_header[11] = '0';
82 break;
Emil Lundmark91c04772020-08-25 11:43:25 +020083 case kVideoCodecAV1:
84 ivf_header[8] = 'A';
85 ivf_header[9] = 'V';
86 ivf_header[10] = '0';
87 ivf_header[11] = '1';
88 break;
kjellander02b3d272016-04-20 05:05:54 -070089 case kVideoCodecH264:
sprang3911c262016-04-15 01:24:14 -070090 ivf_header[8] = 'H';
91 ivf_header[9] = '2';
92 ivf_header[10] = '6';
93 ivf_header[11] = '4';
94 break;
95 default:
Sergey Silkind4b087c2021-07-23 16:28:55 +020096 // 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;
sprang3911c262016-04-15 01:24:14 -0700103 }
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
Niels Möllerb7edf692019-02-08 16:40:53 +0100116 if (!file_.Write(ivf_header, kIvfHeaderSize)) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100117 RTC_LOG(LS_ERROR) << "Unable to write IVF header for ivf output file.";
sprang3911c262016-04-15 01:24:14 -0700118 return false;
119 }
120
palmkviste75f2042016-09-28 06:19:48 -0700121 if (bytes_written_ < kIvfHeaderSize) {
122 bytes_written_ = kIvfHeaderSize;
123 }
124
sprang3911c262016-04-15 01:24:14 -0700125 return true;
126}
127
palmkviste75f2042016-09-28 06:19:48 -0700128bool IvfFileWriter::InitFromFirstFrame(const EncodedImage& encoded_image,
129 VideoCodecType codec_type) {
Shuhai Peng9753fbc2021-12-21 22:04:22 +0800130 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
Niels Möller23775882018-08-16 10:24:12 +0200138 using_capture_timestamps_ = encoded_image.Timestamp() == 0;
sprang3911c262016-04-15 01:24:14 -0700139
palmkviste75f2042016-09-28 06:19:48 -0700140 codec_type_ = codec_type;
141
sprang3911c262016-04-15 01:24:14 -0700142 if (!WriteHeader())
143 return false;
144
Yves Gerey665174f2018-06-19 15:03:05 +0200145 const char* codec_name = CodecTypeToPayloadString(codec_type_);
Mirko Bonadei675513b2017-11-09 11:09:25 +0100146 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.";
sprang3911c262016-04-15 01:24:14 -0700151 return true;
152}
153
palmkviste75f2042016-09-28 06:19:48 -0700154bool IvfFileWriter::WriteFrame(const EncodedImage& encoded_image,
155 VideoCodecType codec_type) {
Niels Möllerb7edf692019-02-08 16:40:53 +0100156 if (!file_.is_open())
sprang3911c262016-04-15 01:24:14 -0700157 return false;
158
palmkviste75f2042016-09-28 06:19:48 -0700159 if (num_frames_ == 0 && !InitFromFirstFrame(encoded_image, codec_type))
160 return false;
161 RTC_DCHECK_EQ(codec_type_, codec_type);
162
sprang3911c262016-04-15 01:24:14 -0700163 if ((encoded_image._encodedWidth > 0 || encoded_image._encodedHeight > 0) &&
164 (encoded_image._encodedHeight != height_ ||
165 encoded_image._encodedWidth != width_)) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100166 RTC_LOG(LS_WARNING)
Elad Alon4cde9ad2019-03-04 17:38:05 +0100167 << "Incoming frame has resolution different from previous: (" << width_
sprang3911c262016-04-15 01:24:14 -0700168 << "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_
Niels Möller23775882018-08-16 10:24:12 +0200174 : wrap_handler_.Unwrap(encoded_image.Timestamp());
sprang3911c262016-04-15 01:24:14 -0700175 if (last_timestamp_ != -1 && timestamp <= last_timestamp_) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100176 RTC_LOG(LS_WARNING) << "Timestamp no increasing: " << last_timestamp_
177 << " -> " << timestamp;
sprang3911c262016-04-15 01:24:14 -0700178 }
179 last_timestamp_ = timestamp;
180
Ilya Nikolaevskiye93b1fe2019-10-08 13:17:09 +0200181 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
204bool IvfFileWriter::WriteOneSpatialLayer(int64_t timestamp,
205 const uint8_t* data,
206 size_t size) {
sprang3911c262016-04-15 01:24:14 -0700207 const size_t kFrameHeaderSize = 12;
palmkviste75f2042016-09-28 06:19:48 -0700208 if (byte_limit_ != 0 &&
Ilya Nikolaevskiye93b1fe2019-10-08 13:17:09 +0200209 bytes_written_ + kFrameHeaderSize + size > byte_limit_) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100210 RTC_LOG(LS_WARNING) << "Closing IVF file due to reaching size limit: "
211 << byte_limit_ << " bytes.";
palmkviste75f2042016-09-28 06:19:48 -0700212 Close();
213 return false;
214 }
sprang3911c262016-04-15 01:24:14 -0700215 uint8_t frame_header[kFrameHeaderSize] = {};
Ilya Nikolaevskiye93b1fe2019-10-08 13:17:09 +0200216 ByteWriter<uint32_t>::WriteLittleEndian(&frame_header[0],
217 static_cast<uint32_t>(size));
sprang3911c262016-04-15 01:24:14 -0700218 ByteWriter<uint64_t>::WriteLittleEndian(&frame_header[4], timestamp);
Niels Möllerb7edf692019-02-08 16:40:53 +0100219 if (!file_.Write(frame_header, kFrameHeaderSize) ||
Ilya Nikolaevskiye93b1fe2019-10-08 13:17:09 +0200220 !file_.Write(data, size)) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100221 RTC_LOG(LS_ERROR) << "Unable to write frame to file.";
sprang3911c262016-04-15 01:24:14 -0700222 return false;
223 }
224
Ilya Nikolaevskiye93b1fe2019-10-08 13:17:09 +0200225 bytes_written_ += kFrameHeaderSize + size;
palmkviste75f2042016-09-28 06:19:48 -0700226
sprang3911c262016-04-15 01:24:14 -0700227 ++num_frames_;
228 return true;
229}
230
231bool IvfFileWriter::Close() {
Niels Möllerb7edf692019-02-08 16:40:53 +0100232 if (!file_.is_open())
sprang3911c262016-04-15 01:24:14 -0700233 return false;
234
235 if (num_frames_ == 0) {
palmkviste75f2042016-09-28 06:19:48 -0700236 file_.Close();
sprang3911c262016-04-15 01:24:14 -0700237 return true;
238 }
239
tommia6219cc2016-06-15 10:30:14 -0700240 bool ret = WriteHeader();
palmkviste75f2042016-09-28 06:19:48 -0700241 file_.Close();
tommia6219cc2016-06-15 10:30:14 -0700242 return ret;
sprang3911c262016-04-15 01:24:14 -0700243}
244
245} // namespace webrtc