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 <string>
14 #include <utility>
15 
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(rtc::File file,size_t byte_limit)27 IvfFileWriter::IvfFileWriter(rtc::File file, size_t byte_limit)
28     : codec_type_(kVideoCodecUnknown),
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(rtc::File file,size_t byte_limit)45 std::unique_ptr<IvfFileWriter> IvfFileWriter::Wrap(rtc::File 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_.Seek(0)) {
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) < 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 =
126       CodecTypeToPayloadString(codec_type_);
127   RTC_LOG(LS_WARNING) << "Created IVF file for codec data of type "
128                       << codec_name << " at resolution " << width_ << " x "
129                       << height_ << ", using "
130                       << (using_capture_timestamps_ ? "1" : "90")
131                       << "kHz clock resolution.";
132   return true;
133 }
134 
WriteFrame(const EncodedImage & encoded_image,VideoCodecType codec_type)135 bool IvfFileWriter::WriteFrame(const EncodedImage& encoded_image,
136                                VideoCodecType codec_type) {
137   if (!file_.IsOpen())
138     return false;
139 
140   if (num_frames_ == 0 && !InitFromFirstFrame(encoded_image, codec_type))
141     return false;
142   RTC_DCHECK_EQ(codec_type_, codec_type);
143 
144   if ((encoded_image._encodedWidth > 0 || encoded_image._encodedHeight > 0) &&
145       (encoded_image._encodedHeight != height_ ||
146        encoded_image._encodedWidth != width_)) {
147     RTC_LOG(LS_WARNING)
148         << "Incomig frame has diffferent resolution then previous: (" << width_
149         << "x" << height_ << ") -> (" << encoded_image._encodedWidth << "x"
150         << encoded_image._encodedHeight << ")";
151   }
152 
153   int64_t timestamp = using_capture_timestamps_
154                           ? encoded_image.capture_time_ms_
155                           : wrap_handler_.Unwrap(encoded_image._timeStamp);
156   if (last_timestamp_ != -1 && timestamp <= last_timestamp_) {
157     RTC_LOG(LS_WARNING) << "Timestamp no increasing: " << last_timestamp_
158                         << " -> " << timestamp;
159   }
160   last_timestamp_ = timestamp;
161 
162   const size_t kFrameHeaderSize = 12;
163   if (byte_limit_ != 0 &&
164       bytes_written_ + kFrameHeaderSize + encoded_image._length > byte_limit_) {
165     RTC_LOG(LS_WARNING) << "Closing IVF file due to reaching size limit: "
166                         << byte_limit_ << " bytes.";
167     Close();
168     return false;
169   }
170   uint8_t frame_header[kFrameHeaderSize] = {};
171   ByteWriter<uint32_t>::WriteLittleEndian(
172       &frame_header[0], static_cast<uint32_t>(encoded_image._length));
173   ByteWriter<uint64_t>::WriteLittleEndian(&frame_header[4], timestamp);
174   if (file_.Write(frame_header, kFrameHeaderSize) < kFrameHeaderSize ||
175       file_.Write(encoded_image._buffer, encoded_image._length) <
176           encoded_image._length) {
177     RTC_LOG(LS_ERROR) << "Unable to write frame to file.";
178     return false;
179   }
180 
181   bytes_written_ += kFrameHeaderSize + encoded_image._length;
182 
183   ++num_frames_;
184   return true;
185 }
186 
Close()187 bool IvfFileWriter::Close() {
188   if (!file_.IsOpen())
189     return false;
190 
191   if (num_frames_ == 0) {
192     file_.Close();
193     return true;
194   }
195 
196   bool ret = WriteHeader();
197   file_.Close();
198   return ret;
199 }
200 
201 }  // namespace webrtc
202