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