mirror of
https://github.com/mollyim/webrtc.git
synced 2025-05-13 22:00:47 +01:00

To avoid name collision with Timestamp type, To avoid confusion with capture time represented as Timestamp Bug: webrtc:9378 Change-Id: I8438a9cf4316e5f81d98c2af9dc9454c21c78e70 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/320601 Reviewed-by: Harald Alvestrand <hta@webrtc.org> Reviewed-by: Rasmus Brandt <brandtr@webrtc.org> Commit-Queue: Danil Chapovalov <danilchap@webrtc.org> Cr-Commit-Position: refs/heads/main@{#40796}
242 lines
7.9 KiB
C++
242 lines
7.9 KiB
C++
/*
|
|
* Copyright (c) 2019 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_reader.h"
|
|
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#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/logging.h"
|
|
|
|
namespace webrtc {
|
|
namespace {
|
|
|
|
constexpr size_t kIvfFrameHeaderSize = 12;
|
|
constexpr int kCodecTypeBytesCount = 4;
|
|
|
|
constexpr uint8_t kFileHeaderStart[kCodecTypeBytesCount] = {'D', 'K', 'I', 'F'};
|
|
constexpr uint8_t kVp8Header[kCodecTypeBytesCount] = {'V', 'P', '8', '0'};
|
|
constexpr uint8_t kVp9Header[kCodecTypeBytesCount] = {'V', 'P', '9', '0'};
|
|
constexpr uint8_t kAv1Header[kCodecTypeBytesCount] = {'A', 'V', '0', '1'};
|
|
constexpr uint8_t kH264Header[kCodecTypeBytesCount] = {'H', '2', '6', '4'};
|
|
constexpr uint8_t kH265Header[kCodecTypeBytesCount] = {'H', '2', '6', '5'};
|
|
|
|
// RTP standard required 90kHz clock rate.
|
|
constexpr int32_t kRtpClockRateHz = 90000;
|
|
|
|
} // namespace
|
|
|
|
std::unique_ptr<IvfFileReader> IvfFileReader::Create(FileWrapper file) {
|
|
auto reader =
|
|
std::unique_ptr<IvfFileReader>(new IvfFileReader(std::move(file)));
|
|
if (!reader->Reset()) {
|
|
return nullptr;
|
|
}
|
|
return reader;
|
|
}
|
|
IvfFileReader::~IvfFileReader() {
|
|
Close();
|
|
}
|
|
|
|
bool IvfFileReader::Reset() {
|
|
// Set error to true while initialization.
|
|
has_error_ = true;
|
|
if (!file_.Rewind()) {
|
|
RTC_LOG(LS_ERROR) << "Failed to rewind IVF file";
|
|
return false;
|
|
}
|
|
|
|
uint8_t ivf_header[kIvfHeaderSize] = {0};
|
|
size_t read = file_.Read(&ivf_header, kIvfHeaderSize);
|
|
if (read != kIvfHeaderSize) {
|
|
RTC_LOG(LS_ERROR) << "Failed to read IVF header";
|
|
return false;
|
|
}
|
|
|
|
if (memcmp(&ivf_header[0], kFileHeaderStart, 4) != 0) {
|
|
RTC_LOG(LS_ERROR) << "File is not in IVF format: DKIF header expected";
|
|
return false;
|
|
}
|
|
|
|
absl::optional<VideoCodecType> codec_type = ParseCodecType(ivf_header, 8);
|
|
if (!codec_type) {
|
|
return false;
|
|
}
|
|
codec_type_ = *codec_type;
|
|
|
|
width_ = ByteReader<uint16_t>::ReadLittleEndian(&ivf_header[12]);
|
|
height_ = ByteReader<uint16_t>::ReadLittleEndian(&ivf_header[14]);
|
|
if (width_ == 0 || height_ == 0) {
|
|
RTC_LOG(LS_ERROR) << "Invalid IVF header: width or height is 0";
|
|
return false;
|
|
}
|
|
|
|
time_scale_ = ByteReader<uint32_t>::ReadLittleEndian(&ivf_header[16]);
|
|
if (time_scale_ == 0) {
|
|
RTC_LOG(LS_ERROR) << "Invalid IVF header: time scale can't be 0";
|
|
return false;
|
|
}
|
|
|
|
num_frames_ = static_cast<size_t>(
|
|
ByteReader<uint32_t>::ReadLittleEndian(&ivf_header[24]));
|
|
if (num_frames_ <= 0) {
|
|
RTC_LOG(LS_ERROR) << "Invalid IVF header: number of frames 0 or negative";
|
|
return false;
|
|
}
|
|
|
|
num_read_frames_ = 0;
|
|
next_frame_header_ = ReadNextFrameHeader();
|
|
if (!next_frame_header_) {
|
|
RTC_LOG(LS_ERROR) << "Failed to read 1st frame header";
|
|
return false;
|
|
}
|
|
// Initialization succeed: reset error.
|
|
has_error_ = false;
|
|
|
|
const char* codec_name = CodecTypeToPayloadString(codec_type_);
|
|
RTC_LOG(LS_INFO) << "Opened IVF file with codec data of type " << codec_name
|
|
<< " at resolution " << width_ << " x " << height_
|
|
<< ", using " << time_scale_ << "Hz clock resolution.";
|
|
|
|
return true;
|
|
}
|
|
|
|
absl::optional<EncodedImage> IvfFileReader::NextFrame() {
|
|
if (has_error_ || !HasMoreFrames()) {
|
|
return absl::nullopt;
|
|
}
|
|
|
|
rtc::scoped_refptr<EncodedImageBuffer> payload = EncodedImageBuffer::Create();
|
|
std::vector<size_t> layer_sizes;
|
|
// next_frame_header_ have to be presented by the way how it was loaded. If it
|
|
// is missing it means there is a bug in error handling.
|
|
RTC_DCHECK(next_frame_header_);
|
|
int64_t current_timestamp = next_frame_header_->timestamp;
|
|
// The first frame from the file should be marked as Key frame.
|
|
bool is_first_frame = num_read_frames_ == 0;
|
|
while (next_frame_header_ &&
|
|
current_timestamp == next_frame_header_->timestamp) {
|
|
// Resize payload to fit next spatial layer.
|
|
size_t current_layer_size = next_frame_header_->frame_size;
|
|
size_t current_layer_start_pos = payload->size();
|
|
payload->Realloc(payload->size() + current_layer_size);
|
|
layer_sizes.push_back(current_layer_size);
|
|
|
|
// Read next layer into payload
|
|
size_t read = file_.Read(&payload->data()[current_layer_start_pos],
|
|
current_layer_size);
|
|
if (read != current_layer_size) {
|
|
RTC_LOG(LS_ERROR) << "Frame #" << num_read_frames_
|
|
<< ": failed to read frame payload";
|
|
has_error_ = true;
|
|
return absl::nullopt;
|
|
}
|
|
num_read_frames_++;
|
|
|
|
current_timestamp = next_frame_header_->timestamp;
|
|
next_frame_header_ = ReadNextFrameHeader();
|
|
}
|
|
if (!next_frame_header_) {
|
|
// If EOF was reached, we need to check that all frames were met.
|
|
if (!has_error_ && num_read_frames_ != num_frames_) {
|
|
RTC_LOG(LS_ERROR) << "Unexpected EOF";
|
|
has_error_ = true;
|
|
return absl::nullopt;
|
|
}
|
|
}
|
|
|
|
EncodedImage image;
|
|
image.capture_time_ms_ = current_timestamp;
|
|
image.SetRtpTimestamp(
|
|
static_cast<uint32_t>(current_timestamp * kRtpClockRateHz / time_scale_));
|
|
image.SetEncodedData(payload);
|
|
image.SetSpatialIndex(static_cast<int>(layer_sizes.size()) - 1);
|
|
for (size_t i = 0; i < layer_sizes.size(); ++i) {
|
|
image.SetSpatialLayerFrameSize(static_cast<int>(i), layer_sizes[i]);
|
|
}
|
|
if (is_first_frame) {
|
|
image._frameType = VideoFrameType::kVideoFrameKey;
|
|
}
|
|
|
|
return image;
|
|
}
|
|
|
|
bool IvfFileReader::Close() {
|
|
if (!file_.is_open())
|
|
return false;
|
|
|
|
file_.Close();
|
|
return true;
|
|
}
|
|
|
|
absl::optional<VideoCodecType> IvfFileReader::ParseCodecType(uint8_t* buffer,
|
|
size_t start_pos) {
|
|
if (memcmp(&buffer[start_pos], kVp8Header, kCodecTypeBytesCount) == 0) {
|
|
return VideoCodecType::kVideoCodecVP8;
|
|
}
|
|
if (memcmp(&buffer[start_pos], kVp9Header, kCodecTypeBytesCount) == 0) {
|
|
return VideoCodecType::kVideoCodecVP9;
|
|
}
|
|
if (memcmp(&buffer[start_pos], kAv1Header, kCodecTypeBytesCount) == 0) {
|
|
return VideoCodecType::kVideoCodecAV1;
|
|
}
|
|
if (memcmp(&buffer[start_pos], kH264Header, kCodecTypeBytesCount) == 0) {
|
|
return VideoCodecType::kVideoCodecH264;
|
|
}
|
|
if (memcmp(&buffer[start_pos], kH265Header, kCodecTypeBytesCount) == 0) {
|
|
return VideoCodecType::kVideoCodecH265;
|
|
}
|
|
has_error_ = true;
|
|
RTC_LOG(LS_ERROR) << "Unknown codec type: "
|
|
<< std::string(
|
|
reinterpret_cast<char const*>(&buffer[start_pos]),
|
|
kCodecTypeBytesCount);
|
|
return absl::nullopt;
|
|
}
|
|
|
|
absl::optional<IvfFileReader::FrameHeader>
|
|
IvfFileReader::ReadNextFrameHeader() {
|
|
uint8_t ivf_frame_header[kIvfFrameHeaderSize] = {0};
|
|
size_t read = file_.Read(&ivf_frame_header, kIvfFrameHeaderSize);
|
|
if (read != kIvfFrameHeaderSize) {
|
|
if (read != 0 || !file_.ReadEof()) {
|
|
has_error_ = true;
|
|
RTC_LOG(LS_ERROR) << "Frame #" << num_read_frames_
|
|
<< ": failed to read IVF frame header";
|
|
}
|
|
return absl::nullopt;
|
|
}
|
|
FrameHeader header;
|
|
header.frame_size = static_cast<size_t>(
|
|
ByteReader<uint32_t>::ReadLittleEndian(&ivf_frame_header[0]));
|
|
header.timestamp =
|
|
ByteReader<uint64_t>::ReadLittleEndian(&ivf_frame_header[4]);
|
|
|
|
if (header.frame_size == 0) {
|
|
has_error_ = true;
|
|
RTC_LOG(LS_ERROR) << "Frame #" << num_read_frames_
|
|
<< ": invalid frame size";
|
|
return absl::nullopt;
|
|
}
|
|
|
|
if (header.timestamp < 0) {
|
|
has_error_ = true;
|
|
RTC_LOG(LS_ERROR) << "Frame #" << num_read_frames_
|
|
<< ": negative timestamp";
|
|
return absl::nullopt;
|
|
}
|
|
|
|
return header;
|
|
}
|
|
|
|
} // namespace webrtc
|