/* * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #include "modules/video_coding/utility/ivf_file_writer.h" #include #include "api/video_codecs/video_codec.h" #include "modules/rtp_rtcp/source/byte_io.h" #include "modules/video_coding/utility/ivf_defines.h" #include "rtc_base/checks.h" #include "rtc_base/logging.h" // TODO(palmkvist): make logging more informative in the absence of a file name // (or get one) namespace webrtc { namespace { constexpr int kDefaultWidth = 1280; constexpr int kDefaultHeight = 720; } // namespace IvfFileWriter::IvfFileWriter(FileWrapper file, size_t byte_limit) : codec_type_(kVideoCodecGeneric), bytes_written_(0), byte_limit_(byte_limit), num_frames_(0), width_(0), height_(0), last_timestamp_(-1), using_capture_timestamps_(false), file_(std::move(file)) { RTC_DCHECK(byte_limit == 0 || webrtc::kIvfHeaderSize <= byte_limit) << "The byte_limit is too low, not even the header will fit."; } IvfFileWriter::~IvfFileWriter() { Close(); } std::unique_ptr IvfFileWriter::Wrap(FileWrapper file, size_t byte_limit) { return std::unique_ptr( new IvfFileWriter(std::move(file), byte_limit)); } bool IvfFileWriter::WriteHeader() { if (!file_.Rewind()) { RTC_LOG(LS_WARNING) << "Unable to rewind ivf output file."; return false; } uint8_t ivf_header[webrtc::kIvfHeaderSize] = {0}; ivf_header[0] = 'D'; ivf_header[1] = 'K'; ivf_header[2] = 'I'; ivf_header[3] = 'F'; ByteWriter::WriteLittleEndian(&ivf_header[4], 0); // Version. ByteWriter::WriteLittleEndian(&ivf_header[6], 32); // Header size. switch (codec_type_) { case kVideoCodecVP8: ivf_header[8] = 'V'; ivf_header[9] = 'P'; ivf_header[10] = '8'; ivf_header[11] = '0'; break; case kVideoCodecVP9: ivf_header[8] = 'V'; ivf_header[9] = 'P'; ivf_header[10] = '9'; ivf_header[11] = '0'; break; case kVideoCodecAV1: ivf_header[8] = 'A'; ivf_header[9] = 'V'; ivf_header[10] = '0'; ivf_header[11] = '1'; break; case kVideoCodecH264: ivf_header[8] = 'H'; ivf_header[9] = '2'; ivf_header[10] = '6'; ivf_header[11] = '4'; break; default: // For unknown codec type use **** code. You can specify actual payload // format when playing the video with ffplay: ffplay -f H263 file.ivf ivf_header[8] = '*'; ivf_header[9] = '*'; ivf_header[10] = '*'; ivf_header[11] = '*'; break; } ByteWriter::WriteLittleEndian(&ivf_header[12], width_); ByteWriter::WriteLittleEndian(&ivf_header[14], height_); // Render timestamps are in ms (1/1000 scale), while RTP timestamps use a // 90kHz clock. ByteWriter::WriteLittleEndian( &ivf_header[16], using_capture_timestamps_ ? 1000 : 90000); ByteWriter::WriteLittleEndian(&ivf_header[20], 1); ByteWriter::WriteLittleEndian(&ivf_header[24], static_cast(num_frames_)); ByteWriter::WriteLittleEndian(&ivf_header[28], 0); // Reserved. if (!file_.Write(ivf_header, webrtc::kIvfHeaderSize)) { RTC_LOG(LS_ERROR) << "Unable to write IVF header for ivf output file."; return false; } if (bytes_written_ < webrtc::kIvfHeaderSize) { bytes_written_ = webrtc::kIvfHeaderSize; } return true; } bool IvfFileWriter::InitFromFirstFrame(const EncodedImage& encoded_image, VideoCodecType codec_type) { if (encoded_image._encodedWidth == 0 || encoded_image._encodedHeight == 0) { width_ = kDefaultWidth; height_ = kDefaultHeight; } else { width_ = encoded_image._encodedWidth; height_ = encoded_image._encodedHeight; } using_capture_timestamps_ = encoded_image.Timestamp() == 0; codec_type_ = codec_type; if (!WriteHeader()) return false; const char* codec_name = CodecTypeToPayloadString(codec_type_); RTC_LOG(LS_WARNING) << "Created IVF file for codec data of type " << codec_name << " at resolution " << width_ << " x " << height_ << ", using " << (using_capture_timestamps_ ? "1" : "90") << "kHz clock resolution."; return true; } bool IvfFileWriter::WriteFrame(const EncodedImage& encoded_image, VideoCodecType codec_type) { if (!file_.is_open()) return false; if (num_frames_ == 0 && !InitFromFirstFrame(encoded_image, codec_type)) return false; RTC_DCHECK_EQ(codec_type_, codec_type); if ((encoded_image._encodedWidth > 0 || encoded_image._encodedHeight > 0) && (encoded_image._encodedHeight != height_ || encoded_image._encodedWidth != width_)) { RTC_LOG(LS_WARNING) << "Incoming frame has resolution different from previous: (" << width_ << "x" << height_ << ") -> (" << encoded_image._encodedWidth << "x" << encoded_image._encodedHeight << ")"; } int64_t timestamp = using_capture_timestamps_ ? encoded_image.capture_time_ms_ : wrap_handler_.Unwrap(encoded_image.Timestamp()); if (last_timestamp_ != -1 && timestamp <= last_timestamp_) { RTC_LOG(LS_WARNING) << "Timestamp no increasing: " << last_timestamp_ << " -> " << timestamp; } last_timestamp_ = timestamp; bool written_frames = false; size_t max_sl_index = encoded_image.SpatialIndex().value_or(0); const uint8_t* data = encoded_image.data(); for (size_t sl_idx = 0; sl_idx <= max_sl_index; ++sl_idx) { size_t cur_size = encoded_image.SpatialLayerFrameSize(sl_idx).value_or(0); if (cur_size > 0) { written_frames = true; if (!WriteOneSpatialLayer(timestamp, data, cur_size)) { return false; } data += cur_size; } } // If frame has only one spatial layer it won't have any spatial layers' // sizes. Therefore this case should be addressed separately. if (!written_frames) { return WriteOneSpatialLayer(timestamp, data, encoded_image.size()); } else { return true; } } bool IvfFileWriter::WriteOneSpatialLayer(int64_t timestamp, const uint8_t* data, size_t size) { const size_t kFrameHeaderSize = 12; if (byte_limit_ != 0 && bytes_written_ + kFrameHeaderSize + size > byte_limit_) { RTC_LOG(LS_WARNING) << "Closing IVF file due to reaching size limit: " << byte_limit_ << " bytes."; Close(); return false; } uint8_t frame_header[kFrameHeaderSize] = {}; ByteWriter::WriteLittleEndian(&frame_header[0], static_cast(size)); ByteWriter::WriteLittleEndian(&frame_header[4], timestamp); if (!file_.Write(frame_header, kFrameHeaderSize) || !file_.Write(data, size)) { RTC_LOG(LS_ERROR) << "Unable to write frame to file."; return false; } bytes_written_ += kFrameHeaderSize + size; ++num_frames_; return true; } bool IvfFileWriter::Close() { if (!file_.is_open()) return false; if (num_frames_ == 0) { file_.Close(); return true; } bool ret = WriteHeader(); file_.Close(); return ret; } } // namespace webrtc