xref: /aosp_15_r20/external/webrtc/modules/video_coding/utility/ivf_file_writer.cc (revision d9f758449e529ab9291ac668be2861e7a55c2422)
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