Expose Ivf logging through the native API
BUG=webrtc:6300 Review-Url: https://codereview.webrtc.org/2303273002 Cr-Commit-Position: refs/heads/master@{#14419}
This commit is contained in:
@ -10,48 +10,47 @@
|
||||
|
||||
#include "webrtc/modules/video_coding/utility/ivf_file_writer.h"
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "webrtc/base/checks.h"
|
||||
#include "webrtc/base/logging.h"
|
||||
#include "webrtc/modules/rtp_rtcp/source/byte_io.h"
|
||||
|
||||
// TODO(palmkvist): make logging more informative in the absence of a file name
|
||||
// (or get one)
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
IvfFileWriter::IvfFileWriter(const std::string& file_name,
|
||||
std::unique_ptr<FileWrapper> file,
|
||||
VideoCodecType codec_type)
|
||||
: codec_type_(codec_type),
|
||||
const size_t kIvfHeaderSize = 32;
|
||||
|
||||
IvfFileWriter::IvfFileWriter(rtc::File file, size_t byte_limit)
|
||||
: codec_type_(kVideoCodecUnknown),
|
||||
bytes_written_(0),
|
||||
byte_limit_(byte_limit),
|
||||
num_frames_(0),
|
||||
width_(0),
|
||||
height_(0),
|
||||
last_timestamp_(-1),
|
||||
using_capture_timestamps_(false),
|
||||
file_name_(file_name),
|
||||
file_(std::move(file)) {}
|
||||
file_(std::move(file)) {
|
||||
RTC_DCHECK(byte_limit == 0 || kIvfHeaderSize <= byte_limit)
|
||||
<< "The byte_limit is too low, not even the header will fit.";
|
||||
}
|
||||
|
||||
IvfFileWriter::~IvfFileWriter() {
|
||||
Close();
|
||||
}
|
||||
|
||||
const size_t kIvfHeaderSize = 32;
|
||||
|
||||
std::unique_ptr<IvfFileWriter> IvfFileWriter::Open(const std::string& file_name,
|
||||
VideoCodecType codec_type) {
|
||||
std::unique_ptr<IvfFileWriter> file_writer;
|
||||
std::unique_ptr<FileWrapper> file(FileWrapper::Create());
|
||||
if (!file->OpenFile(file_name.c_str(), false))
|
||||
return file_writer;
|
||||
|
||||
file_writer.reset(new IvfFileWriter(
|
||||
file_name, std::unique_ptr<FileWrapper>(std::move(file)), codec_type));
|
||||
if (!file_writer->WriteHeader())
|
||||
file_writer.reset();
|
||||
|
||||
return file_writer;
|
||||
std::unique_ptr<IvfFileWriter> IvfFileWriter::Wrap(rtc::File file,
|
||||
size_t byte_limit) {
|
||||
return std::unique_ptr<IvfFileWriter>(
|
||||
new IvfFileWriter(std::move(file), byte_limit));
|
||||
}
|
||||
|
||||
bool IvfFileWriter::WriteHeader() {
|
||||
if (file_->Rewind() != 0) {
|
||||
LOG(LS_WARNING) << "Unable to rewind output file " << file_name_;
|
||||
if (!file_.Seek(0)) {
|
||||
LOG(LS_WARNING) << "Unable to rewind ivf output file.";
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -98,21 +97,28 @@ bool IvfFileWriter::WriteHeader() {
|
||||
static_cast<uint32_t>(num_frames_));
|
||||
ByteWriter<uint32_t>::WriteLittleEndian(&ivf_header[28], 0); // Reserved.
|
||||
|
||||
if (!file_->Write(ivf_header, kIvfHeaderSize)) {
|
||||
LOG(LS_ERROR) << "Unable to write IVF header for file " << file_name_;
|
||||
if (file_.Write(ivf_header, kIvfHeaderSize) < kIvfHeaderSize) {
|
||||
LOG(LS_ERROR) << "Unable to write IVF header for ivf output file.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (bytes_written_ < kIvfHeaderSize) {
|
||||
bytes_written_ = kIvfHeaderSize;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IvfFileWriter::InitFromFirstFrame(const EncodedImage& encoded_image) {
|
||||
bool IvfFileWriter::InitFromFirstFrame(const EncodedImage& encoded_image,
|
||||
VideoCodecType codec_type) {
|
||||
width_ = encoded_image._encodedWidth;
|
||||
height_ = encoded_image._encodedHeight;
|
||||
RTC_CHECK_GT(width_, 0);
|
||||
RTC_CHECK_GT(height_, 0);
|
||||
using_capture_timestamps_ = encoded_image._timeStamp == 0;
|
||||
|
||||
codec_type_ = codec_type;
|
||||
|
||||
if (!WriteHeader())
|
||||
return false;
|
||||
|
||||
@ -130,20 +136,22 @@ bool IvfFileWriter::InitFromFirstFrame(const EncodedImage& encoded_image) {
|
||||
default:
|
||||
codec_name = "Unknown";
|
||||
}
|
||||
LOG(LS_WARNING) << "Created IVF file " << file_name_
|
||||
<< " for codec data of type " << codec_name
|
||||
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) {
|
||||
RTC_DCHECK(file_->is_open());
|
||||
|
||||
if (num_frames_ == 0 && !InitFromFirstFrame(encoded_image))
|
||||
bool IvfFileWriter::WriteFrame(const EncodedImage& encoded_image,
|
||||
VideoCodecType codec_type) {
|
||||
if (!file_.IsOpen())
|
||||
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_)) {
|
||||
@ -163,35 +171,41 @@ bool IvfFileWriter::WriteFrame(const EncodedImage& encoded_image) {
|
||||
last_timestamp_ = timestamp;
|
||||
|
||||
const size_t kFrameHeaderSize = 12;
|
||||
if (byte_limit_ != 0 &&
|
||||
bytes_written_ + kFrameHeaderSize + encoded_image._length > byte_limit_) {
|
||||
LOG(LS_WARNING) << "Closing IVF file due to reaching size limit: "
|
||||
<< byte_limit_ << " bytes.";
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
uint8_t frame_header[kFrameHeaderSize] = {};
|
||||
ByteWriter<uint32_t>::WriteLittleEndian(
|
||||
&frame_header[0], static_cast<uint32_t>(encoded_image._length));
|
||||
ByteWriter<uint64_t>::WriteLittleEndian(&frame_header[4], timestamp);
|
||||
if (!file_->Write(frame_header, kFrameHeaderSize) ||
|
||||
!file_->Write(encoded_image._buffer, encoded_image._length)) {
|
||||
LOG(LS_ERROR) << "Unable to write frame to file " << file_name_;
|
||||
if (file_.Write(frame_header, kFrameHeaderSize) < kFrameHeaderSize ||
|
||||
file_.Write(encoded_image._buffer, encoded_image._length) <
|
||||
encoded_image._length) {
|
||||
LOG(LS_ERROR) << "Unable to write frame to file.";
|
||||
return false;
|
||||
}
|
||||
|
||||
bytes_written_ += kFrameHeaderSize + encoded_image._length;
|
||||
|
||||
++num_frames_;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IvfFileWriter::Close() {
|
||||
if (!file_->is_open())
|
||||
if (!file_.IsOpen())
|
||||
return false;
|
||||
|
||||
if (num_frames_ == 0) {
|
||||
// No frame written to file, close and remove it entirely if possible.
|
||||
file_->CloseFile();
|
||||
if (remove(file_name_.c_str()) != 0)
|
||||
LOG(LS_WARNING) << "Failed to remove empty IVF file " << file_name_;
|
||||
|
||||
file_.Close();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ret = WriteHeader();
|
||||
file_->CloseFile();
|
||||
file_.Close();
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
@ -15,38 +15,42 @@
|
||||
#include <string>
|
||||
|
||||
#include "webrtc/base/constructormagic.h"
|
||||
#include "webrtc/base/file.h"
|
||||
#include "webrtc/base/timeutils.h"
|
||||
#include "webrtc/modules/include/module_common_types.h"
|
||||
#include "webrtc/video_frame.h"
|
||||
#include "webrtc/system_wrappers/include/file_wrapper.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
class IvfFileWriter {
|
||||
public:
|
||||
// Takes ownership of the file, which will be closed either through
|
||||
// Close or ~IvfFileWriter. If writing a frame would take the file above the
|
||||
// |byte_limit| the file will be closed, the write (and all future writes)
|
||||
// will fail. A |byte_limit| of 0 is equivalent to no limit.
|
||||
static std::unique_ptr<IvfFileWriter> Wrap(rtc::File file, size_t byte_limit);
|
||||
~IvfFileWriter();
|
||||
|
||||
static std::unique_ptr<IvfFileWriter> Open(const std::string& file_name,
|
||||
VideoCodecType codec_type);
|
||||
bool WriteFrame(const EncodedImage& encoded_image);
|
||||
bool WriteFrame(const EncodedImage& encoded_image, VideoCodecType codec_type);
|
||||
bool Close();
|
||||
|
||||
private:
|
||||
IvfFileWriter(const std::string& path_name,
|
||||
std::unique_ptr<FileWrapper> file,
|
||||
VideoCodecType codec_type);
|
||||
bool WriteHeader();
|
||||
bool InitFromFirstFrame(const EncodedImage& encoded_image);
|
||||
explicit IvfFileWriter(rtc::File file, size_t byte_limit);
|
||||
|
||||
const VideoCodecType codec_type_;
|
||||
bool WriteHeader();
|
||||
bool InitFromFirstFrame(const EncodedImage& encoded_image,
|
||||
VideoCodecType codec_type);
|
||||
|
||||
VideoCodecType codec_type_;
|
||||
size_t bytes_written_;
|
||||
size_t byte_limit_;
|
||||
size_t num_frames_;
|
||||
uint16_t width_;
|
||||
uint16_t height_;
|
||||
int64_t last_timestamp_;
|
||||
bool using_capture_timestamps_;
|
||||
rtc::TimestampWrapAroundHandler wrap_handler_;
|
||||
const std::string file_name_;
|
||||
std::unique_ptr<FileWrapper> file_;
|
||||
rtc::File file_;
|
||||
|
||||
RTC_DISALLOW_COPY_AND_ASSIGN(IvfFileWriter);
|
||||
};
|
||||
|
||||
@ -26,23 +26,18 @@ namespace {
|
||||
static const int kHeaderSize = 32;
|
||||
static const int kFrameHeaderSize = 12;
|
||||
static uint8_t dummy_payload[4] = {0, 1, 2, 3};
|
||||
static const int kMaxFileRetries = 5;
|
||||
} // namespace
|
||||
|
||||
class IvfFileWriterTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
const uint64_t start_id = rtc::CreateRandomId64();
|
||||
uint64_t id = start_id;
|
||||
do {
|
||||
std::ostringstream oss;
|
||||
oss << test::OutputPath() << "ivf_test_file_" << id++ << ".ivf";
|
||||
file_name_ = oss.str();
|
||||
} while ((id - start_id) < 100u && FileExists(false));
|
||||
ASSERT_LT(id - start_id, 100u);
|
||||
file_name_ =
|
||||
webrtc::test::TempFilename(webrtc::test::OutputPath(), "test_file");
|
||||
}
|
||||
void TearDown() override { rtc::RemoveFile(file_name_); }
|
||||
|
||||
bool WriteDummyTestFrames(int width,
|
||||
bool WriteDummyTestFrames(VideoCodecType codec_type,
|
||||
int width,
|
||||
int height,
|
||||
int num_frames,
|
||||
bool use_capture_tims_ms) {
|
||||
@ -57,21 +52,21 @@ class IvfFileWriterTest : public ::testing::Test {
|
||||
} else {
|
||||
frame._timeStamp = i;
|
||||
}
|
||||
if (!file_writer_->WriteFrame(frame))
|
||||
if (!file_writer_->WriteFrame(frame, codec_type))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void VerifyIvfHeader(FileWrapper* file,
|
||||
void VerifyIvfHeader(rtc::File* file,
|
||||
const uint8_t fourcc[4],
|
||||
int width,
|
||||
int height,
|
||||
uint32_t num_frames,
|
||||
bool use_capture_tims_ms) {
|
||||
ASSERT_TRUE(file->is_open());
|
||||
ASSERT_TRUE(file->IsOpen());
|
||||
uint8_t data[kHeaderSize];
|
||||
ASSERT_EQ(kHeaderSize, file->Read(data, kHeaderSize));
|
||||
ASSERT_EQ(static_cast<size_t>(kHeaderSize), file->Read(data, kHeaderSize));
|
||||
|
||||
uint8_t dkif[4] = {'D', 'K', 'I', 'F'};
|
||||
EXPECT_EQ(0, memcmp(dkif, data, 4));
|
||||
@ -87,11 +82,12 @@ class IvfFileWriterTest : public ::testing::Test {
|
||||
EXPECT_EQ(0u, ByteReader<uint32_t>::ReadLittleEndian(&data[28]));
|
||||
}
|
||||
|
||||
void VerifyDummyTestFrames(FileWrapper* file, uint32_t num_frames) {
|
||||
void VerifyDummyTestFrames(rtc::File* file, uint32_t num_frames) {
|
||||
const int kMaxFrameSize = 4;
|
||||
for (uint32_t i = 1; i <= num_frames; ++i) {
|
||||
uint8_t frame_header[kFrameHeaderSize];
|
||||
ASSERT_EQ(kFrameHeaderSize, file->Read(frame_header, kFrameHeaderSize));
|
||||
ASSERT_EQ(static_cast<unsigned int>(kFrameHeaderSize),
|
||||
file->Read(frame_header, kFrameHeaderSize));
|
||||
uint32_t frame_length =
|
||||
ByteReader<uint32_t>::ReadLittleEndian(&frame_header[0]);
|
||||
EXPECT_EQ(i % 4, frame_length);
|
||||
@ -109,67 +105,27 @@ class IvfFileWriterTest : public ::testing::Test {
|
||||
void RunBasicFileStructureTest(VideoCodecType codec_type,
|
||||
const uint8_t fourcc[4],
|
||||
bool use_capture_tims_ms) {
|
||||
file_writer_ = IvfFileWriter::Open(file_name_, codec_type);
|
||||
file_writer_ = IvfFileWriter::Wrap(rtc::File::Open(file_name_), 0);
|
||||
ASSERT_TRUE(file_writer_.get());
|
||||
const int kWidth = 320;
|
||||
const int kHeight = 240;
|
||||
const int kNumFrames = 257;
|
||||
EXPECT_TRUE(
|
||||
WriteDummyTestFrames(kWidth, kHeight, kNumFrames, use_capture_tims_ms));
|
||||
ASSERT_TRUE(WriteDummyTestFrames(codec_type, kWidth, kHeight, kNumFrames,
|
||||
use_capture_tims_ms));
|
||||
EXPECT_TRUE(file_writer_->Close());
|
||||
|
||||
std::unique_ptr<FileWrapper> out_file(FileWrapper::Create());
|
||||
ASSERT_TRUE(out_file->OpenFile(file_name_.c_str(), true));
|
||||
VerifyIvfHeader(out_file.get(), fourcc, kWidth, kHeight, kNumFrames,
|
||||
rtc::File out_file = rtc::File::Open(file_name_);
|
||||
VerifyIvfHeader(&out_file, fourcc, kWidth, kHeight, kNumFrames,
|
||||
use_capture_tims_ms);
|
||||
VerifyDummyTestFrames(out_file.get(), kNumFrames);
|
||||
VerifyDummyTestFrames(&out_file, kNumFrames);
|
||||
|
||||
out_file->CloseFile();
|
||||
|
||||
bool file_removed = false;
|
||||
for (int i = 0; i < kMaxFileRetries; ++i) {
|
||||
file_removed = remove(file_name_.c_str()) == 0;
|
||||
if (file_removed)
|
||||
break;
|
||||
|
||||
// Couldn't remove file for some reason, wait a sec and try again.
|
||||
rtc::Thread::SleepMs(1000);
|
||||
}
|
||||
EXPECT_TRUE(file_removed);
|
||||
}
|
||||
|
||||
// Check whether file exists or not, and if it does not meet expectation,
|
||||
// wait a bit and check again, up to kMaxFileRetries times. This is an ugly
|
||||
// hack to avoid flakiness on certain operating systems where antivirus
|
||||
// software may unexpectedly lock files and keep them from disappearing or
|
||||
// being reused.
|
||||
bool FileExists(bool expected) {
|
||||
bool file_exists = expected;
|
||||
std::unique_ptr<FileWrapper> file_wrapper;
|
||||
int iterations = 0;
|
||||
do {
|
||||
if (file_wrapper.get() != nullptr)
|
||||
rtc::Thread::SleepMs(1000);
|
||||
file_wrapper.reset(FileWrapper::Create());
|
||||
file_exists = file_wrapper->OpenFile(file_name_.c_str(), true);
|
||||
file_wrapper->CloseFile();
|
||||
} while (file_exists != expected && ++iterations < kMaxFileRetries);
|
||||
return file_exists;
|
||||
out_file.Close();
|
||||
}
|
||||
|
||||
std::string file_name_;
|
||||
std::unique_ptr<IvfFileWriter> file_writer_;
|
||||
};
|
||||
|
||||
TEST_F(IvfFileWriterTest, RemovesUnusedFile) {
|
||||
file_writer_ = IvfFileWriter::Open(file_name_, kVideoCodecVP8);
|
||||
ASSERT_TRUE(file_writer_.get() != nullptr);
|
||||
EXPECT_TRUE(FileExists(true));
|
||||
EXPECT_TRUE(file_writer_->Close());
|
||||
EXPECT_FALSE(FileExists(false));
|
||||
EXPECT_FALSE(file_writer_->Close()); // Can't close twice.
|
||||
}
|
||||
|
||||
TEST_F(IvfFileWriterTest, WritesBasicVP8FileNtpTimestamp) {
|
||||
const uint8_t fourcc[4] = {'V', 'P', '8', '0'};
|
||||
RunBasicFileStructureTest(kVideoCodecVP8, fourcc, false);
|
||||
@ -200,4 +156,28 @@ TEST_F(IvfFileWriterTest, WritesBasicH264FileMsTimestamp) {
|
||||
RunBasicFileStructureTest(kVideoCodecH264, fourcc, true);
|
||||
}
|
||||
|
||||
TEST_F(IvfFileWriterTest, ClosesWhenReachesLimit) {
|
||||
const uint8_t fourcc[4] = {'V', 'P', '8', '0'};
|
||||
const int kWidth = 320;
|
||||
const int kHeight = 240;
|
||||
const int kNumFramesToWrite = 2;
|
||||
const int kNumFramesToFit = 1;
|
||||
|
||||
file_writer_ = IvfFileWriter::Wrap(
|
||||
rtc::File::Open(file_name_),
|
||||
kHeaderSize +
|
||||
kNumFramesToFit * (kFrameHeaderSize + sizeof(dummy_payload)));
|
||||
ASSERT_TRUE(file_writer_.get());
|
||||
|
||||
ASSERT_FALSE(WriteDummyTestFrames(kVideoCodecVP8, kWidth, kHeight,
|
||||
kNumFramesToWrite, true));
|
||||
ASSERT_FALSE(file_writer_->Close());
|
||||
|
||||
rtc::File out_file = rtc::File::Open(file_name_);
|
||||
VerifyIvfHeader(&out_file, fourcc, kWidth, kHeight, kNumFramesToFit, true);
|
||||
VerifyDummyTestFrames(&out_file, kNumFramesToFit);
|
||||
|
||||
out_file.Close();
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
Reference in New Issue
Block a user