// 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 #include #include #include #include #include #include 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(data), data_size); return !output_file_->bad(); } bool IVFWriter::SetNumFrames(uint32_t num_frames) { output_file_->seekp(24); output_file_->write(reinterpret_cast(&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(&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(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(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 moving_avgs = MovingAvgOverSum(); std::sort(moving_avgs.begin(), moving_avgs.end()); int index = static_cast(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 FPSCalculator::MovingAvgOverSum() const { std::vector moving_avgs; double sum = std::accumulate(frame_time_diffs_us_.begin(), frame_time_diffs_us_.end(), 0.0); int data_size = static_cast(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(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 SplitString(const std::string& src, char delim) { std::stringstream ss(src); std::string item; std::vector 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(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