blob: 77c90ee158184b79f50f0fe290cabee3eb90f398 [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"
17#include "rtc_base/checks.h"
18#include "rtc_base/logging.h"
sprang3911c262016-04-15 01:24:14 -070019
palmkviste75f2042016-09-28 06:19:48 -070020// TODO(palmkvist): make logging more informative in the absence of a file name
21// (or get one)
22
sprang3911c262016-04-15 01:24:14 -070023namespace webrtc {
24
palmkviste75f2042016-09-28 06:19:48 -070025const size_t kIvfHeaderSize = 32;
26
Niels Möllerb7edf692019-02-08 16:40:53 +010027IvfFileWriter::IvfFileWriter(FileWrapper file, size_t byte_limit)
Kári Tristan Helgason84ccb2d2018-08-16 14:35:26 +020028 : codec_type_(kVideoCodecGeneric),
palmkviste75f2042016-09-28 06:19:48 -070029 bytes_written_(0),
30 byte_limit_(byte_limit),
sprang3911c262016-04-15 01:24:14 -070031 num_frames_(0),
32 width_(0),
33 height_(0),
34 last_timestamp_(-1),
35 using_capture_timestamps_(false),
palmkviste75f2042016-09-28 06:19:48 -070036 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}
sprang3911c262016-04-15 01:24:14 -070040
41IvfFileWriter::~IvfFileWriter() {
42 Close();
43}
44
Niels Möllerb7edf692019-02-08 16:40:53 +010045std::unique_ptr<IvfFileWriter> IvfFileWriter::Wrap(FileWrapper file,
palmkviste75f2042016-09-28 06:19:48 -070046 size_t byte_limit) {
47 return std::unique_ptr<IvfFileWriter>(
48 new IvfFileWriter(std::move(file), byte_limit));
sprang3911c262016-04-15 01:24:14 -070049}
50
51bool IvfFileWriter::WriteHeader() {
Niels Möllerb7edf692019-02-08 16:40:53 +010052 if (!file_.Rewind()) {
Mirko Bonadei675513b2017-11-09 11:09:25 +010053 RTC_LOG(LS_WARNING) << "Unable to rewind ivf output file.";
sprang3911c262016-04-15 01:24:14 -070054 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_) {
kjellander02b3d272016-04-20 05:05:54 -070066 case kVideoCodecVP8:
sprang3911c262016-04-15 01:24:14 -070067 ivf_header[8] = 'V';
68 ivf_header[9] = 'P';
69 ivf_header[10] = '8';
70 ivf_header[11] = '0';
71 break;
kjellander02b3d272016-04-20 05:05:54 -070072 case kVideoCodecVP9:
sprang3911c262016-04-15 01:24:14 -070073 ivf_header[8] = 'V';
74 ivf_header[9] = 'P';
75 ivf_header[10] = '9';
76 ivf_header[11] = '0';
77 break;
Emil Lundmark91c04772020-08-25 11:43:25 +020078 case kVideoCodecAV1:
79 ivf_header[8] = 'A';
80 ivf_header[9] = 'V';
81 ivf_header[10] = '0';
82 ivf_header[11] = '1';
83 break;
kjellander02b3d272016-04-20 05:05:54 -070084 case kVideoCodecH264:
sprang3911c262016-04-15 01:24:14 -070085 ivf_header[8] = 'H';
86 ivf_header[9] = '2';
87 ivf_header[10] = '6';
88 ivf_header[11] = '4';
89 break;
90 default:
Sergey Silkind4b087c2021-07-23 16:28:55 +020091 // For unknown codec type use **** code. You can specify actual payload
92 // format when playing the video with ffplay: ffplay -f H263 file.ivf
93 ivf_header[8] = '*';
94 ivf_header[9] = '*';
95 ivf_header[10] = '*';
96 ivf_header[11] = '*';
97 break;
sprang3911c262016-04-15 01:24:14 -070098 }
99
100 ByteWriter<uint16_t>::WriteLittleEndian(&ivf_header[12], width_);
101 ByteWriter<uint16_t>::WriteLittleEndian(&ivf_header[14], height_);
102 // Render timestamps are in ms (1/1000 scale), while RTP timestamps use a
103 // 90kHz clock.
104 ByteWriter<uint32_t>::WriteLittleEndian(
105 &ivf_header[16], using_capture_timestamps_ ? 1000 : 90000);
106 ByteWriter<uint32_t>::WriteLittleEndian(&ivf_header[20], 1);
107 ByteWriter<uint32_t>::WriteLittleEndian(&ivf_header[24],
108 static_cast<uint32_t>(num_frames_));
109 ByteWriter<uint32_t>::WriteLittleEndian(&ivf_header[28], 0); // Reserved.
110
Niels Möllerb7edf692019-02-08 16:40:53 +0100111 if (!file_.Write(ivf_header, kIvfHeaderSize)) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100112 RTC_LOG(LS_ERROR) << "Unable to write IVF header for ivf output file.";
sprang3911c262016-04-15 01:24:14 -0700113 return false;
114 }
115
palmkviste75f2042016-09-28 06:19:48 -0700116 if (bytes_written_ < kIvfHeaderSize) {
117 bytes_written_ = kIvfHeaderSize;
118 }
119
sprang3911c262016-04-15 01:24:14 -0700120 return true;
121}
122
palmkviste75f2042016-09-28 06:19:48 -0700123bool IvfFileWriter::InitFromFirstFrame(const EncodedImage& encoded_image,
124 VideoCodecType codec_type) {
sprang3911c262016-04-15 01:24:14 -0700125 width_ = encoded_image._encodedWidth;
126 height_ = encoded_image._encodedHeight;
127 RTC_CHECK_GT(width_, 0);
128 RTC_CHECK_GT(height_, 0);
Niels Möller23775882018-08-16 10:24:12 +0200129 using_capture_timestamps_ = encoded_image.Timestamp() == 0;
sprang3911c262016-04-15 01:24:14 -0700130
palmkviste75f2042016-09-28 06:19:48 -0700131 codec_type_ = codec_type;
132
sprang3911c262016-04-15 01:24:14 -0700133 if (!WriteHeader())
134 return false;
135
Yves Gerey665174f2018-06-19 15:03:05 +0200136 const char* codec_name = CodecTypeToPayloadString(codec_type_);
Mirko Bonadei675513b2017-11-09 11:09:25 +0100137 RTC_LOG(LS_WARNING) << "Created IVF file for codec data of type "
138 << codec_name << " at resolution " << width_ << " x "
139 << height_ << ", using "
140 << (using_capture_timestamps_ ? "1" : "90")
141 << "kHz clock resolution.";
sprang3911c262016-04-15 01:24:14 -0700142 return true;
143}
144
palmkviste75f2042016-09-28 06:19:48 -0700145bool IvfFileWriter::WriteFrame(const EncodedImage& encoded_image,
146 VideoCodecType codec_type) {
Niels Möllerb7edf692019-02-08 16:40:53 +0100147 if (!file_.is_open())
sprang3911c262016-04-15 01:24:14 -0700148 return false;
149
palmkviste75f2042016-09-28 06:19:48 -0700150 if (num_frames_ == 0 && !InitFromFirstFrame(encoded_image, codec_type))
151 return false;
152 RTC_DCHECK_EQ(codec_type_, codec_type);
153
sprang3911c262016-04-15 01:24:14 -0700154 if ((encoded_image._encodedWidth > 0 || encoded_image._encodedHeight > 0) &&
155 (encoded_image._encodedHeight != height_ ||
156 encoded_image._encodedWidth != width_)) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100157 RTC_LOG(LS_WARNING)
Elad Alon4cde9ad2019-03-04 17:38:05 +0100158 << "Incoming frame has resolution different from previous: (" << width_
sprang3911c262016-04-15 01:24:14 -0700159 << "x" << height_ << ") -> (" << encoded_image._encodedWidth << "x"
160 << encoded_image._encodedHeight << ")";
161 }
162
163 int64_t timestamp = using_capture_timestamps_
164 ? encoded_image.capture_time_ms_
Niels Möller23775882018-08-16 10:24:12 +0200165 : wrap_handler_.Unwrap(encoded_image.Timestamp());
sprang3911c262016-04-15 01:24:14 -0700166 if (last_timestamp_ != -1 && timestamp <= last_timestamp_) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100167 RTC_LOG(LS_WARNING) << "Timestamp no increasing: " << last_timestamp_
168 << " -> " << timestamp;
sprang3911c262016-04-15 01:24:14 -0700169 }
170 last_timestamp_ = timestamp;
171
Ilya Nikolaevskiye93b1fe2019-10-08 13:17:09 +0200172 bool written_frames = false;
173 size_t max_sl_index = encoded_image.SpatialIndex().value_or(0);
174 const uint8_t* data = encoded_image.data();
175 for (size_t sl_idx = 0; sl_idx <= max_sl_index; ++sl_idx) {
176 size_t cur_size = encoded_image.SpatialLayerFrameSize(sl_idx).value_or(0);
177 if (cur_size > 0) {
178 written_frames = true;
179 if (!WriteOneSpatialLayer(timestamp, data, cur_size)) {
180 return false;
181 }
182 data += cur_size;
183 }
184 }
185
186 // If frame has only one spatial layer it won't have any spatial layers'
187 // sizes. Therefore this case should be addressed separately.
188 if (!written_frames) {
189 return WriteOneSpatialLayer(timestamp, data, encoded_image.size());
190 } else {
191 return true;
192 }
193}
194
195bool IvfFileWriter::WriteOneSpatialLayer(int64_t timestamp,
196 const uint8_t* data,
197 size_t size) {
sprang3911c262016-04-15 01:24:14 -0700198 const size_t kFrameHeaderSize = 12;
palmkviste75f2042016-09-28 06:19:48 -0700199 if (byte_limit_ != 0 &&
Ilya Nikolaevskiye93b1fe2019-10-08 13:17:09 +0200200 bytes_written_ + kFrameHeaderSize + size > byte_limit_) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100201 RTC_LOG(LS_WARNING) << "Closing IVF file due to reaching size limit: "
202 << byte_limit_ << " bytes.";
palmkviste75f2042016-09-28 06:19:48 -0700203 Close();
204 return false;
205 }
sprang3911c262016-04-15 01:24:14 -0700206 uint8_t frame_header[kFrameHeaderSize] = {};
Ilya Nikolaevskiye93b1fe2019-10-08 13:17:09 +0200207 ByteWriter<uint32_t>::WriteLittleEndian(&frame_header[0],
208 static_cast<uint32_t>(size));
sprang3911c262016-04-15 01:24:14 -0700209 ByteWriter<uint64_t>::WriteLittleEndian(&frame_header[4], timestamp);
Niels Möllerb7edf692019-02-08 16:40:53 +0100210 if (!file_.Write(frame_header, kFrameHeaderSize) ||
Ilya Nikolaevskiye93b1fe2019-10-08 13:17:09 +0200211 !file_.Write(data, size)) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100212 RTC_LOG(LS_ERROR) << "Unable to write frame to file.";
sprang3911c262016-04-15 01:24:14 -0700213 return false;
214 }
215
Ilya Nikolaevskiye93b1fe2019-10-08 13:17:09 +0200216 bytes_written_ += kFrameHeaderSize + size;
palmkviste75f2042016-09-28 06:19:48 -0700217
sprang3911c262016-04-15 01:24:14 -0700218 ++num_frames_;
219 return true;
220}
221
222bool IvfFileWriter::Close() {
Niels Möllerb7edf692019-02-08 16:40:53 +0100223 if (!file_.is_open())
sprang3911c262016-04-15 01:24:14 -0700224 return false;
225
226 if (num_frames_ == 0) {
palmkviste75f2042016-09-28 06:19:48 -0700227 file_.Close();
sprang3911c262016-04-15 01:24:14 -0700228 return true;
229 }
230
tommia6219cc2016-06-15 10:30:14 -0700231 bool ret = WriteHeader();
palmkviste75f2042016-09-28 06:19:48 -0700232 file_.Close();
tommia6219cc2016-06-15 10:30:14 -0700233 return ret;
sprang3911c262016-04-15 01:24:14 -0700234}
235
236} // namespace webrtc