| // 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 <inttypes.h> |
| #include <stdint.h> |
| |
| #include <cstdlib> |
| #include <cstring> |
| #include <limits> |
| #include <memory> |
| #include <queue> |
| #include <string> |
| #include <vector> |
| |
| #include "common/hdr_util.h" |
| #include "common/indent.h" |
| #include "common/vp9_header_parser.h" |
| #include "common/vp9_level_stats.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.4.5"; |
| |
| 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; |
| bool output_vp9_level; |
| }; |
| |
| 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), |
| output_vp9_level(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; |
| output_vp9_level = 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<int>::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<int64_t> 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(" -vp9_level Output VP9 level(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 long 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<int>(private_size)); |
| |
| if (track_type == mkvparser::Track::kVideo) { |
| const std::string codec_id = track->GetCodecId(); |
| const std::string v_vp9 = "V_VP9"; |
| if (codec_id == v_vp9) { |
| libwebm::Vp9CodecFeatures features; |
| if (!libwebm::ParseVpxCodecPrivate(private_data, |
| static_cast<int32_t>(private_size), |
| &features)) { |
| fprintf(stderr, "Error parsing VpxCodecPrivate.\n"); |
| return false; |
| } |
| if (features.profile != -1) |
| fprintf(o, "%sVP9 profile : %d\n", |
| indent->indent_str().c_str(), features.profile); |
| if (features.level != -1) |
| fprintf(o, "%sVP9 level : %d\n", |
| indent->indent_str().c_str(), features.level); |
| if (features.bit_depth != -1) |
| fprintf(o, "%sVP9 bit_depth : %d\n", |
| indent->indent_str().c_str(), features.bit_depth); |
| if (features.chroma_subsampling != -1) |
| fprintf(o, "%sVP9 chroma subsampling : %d\n", |
| indent->indent_str().c_str(), features.chroma_subsampling); |
| } |
| } |
| } |
| |
| 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<const mkvparser::VideoTrack* const>(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); |
| } |
| |
| const mkvparser::Colour* const colour = video_track->GetColour(); |
| if (colour) { |
| // TODO(fgalligan): Add support for Colour's address and size. |
| fprintf(o, "%sColour:\n", indent->indent_str().c_str()); |
| indent->Adjust(libwebm::kIncreaseIndent); |
| |
| const int64_t matrix_coefficients = colour->matrix_coefficients; |
| const int64_t bits_per_channel = colour->bits_per_channel; |
| const int64_t chroma_subsampling_horz = colour->chroma_subsampling_horz; |
| const int64_t chroma_subsampling_vert = colour->chroma_subsampling_vert; |
| const int64_t cb_subsampling_horz = colour->cb_subsampling_horz; |
| const int64_t cb_subsampling_vert = colour->cb_subsampling_vert; |
| const int64_t chroma_siting_horz = colour->chroma_siting_horz; |
| const int64_t chroma_siting_vert = colour->chroma_siting_vert; |
| const int64_t range = colour->range; |
| const int64_t transfer_characteristics = |
| colour->transfer_characteristics; |
| const int64_t primaries = colour->primaries; |
| const int64_t max_cll = colour->max_cll; |
| const int64_t max_fall = colour->max_fall; |
| if (matrix_coefficients != mkvparser::Colour::kValueNotPresent) |
| fprintf(o, "%sMatrixCoefficients : %" PRId64 "\n", |
| indent->indent_str().c_str(), matrix_coefficients); |
| if (bits_per_channel != mkvparser::Colour::kValueNotPresent) |
| fprintf(o, "%sBitsPerChannel : %" PRId64 "\n", |
| indent->indent_str().c_str(), bits_per_channel); |
| if (chroma_subsampling_horz != mkvparser::Colour::kValueNotPresent) |
| fprintf(o, "%sChromaSubsamplingHorz : %" PRId64 "\n", |
| indent->indent_str().c_str(), chroma_subsampling_horz); |
| if (chroma_subsampling_vert != mkvparser::Colour::kValueNotPresent) |
| fprintf(o, "%sChromaSubsamplingVert : %" PRId64 "\n", |
| indent->indent_str().c_str(), chroma_subsampling_vert); |
| if (cb_subsampling_horz != mkvparser::Colour::kValueNotPresent) |
| fprintf(o, "%sCbSubsamplingHorz : %" PRId64 "\n", |
| indent->indent_str().c_str(), cb_subsampling_horz); |
| if (cb_subsampling_vert != mkvparser::Colour::kValueNotPresent) |
| fprintf(o, "%sCbSubsamplingVert : %" PRId64 "\n", |
| indent->indent_str().c_str(), cb_subsampling_vert); |
| if (chroma_siting_horz != mkvparser::Colour::kValueNotPresent) |
| fprintf(o, "%sChromaSitingHorz : %" PRId64 "\n", |
| indent->indent_str().c_str(), chroma_siting_horz); |
| if (chroma_siting_vert != mkvparser::Colour::kValueNotPresent) |
| fprintf(o, "%sChromaSitingVert : %" PRId64 "\n", |
| indent->indent_str().c_str(), chroma_siting_vert); |
| if (range != mkvparser::Colour::kValueNotPresent) |
| fprintf(o, "%sRange : %" PRId64 "\n", |
| indent->indent_str().c_str(), range); |
| if (transfer_characteristics != mkvparser::Colour::kValueNotPresent) |
| fprintf(o, "%sTransferCharacteristics : %" PRId64 "\n", |
| indent->indent_str().c_str(), transfer_characteristics); |
| if (primaries != mkvparser::Colour::kValueNotPresent) |
| fprintf(o, "%sPrimaries : %" PRId64 "\n", |
| indent->indent_str().c_str(), primaries); |
| if (max_cll != mkvparser::Colour::kValueNotPresent) |
| fprintf(o, "%sMaxCLL : %" PRId64 "\n", |
| indent->indent_str().c_str(), max_cll); |
| if (max_fall != mkvparser::Colour::kValueNotPresent) |
| fprintf(o, "%sMaxFALL : %" PRId64 "\n", |
| indent->indent_str().c_str(), max_fall); |
| |
| const mkvparser::MasteringMetadata* const metadata = |
| colour->mastering_metadata; |
| if (metadata) { |
| // TODO(fgalligan): Add support for MasteringMetadata's address and |
| // size. |
| fprintf(o, "%sMasteringMetadata:\n", indent->indent_str().c_str()); |
| indent->Adjust(libwebm::kIncreaseIndent); |
| |
| const mkvparser::PrimaryChromaticity* const red = metadata->r; |
| const mkvparser::PrimaryChromaticity* const green = metadata->g; |
| const mkvparser::PrimaryChromaticity* const blue = metadata->b; |
| const mkvparser::PrimaryChromaticity* const white = |
| metadata->white_point; |
| const float max = metadata->luminance_max; |
| const float min = metadata->luminance_min; |
| if (red) { |
| fprintf(o, "%sPrimaryRChromaticityX : %g\n", |
| indent->indent_str().c_str(), red->x); |
| fprintf(o, "%sPrimaryRChromaticityY : %g\n", |
| indent->indent_str().c_str(), red->y); |
| } |
| if (green) { |
| fprintf(o, "%sPrimaryGChromaticityX : %g\n", |
| indent->indent_str().c_str(), green->x); |
| fprintf(o, "%sPrimaryGChromaticityY : %g\n", |
| indent->indent_str().c_str(), green->y); |
| } |
| if (blue) { |
| fprintf(o, "%sPrimaryBChromaticityX : %g\n", |
| indent->indent_str().c_str(), blue->x); |
| fprintf(o, "%sPrimaryBChromaticityY : %g\n", |
| indent->indent_str().c_str(), blue->y); |
| } |
| if (white) { |
| fprintf(o, "%sWhitePointChromaticityX : %g\n", |
| indent->indent_str().c_str(), white->x); |
| fprintf(o, "%sWhitePointChromaticityY : %g\n", |
| indent->indent_str().c_str(), white->y); |
| } |
| if (max != mkvparser::MasteringMetadata::kValueNotPresent) |
| fprintf(o, "%sLuminanceMax : %g\n", |
| indent->indent_str().c_str(), max); |
| if (min != mkvparser::MasteringMetadata::kValueNotPresent) |
| fprintf(o, "%sLuminanceMin : %g\n", |
| indent->indent_str().c_str(), min); |
| indent->Adjust(libwebm::kDecreaseIndent); |
| } |
| indent->Adjust(libwebm::kDecreaseIndent); |
| } |
| |
| const mkvparser::Projection* const projection = |
| video_track->GetProjection(); |
| if (projection) { |
| fprintf(o, "%sProjection:\n", indent->indent_str().c_str()); |
| indent->Adjust(libwebm::kIncreaseIndent); |
| |
| const int projection_type = static_cast<int>(projection->type); |
| const int kTypeNotPresent = |
| static_cast<int>(mkvparser::Projection::kTypeNotPresent); |
| const float kValueNotPresent = mkvparser::Projection::kValueNotPresent; |
| if (projection_type != kTypeNotPresent) |
| fprintf(o, "%sProjectionType : %d\n", |
| indent->indent_str().c_str(), projection_type); |
| if (projection->private_data) |
| fprintf(o, "%sProjectionPrivate(size) : %d\n", |
| indent->indent_str().c_str(), |
| static_cast<int>(projection->private_data_length)); |
| if (projection->pose_yaw != kValueNotPresent) |
| fprintf(o, "%sProjectionPoseYaw : %g\n", |
| indent->indent_str().c_str(), projection->pose_yaw); |
| if (projection->pose_pitch != kValueNotPresent) |
| fprintf(o, "%sProjectionPosePitch : %g\n", |
| indent->indent_str().c_str(), projection->pose_pitch); |
| if (projection->pose_roll != kValueNotPresent) |
| fprintf(o, "%sProjectionPoseRoll : %g\n", |
| indent->indent_str().c_str(), projection->pose_roll); |
| indent->Adjust(libwebm::kDecreaseIndent); |
| } |
| } else if (track_type == mkvparser::Track::kAudio) { |
| const mkvparser::AudioTrack* const audio_track = |
| static_cast<const mkvparser::AudioTrack* const>(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, vp9_parser::Vp9HeaderParser* parser, |
| vp9_parser::Vp9LevelStats* level_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 size_t frame_length = (count > 0) ? sizes[i] : size; |
| if (frame_length > std::numeric_limits<int>::max() || |
| static_cast<int>(frame_length) > size) { |
| fprintf(o, " invalid VP9 frame size (%u)\n", |
| static_cast<uint32_t>(frame_length)); |
| return; |
| } |
| if (!parser->ParseUncompressedHeader(data, frame_length)) |
| return; |
| level_stats->AddFrame(*parser, time_ns); |
| |
| // const int frame_marker = (data[0] >> 6) & 0x3; |
| const int version = parser->profile(); |
| const int key = parser->key(); |
| const int altref_frame = parser->altref(); |
| const int error_resilient_mode = parser->error_resilient_mode(); |
| const int column_tiles = parser->column_tiles(); |
| const int frame_parallel_mode = parser->frame_parallel_mode(); |
| |
| 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 ct:%d fpm:%d", key, version, |
| altref_frame, error_resilient_mode, column_tiles, |
| frame_parallel_mode); |
| |
| if (key && size > 4) { |
| fprintf(o, " cs:%d", parser->color_space()); |
| } |
| |
| 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<int64_t>(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); |
| } |
| |
| // Prints the partition offsets of the sub-sample encryption. |data| must point |
| // to an encrypted frame just after the signal byte. Returns the number of |
| // bytes read from the sub-sample partition information. |
| int PrintSubSampleEncryption(const uint8_t* data, int size, FILE* o) { |
| int read_end = sizeof(uint64_t); |
| |
| // Skip past IV. |
| if (size < read_end) |
| return 0; |
| data += sizeof(uint64_t); |
| |
| // Read number of partitions. |
| read_end += sizeof(uint8_t); |
| if (size < read_end) |
| return 0; |
| const int num_partitions = data[0]; |
| data += sizeof(uint8_t); |
| |
| // Read partitions. |
| for (int i = 0; i < num_partitions; ++i) { |
| read_end += sizeof(uint32_t); |
| if (size < read_end) |
| return 0; |
| uint32_t partition_offset; |
| memcpy(&partition_offset, data, sizeof(partition_offset)); |
| partition_offset = libwebm::bigendian_to_host(partition_offset); |
| fprintf(o, " off[%d]:%u", i, partition_offset); |
| data += sizeof(uint32_t); |
| } |
| |
| return read_end; |
| } |
| |
| 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, |
| vp9_parser::Vp9HeaderParser* parser, |
| vp9_parser::Vp9LevelStats* level_stats) { |
| if (clusters_size) { |
| // Load the Cluster. |
| const mkvparser::BlockEntry* block_entry; |
| long 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; |
| long status = cluster.GetFirst(block_entry); |
| if (status) { |
| fprintf(stderr, "Could not get first Block of Cluster.\n"); |
| return false; |
| } |
| |
| std::vector<unsigned char> 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<unsigned int>(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 uint8_t kSubSampleBit = 0x2; |
| 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<int>(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); |
| const bool sub_sample_encrypt = !!(data[0] & kSubSampleBit); |
| fprintf(o, " enc: %d", encrypted_frame ? 1 : 0); |
| fprintf(o, " sub: %d", sub_sample_encrypt ? 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<int>(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; |
| bool sub_sample_encrypt = false; |
| int frame_size = static_cast<int>(frame.len); |
| |
| int frame_offset = 0; |
| if (encrypted_stream) { |
| if (data[0] & KEncryptedBit) { |
| encrypted_frame = true; |
| if (data[0] & kSubSampleBit) { |
| sub_sample_encrypt = true; |
| data += kSignalByteSize; |
| frame_size -= kSignalByteSize; |
| frame_offset = |
| PrintSubSampleEncryption(data, frame_size, o); |
| } |
| } else { |
| frame_offset = kSignalByteSize; |
| } |
| } |
| |
| if (!encrypted_frame || sub_sample_encrypt) { |
| data += frame_offset; |
| frame_size -= frame_offset; |
| |
| const string codec_id = track->GetCodecId(); |
| if (codec_id == "V_VP8") { |
| PrintVP8Info(data, frame_size, o); |
| } else if (codec_id == "V_VP9") { |
| PrintVP9Info(data, frame_size, o, time_ns, stats, parser, |
| level_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 = static_cast<int>(tracks.GetTracksCount()); |
| indent->Adjust(libwebm::kIncreaseIndent); |
| |
| do { |
| for (int track_num = 0; track_num < num_tracks; ++track_num) { |
| const mkvparser::Track* const track = tracks.GetTrackByIndex(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, |
| static_cast<int>(track->GetNumber())); |
| |
| 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]); |
| } else if (Options::MatchesBooleanOption("vp9_level", argv[i])) { |
| options.output_vp9_level = !strcmp("-vp9_level", argv[i]); |
| } |
| } |
| |
| if (argc < 3 || input.empty()) { |
| Usage(); |
| return EXIT_FAILURE; |
| } |
| |
| std::unique_ptr<mkvparser::MkvReader> 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::unique_ptr<mkvparser::EBMLHeader> 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::unique_ptr<mkvparser::Segment> 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; |
| vp9_parser::Vp9HeaderParser parser; |
| vp9_parser::Vp9LevelStats level_stats; |
| const mkvparser::Cluster* cluster = segment->GetFirst(); |
| while (cluster != NULL && !cluster->EOS()) { |
| if (!OutputCluster(*cluster, *tracks, options, out, reader.get(), &indent, |
| &clusters_size, &stats, &parser, &level_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<int>::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); |
| } |
| |
| if (options.output_vp9_level) { |
| level_stats.set_duration(segment->GetInfo()->GetDuration()); |
| const vp9_parser::Vp9Level level = level_stats.GetLevel(); |
| fprintf(out, "VP9 Level:%d\n", level); |
| fprintf( |
| out, "mlsr:%" PRId64 " mlps:%" PRId64 " mlpb:%" PRId64 |
| " abr:%g mcs:%g cr:%g mct:%d" |
| " mad:%d mrf:%d\n", |
| level_stats.GetMaxLumaSampleRate(), level_stats.GetMaxLumaPictureSize(), |
| level_stats.GetMaxLumaPictureBreadth(), level_stats.GetAverageBitRate(), |
| level_stats.GetMaxCpbSize(), level_stats.GetCompressionRatio(), |
| level_stats.GetMaxColumnTiles(), level_stats.GetMinimumAltrefDistance(), |
| level_stats.GetMaxReferenceFrames()); |
| } |
| return EXIT_SUCCESS; |
| } |