295 lines
8.8 KiB
C++
295 lines
8.8 KiB
C++
// Copyright 2018 The Chromium OS Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
// #define LOG_NDEBUG 0
|
|
#define LOG_TAG "Common"
|
|
|
|
#include "common.h"
|
|
|
|
#include <strings.h>
|
|
#include <time.h>
|
|
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <numeric>
|
|
#include <sstream>
|
|
|
|
#include <log/log.h>
|
|
|
|
namespace android {
|
|
|
|
InputFile::InputFile(std::string file_path) {
|
|
file_ = std::ifstream(file_path);
|
|
}
|
|
|
|
InputFile::InputFile(std::string file_path, std::ios_base::openmode openmode) {
|
|
file_ = std::ifstream(file_path, openmode);
|
|
}
|
|
|
|
bool InputFile::IsValid() const {
|
|
return file_.is_open();
|
|
}
|
|
|
|
size_t InputFile::GetLength() {
|
|
int current_pos = file_.tellg();
|
|
|
|
file_.seekg(0, file_.end);
|
|
size_t ret = file_.tellg();
|
|
|
|
file_.seekg(current_pos, file_.beg);
|
|
return ret;
|
|
}
|
|
|
|
void InputFile::Rewind() {
|
|
file_.clear();
|
|
file_.seekg(0);
|
|
}
|
|
|
|
CachedInputFileStream::CachedInputFileStream(std::string file_path)
|
|
: InputFile(file_path, std::ifstream::binary) {
|
|
if (IsValid()) {
|
|
data_.resize(GetLength());
|
|
file_.read(data_.data(), GetLength());
|
|
}
|
|
}
|
|
|
|
size_t CachedInputFileStream::Read(char* buffer, size_t size) {
|
|
memcpy(buffer, data_.data() + position_, size);
|
|
position_ += size;
|
|
return size;
|
|
}
|
|
|
|
void CachedInputFileStream::Rewind() {
|
|
position_ = 0;
|
|
}
|
|
|
|
InputFileASCII::InputFileASCII(std::string file_path) : InputFile(file_path) {}
|
|
|
|
bool InputFileASCII::ReadLine(std::string* line) {
|
|
std::string read_line;
|
|
while (std::getline(file_, read_line)) {
|
|
if (read_line.empty()) // be careful: an empty line might be read
|
|
continue; // even if none exist.
|
|
*line = read_line;
|
|
return true;
|
|
}
|
|
return false; // no more lines
|
|
}
|
|
|
|
IVFWriter::IVFWriter(std::ofstream* output_file, VideoCodecType codec)
|
|
: output_file_(output_file), codec_(codec) {}
|
|
|
|
bool IVFWriter::WriteHeader(const Size& resolution, uint32_t frame_rate, uint32_t num_frames) {
|
|
constexpr uint16_t kIVFHeaderSize = 32;
|
|
char header[kIVFHeaderSize];
|
|
|
|
// Helper lambdas to write 16bit and 32bit data, expects the device to use little endian.
|
|
auto write16 = [&header](int i, uint16_t data) { memcpy(&header[i], &data, sizeof(data)); };
|
|
auto write32 = [&header](int i, uint32_t data) { memcpy(&header[i], &data, sizeof(data)); };
|
|
|
|
const char* codec_str;
|
|
switch (codec_) {
|
|
case VideoCodecType::VP8:
|
|
codec_str = "VP80";
|
|
break;
|
|
case VideoCodecType::VP9:
|
|
codec_str = "VP90";
|
|
break;
|
|
default:
|
|
printf("[ERR] Unknown codec: \n");
|
|
return false;
|
|
}
|
|
|
|
strcpy(&header[0], "DKIF"); // Bytes 0-3 of an IVF file header always contain 'DKIF' signature.
|
|
constexpr uint16_t kVersion = 0;
|
|
write16(4, kVersion);
|
|
write16(6, kIVFHeaderSize);
|
|
strcpy(&header[8], codec_str);
|
|
write16(12, resolution.width);
|
|
write16(14, resolution.height);
|
|
write32(16, frame_rate);
|
|
write32(20, 1);
|
|
write32(24, num_frames);
|
|
write32(28, 0); // Reserved.
|
|
|
|
output_file_->write(header, kIVFHeaderSize);
|
|
return !output_file_->bad();
|
|
}
|
|
|
|
bool IVFWriter::WriteFrame(const uint8_t* data, uint32_t data_size, uint64_t timestamp) {
|
|
constexpr size_t kIVFFrameHeaderSize = 12;
|
|
char frame_header[kIVFFrameHeaderSize];
|
|
memcpy(&frame_header[0], &data_size, sizeof(data_size));
|
|
memcpy(&frame_header[4], ×tamp, sizeof(timestamp));
|
|
output_file_->write(frame_header, kIVFFrameHeaderSize);
|
|
output_file_->write(reinterpret_cast<const char*>(data), data_size);
|
|
return !output_file_->bad();
|
|
}
|
|
|
|
bool IVFWriter::SetNumFrames(uint32_t num_frames) {
|
|
output_file_->seekp(24);
|
|
output_file_->write(reinterpret_cast<const char*>(&num_frames), sizeof(num_frames));
|
|
return !output_file_->bad();
|
|
}
|
|
|
|
bool OutputFile::Open(const std::string& file_path, VideoCodecType codec) {
|
|
output_file_.open(file_path, std::ofstream::binary);
|
|
if (!output_file_.is_open()) {
|
|
return false;
|
|
}
|
|
|
|
if ((codec == VideoCodecType::VP8) || (codec == VideoCodecType::VP9)) {
|
|
ivf_writer_ = std::make_unique<IVFWriter>(&output_file_, codec);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void OutputFile::Close() {
|
|
if (ivf_writer_) {
|
|
ivf_writer_->SetNumFrames(frame_index_);
|
|
ivf_writer_.reset();
|
|
}
|
|
output_file_.close();
|
|
}
|
|
|
|
bool OutputFile::IsOpen() {
|
|
return output_file_.is_open();
|
|
}
|
|
|
|
// Write the file header.
|
|
bool OutputFile::WriteHeader(const Size& resolution, uint32_t frame_rate, uint32_t num_frames) {
|
|
return !ivf_writer_ || ivf_writer_->WriteHeader(resolution, frame_rate, num_frames);
|
|
}
|
|
|
|
bool OutputFile::WriteFrame(uint32_t data_size, const uint8_t* data) {
|
|
if (ivf_writer_) {
|
|
return (ivf_writer_->WriteFrame(data, data_size, frame_index_++));
|
|
} else {
|
|
output_file_.write(reinterpret_cast<const char*>(data), data_size);
|
|
return (output_file_.fail());
|
|
}
|
|
}
|
|
|
|
bool FPSCalculator::RecordFrameTimeDiff() {
|
|
int64_t now_us = GetNowUs();
|
|
if (last_frame_time_us_ != 0) {
|
|
int64_t frame_diff_us = now_us - last_frame_time_us_;
|
|
if (frame_diff_us <= 0) return false;
|
|
frame_time_diffs_us_.push_back(static_cast<double>(frame_diff_us));
|
|
}
|
|
last_frame_time_us_ = now_us;
|
|
return true;
|
|
}
|
|
|
|
// Reference: (https://cs.corp.google.com/android/cts/common/device-side/util/
|
|
// src/com/android/compatibility/common/util/MediaPerfUtils.java)
|
|
// addPerformanceStatsToLog
|
|
double FPSCalculator::CalculateFPS() const {
|
|
std::vector<double> moving_avgs = MovingAvgOverSum();
|
|
std::sort(moving_avgs.begin(), moving_avgs.end());
|
|
|
|
int index = static_cast<int>(std::round(kRegardedPercentile * (moving_avgs.size() - 1) / 100));
|
|
ALOGD("Frame decode time stats (us): { min=%.4f, regarded=%.4f, "
|
|
"max=%.4f}, window=%.0f",
|
|
moving_avgs[0], moving_avgs[index], moving_avgs[moving_avgs.size() - 1],
|
|
kMovingAvgWindowUs);
|
|
|
|
return 1E6 / moving_avgs[index];
|
|
}
|
|
|
|
// Reference: (https://cs.corp.google.com/android/cts/common/device-side/util/
|
|
// src/com/android/compatibility/common/util/MediaUtils.java)
|
|
// movingAverageOverSum
|
|
std::vector<double> FPSCalculator::MovingAvgOverSum() const {
|
|
std::vector<double> moving_avgs;
|
|
|
|
double sum = std::accumulate(frame_time_diffs_us_.begin(), frame_time_diffs_us_.end(), 0.0);
|
|
int data_size = static_cast<int>(frame_time_diffs_us_.size());
|
|
double avg = sum / data_size;
|
|
if (kMovingAvgWindowUs >= sum) {
|
|
moving_avgs.push_back(avg);
|
|
return moving_avgs;
|
|
}
|
|
|
|
int samples = static_cast<int>(std::ceil((sum - kMovingAvgWindowUs) / avg));
|
|
double cumulative_sum = 0;
|
|
int num = 0;
|
|
int bi = 0;
|
|
int ei = 0;
|
|
double space = kMovingAvgWindowUs;
|
|
double foot = 0;
|
|
|
|
int ix = 0;
|
|
while (ix < samples) {
|
|
while (ei < data_size && frame_time_diffs_us_[ei] <= space) {
|
|
space -= frame_time_diffs_us_[ei];
|
|
cumulative_sum += frame_time_diffs_us_[ei];
|
|
num++;
|
|
ei++;
|
|
}
|
|
|
|
if (num > 0) {
|
|
moving_avgs.push_back(cumulative_sum / num);
|
|
} else if (bi > 0 && foot > space) {
|
|
moving_avgs.push_back(frame_time_diffs_us_[bi - 1]);
|
|
} else if (ei == data_size) {
|
|
break;
|
|
} else {
|
|
moving_avgs.push_back(frame_time_diffs_us_[ei]);
|
|
}
|
|
|
|
ix++;
|
|
foot -= avg;
|
|
space += avg;
|
|
|
|
while (bi < ei && foot < 0) {
|
|
foot += frame_time_diffs_us_[bi];
|
|
cumulative_sum -= frame_time_diffs_us_[bi];
|
|
num--;
|
|
bi++;
|
|
}
|
|
}
|
|
return moving_avgs;
|
|
}
|
|
|
|
VideoCodecType VideoCodecProfileToType(VideoCodecProfile profile) {
|
|
if (profile >= H264PROFILE_MIN && profile <= H264PROFILE_MAX) return VideoCodecType::H264;
|
|
if (profile >= VP8PROFILE_MIN && profile <= VP8PROFILE_MAX) return VideoCodecType::VP8;
|
|
if (profile >= VP9PROFILE_MIN && profile <= VP9PROFILE_MAX) return VideoCodecType::VP9;
|
|
return VideoCodecType::UNKNOWN;
|
|
}
|
|
|
|
std::vector<std::string> SplitString(const std::string& src, char delim) {
|
|
std::stringstream ss(src);
|
|
std::string item;
|
|
std::vector<std::string> ret;
|
|
while (std::getline(ss, item, delim)) {
|
|
ret.push_back(item);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
int64_t GetNowUs() {
|
|
struct timespec t;
|
|
t.tv_sec = t.tv_nsec = 0;
|
|
clock_gettime(CLOCK_MONOTONIC, &t);
|
|
int64_t nsecs = static_cast<int64_t>(t.tv_sec) * 1000000000LL + t.tv_nsec;
|
|
return nsecs / 1000ll;
|
|
}
|
|
|
|
const char* GetMimeType(VideoCodecType type) {
|
|
switch (type) {
|
|
case VideoCodecType::H264:
|
|
return "video/avc";
|
|
case VideoCodecType::VP8:
|
|
return "video/x-vnd.on2.vp8";
|
|
case VideoCodecType::VP9:
|
|
return "video/x-vnd.on2.vp9";
|
|
default: // unknown type
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
} // namespace android
|