Introduce IVF file reader

Bug: webrtc:10138
Change-Id: I97d332942f4e645527330159efefb1cb1d8034a0
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/160008
Commit-Queue: Artem Titov <titovartem@webrtc.org>
Reviewed-by: Ilya Nikolaevskiy <ilnik@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#29844}
This commit is contained in:
Artem Titov 2019-11-20 13:30:19 +01:00 committed by Commit Bot
parent 1721de12bd
commit 5831ddad65
4 changed files with 486 additions and 0 deletions

View File

@ -263,6 +263,8 @@ rtc_library("video_coding_utility") {
"utility/frame_dropper.h",
"utility/framerate_controller.cc",
"utility/framerate_controller.h",
"utility/ivf_file_reader.cc",
"utility/ivf_file_reader.h",
"utility/ivf_file_writer.cc",
"utility/ivf_file_writer.h",
"utility/quality_scaler.cc",
@ -844,6 +846,7 @@ if (rtc_include_tests) {
"utility/default_video_bitrate_allocator_unittest.cc",
"utility/frame_dropper_unittest.cc",
"utility/framerate_controller_unittest.cc",
"utility/ivf_file_reader_unittest.cc",
"utility/ivf_file_writer_unittest.cc",
"utility/quality_scaler_unittest.cc",
"utility/simulcast_rate_allocator_unittest.cc",

View File

@ -0,0 +1,234 @@
/*
* 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 "rtc_base/logging.h"
namespace webrtc {
namespace {
constexpr size_t kIvfHeaderSize = 32;
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 kH264Header[kCodecTypeBytesCount] = {'H', '2', '6', '4'};
} // 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;
}
uint32_t time_scale = ByteReader<uint32_t>::ReadLittleEndian(&ivf_header[16]);
if (time_scale == 1000) {
using_capture_timestamps_ = true;
} else if (time_scale == 90000) {
using_capture_timestamps_ = false;
} else {
RTC_LOG(LS_ERROR) << "Invalid IVF header: Unknown time scale";
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(INFO) << "Opened IVF file with codec data of type " << codec_name
<< " at resolution " << width_ << " x " << height_ << ", using "
<< (using_capture_timestamps_ ? "1" : "90")
<< "kHz 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;
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;
if (using_capture_timestamps_) {
image.capture_time_ms_ = current_timestamp;
image.SetTimestamp(static_cast<uint32_t>(90 * current_timestamp));
} else {
image.SetTimestamp(static_cast<uint32_t>(current_timestamp));
}
image.SetEncodedData(payload);
image.SetSpatialIndex(static_cast<int>(layer_sizes.size()));
for (size_t i = 0; i < layer_sizes.size(); ++i) {
image.SetSpatialLayerFrameSize(static_cast<int>(i), layer_sizes[i]);
}
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], kH264Header, kCodecTypeBytesCount) == 0) {
return VideoCodecType::kVideoCodecH264;
}
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

View File

@ -0,0 +1,76 @@
/*
* 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.
*/
#ifndef MODULES_VIDEO_CODING_UTILITY_IVF_FILE_READER_H_
#define MODULES_VIDEO_CODING_UTILITY_IVF_FILE_READER_H_
#include <memory>
#include <utility>
#include "absl/types/optional.h"
#include "api/video/encoded_image.h"
#include "rtc_base/system/file_wrapper.h"
namespace webrtc {
class IvfFileReader {
public:
// Creates IvfFileReader. Returns nullptr if error acquired.
static std::unique_ptr<IvfFileReader> Create(FileWrapper file);
~IvfFileReader();
// Reinitializes reader. Returns false if any error acquired.
bool Reset();
// Returns codec type which was used to create this IVF file and which should
// be used to decode EncodedImages from this file.
VideoCodecType GetVideoCodecType() const { return codec_type_; }
// Returns count of frames in this file.
size_t GetFramesCount() const { return num_frames_; }
// Returns next frame or absl::nullopt if any error acquired. Always returns
// absl::nullopt after first error was spotted.
absl::optional<EncodedImage> NextFrame();
bool HasMoreFrames() const { return num_read_frames_ < num_frames_; }
bool HasError() const { return has_error_; }
bool Close();
private:
struct FrameHeader {
size_t frame_size;
int64_t timestamp;
};
explicit IvfFileReader(FileWrapper file) : file_(std::move(file)) {}
// Parses codec type from specified position of the buffer. Codec type
// contains kCodecTypeBytesCount bytes and caller has to ensure that buffer
// won't overflow.
absl::optional<VideoCodecType> ParseCodecType(uint8_t* buffer,
size_t start_pos);
absl::optional<FrameHeader> ReadNextFrameHeader();
VideoCodecType codec_type_;
size_t num_frames_;
size_t num_read_frames_;
uint16_t width_;
uint16_t height_;
bool using_capture_timestamps_;
FileWrapper file_;
absl::optional<FrameHeader> next_frame_header_;
bool has_error_;
RTC_DISALLOW_COPY_AND_ASSIGN(IvfFileReader);
};
} // namespace webrtc
#endif // MODULES_VIDEO_CODING_UTILITY_IVF_FILE_READER_H_

View File

@ -0,0 +1,173 @@
/*
* 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 "modules/video_coding/utility/ivf_file_writer.h"
#include <memory>
#include <string>
#include "test/gtest.h"
#include "test/testsupport/file_utils.h"
namespace webrtc {
namespace {
constexpr int kWidth = 320;
constexpr int kHeight = 240;
constexpr int kNumFrames = 3;
constexpr uint8_t kDummyPayload[4] = {'0', '1', '2', '3'};
} // namespace
class IvfFileReaderTest : public ::testing::Test {
protected:
void SetUp() override {
file_name_ =
webrtc::test::TempFilename(webrtc::test::OutputPath(), "test_file.ivf");
}
void TearDown() override { webrtc::test::RemoveFile(file_name_); }
bool WriteDummyTestFrames(IvfFileWriter* file_writer,
VideoCodecType codec_type,
int width,
int height,
int num_frames,
bool use_capture_tims_ms,
int spatial_layers_count) {
EncodedImage frame;
frame.SetSpatialIndex(spatial_layers_count);
rtc::scoped_refptr<EncodedImageBuffer> payload = EncodedImageBuffer::Create(
sizeof(kDummyPayload) * spatial_layers_count);
for (int i = 0; i < spatial_layers_count; ++i) {
memcpy(&payload->data()[i * sizeof(kDummyPayload)], kDummyPayload,
sizeof(kDummyPayload));
frame.SetSpatialLayerFrameSize(i, sizeof(kDummyPayload));
}
frame.SetEncodedData(payload);
frame._encodedWidth = width;
frame._encodedHeight = height;
for (int i = 1; i <= num_frames; ++i) {
if (use_capture_tims_ms) {
frame.capture_time_ms_ = i;
} else {
frame.SetTimestamp(i);
}
if (!file_writer->WriteFrame(frame, codec_type))
return false;
}
return true;
}
void CreateTestFile(VideoCodecType codec_type,
bool use_capture_tims_ms,
int spatial_layers_count) {
std::unique_ptr<IvfFileWriter> file_writer =
IvfFileWriter::Wrap(FileWrapper::OpenWriteOnly(file_name_), 0);
ASSERT_TRUE(file_writer.get());
ASSERT_TRUE(WriteDummyTestFrames(file_writer.get(), codec_type, kWidth,
kHeight, kNumFrames, use_capture_tims_ms,
spatial_layers_count));
ASSERT_TRUE(file_writer->Close());
}
void ValidateFrame(absl::optional<EncodedImage> frame,
int frame_index,
bool use_capture_tims_ms,
int spatial_layers_count) {
ASSERT_TRUE(frame);
EXPECT_EQ(frame->SpatialIndex(), spatial_layers_count);
if (use_capture_tims_ms) {
EXPECT_EQ(frame->capture_time_ms_, static_cast<int64_t>(frame_index));
EXPECT_EQ(frame->Timestamp(), static_cast<int64_t>(90 * frame_index));
} else {
EXPECT_EQ(frame->Timestamp(), static_cast<int64_t>(frame_index));
}
ASSERT_EQ(frame->size(), sizeof(kDummyPayload) * spatial_layers_count);
for (int i = 0; i < spatial_layers_count; ++i) {
EXPECT_EQ(memcmp(&frame->data()[i * sizeof(kDummyPayload)], kDummyPayload,
sizeof(kDummyPayload)),
0)
<< std::string(reinterpret_cast<char const*>(
&frame->data()[i * sizeof(kDummyPayload)]),
sizeof(kDummyPayload));
}
}
void ValidateContent(VideoCodecType codec_type,
bool use_capture_tims_ms,
int spatial_layers_count) {
std::unique_ptr<IvfFileReader> reader =
IvfFileReader::Create(FileWrapper::OpenReadOnly(file_name_));
ASSERT_TRUE(reader.get());
EXPECT_EQ(reader->GetVideoCodecType(), codec_type);
EXPECT_EQ(reader->GetFramesCount(),
spatial_layers_count * static_cast<size_t>(kNumFrames));
for (int i = 1; i <= kNumFrames; ++i) {
ASSERT_TRUE(reader->HasMoreFrames());
ValidateFrame(reader->NextFrame(), i, use_capture_tims_ms,
spatial_layers_count);
EXPECT_FALSE(reader->HasError());
}
EXPECT_FALSE(reader->HasMoreFrames());
EXPECT_FALSE(reader->NextFrame());
EXPECT_FALSE(reader->HasError());
ASSERT_TRUE(reader->Close());
}
std::string file_name_;
};
TEST_F(IvfFileReaderTest, BasicVp8FileNtpTimestamp) {
CreateTestFile(kVideoCodecVP8, false, 1);
ValidateContent(kVideoCodecVP8, false, 1);
}
TEST_F(IvfFileReaderTest, BasicVP8FileMsTimestamp) {
CreateTestFile(kVideoCodecVP8, true, 1);
ValidateContent(kVideoCodecVP8, true, 1);
}
TEST_F(IvfFileReaderTest, BasicVP9FileNtpTimestamp) {
CreateTestFile(kVideoCodecVP9, false, 1);
ValidateContent(kVideoCodecVP9, false, 1);
}
TEST_F(IvfFileReaderTest, BasicVP9FileMsTimestamp) {
CreateTestFile(kVideoCodecVP9, true, 1);
ValidateContent(kVideoCodecVP9, true, 1);
}
TEST_F(IvfFileReaderTest, BasicH264FileNtpTimestamp) {
CreateTestFile(kVideoCodecH264, false, 1);
ValidateContent(kVideoCodecH264, false, 1);
}
TEST_F(IvfFileReaderTest, BasicH264FileMsTimestamp) {
CreateTestFile(kVideoCodecH264, true, 1);
ValidateContent(kVideoCodecH264, true, 1);
}
TEST_F(IvfFileReaderTest, MultilayerVp8FileNtpTimestamp) {
CreateTestFile(kVideoCodecVP8, false, 3);
ValidateContent(kVideoCodecVP8, false, 3);
}
TEST_F(IvfFileReaderTest, MultilayerVP9FileNtpTimestamp) {
CreateTestFile(kVideoCodecVP9, false, 3);
ValidateContent(kVideoCodecVP9, false, 3);
}
TEST_F(IvfFileReaderTest, MultilayerH264FileNtpTimestamp) {
CreateTestFile(kVideoCodecH264, false, 3);
ValidateContent(kVideoCodecH264, false, 3);
}
} // namespace webrtc