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:
palmkvist
2016-09-28 06:19:48 -07:00
committed by Commit bot
parent 242d8bdddd
commit e75f204b06
17 changed files with 449 additions and 167 deletions

View File

@ -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;
}

View File

@ -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);
};

View File

@ -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