From 596f5e0544c99135ed388f10b67ba042b29d5f24 Mon Sep 17 00:00:00 2001 From: Tom Finegan Date: Thu, 31 Mar 2016 19:46:21 -0700 Subject: [PATCH] Add webm_info. Migrated from the webm-tools repository with minor tweaks to fix its build in the new location. Last location/revision: https://chromium.googlesource.com/webm/webm-tools a7e97e8f0a913ddd97444392bb8816f44a4821a1 Change-Id: Icfad43d9fdd37fc413a6a28b57b370c97c7c28df --- .gitignore | 1 + CMakeLists.txt | 11 + Makefile.unix | 15 +- common/indent.cc | 29 ++ common/indent.h | 48 ++ common/webm_constants.h | 20 + common/webm_endian.cc | 54 ++ common/webm_endian.h | 28 + webm_info.cc | 1074 +++++++++++++++++++++++++++++++++++++++ 9 files changed, 1275 insertions(+), 5 deletions(-) create mode 100644 common/indent.cc create mode 100644 common/indent.h create mode 100644 common/webm_constants.h create mode 100644 common/webm_endian.cc create mode 100644 common/webm_endian.h create mode 100644 webm_info.cc diff --git a/.gitignore b/.gitignore index ef02551..2abad94 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,7 @@ sample_muxer vttdemux muxer_tests parser_tests +webm_info webm2pes webm2pes_tests webm2ts diff --git a/CMakeLists.txt b/CMakeLists.txt index e3cbc2c..df7b755 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,6 +40,7 @@ else () endif () set(CMAKE_CXX_FLAGS "-D__STDC_CONSTANT_MACROS ${CMAKE_CXX_FLAGS}") +set(CMAKE_CXX_FLAGS "-D__STDC_FORMAT_MACROS ${CMAKE_CXX_FLAGS}") set(CMAKE_CXX_FLAGS "-D__STDC_LIMIT_MACROS ${CMAKE_CXX_FLAGS}") # mkvmuxer section. @@ -113,6 +114,16 @@ add_executable(vttdemux "${LIBWEBM_SRC_DIR}/webvtt/webvttparser.h") target_link_libraries(vttdemux LINK_PUBLIC webm) +# Webm_info section. +add_executable(webm_info + "${LIBWEBM_SRC_DIR}/common/indent.cc" + "${LIBWEBM_SRC_DIR}/common/indent.h" + "${LIBWEBM_SRC_DIR}/common/webm_constants.h" + "${LIBWEBM_SRC_DIR}/common/webm_endian.cc" + "${LIBWEBM_SRC_DIR}/common/webm_endian.h" + "${LIBWEBM_SRC_DIR}/webm_info.cc") +target_link_libraries(webm_info LINK_PUBLIC webm) + if (ENABLE_WEBMTS) # webmts (PES/TS support) library section. add_library(webmts diff --git a/Makefile.unix b/Makefile.unix index e01e900..7a852ba 100644 --- a/Makefile.unix +++ b/Makefile.unix @@ -1,5 +1,6 @@ CXX := g++ -DEFINES := -D__STDC_CONSTANT_MACROS -D__STDC_LIMIT_MACROS +DEFINES := -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS +DEFINES += -D__STDC_LIMIT_MACROS INCLUDES := -I. CXXFLAGS := -W -Wall -g ALL_CXXFLAGS := -MMD -MP $(DEFINES) $(INCLUDES) $(CXXFLAGS) @@ -11,13 +12,14 @@ WEBMOBJS += common/file_util.o common/hdr_util.o OBJSA := $(WEBMOBJS:.o=_a.o) OBJSSO := $(WEBMOBJS:.o=_so.o) VTTOBJS := webvtt/vttreader.o webvtt/webvttparser.o sample_muxer_metadata.o -EXEOBJS := sample.o sample_muxer.o dumpvtt.o vttdemux.o +EXEOBJS := sample.o sample_muxer.o dumpvtt.o vttdemux.o webm_info.o +INFOOBJS := common/indent.o common/webm_endian.o DEPS := $(WEBMOBJS:.o=.d) $(OBJECTS1:.o=.d) $(OBJECTS2:.o=.d) DEPS += $(OBJECTS3:.o=.d) $(OBJECTS4:.o=.d) $(OBJSA:.o=.d) $(OBJSSO:.o=.d) -DEPS += $(VTTOBJS:.o=.d) $(EXEOBJS:.o=.d) -EXES := sample_muxer sample dumpvtt vttdemux +DEPS += $(VTTOBJS:.o=.d) $(EXEOBJS:.o=.d) $(INFOOBJS:.o=.d) +EXES := sample_muxer sample dumpvtt vttdemux webm_info CLEAN := $(EXEOBJS) $(VTTOBJS) $(WEBMOBJS) $(OBJSA) $(OBJSSO) $(LIBWEBMA) -CLEAN += $(LIBWEBMSO) $(EXES) $(DEPS) +CLEAN += $(LIBWEBMSO) $(EXES) $(DEPS) $(INFOOBJS) all: $(EXES) @@ -33,6 +35,9 @@ dumpvtt: dumpvtt.o $(VTTOBJS) $(WEBMOBJS) vttdemux: vttdemux.o $(VTTOBJS) $(LIBWEBMA) $(CXX) $^ -o $@ +webm_info: webm_info.o $(INFOOBJS) $(LIBWEBMA) + $(CXX) $^ -o $@ + shared: $(LIBWEBMSO) libwebm.a: $(OBJSA) diff --git a/common/indent.cc b/common/indent.cc new file mode 100644 index 0000000..87d656c --- /dev/null +++ b/common/indent.cc @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2012 The WebM 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 "common/indent.h" + +#include + +namespace libwebm { + +Indent::Indent(int indent) : indent_(indent), indent_str_() { Update(); } + +void Indent::Adjust(int indent) { + indent_ += indent; + if (indent_ < 0) + indent_ = 0; + + Update(); +} + +void Indent::Update() { indent_str_ = std::string(indent_, ' '); } + +} // namespace libwebm diff --git a/common/indent.h b/common/indent.h new file mode 100644 index 0000000..22d3d26 --- /dev/null +++ b/common/indent.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2012 The WebM 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. + */ + +#ifndef LIBWEBM_COMMON_INDENT_H_ +#define LIBWEBM_COMMON_INDENT_H_ + +#include + +#include "mkvmuxer/mkvmuxertypes.h" + +namespace libwebm { + +const int kIncreaseIndent = 2; +const int kDecreaseIndent = -2; + +// Used for formatting output so objects only have to keep track of spacing +// within their scope. +class Indent { + public: + explicit Indent(int indent); + + // Changes the number of spaces output. The value adjusted is relative to + // |indent_|. + void Adjust(int indent); + + std::string indent_str() const { return indent_str_; } + + private: + // Called after |indent_| is changed. This will set |indent_str_| to the + // proper number of spaces. + void Update(); + + int indent_; + std::string indent_str_; + + LIBWEBM_DISALLOW_COPY_AND_ASSIGN(Indent); +}; + +} // namespace libwebm + +#endif // LIBWEBM_COMMON_INDENT_H_ diff --git a/common/webm_constants.h b/common/webm_constants.h new file mode 100644 index 0000000..a082ce8 --- /dev/null +++ b/common/webm_constants.h @@ -0,0 +1,20 @@ +// Copyright (c) 2012 The WebM 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. + +#ifndef LIBWEBM_COMMON_WEBM_CONSTANTS_H_ +#define LIBWEBM_COMMON_WEBM_CONSTANTS_H_ + +namespace libwebm { + +const double kNanosecondsPerSecond = 1000000000.0; +const int kNanosecondsPerSecondi = 1000000000; +const int kNanosecondsPerMillisecond = 1000000; + +} // namespace libwebm + +#endif // LIBWEBM_COMMON_WEBM_CONSTANTS_H_ diff --git a/common/webm_endian.cc b/common/webm_endian.cc new file mode 100644 index 0000000..f589a8f --- /dev/null +++ b/common/webm_endian.cc @@ -0,0 +1,54 @@ +// Copyright (c) 2012 The WebM 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 "common/webm_endian.h" + +#include + +namespace { + +// Swaps unsigned 64 bit values to big endian if needed. Returns |value| +// unmodified if architecture is big endian. Returns swapped bytes of |value| +// if architecture is little endian. Returns 0 otherwise. +uint64_t swap64_check_little_endian(uint64_t value) { + // Check endianness. + union { + uint64_t val64; + uint8_t c[8]; + } check; + check.val64 = 0x0123456789ABCDEFULL; + + // Check for big endian. + if (check.c[7] == 0xEF) + return value; + + // Check for not little endian. + if (check.c[0] != 0xEF) + return 0; + + return value << 56 | ((value << 40) & 0x00FF000000000000ULL) | + ((value << 24) & 0x0000FF0000000000ULL) | + ((value << 8) & 0x000000FF00000000ULL) | + ((value >> 8) & 0x00000000FF000000ULL) | + ((value >> 24) & 0x0000000000FF0000ULL) | + ((value >> 40) & 0x000000000000FF00ULL) | value >> 56; +} + +} // namespace + +namespace libwebm { + +uint64_t host_to_bigendian(uint64_t value) { + return swap64_check_little_endian(value); +} + +uint64_t bigendian_to_host(uint64_t value) { + return swap64_check_little_endian(value); +} + +} // namespace libwebm \ No newline at end of file diff --git a/common/webm_endian.h b/common/webm_endian.h new file mode 100644 index 0000000..9d873d4 --- /dev/null +++ b/common/webm_endian.h @@ -0,0 +1,28 @@ +// Copyright (c) 2012 The WebM 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. + +#ifndef LIBWEBM_COMMON_WEBM_ENDIAN_H_ +#define LIBWEBM_COMMON_WEBM_ENDIAN_H_ + +#include + +namespace libwebm { + +// Swaps unsigned 64 bit values to big endian if needed. Returns |value| if +// architecture is big endian. Returns little endian value if architecture is +// little endian. Returns 0 otherwise. +uint64_t host_to_bigendian(uint64_t value); + +// Swaps unsigned 64 bit values to little endian if needed. Returns |value| if +// architecture is big endian. Returns little endian value if architecture is +// little endian. Returns 0 otherwise. +uint64_t bigendian_to_host(uint64_t value); + +} // namespace libwebm + +#endif // LIBWEBM_COMMON_WEBM_ENDIAN_H_ diff --git a/webm_info.cc b/webm_info.cc new file mode 100644 index 0000000..59c1cfb --- /dev/null +++ b/webm_info.cc @@ -0,0 +1,1074 @@ +// Copyright (c) 2012 The WebM 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 +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "common/indent.h" +#include "common/webm_constants.h" +#include "common/webm_endian.h" + +#include "mkvparser/mkvparser.h" +#include "mkvparser/mkvreader.h" + +namespace { + +using mkvparser::ContentEncoding; +using std::string; +using std::wstring; +using libwebm::Indent; +using libwebm::kNanosecondsPerSecond; +using libwebm::kNanosecondsPerSecondi; + +const char VERSION_STRING[] = "1.0.3.0"; + +struct Options { + Options(); + + // Returns true if |value| matches -|option| or -no|option|. + static bool MatchesBooleanOption(const string& option, const string& value); + + // Set all of the member variables to |value|. + void SetAll(bool value); + + bool output_video; + bool output_audio; + bool output_size; + bool output_offset; + bool output_seconds; + bool output_ebml_header; + bool output_segment; + bool output_seekhead; + bool output_segment_info; + bool output_tracks; + bool output_clusters; + bool output_blocks; + bool output_codec_info; + bool output_clusters_size; + bool output_encrypted_info; + bool output_cues; + bool output_frame_stats; +}; + +Options::Options() + : output_video(true), + output_audio(true), + output_size(false), + output_offset(false), + output_seconds(true), + output_ebml_header(true), + output_segment(true), + output_seekhead(false), + output_segment_info(true), + output_tracks(true), + output_clusters(false), + output_blocks(false), + output_codec_info(false), + output_clusters_size(false), + output_encrypted_info(false), + output_cues(false), + output_frame_stats(false) {} + +void Options::SetAll(bool value) { + output_video = value; + output_audio = value; + output_size = value; + output_offset = value; + output_ebml_header = value; + output_seconds = value; + output_segment = value; + output_segment_info = value; + output_tracks = value; + output_clusters = value; + output_blocks = value; + output_codec_info = value; + output_clusters_size = value; + output_encrypted_info = value; + output_cues = value; + output_frame_stats = value; +} + +bool Options::MatchesBooleanOption(const string& option, const string& value) { + const string opt = "-" + option; + const string noopt = "-no" + option; + return value == opt || value == noopt; +} + +struct FrameStats { + FrameStats() + : frames(0), + displayed_frames(0), + first_altref(true), + frames_since_last_altref(0), + minimum_altref_distance(std::numeric_limits::max()), + min_altref_end_ns(0), + max_window_size(0), + max_window_end_ns(0) {} + + int frames; + int displayed_frames; + + bool first_altref; + int frames_since_last_altref; + int minimum_altref_distance; + int64_t min_altref_end_ns; + + std::queue window; + int64_t max_window_size; + int64_t max_window_end_ns; +}; + +void Usage() { + printf("Usage: webm_info [options] -i input\n"); + printf("\n"); + printf("Main options:\n"); + printf(" -h | -? show help\n"); + printf(" -v show version\n"); + printf(" -all Enable all output options.\n"); + printf(" -video Output video tracks (true)\n"); + printf(" -audio Output audio tracks (true)\n"); + printf(" -size Output element sizes (false)\n"); + printf(" -offset Output element offsets (false)\n"); + printf(" -times_seconds Output times as seconds (true)\n"); + printf(" -ebml_header Output EBML header (true)\n"); + printf(" -segment Output Segment (true)\n"); + printf(" -seekhead Output SeekHead (false)\n"); + printf(" -segment_info Output SegmentInfo (true)\n"); + printf(" -tracks Output Tracks (true)\n"); + printf(" -clusters Output Clusters (false)\n"); + printf(" -blocks Output Blocks (false)\n"); + printf(" -codec_info Output video codec information (false)\n"); + printf(" -clusters_size Output Total Clusters size (false)\n"); + printf(" -encrypted_info Output encrypted frame info (false)\n"); + printf(" -cues Output Cues entries (false)\n"); + printf(" -frame_stats Output frame stats (VP9)(false)\n"); + printf("\nOutput options may be negated by prefixing 'no'.\n"); +} + +// TODO(fgalligan): Add support for non-ascii. +wstring UTF8ToWideString(const char* str) { + wstring wstr; + + if (str == NULL) + return wstr; + + string temp_str(str, strlen(str)); + wstr.assign(temp_str.begin(), temp_str.end()); + + return wstr; +} + +void OutputEBMLHeader(const mkvparser::EBMLHeader& ebml, FILE* o, + Indent* indent) { + fprintf(o, "EBML Header:\n"); + indent->Adjust(libwebm::kIncreaseIndent); + fprintf(o, "%sEBMLVersion : %lld\n", indent->indent_str().c_str(), + ebml.m_version); + fprintf(o, "%sEBMLReadVersion : %lld\n", indent->indent_str().c_str(), + ebml.m_readVersion); + fprintf(o, "%sEBMLMaxIDLength : %lld\n", indent->indent_str().c_str(), + ebml.m_maxIdLength); + fprintf(o, "%sEBMLMaxSizeLength : %lld\n", indent->indent_str().c_str(), + ebml.m_maxSizeLength); + fprintf(o, "%sDoc Type : %s\n", indent->indent_str().c_str(), + ebml.m_docType); + fprintf(o, "%sDocTypeVersion : %lld\n", indent->indent_str().c_str(), + ebml.m_docTypeVersion); + fprintf(o, "%sDocTypeReadVersion: %lld\n", indent->indent_str().c_str(), + ebml.m_docTypeReadVersion); + indent->Adjust(libwebm::kDecreaseIndent); +} + +void OutputSegment(const mkvparser::Segment& segment, const Options& options, + FILE* o) { + fprintf(o, "Segment:"); + if (options.output_offset) + fprintf(o, " @: %lld", segment.m_element_start); + if (options.output_size) + fprintf(o, " size: %lld", + segment.m_size + segment.m_start - segment.m_element_start); + fprintf(o, "\n"); +} + +bool OutputSeekHead(const mkvparser::Segment& segment, const Options& options, + FILE* o, Indent* indent) { + const mkvparser::SeekHead* const seekhead = segment.GetSeekHead(); + if (!seekhead) { + // SeekHeads are optional. + return true; + } + + fprintf(o, "%sSeekHead:", indent->indent_str().c_str()); + if (options.output_offset) + fprintf(o, " @: %lld", seekhead->m_element_start); + if (options.output_size) + fprintf(o, " size: %lld", seekhead->m_element_size); + fprintf(o, "\n"); + + indent->Adjust(libwebm::kIncreaseIndent); + + for (int i = 0; i < seekhead->GetCount(); ++i) { + const mkvparser::SeekHead::Entry* const entry = seekhead->GetEntry(i); + if (!entry) { + fprintf(stderr, "Error retrieving SeekHead entry #%d\n", i); + return false; + } + + fprintf(o, "%sEntry[%d]", indent->indent_str().c_str(), i); + if (options.output_offset) + fprintf(o, " @: %lld", entry->element_start); + if (options.output_size) + fprintf(o, " size: %lld", entry->element_size); + fprintf(o, "\n"); + + indent->Adjust(libwebm::kIncreaseIndent); + const char* const entry_indent = indent->indent_str().c_str(); + // TODO(jzern): 1) known ids could be stringified. 2) ids could be + // reencoded to EBML for ease of lookup. + fprintf(o, "%sSeek ID : %llx\n", entry_indent, entry->id); + fprintf(o, "%sSeek position : %lld\n", entry_indent, entry->pos); + indent->Adjust(libwebm::kDecreaseIndent); + } + + for (int i = 0; i < seekhead->GetVoidElementCount(); ++i) { + const mkvparser::SeekHead::VoidElement* const entry = + seekhead->GetVoidElement(i); + if (!entry) { + fprintf(stderr, "Error retrieving SeekHead void element #%d\n", i); + return false; + } + + fprintf(o, "%sVoid element[%d]", indent->indent_str().c_str(), i); + if (options.output_offset) + fprintf(o, " @: %lld", entry->element_start); + if (options.output_size) + fprintf(o, " size: %lld", entry->element_size); + fprintf(o, "\n"); + } + + indent->Adjust(libwebm::kDecreaseIndent); + return true; +} + +bool OutputSegmentInfo(const mkvparser::Segment& segment, + const Options& options, FILE* o, Indent* indent) { + const mkvparser::SegmentInfo* const segment_info = segment.GetInfo(); + if (!segment_info) { + fprintf(stderr, "SegmentInfo was NULL.\n"); + return false; + } + + const int64_t timecode_scale = segment_info->GetTimeCodeScale(); + const int64_t duration_ns = segment_info->GetDuration(); + const wstring title = UTF8ToWideString(segment_info->GetTitleAsUTF8()); + const wstring muxing_app = + UTF8ToWideString(segment_info->GetMuxingAppAsUTF8()); + const wstring writing_app = + UTF8ToWideString(segment_info->GetWritingAppAsUTF8()); + + fprintf(o, "%sSegmentInfo:", indent->indent_str().c_str()); + if (options.output_offset) + fprintf(o, " @: %lld", segment_info->m_element_start); + if (options.output_size) + fprintf(o, " size: %lld", segment_info->m_element_size); + fprintf(o, "\n"); + + indent->Adjust(libwebm::kIncreaseIndent); + fprintf(o, "%sTimecodeScale : %" PRId64 " \n", indent->indent_str().c_str(), + timecode_scale); + if (options.output_seconds) + fprintf(o, "%sDuration(secs): %g\n", indent->indent_str().c_str(), + duration_ns / kNanosecondsPerSecond); + else + fprintf(o, "%sDuration(nano): %" PRId64 "\n", indent->indent_str().c_str(), + duration_ns); + + if (!title.empty()) + fprintf(o, "%sTitle : %ls\n", indent->indent_str().c_str(), + title.c_str()); + if (!muxing_app.empty()) + fprintf(o, "%sMuxingApp : %ls\n", indent->indent_str().c_str(), + muxing_app.c_str()); + if (!writing_app.empty()) + fprintf(o, "%sWritingApp : %ls\n", indent->indent_str().c_str(), + writing_app.c_str()); + indent->Adjust(libwebm::kDecreaseIndent); + return true; +} + +bool OutputTracks(const mkvparser::Segment& segment, const Options& options, + FILE* o, Indent* indent) { + const mkvparser::Tracks* const tracks = segment.GetTracks(); + if (!tracks) { + fprintf(stderr, "Tracks was NULL.\n"); + return false; + } + + fprintf(o, "%sTracks:", indent->indent_str().c_str()); + if (options.output_offset) + fprintf(o, " @: %lld", tracks->m_element_start); + if (options.output_size) + fprintf(o, " size: %lld", tracks->m_element_size); + fprintf(o, "\n"); + + unsigned int i = 0; + const unsigned int j = tracks->GetTracksCount(); + while (i != j) { + const mkvparser::Track* const track = tracks->GetTrackByIndex(i++); + if (track == NULL) + continue; + + indent->Adjust(libwebm::kIncreaseIndent); + fprintf(o, "%sTrack:", indent->indent_str().c_str()); + if (options.output_offset) + fprintf(o, " @: %lld", track->m_element_start); + if (options.output_size) + fprintf(o, " size: %lld", track->m_element_size); + fprintf(o, "\n"); + + const int64_t track_type = track->GetType(); + const int64_t track_number = track->GetNumber(); + const wstring track_name = UTF8ToWideString(track->GetNameAsUTF8()); + + indent->Adjust(libwebm::kIncreaseIndent); + fprintf(o, "%sTrackType : %" PRId64 "\n", indent->indent_str().c_str(), + track_type); + fprintf(o, "%sTrackNumber : %" PRId64 "\n", indent->indent_str().c_str(), + track_number); + if (!track_name.empty()) + fprintf(o, "%sName : %ls\n", indent->indent_str().c_str(), + track_name.c_str()); + + const char* const codec_id = track->GetCodecId(); + if (codec_id) + fprintf(o, "%sCodecID : %s\n", indent->indent_str().c_str(), + codec_id); + + const wstring codec_name = UTF8ToWideString(track->GetCodecNameAsUTF8()); + if (!codec_name.empty()) + fprintf(o, "%sCodecName : %ls\n", indent->indent_str().c_str(), + codec_name.c_str()); + + size_t private_size; + const unsigned char* const private_data = + track->GetCodecPrivate(private_size); + if (private_data) + fprintf(o, "%sPrivateData(size): %d\n", indent->indent_str().c_str(), + static_cast(private_size)); + + const uint64_t default_duration = track->GetDefaultDuration(); + if (default_duration > 0) + fprintf(o, "%sDefaultDuration: %" PRIu64 "\n", + indent->indent_str().c_str(), default_duration); + + if (track->GetContentEncodingCount() > 0) { + // Only check the first content encoding. + const ContentEncoding* const encoding = + track->GetContentEncodingByIndex(0); + if (!encoding) { + printf("Could not get first ContentEncoding.\n"); + return false; + } + + fprintf(o, "%sContentEncodingOrder : %lld\n", + indent->indent_str().c_str(), encoding->encoding_order()); + fprintf(o, "%sContentEncodingScope : %lld\n", + indent->indent_str().c_str(), encoding->encoding_scope()); + fprintf(o, "%sContentEncodingType : %lld\n", + indent->indent_str().c_str(), encoding->encoding_type()); + + if (encoding->GetEncryptionCount() > 0) { + // Only check the first encryption. + const ContentEncoding::ContentEncryption* const encryption = + encoding->GetEncryptionByIndex(0); + if (!encryption) { + printf("Could not get first ContentEncryption.\n"); + return false; + } + + fprintf(o, "%sContentEncAlgo : %lld\n", + indent->indent_str().c_str(), encryption->algo); + + if (encryption->key_id_len > 0) { + fprintf(o, "%sContentEncKeyID : ", indent->indent_str().c_str()); + for (int k = 0; k < encryption->key_id_len; ++k) { + fprintf(o, "0x%02x, ", encryption->key_id[k]); + } + fprintf(o, "\n"); + } + + if (encryption->signature_len > 0) { + fprintf(o, "%sContentSignature : 0x", + indent->indent_str().c_str()); + for (int k = 0; k < encryption->signature_len; ++k) { + fprintf(o, "%x", encryption->signature[k]); + } + fprintf(o, "\n"); + } + + if (encryption->sig_key_id_len > 0) { + fprintf(o, "%sContentSigKeyID : 0x", + indent->indent_str().c_str()); + for (int k = 0; k < encryption->sig_key_id_len; ++k) { + fprintf(o, "%x", encryption->sig_key_id[k]); + } + fprintf(o, "\n"); + } + + fprintf(o, "%sContentSigAlgo : %lld\n", + indent->indent_str().c_str(), encryption->sig_algo); + fprintf(o, "%sContentSigHashAlgo : %lld\n", + indent->indent_str().c_str(), encryption->sig_hash_algo); + + const ContentEncoding::ContentEncAESSettings& aes = + encryption->aes_settings; + fprintf(o, "%sCipherMode : %lld\n", + indent->indent_str().c_str(), aes.cipher_mode); + } + } + + if (track_type == mkvparser::Track::kVideo) { + const mkvparser::VideoTrack* const video_track = + static_cast(track); + const int64_t width = video_track->GetWidth(); + const int64_t height = video_track->GetHeight(); + const int64_t display_width = video_track->GetDisplayWidth(); + const int64_t display_height = video_track->GetDisplayHeight(); + const int64_t display_unit = video_track->GetDisplayUnit(); + const double frame_rate = video_track->GetFrameRate(); + fprintf(o, "%sPixelWidth : %" PRId64 "\n", indent->indent_str().c_str(), + width); + fprintf(o, "%sPixelHeight : %" PRId64 "\n", indent->indent_str().c_str(), + height); + if (frame_rate > 0.0) + fprintf(o, "%sFrameRate : %g\n", indent->indent_str().c_str(), + video_track->GetFrameRate()); + if (display_unit > 0 || display_width != width || + display_height != height) { + fprintf(o, "%sDisplayWidth : %" PRId64 "\n", + indent->indent_str().c_str(), display_width); + fprintf(o, "%sDisplayHeight : %" PRId64 "\n", + indent->indent_str().c_str(), display_height); + fprintf(o, "%sDisplayUnit : %" PRId64 "\n", + indent->indent_str().c_str(), display_unit); + } + } else if (track_type == mkvparser::Track::kAudio) { + const mkvparser::AudioTrack* const audio_track = + static_cast(track); + const int64_t channels = audio_track->GetChannels(); + const int64_t bit_depth = audio_track->GetBitDepth(); + const uint64_t codec_delay = audio_track->GetCodecDelay(); + const uint64_t seek_preroll = audio_track->GetSeekPreRoll(); + fprintf(o, "%sChannels : %" PRId64 "\n", + indent->indent_str().c_str(), channels); + if (bit_depth > 0) + fprintf(o, "%sBitDepth : %" PRId64 "\n", + indent->indent_str().c_str(), bit_depth); + fprintf(o, "%sSamplingFrequency: %g\n", indent->indent_str().c_str(), + audio_track->GetSamplingRate()); + if (codec_delay) + fprintf(o, "%sCodecDelay : %" PRIu64 "\n", + indent->indent_str().c_str(), codec_delay); + if (seek_preroll) + fprintf(o, "%sSeekPreRoll : %" PRIu64 "\n", + indent->indent_str().c_str(), seek_preroll); + } + indent->Adjust(libwebm::kDecreaseIndent * 2); + } + + return true; +} + +// libvpx reference: vp9/vp9_dx_iface.c +void ParseSuperframeIndex(const uint8_t* data, size_t data_sz, + uint32_t sizes[8], int* count) { + const uint8_t marker = data[data_sz - 1]; + *count = 0; + + if ((marker & 0xe0) == 0xc0) { + const int frames = (marker & 0x7) + 1; + const int mag = ((marker >> 3) & 0x3) + 1; + const size_t index_sz = 2 + mag * frames; + + if (data_sz >= index_sz && data[data_sz - index_sz] == marker) { + // found a valid superframe index + const uint8_t* x = data + data_sz - index_sz + 1; + + for (int i = 0; i < frames; ++i) { + uint32_t this_sz = 0; + + for (int j = 0; j < mag; ++j) { + this_sz |= (*x++) << (j * 8); + } + sizes[i] = this_sz; + } + *count = frames; + } + } +} + +void PrintVP9Info(const uint8_t* data, int size, FILE* o, int64_t time_ns, + FrameStats* stats) { + if (size < 1) + return; + + uint32_t sizes[8]; + int i = 0, count = 0; + ParseSuperframeIndex(data, size, sizes, &count); + + // Remove all frames that are less than window size. + while (!stats->window.empty() && + stats->window.front() < (time_ns - (kNanosecondsPerSecondi - 1))) + stats->window.pop(); + + do { + // const int frame_marker = (data[0] >> 6) & 0x3; + // TODO(jzern): profile > 2 uses 3 bits in the header. + const int version = (data[0] >> 4) & 0x3; + const int key = !((data[0] >> 2) & 0x1); + const int altref_frame = !((data[0] >> 1) & 0x1); + const int error_resilient_mode = data[0] & 0x1; + if (version > 2) { + fprintf(o, " profile > 2 is unsupported"); + return; + } + + if (key && + !(size >= 4 && data[1] == 0x49 && data[2] == 0x83 && data[3] == 0x42)) { + fprintf(o, " invalid VP9 signature"); + return; + } + + stats->window.push(time_ns); + ++stats->frames; + + if (altref_frame) { + const int delta_altref = stats->frames_since_last_altref; + if (stats->first_altref) { + stats->first_altref = false; + } else if (delta_altref < stats->minimum_altref_distance) { + stats->minimum_altref_distance = delta_altref; + stats->min_altref_end_ns = time_ns; + } + stats->frames_since_last_altref = 0; + } else { + ++stats->frames_since_last_altref; + ++stats->displayed_frames; + } + + if (count > 0) { + fprintf(o, " packed [%d]: {", i); + } + + fprintf(o, " key:%d v:%d altref:%d errm:%d", key, version, altref_frame, + error_resilient_mode); + + if (key && size > 4) { + fprintf(o, " cs:%d", (data[4] >> 5) & 0x7); + } + + if (count > 0) { + fprintf(o, " size: %u }", sizes[i]); + data += sizes[i]; + size -= sizes[i]; + } + ++i; + } while (i < count); + + if (stats->max_window_size < static_cast(stats->window.size())) { + stats->max_window_size = stats->window.size(); + stats->max_window_end_ns = time_ns; + } +} + +void PrintVP8Info(const uint8_t* data, int size, FILE* o) { + if (size < 3) + return; + + const uint32_t bits = data[0] | (data[1] << 8) | (data[2] << 16); + const int key = !(bits & 0x1); + const int altref_frame = !((bits >> 4) & 0x1); + const int version = (bits >> 1) & 0x7; + const int partition_length = (bits >> 5) & 0x7FFFF; + if (key && + !(size >= 6 && data[3] == 0x9d && data[4] == 0x01 && data[5] == 0x2a)) { + fprintf(o, " invalid VP8 signature"); + return; + } + fprintf(o, " key:%d v:%d altref:%d partition_length:%d", key, version, + altref_frame, partition_length); +} + +bool OutputCluster(const mkvparser::Cluster& cluster, + const mkvparser::Tracks& tracks, const Options& options, + FILE* o, mkvparser::MkvReader* reader, Indent* indent, + int64_t* clusters_size, FrameStats* stats) { + if (clusters_size) { + // Load the Cluster. + const mkvparser::BlockEntry* block_entry; + int status = cluster.GetFirst(block_entry); + if (status) { + fprintf(stderr, "Could not get first Block of Cluster.\n"); + return false; + } + + *clusters_size += cluster.GetElementSize(); + } + + if (options.output_clusters) { + const int64_t time_ns = cluster.GetTime(); + const int64_t duration_ns = cluster.GetLastTime() - cluster.GetFirstTime(); + + fprintf(o, "%sCluster:", indent->indent_str().c_str()); + if (options.output_offset) + fprintf(o, " @: %lld", cluster.m_element_start); + if (options.output_size) + fprintf(o, " size: %lld", cluster.GetElementSize()); + fprintf(o, "\n"); + indent->Adjust(libwebm::kIncreaseIndent); + if (options.output_seconds) + fprintf(o, "%sTimecode (sec) : %g\n", indent->indent_str().c_str(), + time_ns / kNanosecondsPerSecond); + else + fprintf(o, "%sTimecode (nano): %" PRId64 "\n", + indent->indent_str().c_str(), time_ns); + if (options.output_seconds) + fprintf(o, "%sDuration (sec) : %g\n", indent->indent_str().c_str(), + duration_ns / kNanosecondsPerSecond); + else + fprintf(o, "%sDuration (nano): %" PRId64 "\n", + indent->indent_str().c_str(), duration_ns); + + fprintf(o, "%s# Blocks : %ld\n", indent->indent_str().c_str(), + cluster.GetEntryCount()); + } + + if (options.output_blocks) { + const mkvparser::BlockEntry* block_entry; + int status = cluster.GetFirst(block_entry); + if (status) { + fprintf(stderr, "Could not get first Block of Cluster.\n"); + return false; + } + + std::vector vector_data; + while (block_entry != NULL && !block_entry->EOS()) { + const mkvparser::Block* const block = block_entry->GetBlock(); + if (!block) { + fprintf(stderr, "Could not getblock entry.\n"); + return false; + } + + const unsigned int track_number = + static_cast(block->GetTrackNumber()); + const mkvparser::Track* track = tracks.GetTrackByNumber(track_number); + if (!track) { + fprintf(stderr, "Could not get Track.\n"); + return false; + } + + const int64_t track_type = track->GetType(); + if ((track_type == mkvparser::Track::kVideo && options.output_video) || + (track_type == mkvparser::Track::kAudio && options.output_audio)) { + const int64_t time_ns = block->GetTime(&cluster); + const bool is_key = block->IsKey(); + + if (block_entry->GetKind() == mkvparser::BlockEntry::kBlockGroup) { + fprintf(o, "%sBlockGroup:\n", indent->indent_str().c_str()); + indent->Adjust(libwebm::kIncreaseIndent); + } + + fprintf(o, "%sBlock: type:%s frame:%s", indent->indent_str().c_str(), + track_type == mkvparser::Track::kVideo ? "V" : "A", + is_key ? "I" : "P"); + if (options.output_seconds) + fprintf(o, " secs:%5g", time_ns / kNanosecondsPerSecond); + else + fprintf(o, " nano:%10" PRId64, time_ns); + + if (options.output_offset) + fprintf(o, " @_payload: %lld", block->m_start); + if (options.output_size) + fprintf(o, " size_payload: %lld", block->m_size); + + const uint8_t KEncryptedBit = 0x1; + const int kSignalByteSize = 1; + bool encrypted_stream = false; + if (options.output_encrypted_info) { + if (track->GetContentEncodingCount() > 0) { + // Only check the first content encoding. + const ContentEncoding* const encoding = + track->GetContentEncodingByIndex(0); + if (encoding) { + if (encoding->GetEncryptionCount() > 0) { + const ContentEncoding::ContentEncryption* const encryption = + encoding->GetEncryptionByIndex(0); + if (encryption) { + const ContentEncoding::ContentEncAESSettings& aes = + encryption->aes_settings; + if (aes.cipher_mode == 1) { + encrypted_stream = true; + } + } + } + } + } + + if (encrypted_stream) { + const mkvparser::Block::Frame& frame = block->GetFrame(0); + if (frame.len > static_cast(vector_data.size())) { + vector_data.resize(frame.len + 1024); + } + + unsigned char* data = &vector_data[0]; + if (frame.Read(reader, data) < 0) { + fprintf(stderr, "Could not read frame.\n"); + return false; + } + + const bool encrypted_frame = (data[0] & KEncryptedBit) ? 1 : 0; + fprintf(o, " enc: %d", encrypted_frame ? 1 : 0); + + if (encrypted_frame) { + uint64_t iv; + memcpy(&iv, data + kSignalByteSize, sizeof(iv)); + fprintf(o, " iv: %" PRIx64, iv); + } + } + } + + if (options.output_codec_info) { + const int frame_count = block->GetFrameCount(); + + if (frame_count > 1) { + fprintf(o, "\n"); + indent->Adjust(libwebm::kIncreaseIndent); + } + + for (int i = 0; i < frame_count; ++i) { + if (track_type == mkvparser::Track::kVideo) { + const mkvparser::Block::Frame& frame = block->GetFrame(i); + if (frame.len > static_cast(vector_data.size())) { + vector_data.resize(frame.len + 1024); + } + + unsigned char* data = &vector_data[0]; + if (frame.Read(reader, data) < 0) { + fprintf(stderr, "Could not read frame.\n"); + return false; + } + + if (frame_count > 1) + fprintf(o, "\n%sVP8 data :", indent->indent_str().c_str()); + + bool encrypted_frame = false; + int frame_offset = 0; + if (encrypted_stream) { + if (data[0] & KEncryptedBit) { + encrypted_frame = true; + } else { + frame_offset = kSignalByteSize; + } + } + + if (!encrypted_frame) { + data += frame_offset; + + const string codec_id = track->GetCodecId(); + if (codec_id == "V_VP8") { + PrintVP8Info(data, frame.len, o); + } else if (codec_id == "V_VP9") { + PrintVP9Info(data, frame.len, o, time_ns, stats); + } + } + } + } + + if (frame_count > 1) + indent->Adjust(libwebm::kDecreaseIndent); + } + + if (block_entry->GetKind() == mkvparser::BlockEntry::kBlockGroup) { + const int64_t discard_padding = block->GetDiscardPadding(); + if (discard_padding != 0) { + fprintf(o, "\n%sDiscardPadding: %10" PRId64, + indent->indent_str().c_str(), discard_padding); + } + indent->Adjust(libwebm::kDecreaseIndent); + } + + fprintf(o, "\n"); + } + + status = cluster.GetNext(block_entry, block_entry); + if (status) { + printf("\n Could not get next block of cluster.\n"); + return false; + } + } + } + + if (options.output_clusters) + indent->Adjust(libwebm::kDecreaseIndent); + + return true; +} + +bool OutputCues(const mkvparser::Segment& segment, + const mkvparser::Tracks& tracks, const Options& options, + FILE* o, Indent* indent) { + const mkvparser::Cues* const cues = segment.GetCues(); + if (cues == NULL) + return true; + + // Load all of the cue points. + while (!cues->DoneParsing()) + cues->LoadCuePoint(); + + // Confirm that the input has cue points. + const mkvparser::CuePoint* const first_cue = cues->GetFirst(); + if (first_cue == NULL) { + fprintf(o, "%sNo cue points.\n", indent->indent_str().c_str()); + return true; + } + + // Input has cue points, dump them: + fprintf(o, "%sCues:", indent->indent_str().c_str()); + if (options.output_offset) + fprintf(o, " @:%lld", cues->m_element_start); + if (options.output_size) + fprintf(o, " size:%lld", cues->m_element_size); + fprintf(o, "\n"); + + const mkvparser::CuePoint* cue_point = first_cue; + int cue_point_num = 1; + const int num_tracks = tracks.GetTracksCount(); + indent->Adjust(libwebm::kIncreaseIndent); + + do { + for (int track_num = 1; track_num <= num_tracks; ++track_num) { + const mkvparser::Track* const track = tracks.GetTrackByNumber(track_num); + const mkvparser::CuePoint::TrackPosition* const track_pos = + cue_point->Find(track); + + if (track_pos != NULL) { + const char track_type = + (track->GetType() == mkvparser::Track::kVideo) ? 'V' : 'A'; + fprintf(o, "%sCue Point:%d type:%c track:%d", + indent->indent_str().c_str(), cue_point_num, track_type, + track_num); + + if (options.output_seconds) { + fprintf(o, " secs:%g", + cue_point->GetTime(&segment) / kNanosecondsPerSecond); + } else { + fprintf(o, " nano:%lld", cue_point->GetTime(&segment)); + } + + if (options.output_blocks) + fprintf(o, " block:%lld", track_pos->m_block); + + if (options.output_offset) + fprintf(o, " @:%lld", track_pos->m_pos); + + fprintf(o, "\n"); + } + } + + cue_point = cues->GetNext(cue_point); + ++cue_point_num; + } while (cue_point != NULL); + + indent->Adjust(libwebm::kDecreaseIndent); + return true; +} + +} // namespace + +int main(int argc, char* argv[]) { + string input; + Options options; + + const int argc_check = argc - 1; + for (int i = 1; i < argc; ++i) { + if (!strcmp("-h", argv[i]) || !strcmp("-?", argv[i])) { + Usage(); + return EXIT_SUCCESS; + } else if (!strcmp("-v", argv[i])) { + printf("version: %s\n", VERSION_STRING); + } else if (!strcmp("-i", argv[i]) && i < argc_check) { + input = argv[++i]; + } else if (!strcmp("-all", argv[i])) { + options.SetAll(true); + } else if (Options::MatchesBooleanOption("video", argv[i])) { + options.output_video = !strcmp("-video", argv[i]); + } else if (Options::MatchesBooleanOption("audio", argv[i])) { + options.output_audio = !strcmp("-audio", argv[i]); + } else if (Options::MatchesBooleanOption("size", argv[i])) { + options.output_size = !strcmp("-size", argv[i]); + } else if (Options::MatchesBooleanOption("offset", argv[i])) { + options.output_offset = !strcmp("-offset", argv[i]); + } else if (Options::MatchesBooleanOption("times_seconds", argv[i])) { + options.output_seconds = !strcmp("-times_seconds", argv[i]); + } else if (Options::MatchesBooleanOption("ebml_header", argv[i])) { + options.output_ebml_header = !strcmp("-ebml_header", argv[i]); + } else if (Options::MatchesBooleanOption("segment", argv[i])) { + options.output_segment = !strcmp("-segment", argv[i]); + } else if (Options::MatchesBooleanOption("seekhead", argv[i])) { + options.output_seekhead = !strcmp("-seekhead", argv[i]); + } else if (Options::MatchesBooleanOption("segment_info", argv[i])) { + options.output_segment_info = !strcmp("-segment_info", argv[i]); + } else if (Options::MatchesBooleanOption("tracks", argv[i])) { + options.output_tracks = !strcmp("-tracks", argv[i]); + } else if (Options::MatchesBooleanOption("clusters", argv[i])) { + options.output_clusters = !strcmp("-clusters", argv[i]); + } else if (Options::MatchesBooleanOption("blocks", argv[i])) { + options.output_blocks = !strcmp("-blocks", argv[i]); + } else if (Options::MatchesBooleanOption("codec_info", argv[i])) { + options.output_codec_info = !strcmp("-codec_info", argv[i]); + } else if (Options::MatchesBooleanOption("clusters_size", argv[i])) { + options.output_clusters_size = !strcmp("-clusters_size", argv[i]); + } else if (Options::MatchesBooleanOption("encrypted_info", argv[i])) { + options.output_encrypted_info = !strcmp("-encrypted_info", argv[i]); + } else if (Options::MatchesBooleanOption("cues", argv[i])) { + options.output_cues = !strcmp("-cues", argv[i]); + } else if (Options::MatchesBooleanOption("frame_stats", argv[i])) { + options.output_frame_stats = !strcmp("-frame_stats", argv[i]); + } + } + + if (argc < 3 || input.empty()) { + Usage(); + return EXIT_FAILURE; + } + + // TODO(fgalligan): Replace auto_ptr with scoped_ptr. + std::auto_ptr reader( + new (std::nothrow) mkvparser::MkvReader()); // NOLINT + if (reader->Open(input.c_str())) { + fprintf(stderr, "Error opening file:%s\n", input.c_str()); + return EXIT_FAILURE; + } + + long long int pos = 0; + std::auto_ptr ebml_header( + new (std::nothrow) mkvparser::EBMLHeader()); // NOLINT + if (ebml_header->Parse(reader.get(), pos) < 0) { + fprintf(stderr, "Error parsing EBML header.\n"); + return EXIT_FAILURE; + } + + Indent indent(0); + FILE* out = stdout; + + if (options.output_ebml_header) + OutputEBMLHeader(*ebml_header.get(), out, &indent); + + mkvparser::Segment* temp_segment; + if (mkvparser::Segment::CreateInstance(reader.get(), pos, temp_segment)) { + fprintf(stderr, "Segment::CreateInstance() failed.\n"); + return EXIT_FAILURE; + } + std::auto_ptr segment(temp_segment); + + if (segment->Load() < 0) { + fprintf(stderr, "Segment::Load() failed.\n"); + return EXIT_FAILURE; + } + + if (options.output_segment) { + OutputSegment(*(segment.get()), options, out); + indent.Adjust(libwebm::kIncreaseIndent); + } + + if (options.output_seekhead) + if (!OutputSeekHead(*(segment.get()), options, out, &indent)) + return EXIT_FAILURE; + + if (options.output_segment_info) + if (!OutputSegmentInfo(*(segment.get()), options, out, &indent)) + return EXIT_FAILURE; + + if (options.output_tracks) + if (!OutputTracks(*(segment.get()), options, out, &indent)) + return EXIT_FAILURE; + + const mkvparser::Tracks* const tracks = segment->GetTracks(); + if (!tracks) { + fprintf(stderr, "Could not get Tracks.\n"); + return EXIT_FAILURE; + } + + // If Cues are before the clusters output them first. + if (options.output_cues) { + const mkvparser::Cluster* cluster = segment->GetFirst(); + const mkvparser::Cues* const cues = segment->GetCues(); + if (cluster != NULL && cues != NULL) { + if (cues->m_element_start < cluster->m_element_start) { + if (!OutputCues(*segment, *tracks, options, out, &indent)) { + return EXIT_FAILURE; + } + options.output_cues = false; + } + } + } + + if (options.output_clusters) + fprintf(out, "%sClusters (count):%ld\n", indent.indent_str().c_str(), + segment->GetCount()); + + int64_t clusters_size = 0; + FrameStats stats; + const mkvparser::Cluster* cluster = segment->GetFirst(); + while (cluster != NULL && !cluster->EOS()) { + if (!OutputCluster(*cluster, *tracks, options, out, reader.get(), &indent, + &clusters_size, &stats)) + return EXIT_FAILURE; + cluster = segment->GetNext(cluster); + } + + if (options.output_clusters_size) + fprintf(out, "%sClusters (size):%" PRId64 "\n", indent.indent_str().c_str(), + clusters_size); + + if (options.output_cues) + if (!OutputCues(*segment, *tracks, options, out, &indent)) + return EXIT_FAILURE; + + // TODO(fgalligan): Add support for VP8. + if (options.output_frame_stats && + stats.minimum_altref_distance != std::numeric_limits::max()) { + const double actual_fps = + stats.frames / + (segment->GetInfo()->GetDuration() / kNanosecondsPerSecond); + const double displayed_fps = + stats.displayed_frames / + (segment->GetInfo()->GetDuration() / kNanosecondsPerSecond); + fprintf(out, "\nActual fps:%g Displayed fps:%g\n", actual_fps, + displayed_fps); + + fprintf(out, "Minimum Altref Distance:%d at:%g seconds\n", + stats.minimum_altref_distance, + stats.min_altref_end_ns / kNanosecondsPerSecond); + + // TODO(fgalligan): Add support for window duration other than 1 second. + const double sec_end = stats.max_window_end_ns / kNanosecondsPerSecond; + const double sec_start = + stats.max_window_end_ns > kNanosecondsPerSecondi ? sec_end - 1.0 : 0.0; + fprintf(out, "Maximum Window:%g-%g seconds Window fps:%" PRId64 "\n", + sec_start, sec_end, stats.max_window_size); + } + return EXIT_SUCCESS; +}