// 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/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::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(" -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(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(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(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); } } 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, 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; parser->SetFrame(data, frame_length); parser->ParseUncompressedHeader(); 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(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, 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 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, static_cast(frame.len), o); } else if (codec_id == "V_VP9") { PrintVP9Info(data, static_cast(frame.len), 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(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]); } 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; } // 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; 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::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 " abr:%g mcs:%g cr:%g mct:%d" " mad:%d mrf:%d\n", level_stats.GetMaxLumaSampleRate(), level_stats.GetMaxLumaPictureSize(), level_stats.GetAverageBitRate(), level_stats.GetMaxCpbSize(), level_stats.GetCompressionRatio(), level_stats.GetMaxColumnTiles(), level_stats.GetMinimumAltrefDistance(), level_stats.GetMaxReferenceFrames()); } return EXIT_SUCCESS; }