• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 kVideoCodecH264:
79       ivf_header[8] = 'H';
80       ivf_header[9] = '2';
81       ivf_header[10] = '6';
82       ivf_header[11] = '4';
83       break;
84     default:
85       RTC_LOG(LS_ERROR) << "Unknown CODEC type: " << codec_type_;
86       return false;
87   }
88 
89   ByteWriter<uint16_t>::WriteLittleEndian(&ivf_header[12], width_);
90   ByteWriter<uint16_t>::WriteLittleEndian(&ivf_header[14], height_);
91   // Render timestamps are in ms (1/1000 scale), while RTP timestamps use a
92   // 90kHz clock.
93   ByteWriter<uint32_t>::WriteLittleEndian(
94       &ivf_header[16], using_capture_timestamps_ ? 1000 : 90000);
95   ByteWriter<uint32_t>::WriteLittleEndian(&ivf_header[20], 1);
96   ByteWriter<uint32_t>::WriteLittleEndian(&ivf_header[24],
97                                           static_cast<uint32_t>(num_frames_));
98   ByteWriter<uint32_t>::WriteLittleEndian(&ivf_header[28], 0);  // Reserved.
99 
100   if (!file_.Write(ivf_header, kIvfHeaderSize)) {
101     RTC_LOG(LS_ERROR) << "Unable to write IVF header for ivf output file.";
102     return false;
103   }
104 
105   if (bytes_written_ < kIvfHeaderSize) {
106     bytes_written_ = kIvfHeaderSize;
107   }
108 
109   return true;
110 }
111 
InitFromFirstFrame(const EncodedImage & encoded_image,VideoCodecType codec_type)112 bool IvfFileWriter::InitFromFirstFrame(const EncodedImage& encoded_image,
113                                        VideoCodecType codec_type) {
114   width_ = encoded_image._encodedWidth;
115   height_ = encoded_image._encodedHeight;
116   RTC_CHECK_GT(width_, 0);
117   RTC_CHECK_GT(height_, 0);
118   using_capture_timestamps_ = encoded_image.Timestamp() == 0;
119 
120   codec_type_ = codec_type;
121 
122   if (!WriteHeader())
123     return false;
124 
125   const char* codec_name = CodecTypeToPayloadString(codec_type_);
126   RTC_LOG(LS_WARNING) << "Created IVF file for codec data of type "
127                       << codec_name << " at resolution " << width_ << " x "
128                       << height_ << ", using "
129                       << (using_capture_timestamps_ ? "1" : "90")
130                       << "kHz clock resolution.";
131   return true;
132 }
133 
WriteFrame(const EncodedImage & encoded_image,VideoCodecType codec_type)134 bool IvfFileWriter::WriteFrame(const EncodedImage& encoded_image,
135                                VideoCodecType codec_type) {
136   if (!file_.is_open())
137     return false;
138 
139   if (num_frames_ == 0 && !InitFromFirstFrame(encoded_image, codec_type))
140     return false;
141   RTC_DCHECK_EQ(codec_type_, codec_type);
142 
143   if ((encoded_image._encodedWidth > 0 || encoded_image._encodedHeight > 0) &&
144       (encoded_image._encodedHeight != height_ ||
145        encoded_image._encodedWidth != width_)) {
146     RTC_LOG(LS_WARNING)
147         << "Incoming frame has resolution different from previous: (" << width_
148         << "x" << height_ << ") -> (" << encoded_image._encodedWidth << "x"
149         << encoded_image._encodedHeight << ")";
150   }
151 
152   int64_t timestamp = using_capture_timestamps_
153                           ? encoded_image.capture_time_ms_
154                           : wrap_handler_.Unwrap(encoded_image.Timestamp());
155   if (last_timestamp_ != -1 && timestamp <= last_timestamp_) {
156     RTC_LOG(LS_WARNING) << "Timestamp no increasing: " << last_timestamp_
157                         << " -> " << timestamp;
158   }
159   last_timestamp_ = timestamp;
160 
161   bool written_frames = false;
162   size_t max_sl_index = encoded_image.SpatialIndex().value_or(0);
163   const uint8_t* data = encoded_image.data();
164   for (size_t sl_idx = 0; sl_idx <= max_sl_index; ++sl_idx) {
165     size_t cur_size = encoded_image.SpatialLayerFrameSize(sl_idx).value_or(0);
166     if (cur_size > 0) {
167       written_frames = true;
168       if (!WriteOneSpatialLayer(timestamp, data, cur_size)) {
169         return false;
170       }
171       data += cur_size;
172     }
173   }
174 
175   // If frame has only one spatial layer it won't have any spatial layers'
176   // sizes. Therefore this case should be addressed separately.
177   if (!written_frames) {
178     return WriteOneSpatialLayer(timestamp, data, encoded_image.size());
179   } else {
180     return true;
181   }
182 }
183 
WriteOneSpatialLayer(int64_t timestamp,const uint8_t * data,size_t size)184 bool IvfFileWriter::WriteOneSpatialLayer(int64_t timestamp,
185                                          const uint8_t* data,
186                                          size_t size) {
187   const size_t kFrameHeaderSize = 12;
188   if (byte_limit_ != 0 &&
189       bytes_written_ + kFrameHeaderSize + size > byte_limit_) {
190     RTC_LOG(LS_WARNING) << "Closing IVF file due to reaching size limit: "
191                         << byte_limit_ << " bytes.";
192     Close();
193     return false;
194   }
195   uint8_t frame_header[kFrameHeaderSize] = {};
196   ByteWriter<uint32_t>::WriteLittleEndian(&frame_header[0],
197                                           static_cast<uint32_t>(size));
198   ByteWriter<uint64_t>::WriteLittleEndian(&frame_header[4], timestamp);
199   if (!file_.Write(frame_header, kFrameHeaderSize) ||
200       !file_.Write(data, size)) {
201     RTC_LOG(LS_ERROR) << "Unable to write frame to file.";
202     return false;
203   }
204 
205   bytes_written_ += kFrameHeaderSize + size;
206 
207   ++num_frames_;
208   return true;
209 }
210 
Close()211 bool IvfFileWriter::Close() {
212   if (!file_.is_open())
213     return false;
214 
215   if (num_frames_ == 0) {
216     file_.Close();
217     return true;
218   }
219 
220   bool ret = WriteHeader();
221   file_.Close();
222   return ret;
223 }
224 
225 }  // namespace webrtc
226