blob: f22aff78cd1c6957cdff010179adfcdc6d68b4f8 [file] [log] [blame]
/*
* 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 "webrtc/modules/video_coding/utility/ivf_file_writer.h"
#include <memory>
#include "testing/gtest/include/gtest/gtest.h"
#include "webrtc/base/helpers.h"
#include "webrtc/base/logging.h"
#include "webrtc/base/thread.h"
#include "webrtc/base/timeutils.h"
#include "webrtc/modules/rtp_rtcp/source/byte_io.h"
#include "webrtc/test/testsupport/fileutils.h"
namespace webrtc {
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);
}
bool WriteDummyTestFrames(int width,
int height,
int num_frames,
bool use_capture_tims_ms) {
EncodedImage frame;
frame._buffer = dummy_payload;
frame._encodedWidth = width;
frame._encodedHeight = height;
for (int i = 1; i <= num_frames; ++i) {
frame._length = i % sizeof(dummy_payload);
if (use_capture_tims_ms) {
frame.capture_time_ms_ = i;
} else {
frame._timeStamp = i;
}
if (!file_writer_->WriteFrame(frame))
return false;
}
return true;
}
void VerifyIvfHeader(FileWrapper* file,
const uint8_t fourcc[4],
int width,
int height,
uint32_t num_frames,
bool use_capture_tims_ms) {
ASSERT_TRUE(file->is_open());
uint8_t data[kHeaderSize];
ASSERT_EQ(kHeaderSize, file->Read(data, kHeaderSize));
uint8_t dkif[4] = {'D', 'K', 'I', 'F'};
EXPECT_EQ(0, memcmp(dkif, data, 4));
EXPECT_EQ(0u, ByteReader<uint16_t>::ReadLittleEndian(&data[4]));
EXPECT_EQ(32u, ByteReader<uint16_t>::ReadLittleEndian(&data[6]));
EXPECT_EQ(0, memcmp(fourcc, &data[8], 4));
EXPECT_EQ(width, ByteReader<uint16_t>::ReadLittleEndian(&data[12]));
EXPECT_EQ(height, ByteReader<uint16_t>::ReadLittleEndian(&data[14]));
EXPECT_EQ(use_capture_tims_ms ? 1000u : 90000u,
ByteReader<uint32_t>::ReadLittleEndian(&data[16]));
EXPECT_EQ(1u, ByteReader<uint32_t>::ReadLittleEndian(&data[20]));
EXPECT_EQ(num_frames, ByteReader<uint32_t>::ReadLittleEndian(&data[24]));
EXPECT_EQ(0u, ByteReader<uint32_t>::ReadLittleEndian(&data[28]));
}
void VerifyDummyTestFrames(FileWrapper* 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));
uint32_t frame_length =
ByteReader<uint32_t>::ReadLittleEndian(&frame_header[0]);
EXPECT_EQ(i % 4, frame_length);
uint64_t timestamp =
ByteReader<uint64_t>::ReadLittleEndian(&frame_header[4]);
EXPECT_EQ(i, timestamp);
uint8_t data[kMaxFrameSize] = {};
ASSERT_EQ(frame_length,
static_cast<uint32_t>(file->Read(data, frame_length)));
EXPECT_EQ(0, memcmp(data, dummy_payload, frame_length));
}
}
void RunBasicFileStructureTest(VideoCodecType codec_type,
const uint8_t fourcc[4],
bool use_capture_tims_ms) {
file_writer_ = IvfFileWriter::Open(file_name_, codec_type);
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));
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,
use_capture_tims_ms);
VerifyDummyTestFrames(out_file.get(), 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;
}
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);
}
TEST_F(IvfFileWriterTest, WritesBasicVP8FileMsTimestamp) {
const uint8_t fourcc[4] = {'V', 'P', '8', '0'};
RunBasicFileStructureTest(kVideoCodecVP8, fourcc, true);
}
TEST_F(IvfFileWriterTest, WritesBasicVP9FileNtpTimestamp) {
const uint8_t fourcc[4] = {'V', 'P', '9', '0'};
RunBasicFileStructureTest(kVideoCodecVP9, fourcc, false);
}
TEST_F(IvfFileWriterTest, WritesBasicVP9FileMsTimestamp) {
const uint8_t fourcc[4] = {'V', 'P', '9', '0'};
RunBasicFileStructureTest(kVideoCodecVP9, fourcc, true);
}
TEST_F(IvfFileWriterTest, WritesBasicH264FileNtpTimestamp) {
const uint8_t fourcc[4] = {'H', '2', '6', '4'};
RunBasicFileStructureTest(kVideoCodecH264, fourcc, false);
}
TEST_F(IvfFileWriterTest, WritesBasicH264FileMsTimestamp) {
const uint8_t fourcc[4] = {'H', '2', '6', '4'};
RunBasicFileStructureTest(kVideoCodecH264, fourcc, true);
}
} // namespace webrtc