1 // Copyright (c) 2012 The WebM project authors. All Rights Reserved.
2 //
3 // Use of this source code is governed by a BSD-style license
4 // that can be found in the LICENSE file in the root of the source
5 // tree. An additional intellectual property rights grant can be found
6 // in the file PATENTS. All contributing project authors may
7 // be found in the AUTHORS file in the root of the source tree.
8 #include <inttypes.h>
9 #include <stdint.h>
10
11 #include <cstdlib>
12 #include <cstring>
13 #include <limits>
14 #include <memory>
15 #include <queue>
16 #include <string>
17 #include <vector>
18
19 #include "common/hdr_util.h"
20 #include "common/indent.h"
21 #include "common/vp9_header_parser.h"
22 #include "common/vp9_level_stats.h"
23 #include "common/webm_constants.h"
24 #include "common/webm_endian.h"
25
26 #include "mkvparser/mkvparser.h"
27 #include "mkvparser/mkvreader.h"
28
29 namespace {
30
31 using libwebm::Indent;
32 using libwebm::kNanosecondsPerSecond;
33 using libwebm::kNanosecondsPerSecondi;
34 using mkvparser::ContentEncoding;
35 using std::string;
36 using std::wstring;
37
38 const char VERSION_STRING[] = "1.0.4.5";
39
40 struct Options {
41 Options();
42
43 // Returns true if |value| matches -|option| or -no|option|.
44 static bool MatchesBooleanOption(const string& option, const string& value);
45
46 // Set all of the member variables to |value|.
47 void SetAll(bool value);
48
49 bool output_video;
50 bool output_audio;
51 bool output_size;
52 bool output_offset;
53 bool output_seconds;
54 bool output_ebml_header;
55 bool output_segment;
56 bool output_seekhead;
57 bool output_segment_info;
58 bool output_tracks;
59 bool output_clusters;
60 bool output_blocks;
61 bool output_codec_info;
62 bool output_clusters_size;
63 bool output_encrypted_info;
64 bool output_cues;
65 bool output_frame_stats;
66 bool output_vp9_level;
67 };
68
Options()69 Options::Options()
70 : output_video(true),
71 output_audio(true),
72 output_size(false),
73 output_offset(false),
74 output_seconds(true),
75 output_ebml_header(true),
76 output_segment(true),
77 output_seekhead(false),
78 output_segment_info(true),
79 output_tracks(true),
80 output_clusters(false),
81 output_blocks(false),
82 output_codec_info(false),
83 output_clusters_size(false),
84 output_encrypted_info(false),
85 output_cues(false),
86 output_frame_stats(false),
87 output_vp9_level(false) {}
88
SetAll(bool value)89 void Options::SetAll(bool value) {
90 output_video = value;
91 output_audio = value;
92 output_size = value;
93 output_offset = value;
94 output_ebml_header = value;
95 output_seconds = value;
96 output_segment = value;
97 output_segment_info = value;
98 output_tracks = value;
99 output_clusters = value;
100 output_blocks = value;
101 output_codec_info = value;
102 output_clusters_size = value;
103 output_encrypted_info = value;
104 output_cues = value;
105 output_frame_stats = value;
106 output_vp9_level = value;
107 }
108
MatchesBooleanOption(const string & option,const string & value)109 bool Options::MatchesBooleanOption(const string& option, const string& value) {
110 const string opt = "-" + option;
111 const string noopt = "-no" + option;
112 return value == opt || value == noopt;
113 }
114
115 struct FrameStats {
FrameStats__anonaa7ba1c20111::FrameStats116 FrameStats()
117 : frames(0),
118 displayed_frames(0),
119 first_altref(true),
120 frames_since_last_altref(0),
121 minimum_altref_distance(std::numeric_limits<int>::max()),
122 min_altref_end_ns(0),
123 max_window_size(0),
124 max_window_end_ns(0) {}
125
126 int frames;
127 int displayed_frames;
128
129 bool first_altref;
130 int frames_since_last_altref;
131 int minimum_altref_distance;
132 int64_t min_altref_end_ns;
133
134 std::queue<int64_t> window;
135 int64_t max_window_size;
136 int64_t max_window_end_ns;
137 };
138
Usage()139 void Usage() {
140 printf("Usage: webm_info [options] -i input\n");
141 printf("\n");
142 printf("Main options:\n");
143 printf(" -h | -? show help\n");
144 printf(" -v show version\n");
145 printf(" -all Enable all output options.\n");
146 printf(" -video Output video tracks (true)\n");
147 printf(" -audio Output audio tracks (true)\n");
148 printf(" -size Output element sizes (false)\n");
149 printf(" -offset Output element offsets (false)\n");
150 printf(" -times_seconds Output times as seconds (true)\n");
151 printf(" -ebml_header Output EBML header (true)\n");
152 printf(" -segment Output Segment (true)\n");
153 printf(" -seekhead Output SeekHead (false)\n");
154 printf(" -segment_info Output SegmentInfo (true)\n");
155 printf(" -tracks Output Tracks (true)\n");
156 printf(" -clusters Output Clusters (false)\n");
157 printf(" -blocks Output Blocks (false)\n");
158 printf(" -codec_info Output video codec information (false)\n");
159 printf(" -clusters_size Output Total Clusters size (false)\n");
160 printf(" -encrypted_info Output encrypted frame info (false)\n");
161 printf(" -cues Output Cues entries (false)\n");
162 printf(" -frame_stats Output frame stats (VP9)(false)\n");
163 printf(" -vp9_level Output VP9 level(false)\n");
164 printf("\nOutput options may be negated by prefixing 'no'.\n");
165 }
166
167 // TODO(fgalligan): Add support for non-ascii.
UTF8ToWideString(const char * str)168 wstring UTF8ToWideString(const char* str) {
169 wstring wstr;
170
171 if (str == NULL)
172 return wstr;
173
174 string temp_str(str, strlen(str));
175 wstr.assign(temp_str.begin(), temp_str.end());
176
177 return wstr;
178 }
179
ToString(const char * str)180 string ToString(const char* str) { return string((str == NULL) ? "" : str); }
181
OutputEBMLHeader(const mkvparser::EBMLHeader & ebml,FILE * o,Indent * indent)182 void OutputEBMLHeader(const mkvparser::EBMLHeader& ebml, FILE* o,
183 Indent* indent) {
184 fprintf(o, "EBML Header:\n");
185 indent->Adjust(libwebm::kIncreaseIndent);
186 fprintf(o, "%sEBMLVersion : %lld\n", indent->indent_str().c_str(),
187 ebml.m_version);
188 fprintf(o, "%sEBMLReadVersion : %lld\n", indent->indent_str().c_str(),
189 ebml.m_readVersion);
190 fprintf(o, "%sEBMLMaxIDLength : %lld\n", indent->indent_str().c_str(),
191 ebml.m_maxIdLength);
192 fprintf(o, "%sEBMLMaxSizeLength : %lld\n", indent->indent_str().c_str(),
193 ebml.m_maxSizeLength);
194 fprintf(o, "%sDoc Type : %s\n", indent->indent_str().c_str(),
195 ebml.m_docType);
196 fprintf(o, "%sDocTypeVersion : %lld\n", indent->indent_str().c_str(),
197 ebml.m_docTypeVersion);
198 fprintf(o, "%sDocTypeReadVersion: %lld\n", indent->indent_str().c_str(),
199 ebml.m_docTypeReadVersion);
200 indent->Adjust(libwebm::kDecreaseIndent);
201 }
202
OutputSegment(const mkvparser::Segment & segment,const Options & options,FILE * o)203 void OutputSegment(const mkvparser::Segment& segment, const Options& options,
204 FILE* o) {
205 fprintf(o, "Segment:");
206 if (options.output_offset)
207 fprintf(o, " @: %lld", segment.m_element_start);
208 if (options.output_size)
209 fprintf(o, " size: %lld",
210 segment.m_size + segment.m_start - segment.m_element_start);
211 fprintf(o, "\n");
212 }
213
OutputSeekHead(const mkvparser::Segment & segment,const Options & options,FILE * o,Indent * indent)214 bool OutputSeekHead(const mkvparser::Segment& segment, const Options& options,
215 FILE* o, Indent* indent) {
216 const mkvparser::SeekHead* const seekhead = segment.GetSeekHead();
217 if (!seekhead) {
218 // SeekHeads are optional.
219 return true;
220 }
221
222 fprintf(o, "%sSeekHead:", indent->indent_str().c_str());
223 if (options.output_offset)
224 fprintf(o, " @: %lld", seekhead->m_element_start);
225 if (options.output_size)
226 fprintf(o, " size: %lld", seekhead->m_element_size);
227 fprintf(o, "\n");
228
229 indent->Adjust(libwebm::kIncreaseIndent);
230
231 for (int i = 0; i < seekhead->GetCount(); ++i) {
232 const mkvparser::SeekHead::Entry* const entry = seekhead->GetEntry(i);
233 if (!entry) {
234 fprintf(stderr, "Error retrieving SeekHead entry #%d\n", i);
235 return false;
236 }
237
238 fprintf(o, "%sEntry[%d]", indent->indent_str().c_str(), i);
239 if (options.output_offset)
240 fprintf(o, " @: %lld", entry->element_start);
241 if (options.output_size)
242 fprintf(o, " size: %lld", entry->element_size);
243 fprintf(o, "\n");
244
245 indent->Adjust(libwebm::kIncreaseIndent);
246 std::string entry_indent = indent->indent_str();
247 // TODO(jzern): 1) known ids could be stringified. 2) ids could be
248 // reencoded to EBML for ease of lookup.
249 fprintf(o, "%sSeek ID : %llx\n", entry_indent.c_str(), entry->id);
250 fprintf(o, "%sSeek position : %lld\n", entry_indent.c_str(), entry->pos);
251 indent->Adjust(libwebm::kDecreaseIndent);
252 }
253
254 for (int i = 0; i < seekhead->GetVoidElementCount(); ++i) {
255 const mkvparser::SeekHead::VoidElement* const entry =
256 seekhead->GetVoidElement(i);
257 if (!entry) {
258 fprintf(stderr, "Error retrieving SeekHead void element #%d\n", i);
259 return false;
260 }
261
262 fprintf(o, "%sVoid element[%d]", indent->indent_str().c_str(), i);
263 if (options.output_offset)
264 fprintf(o, " @: %lld", entry->element_start);
265 if (options.output_size)
266 fprintf(o, " size: %lld", entry->element_size);
267 fprintf(o, "\n");
268 }
269
270 indent->Adjust(libwebm::kDecreaseIndent);
271 return true;
272 }
273
OutputSegmentInfo(const mkvparser::Segment & segment,const Options & options,FILE * o,Indent * indent)274 bool OutputSegmentInfo(const mkvparser::Segment& segment,
275 const Options& options, FILE* o, Indent* indent) {
276 const mkvparser::SegmentInfo* const segment_info = segment.GetInfo();
277 if (!segment_info) {
278 fprintf(stderr, "SegmentInfo was NULL.\n");
279 return false;
280 }
281
282 const int64_t timecode_scale = segment_info->GetTimeCodeScale();
283 const int64_t duration_ns = segment_info->GetDuration();
284 const wstring title = UTF8ToWideString(segment_info->GetTitleAsUTF8());
285 const wstring muxing_app =
286 UTF8ToWideString(segment_info->GetMuxingAppAsUTF8());
287 const wstring writing_app =
288 UTF8ToWideString(segment_info->GetWritingAppAsUTF8());
289
290 fprintf(o, "%sSegmentInfo:", indent->indent_str().c_str());
291 if (options.output_offset)
292 fprintf(o, " @: %lld", segment_info->m_element_start);
293 if (options.output_size)
294 fprintf(o, " size: %lld", segment_info->m_element_size);
295 fprintf(o, "\n");
296
297 indent->Adjust(libwebm::kIncreaseIndent);
298 fprintf(o, "%sTimecodeScale : %" PRId64 " \n", indent->indent_str().c_str(),
299 timecode_scale);
300 if (options.output_seconds)
301 fprintf(o, "%sDuration(secs): %g\n", indent->indent_str().c_str(),
302 duration_ns / kNanosecondsPerSecond);
303 else
304 fprintf(o, "%sDuration(nano): %" PRId64 "\n", indent->indent_str().c_str(),
305 duration_ns);
306
307 if (!title.empty())
308 fprintf(o, "%sTitle : %ls\n", indent->indent_str().c_str(),
309 title.c_str());
310 if (!muxing_app.empty())
311 fprintf(o, "%sMuxingApp : %ls\n", indent->indent_str().c_str(),
312 muxing_app.c_str());
313 if (!writing_app.empty())
314 fprintf(o, "%sWritingApp : %ls\n", indent->indent_str().c_str(),
315 writing_app.c_str());
316 indent->Adjust(libwebm::kDecreaseIndent);
317 return true;
318 }
319
OutputTracks(const mkvparser::Segment & segment,const Options & options,FILE * o,Indent * indent)320 bool OutputTracks(const mkvparser::Segment& segment, const Options& options,
321 FILE* o, Indent* indent) {
322 const mkvparser::Tracks* const tracks = segment.GetTracks();
323 if (!tracks) {
324 fprintf(stderr, "Tracks was NULL.\n");
325 return false;
326 }
327
328 fprintf(o, "%sTracks:", indent->indent_str().c_str());
329 if (options.output_offset)
330 fprintf(o, " @: %lld", tracks->m_element_start);
331 if (options.output_size)
332 fprintf(o, " size: %lld", tracks->m_element_size);
333 fprintf(o, "\n");
334
335 unsigned int i = 0;
336 const unsigned long j = tracks->GetTracksCount();
337 while (i != j) {
338 const mkvparser::Track* const track = tracks->GetTrackByIndex(i++);
339 if (track == NULL)
340 continue;
341
342 indent->Adjust(libwebm::kIncreaseIndent);
343 fprintf(o, "%sTrack:", indent->indent_str().c_str());
344 if (options.output_offset)
345 fprintf(o, " @: %lld", track->m_element_start);
346 if (options.output_size)
347 fprintf(o, " size: %lld", track->m_element_size);
348 fprintf(o, "\n");
349
350 const int64_t track_type = track->GetType();
351 const int64_t track_number = track->GetNumber();
352 const wstring track_name = UTF8ToWideString(track->GetNameAsUTF8());
353
354 indent->Adjust(libwebm::kIncreaseIndent);
355 fprintf(o, "%sTrackType : %" PRId64 "\n", indent->indent_str().c_str(),
356 track_type);
357 fprintf(o, "%sTrackNumber : %" PRId64 "\n", indent->indent_str().c_str(),
358 track_number);
359 if (!track_name.empty())
360 fprintf(o, "%sName : %ls\n", indent->indent_str().c_str(),
361 track_name.c_str());
362
363 const char* const codec_id = track->GetCodecId();
364 if (codec_id)
365 fprintf(o, "%sCodecID : %s\n", indent->indent_str().c_str(),
366 codec_id);
367
368 const wstring codec_name = UTF8ToWideString(track->GetCodecNameAsUTF8());
369 if (!codec_name.empty())
370 fprintf(o, "%sCodecName : %ls\n", indent->indent_str().c_str(),
371 codec_name.c_str());
372
373 size_t private_size;
374 const unsigned char* const private_data =
375 track->GetCodecPrivate(private_size);
376 if (private_data) {
377 fprintf(o, "%sPrivateData(size): %d\n", indent->indent_str().c_str(),
378 static_cast<int>(private_size));
379
380 if (track_type == mkvparser::Track::kVideo) {
381 const std::string codec_id = ToString(track->GetCodecId());
382 const std::string v_vp9 = "V_VP9";
383 if (codec_id == v_vp9) {
384 libwebm::Vp9CodecFeatures features;
385 if (!libwebm::ParseVpxCodecPrivate(private_data,
386 static_cast<int32_t>(private_size),
387 &features)) {
388 fprintf(stderr, "Error parsing VpxCodecPrivate.\n");
389 return false;
390 }
391 if (features.profile != -1)
392 fprintf(o, "%sVP9 profile : %d\n",
393 indent->indent_str().c_str(), features.profile);
394 if (features.level != -1)
395 fprintf(o, "%sVP9 level : %d\n",
396 indent->indent_str().c_str(), features.level);
397 if (features.bit_depth != -1)
398 fprintf(o, "%sVP9 bit_depth : %d\n",
399 indent->indent_str().c_str(), features.bit_depth);
400 if (features.chroma_subsampling != -1)
401 fprintf(o, "%sVP9 chroma subsampling : %d\n",
402 indent->indent_str().c_str(), features.chroma_subsampling);
403 }
404 }
405 }
406
407 const uint64_t default_duration = track->GetDefaultDuration();
408 if (default_duration > 0)
409 fprintf(o, "%sDefaultDuration: %" PRIu64 "\n",
410 indent->indent_str().c_str(), default_duration);
411
412 if (track->GetContentEncodingCount() > 0) {
413 // Only check the first content encoding.
414 const ContentEncoding* const encoding =
415 track->GetContentEncodingByIndex(0);
416 if (!encoding) {
417 printf("Could not get first ContentEncoding.\n");
418 return false;
419 }
420
421 fprintf(o, "%sContentEncodingOrder : %lld\n",
422 indent->indent_str().c_str(), encoding->encoding_order());
423 fprintf(o, "%sContentEncodingScope : %lld\n",
424 indent->indent_str().c_str(), encoding->encoding_scope());
425 fprintf(o, "%sContentEncodingType : %lld\n",
426 indent->indent_str().c_str(), encoding->encoding_type());
427
428 if (encoding->GetEncryptionCount() > 0) {
429 // Only check the first encryption.
430 const ContentEncoding::ContentEncryption* const encryption =
431 encoding->GetEncryptionByIndex(0);
432 if (!encryption) {
433 printf("Could not get first ContentEncryption.\n");
434 return false;
435 }
436
437 fprintf(o, "%sContentEncAlgo : %lld\n",
438 indent->indent_str().c_str(), encryption->algo);
439
440 if (encryption->key_id_len > 0) {
441 fprintf(o, "%sContentEncKeyID : ", indent->indent_str().c_str());
442 for (int k = 0; k < encryption->key_id_len; ++k) {
443 fprintf(o, "0x%02x, ", encryption->key_id[k]);
444 }
445 fprintf(o, "\n");
446 }
447
448 if (encryption->signature_len > 0) {
449 fprintf(o, "%sContentSignature : 0x",
450 indent->indent_str().c_str());
451 for (int k = 0; k < encryption->signature_len; ++k) {
452 fprintf(o, "%x", encryption->signature[k]);
453 }
454 fprintf(o, "\n");
455 }
456
457 if (encryption->sig_key_id_len > 0) {
458 fprintf(o, "%sContentSigKeyID : 0x",
459 indent->indent_str().c_str());
460 for (int k = 0; k < encryption->sig_key_id_len; ++k) {
461 fprintf(o, "%x", encryption->sig_key_id[k]);
462 }
463 fprintf(o, "\n");
464 }
465
466 fprintf(o, "%sContentSigAlgo : %lld\n",
467 indent->indent_str().c_str(), encryption->sig_algo);
468 fprintf(o, "%sContentSigHashAlgo : %lld\n",
469 indent->indent_str().c_str(), encryption->sig_hash_algo);
470
471 const ContentEncoding::ContentEncAESSettings& aes =
472 encryption->aes_settings;
473 fprintf(o, "%sCipherMode : %lld\n",
474 indent->indent_str().c_str(), aes.cipher_mode);
475 }
476 }
477
478 if (track_type == mkvparser::Track::kVideo) {
479 const mkvparser::VideoTrack* const video_track =
480 static_cast<const mkvparser::VideoTrack*>(track);
481 const int64_t width = video_track->GetWidth();
482 const int64_t height = video_track->GetHeight();
483 const int64_t display_width = video_track->GetDisplayWidth();
484 const int64_t display_height = video_track->GetDisplayHeight();
485 const int64_t display_unit = video_track->GetDisplayUnit();
486 const double frame_rate = video_track->GetFrameRate();
487 fprintf(o, "%sPixelWidth : %" PRId64 "\n", indent->indent_str().c_str(),
488 width);
489 fprintf(o, "%sPixelHeight : %" PRId64 "\n", indent->indent_str().c_str(),
490 height);
491 if (frame_rate > 0.0)
492 fprintf(o, "%sFrameRate : %g\n", indent->indent_str().c_str(),
493 video_track->GetFrameRate());
494 if (display_unit > 0 || display_width != width ||
495 display_height != height) {
496 fprintf(o, "%sDisplayWidth : %" PRId64 "\n",
497 indent->indent_str().c_str(), display_width);
498 fprintf(o, "%sDisplayHeight : %" PRId64 "\n",
499 indent->indent_str().c_str(), display_height);
500 fprintf(o, "%sDisplayUnit : %" PRId64 "\n",
501 indent->indent_str().c_str(), display_unit);
502 }
503
504 const mkvparser::Colour* const colour = video_track->GetColour();
505 if (colour) {
506 // TODO(fgalligan): Add support for Colour's address and size.
507 fprintf(o, "%sColour:\n", indent->indent_str().c_str());
508 indent->Adjust(libwebm::kIncreaseIndent);
509
510 const int64_t matrix_coefficients = colour->matrix_coefficients;
511 const int64_t bits_per_channel = colour->bits_per_channel;
512 const int64_t chroma_subsampling_horz = colour->chroma_subsampling_horz;
513 const int64_t chroma_subsampling_vert = colour->chroma_subsampling_vert;
514 const int64_t cb_subsampling_horz = colour->cb_subsampling_horz;
515 const int64_t cb_subsampling_vert = colour->cb_subsampling_vert;
516 const int64_t chroma_siting_horz = colour->chroma_siting_horz;
517 const int64_t chroma_siting_vert = colour->chroma_siting_vert;
518 const int64_t range = colour->range;
519 const int64_t transfer_characteristics =
520 colour->transfer_characteristics;
521 const int64_t primaries = colour->primaries;
522 const int64_t max_cll = colour->max_cll;
523 const int64_t max_fall = colour->max_fall;
524 if (matrix_coefficients != mkvparser::Colour::kValueNotPresent)
525 fprintf(o, "%sMatrixCoefficients : %" PRId64 "\n",
526 indent->indent_str().c_str(), matrix_coefficients);
527 if (bits_per_channel != mkvparser::Colour::kValueNotPresent)
528 fprintf(o, "%sBitsPerChannel : %" PRId64 "\n",
529 indent->indent_str().c_str(), bits_per_channel);
530 if (chroma_subsampling_horz != mkvparser::Colour::kValueNotPresent)
531 fprintf(o, "%sChromaSubsamplingHorz : %" PRId64 "\n",
532 indent->indent_str().c_str(), chroma_subsampling_horz);
533 if (chroma_subsampling_vert != mkvparser::Colour::kValueNotPresent)
534 fprintf(o, "%sChromaSubsamplingVert : %" PRId64 "\n",
535 indent->indent_str().c_str(), chroma_subsampling_vert);
536 if (cb_subsampling_horz != mkvparser::Colour::kValueNotPresent)
537 fprintf(o, "%sCbSubsamplingHorz : %" PRId64 "\n",
538 indent->indent_str().c_str(), cb_subsampling_horz);
539 if (cb_subsampling_vert != mkvparser::Colour::kValueNotPresent)
540 fprintf(o, "%sCbSubsamplingVert : %" PRId64 "\n",
541 indent->indent_str().c_str(), cb_subsampling_vert);
542 if (chroma_siting_horz != mkvparser::Colour::kValueNotPresent)
543 fprintf(o, "%sChromaSitingHorz : %" PRId64 "\n",
544 indent->indent_str().c_str(), chroma_siting_horz);
545 if (chroma_siting_vert != mkvparser::Colour::kValueNotPresent)
546 fprintf(o, "%sChromaSitingVert : %" PRId64 "\n",
547 indent->indent_str().c_str(), chroma_siting_vert);
548 if (range != mkvparser::Colour::kValueNotPresent)
549 fprintf(o, "%sRange : %" PRId64 "\n",
550 indent->indent_str().c_str(), range);
551 if (transfer_characteristics != mkvparser::Colour::kValueNotPresent)
552 fprintf(o, "%sTransferCharacteristics : %" PRId64 "\n",
553 indent->indent_str().c_str(), transfer_characteristics);
554 if (primaries != mkvparser::Colour::kValueNotPresent)
555 fprintf(o, "%sPrimaries : %" PRId64 "\n",
556 indent->indent_str().c_str(), primaries);
557 if (max_cll != mkvparser::Colour::kValueNotPresent)
558 fprintf(o, "%sMaxCLL : %" PRId64 "\n",
559 indent->indent_str().c_str(), max_cll);
560 if (max_fall != mkvparser::Colour::kValueNotPresent)
561 fprintf(o, "%sMaxFALL : %" PRId64 "\n",
562 indent->indent_str().c_str(), max_fall);
563
564 const mkvparser::MasteringMetadata* const metadata =
565 colour->mastering_metadata;
566 if (metadata) {
567 // TODO(fgalligan): Add support for MasteringMetadata's address and
568 // size.
569 fprintf(o, "%sMasteringMetadata:\n", indent->indent_str().c_str());
570 indent->Adjust(libwebm::kIncreaseIndent);
571
572 const mkvparser::PrimaryChromaticity* const red = metadata->r;
573 const mkvparser::PrimaryChromaticity* const green = metadata->g;
574 const mkvparser::PrimaryChromaticity* const blue = metadata->b;
575 const mkvparser::PrimaryChromaticity* const white =
576 metadata->white_point;
577 const float max = metadata->luminance_max;
578 const float min = metadata->luminance_min;
579 if (red) {
580 fprintf(o, "%sPrimaryRChromaticityX : %g\n",
581 indent->indent_str().c_str(), red->x);
582 fprintf(o, "%sPrimaryRChromaticityY : %g\n",
583 indent->indent_str().c_str(), red->y);
584 }
585 if (green) {
586 fprintf(o, "%sPrimaryGChromaticityX : %g\n",
587 indent->indent_str().c_str(), green->x);
588 fprintf(o, "%sPrimaryGChromaticityY : %g\n",
589 indent->indent_str().c_str(), green->y);
590 }
591 if (blue) {
592 fprintf(o, "%sPrimaryBChromaticityX : %g\n",
593 indent->indent_str().c_str(), blue->x);
594 fprintf(o, "%sPrimaryBChromaticityY : %g\n",
595 indent->indent_str().c_str(), blue->y);
596 }
597 if (white) {
598 fprintf(o, "%sWhitePointChromaticityX : %g\n",
599 indent->indent_str().c_str(), white->x);
600 fprintf(o, "%sWhitePointChromaticityY : %g\n",
601 indent->indent_str().c_str(), white->y);
602 }
603 if (max != mkvparser::MasteringMetadata::kValueNotPresent)
604 fprintf(o, "%sLuminanceMax : %g\n",
605 indent->indent_str().c_str(), max);
606 if (min != mkvparser::MasteringMetadata::kValueNotPresent)
607 fprintf(o, "%sLuminanceMin : %g\n",
608 indent->indent_str().c_str(), min);
609 indent->Adjust(libwebm::kDecreaseIndent);
610 }
611 indent->Adjust(libwebm::kDecreaseIndent);
612 }
613
614 const mkvparser::Projection* const projection =
615 video_track->GetProjection();
616 if (projection) {
617 fprintf(o, "%sProjection:\n", indent->indent_str().c_str());
618 indent->Adjust(libwebm::kIncreaseIndent);
619
620 const int projection_type = static_cast<int>(projection->type);
621 const int kTypeNotPresent =
622 static_cast<int>(mkvparser::Projection::kTypeNotPresent);
623 const float kValueNotPresent = mkvparser::Projection::kValueNotPresent;
624 if (projection_type != kTypeNotPresent)
625 fprintf(o, "%sProjectionType : %d\n",
626 indent->indent_str().c_str(), projection_type);
627 if (projection->private_data)
628 fprintf(o, "%sProjectionPrivate(size) : %d\n",
629 indent->indent_str().c_str(),
630 static_cast<int>(projection->private_data_length));
631 if (projection->pose_yaw != kValueNotPresent)
632 fprintf(o, "%sProjectionPoseYaw : %g\n",
633 indent->indent_str().c_str(), projection->pose_yaw);
634 if (projection->pose_pitch != kValueNotPresent)
635 fprintf(o, "%sProjectionPosePitch : %g\n",
636 indent->indent_str().c_str(), projection->pose_pitch);
637 if (projection->pose_roll != kValueNotPresent)
638 fprintf(o, "%sProjectionPoseRoll : %g\n",
639 indent->indent_str().c_str(), projection->pose_roll);
640 indent->Adjust(libwebm::kDecreaseIndent);
641 }
642 } else if (track_type == mkvparser::Track::kAudio) {
643 const mkvparser::AudioTrack* const audio_track =
644 static_cast<const mkvparser::AudioTrack*>(track);
645 const int64_t channels = audio_track->GetChannels();
646 const int64_t bit_depth = audio_track->GetBitDepth();
647 const uint64_t codec_delay = audio_track->GetCodecDelay();
648 const uint64_t seek_preroll = audio_track->GetSeekPreRoll();
649 fprintf(o, "%sChannels : %" PRId64 "\n",
650 indent->indent_str().c_str(), channels);
651 if (bit_depth > 0)
652 fprintf(o, "%sBitDepth : %" PRId64 "\n",
653 indent->indent_str().c_str(), bit_depth);
654 fprintf(o, "%sSamplingFrequency: %g\n", indent->indent_str().c_str(),
655 audio_track->GetSamplingRate());
656 if (codec_delay)
657 fprintf(o, "%sCodecDelay : %" PRIu64 "\n",
658 indent->indent_str().c_str(), codec_delay);
659 if (seek_preroll)
660 fprintf(o, "%sSeekPreRoll : %" PRIu64 "\n",
661 indent->indent_str().c_str(), seek_preroll);
662 }
663 indent->Adjust(libwebm::kDecreaseIndent * 2);
664 }
665
666 return true;
667 }
668
669 // libvpx reference: vp9/vp9_dx_iface.c
ParseSuperframeIndex(const uint8_t * data,size_t data_sz,uint32_t sizes[8],int * count)670 void ParseSuperframeIndex(const uint8_t* data, size_t data_sz,
671 uint32_t sizes[8], int* count) {
672 const uint8_t marker = data[data_sz - 1];
673 *count = 0;
674
675 if ((marker & 0xe0) == 0xc0) {
676 const int frames = (marker & 0x7) + 1;
677 const int mag = ((marker >> 3) & 0x3) + 1;
678 const size_t index_sz = 2 + mag * frames;
679
680 if (data_sz >= index_sz && data[data_sz - index_sz] == marker) {
681 // found a valid superframe index
682 const uint8_t* x = data + data_sz - index_sz + 1;
683
684 for (int i = 0; i < frames; ++i) {
685 uint32_t this_sz = 0;
686
687 for (int j = 0; j < mag; ++j) {
688 this_sz |= (*x++) << (j * 8);
689 }
690 sizes[i] = this_sz;
691 }
692 *count = frames;
693 }
694 }
695 }
696
PrintVP9Info(const uint8_t * data,int size,FILE * o,int64_t time_ns,FrameStats * stats,vp9_parser::Vp9HeaderParser * parser,vp9_parser::Vp9LevelStats * level_stats)697 void PrintVP9Info(const uint8_t* data, int size, FILE* o, int64_t time_ns,
698 FrameStats* stats, vp9_parser::Vp9HeaderParser* parser,
699 vp9_parser::Vp9LevelStats* level_stats) {
700 if (size < 1)
701 return;
702
703 uint32_t sizes[8];
704 int i = 0, count = 0;
705 ParseSuperframeIndex(data, size, sizes, &count);
706
707 // Remove all frames that are less than window size.
708 while (!stats->window.empty() &&
709 stats->window.front() < (time_ns - (kNanosecondsPerSecondi - 1)))
710 stats->window.pop();
711
712 do {
713 const size_t frame_length = (count > 0) ? sizes[i] : size;
714 if (frame_length > static_cast<size_t>(std::numeric_limits<int>::max()) ||
715 static_cast<int>(frame_length) > size) {
716 fprintf(o, " invalid VP9 frame size (%u)\n",
717 static_cast<uint32_t>(frame_length));
718 return;
719 }
720 if (!parser->ParseUncompressedHeader(data, frame_length))
721 return;
722 level_stats->AddFrame(*parser, time_ns);
723
724 // const int frame_marker = (data[0] >> 6) & 0x3;
725 const int version = parser->profile();
726 const int key = parser->key();
727 const int altref_frame = parser->altref();
728 const int error_resilient_mode = parser->error_resilient_mode();
729 const int row_tiles = parser->row_tiles();
730 const int column_tiles = parser->column_tiles();
731 const int frame_parallel_mode = parser->frame_parallel_mode();
732
733 if (key &&
734 !(size >= 4 && data[1] == 0x49 && data[2] == 0x83 && data[3] == 0x42)) {
735 fprintf(o, " invalid VP9 signature");
736 return;
737 }
738
739 stats->window.push(time_ns);
740 ++stats->frames;
741
742 if (altref_frame) {
743 const int delta_altref = stats->frames_since_last_altref;
744 if (stats->first_altref) {
745 stats->first_altref = false;
746 } else if (delta_altref < stats->minimum_altref_distance) {
747 stats->minimum_altref_distance = delta_altref;
748 stats->min_altref_end_ns = time_ns;
749 }
750 stats->frames_since_last_altref = 0;
751 } else {
752 ++stats->frames_since_last_altref;
753 ++stats->displayed_frames;
754 }
755
756 if (count > 0) {
757 fprintf(o, " packed [%d]: {", i);
758 }
759
760 fprintf(o, " key:%d v:%d altref:%d errm:%d rt:%d ct:%d fpm:%d", key,
761 version, altref_frame, error_resilient_mode, row_tiles,
762 column_tiles, frame_parallel_mode);
763
764 if (key) {
765 if (size > 4) {
766 fprintf(o, " cs:%d", parser->color_space());
767 }
768 if (parser->display_width() != parser->width() ||
769 parser->display_height() != parser->height()) {
770 fprintf(o, " dw:%d dh:%d", parser->display_width(),
771 parser->display_height());
772 }
773 }
774
775 if (count > 0) {
776 fprintf(o, " size: %u }", sizes[i]);
777 data += sizes[i];
778 size -= sizes[i];
779 }
780 ++i;
781 } while (i < count);
782
783 if (stats->max_window_size < static_cast<int64_t>(stats->window.size())) {
784 stats->max_window_size = stats->window.size();
785 stats->max_window_end_ns = time_ns;
786 }
787 }
788
PrintVP8Info(const uint8_t * data,int size,FILE * o)789 void PrintVP8Info(const uint8_t* data, int size, FILE* o) {
790 if (size < 3)
791 return;
792
793 const uint32_t bits = data[0] | (data[1] << 8) | (data[2] << 16);
794 const int key = !(bits & 0x1);
795 const int altref_frame = !((bits >> 4) & 0x1);
796 const int version = (bits >> 1) & 0x7;
797 const int partition_length = (bits >> 5) & 0x7FFFF;
798 if (key &&
799 !(size >= 6 && data[3] == 0x9d && data[4] == 0x01 && data[5] == 0x2a)) {
800 fprintf(o, " invalid VP8 signature");
801 return;
802 }
803 fprintf(o, " key:%d v:%d altref:%d partition_length:%d", key, version,
804 altref_frame, partition_length);
805 }
806
807 // Prints the partition offsets of the sub-sample encryption. |data| must point
808 // to an encrypted frame just after the signal byte. Returns the number of
809 // bytes read from the sub-sample partition information.
PrintSubSampleEncryption(const uint8_t * data,int size,FILE * o)810 int PrintSubSampleEncryption(const uint8_t* data, int size, FILE* o) {
811 int read_end = sizeof(uint64_t);
812
813 // Skip past IV.
814 if (size < read_end)
815 return 0;
816 data += sizeof(uint64_t);
817
818 // Read number of partitions.
819 read_end += sizeof(uint8_t);
820 if (size < read_end)
821 return 0;
822 const int num_partitions = data[0];
823 data += sizeof(uint8_t);
824
825 // Read partitions.
826 for (int i = 0; i < num_partitions; ++i) {
827 read_end += sizeof(uint32_t);
828 if (size < read_end)
829 return 0;
830 uint32_t partition_offset;
831 memcpy(&partition_offset, data, sizeof(partition_offset));
832 partition_offset = libwebm::bigendian_to_host(partition_offset);
833 fprintf(o, " off[%d]:%u", i, partition_offset);
834 data += sizeof(uint32_t);
835 }
836
837 return read_end;
838 }
839
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)840 bool OutputCluster(const mkvparser::Cluster& cluster,
841 const mkvparser::Tracks& tracks, const Options& options,
842 FILE* o, mkvparser::MkvReader* reader, Indent* indent,
843 int64_t* clusters_size, FrameStats* stats,
844 vp9_parser::Vp9HeaderParser* parser,
845 vp9_parser::Vp9LevelStats* level_stats) {
846 if (clusters_size) {
847 // Load the Cluster.
848 const mkvparser::BlockEntry* block_entry;
849 long status = cluster.GetFirst(block_entry);
850 if (status) {
851 fprintf(stderr, "Could not get first Block of Cluster.\n");
852 return false;
853 }
854
855 *clusters_size += cluster.GetElementSize();
856 }
857
858 if (options.output_clusters) {
859 const int64_t time_ns = cluster.GetTime();
860 const int64_t duration_ns = cluster.GetLastTime() - cluster.GetFirstTime();
861
862 fprintf(o, "%sCluster:", indent->indent_str().c_str());
863 if (options.output_offset)
864 fprintf(o, " @: %lld", cluster.m_element_start);
865 if (options.output_size)
866 fprintf(o, " size: %lld", cluster.GetElementSize());
867 fprintf(o, "\n");
868 indent->Adjust(libwebm::kIncreaseIndent);
869 if (options.output_seconds)
870 fprintf(o, "%sTimecode (sec) : %g\n", indent->indent_str().c_str(),
871 time_ns / kNanosecondsPerSecond);
872 else
873 fprintf(o, "%sTimecode (nano): %" PRId64 "\n",
874 indent->indent_str().c_str(), time_ns);
875 if (options.output_seconds)
876 fprintf(o, "%sDuration (sec) : %g\n", indent->indent_str().c_str(),
877 duration_ns / kNanosecondsPerSecond);
878 else
879 fprintf(o, "%sDuration (nano): %" PRId64 "\n",
880 indent->indent_str().c_str(), duration_ns);
881
882 fprintf(o, "%s# Blocks : %ld\n", indent->indent_str().c_str(),
883 cluster.GetEntryCount());
884 }
885
886 if (options.output_blocks) {
887 const mkvparser::BlockEntry* block_entry;
888 long status = cluster.GetFirst(block_entry);
889 if (status) {
890 fprintf(stderr, "Could not get first Block of Cluster.\n");
891 return false;
892 }
893
894 std::vector<unsigned char> vector_data;
895 while (block_entry != NULL && !block_entry->EOS()) {
896 const mkvparser::Block* const block = block_entry->GetBlock();
897 if (!block) {
898 fprintf(stderr, "Could not getblock entry.\n");
899 return false;
900 }
901
902 const unsigned int track_number =
903 static_cast<unsigned int>(block->GetTrackNumber());
904 const mkvparser::Track* track = tracks.GetTrackByNumber(track_number);
905 if (!track) {
906 fprintf(stderr, "Could not get Track.\n");
907 return false;
908 }
909
910 const int64_t track_type = track->GetType();
911 if ((track_type == mkvparser::Track::kVideo && options.output_video) ||
912 (track_type == mkvparser::Track::kAudio && options.output_audio)) {
913 const int64_t time_ns = block->GetTime(&cluster);
914 const bool is_key = block->IsKey();
915
916 if (block_entry->GetKind() == mkvparser::BlockEntry::kBlockGroup) {
917 fprintf(o, "%sBlockGroup:\n", indent->indent_str().c_str());
918 indent->Adjust(libwebm::kIncreaseIndent);
919 }
920
921 fprintf(o, "%sBlock: type:%s frame:%s", indent->indent_str().c_str(),
922 track_type == mkvparser::Track::kVideo ? "V" : "A",
923 is_key ? "I" : "P");
924 if (options.output_seconds)
925 fprintf(o, " secs:%5g", time_ns / kNanosecondsPerSecond);
926 else
927 fprintf(o, " nano:%10" PRId64, time_ns);
928
929 if (options.output_offset)
930 fprintf(o, " @_payload: %lld", block->m_start);
931 if (options.output_size)
932 fprintf(o, " size_payload: %lld", block->m_size);
933
934 const uint8_t KEncryptedBit = 0x1;
935 const uint8_t kSubSampleBit = 0x2;
936 const int kSignalByteSize = 1;
937 bool encrypted_stream = false;
938 if (options.output_encrypted_info) {
939 if (track->GetContentEncodingCount() > 0) {
940 // Only check the first content encoding.
941 const ContentEncoding* const encoding =
942 track->GetContentEncodingByIndex(0);
943 if (encoding) {
944 if (encoding->GetEncryptionCount() > 0) {
945 const ContentEncoding::ContentEncryption* const encryption =
946 encoding->GetEncryptionByIndex(0);
947 if (encryption) {
948 const ContentEncoding::ContentEncAESSettings& aes =
949 encryption->aes_settings;
950 if (aes.cipher_mode == 1) {
951 encrypted_stream = true;
952 }
953 }
954 }
955 }
956 }
957
958 if (encrypted_stream) {
959 const mkvparser::Block::Frame& frame = block->GetFrame(0);
960 if (frame.len > static_cast<int>(vector_data.size())) {
961 vector_data.resize(frame.len + 1024);
962 }
963
964 unsigned char* data = &vector_data[0];
965 if (frame.Read(reader, data) < 0) {
966 fprintf(stderr, "Could not read frame.\n");
967 return false;
968 }
969
970 const bool encrypted_frame = !!(data[0] & KEncryptedBit);
971 const bool sub_sample_encrypt = !!(data[0] & kSubSampleBit);
972 fprintf(o, " enc: %d", encrypted_frame ? 1 : 0);
973 fprintf(o, " sub: %d", sub_sample_encrypt ? 1 : 0);
974
975 if (encrypted_frame) {
976 uint64_t iv;
977 memcpy(&iv, data + kSignalByteSize, sizeof(iv));
978 fprintf(o, " iv: %" PRIx64, iv);
979 }
980 }
981 }
982
983 if (options.output_codec_info) {
984 const int frame_count = block->GetFrameCount();
985
986 if (frame_count > 1) {
987 fprintf(o, "\n");
988 indent->Adjust(libwebm::kIncreaseIndent);
989 }
990
991 for (int i = 0; i < frame_count; ++i) {
992 if (track_type == mkvparser::Track::kVideo) {
993 const mkvparser::Block::Frame& frame = block->GetFrame(i);
994 if (frame.len > static_cast<int>(vector_data.size())) {
995 vector_data.resize(frame.len + 1024);
996 }
997
998 unsigned char* data = &vector_data[0];
999 if (frame.Read(reader, data) < 0) {
1000 fprintf(stderr, "Could not read frame.\n");
1001 return false;
1002 }
1003
1004 if (frame_count > 1)
1005 fprintf(o, "\n%sVP8 data :", indent->indent_str().c_str());
1006
1007 bool encrypted_frame = false;
1008 bool sub_sample_encrypt = false;
1009 int frame_size = static_cast<int>(frame.len);
1010
1011 int frame_offset = 0;
1012 if (encrypted_stream) {
1013 if (data[0] & KEncryptedBit) {
1014 encrypted_frame = true;
1015 if (data[0] & kSubSampleBit) {
1016 sub_sample_encrypt = true;
1017 data += kSignalByteSize;
1018 frame_size -= kSignalByteSize;
1019 frame_offset =
1020 PrintSubSampleEncryption(data, frame_size, o);
1021 }
1022 } else {
1023 frame_offset = kSignalByteSize;
1024 }
1025 }
1026
1027 if (!encrypted_frame || sub_sample_encrypt) {
1028 data += frame_offset;
1029 frame_size -= frame_offset;
1030
1031 const string codec_id = ToString(track->GetCodecId());
1032 if (codec_id == "V_VP8") {
1033 PrintVP8Info(data, frame_size, o);
1034 } else if (codec_id == "V_VP9") {
1035 PrintVP9Info(data, frame_size, o, time_ns, stats, parser,
1036 level_stats);
1037 }
1038 }
1039 }
1040 }
1041
1042 if (frame_count > 1)
1043 indent->Adjust(libwebm::kDecreaseIndent);
1044 }
1045
1046 if (block_entry->GetKind() == mkvparser::BlockEntry::kBlockGroup) {
1047 const int64_t discard_padding = block->GetDiscardPadding();
1048 if (discard_padding != 0) {
1049 fprintf(o, "\n%sDiscardPadding: %10" PRId64,
1050 indent->indent_str().c_str(), discard_padding);
1051 }
1052 indent->Adjust(libwebm::kDecreaseIndent);
1053 }
1054
1055 fprintf(o, "\n");
1056 }
1057
1058 status = cluster.GetNext(block_entry, block_entry);
1059 if (status) {
1060 printf("\n Could not get next block of cluster.\n");
1061 return false;
1062 }
1063 }
1064 }
1065
1066 if (options.output_clusters)
1067 indent->Adjust(libwebm::kDecreaseIndent);
1068
1069 return true;
1070 }
1071
OutputCues(const mkvparser::Segment & segment,const mkvparser::Tracks & tracks,const Options & options,FILE * o,Indent * indent)1072 bool OutputCues(const mkvparser::Segment& segment,
1073 const mkvparser::Tracks& tracks, const Options& options,
1074 FILE* o, Indent* indent) {
1075 const mkvparser::Cues* const cues = segment.GetCues();
1076 if (cues == NULL)
1077 return true;
1078
1079 // Load all of the cue points.
1080 while (!cues->DoneParsing())
1081 cues->LoadCuePoint();
1082
1083 // Confirm that the input has cue points.
1084 const mkvparser::CuePoint* const first_cue = cues->GetFirst();
1085 if (first_cue == NULL) {
1086 fprintf(o, "%sNo cue points.\n", indent->indent_str().c_str());
1087 return true;
1088 }
1089
1090 // Input has cue points, dump them:
1091 fprintf(o, "%sCues:", indent->indent_str().c_str());
1092 if (options.output_offset)
1093 fprintf(o, " @:%lld", cues->m_element_start);
1094 if (options.output_size)
1095 fprintf(o, " size:%lld", cues->m_element_size);
1096 fprintf(o, "\n");
1097
1098 const mkvparser::CuePoint* cue_point = first_cue;
1099 int cue_point_num = 1;
1100 const int num_tracks = static_cast<int>(tracks.GetTracksCount());
1101 indent->Adjust(libwebm::kIncreaseIndent);
1102
1103 do {
1104 for (int track_num = 0; track_num < num_tracks; ++track_num) {
1105 const mkvparser::Track* const track = tracks.GetTrackByIndex(track_num);
1106 const mkvparser::CuePoint::TrackPosition* const track_pos =
1107 cue_point->Find(track);
1108
1109 if (track_pos != NULL) {
1110 const char track_type =
1111 (track->GetType() == mkvparser::Track::kVideo) ? 'V' : 'A';
1112 fprintf(o, "%sCue Point:%d type:%c track:%d",
1113 indent->indent_str().c_str(), cue_point_num, track_type,
1114 static_cast<int>(track->GetNumber()));
1115
1116 if (options.output_seconds) {
1117 fprintf(o, " secs:%g",
1118 cue_point->GetTime(&segment) / kNanosecondsPerSecond);
1119 } else {
1120 fprintf(o, " nano:%lld", cue_point->GetTime(&segment));
1121 }
1122
1123 if (options.output_blocks)
1124 fprintf(o, " block:%lld", track_pos->m_block);
1125
1126 if (options.output_offset)
1127 fprintf(o, " @:%lld", track_pos->m_pos);
1128
1129 fprintf(o, "\n");
1130 }
1131 }
1132
1133 cue_point = cues->GetNext(cue_point);
1134 ++cue_point_num;
1135 } while (cue_point != NULL);
1136
1137 indent->Adjust(libwebm::kDecreaseIndent);
1138 return true;
1139 }
1140
1141 } // namespace
1142
main(int argc,char * argv[])1143 int main(int argc, char* argv[]) {
1144 string input;
1145 Options options;
1146
1147 const int argc_check = argc - 1;
1148 for (int i = 1; i < argc; ++i) {
1149 if (!strcmp("-h", argv[i]) || !strcmp("-?", argv[i])) {
1150 Usage();
1151 return EXIT_SUCCESS;
1152 } else if (!strcmp("-v", argv[i])) {
1153 printf("version: %s\n", VERSION_STRING);
1154 } else if (!strcmp("-i", argv[i]) && i < argc_check) {
1155 input = argv[++i];
1156 } else if (!strcmp("-all", argv[i])) {
1157 options.SetAll(true);
1158 } else if (Options::MatchesBooleanOption("video", argv[i])) {
1159 options.output_video = !strcmp("-video", argv[i]);
1160 } else if (Options::MatchesBooleanOption("audio", argv[i])) {
1161 options.output_audio = !strcmp("-audio", argv[i]);
1162 } else if (Options::MatchesBooleanOption("size", argv[i])) {
1163 options.output_size = !strcmp("-size", argv[i]);
1164 } else if (Options::MatchesBooleanOption("offset", argv[i])) {
1165 options.output_offset = !strcmp("-offset", argv[i]);
1166 } else if (Options::MatchesBooleanOption("times_seconds", argv[i])) {
1167 options.output_seconds = !strcmp("-times_seconds", argv[i]);
1168 } else if (Options::MatchesBooleanOption("ebml_header", argv[i])) {
1169 options.output_ebml_header = !strcmp("-ebml_header", argv[i]);
1170 } else if (Options::MatchesBooleanOption("segment", argv[i])) {
1171 options.output_segment = !strcmp("-segment", argv[i]);
1172 } else if (Options::MatchesBooleanOption("seekhead", argv[i])) {
1173 options.output_seekhead = !strcmp("-seekhead", argv[i]);
1174 } else if (Options::MatchesBooleanOption("segment_info", argv[i])) {
1175 options.output_segment_info = !strcmp("-segment_info", argv[i]);
1176 } else if (Options::MatchesBooleanOption("tracks", argv[i])) {
1177 options.output_tracks = !strcmp("-tracks", argv[i]);
1178 } else if (Options::MatchesBooleanOption("clusters", argv[i])) {
1179 options.output_clusters = !strcmp("-clusters", argv[i]);
1180 } else if (Options::MatchesBooleanOption("blocks", argv[i])) {
1181 options.output_blocks = !strcmp("-blocks", argv[i]);
1182 } else if (Options::MatchesBooleanOption("codec_info", argv[i])) {
1183 options.output_codec_info = !strcmp("-codec_info", argv[i]);
1184 } else if (Options::MatchesBooleanOption("clusters_size", argv[i])) {
1185 options.output_clusters_size = !strcmp("-clusters_size", argv[i]);
1186 } else if (Options::MatchesBooleanOption("encrypted_info", argv[i])) {
1187 options.output_encrypted_info = !strcmp("-encrypted_info", argv[i]);
1188 } else if (Options::MatchesBooleanOption("cues", argv[i])) {
1189 options.output_cues = !strcmp("-cues", argv[i]);
1190 } else if (Options::MatchesBooleanOption("frame_stats", argv[i])) {
1191 options.output_frame_stats = !strcmp("-frame_stats", argv[i]);
1192 } else if (Options::MatchesBooleanOption("vp9_level", argv[i])) {
1193 options.output_vp9_level = !strcmp("-vp9_level", argv[i]);
1194 }
1195 }
1196
1197 if (argc < 3 || input.empty()) {
1198 Usage();
1199 return EXIT_FAILURE;
1200 }
1201
1202 std::unique_ptr<mkvparser::MkvReader> reader(
1203 new (std::nothrow) mkvparser::MkvReader()); // NOLINT
1204 if (reader->Open(input.c_str())) {
1205 fprintf(stderr, "Error opening file:%s\n", input.c_str());
1206 return EXIT_FAILURE;
1207 }
1208
1209 long long int pos = 0;
1210 std::unique_ptr<mkvparser::EBMLHeader> ebml_header(
1211 new (std::nothrow) mkvparser::EBMLHeader()); // NOLINT
1212 if (ebml_header->Parse(reader.get(), pos) < 0) {
1213 fprintf(stderr, "Error parsing EBML header.\n");
1214 return EXIT_FAILURE;
1215 }
1216
1217 Indent indent(0);
1218 FILE* out = stdout;
1219
1220 if (options.output_ebml_header)
1221 OutputEBMLHeader(*ebml_header.get(), out, &indent);
1222
1223 mkvparser::Segment* temp_segment;
1224 if (mkvparser::Segment::CreateInstance(reader.get(), pos, temp_segment)) {
1225 fprintf(stderr, "Segment::CreateInstance() failed.\n");
1226 return EXIT_FAILURE;
1227 }
1228 std::unique_ptr<mkvparser::Segment> segment(temp_segment);
1229
1230 if (segment->Load() < 0) {
1231 fprintf(stderr, "Segment::Load() failed.\n");
1232 return EXIT_FAILURE;
1233 }
1234
1235 if (options.output_segment) {
1236 OutputSegment(*(segment.get()), options, out);
1237 indent.Adjust(libwebm::kIncreaseIndent);
1238 }
1239
1240 if (options.output_seekhead)
1241 if (!OutputSeekHead(*(segment.get()), options, out, &indent))
1242 return EXIT_FAILURE;
1243
1244 if (options.output_segment_info)
1245 if (!OutputSegmentInfo(*(segment.get()), options, out, &indent))
1246 return EXIT_FAILURE;
1247
1248 if (options.output_tracks)
1249 if (!OutputTracks(*(segment.get()), options, out, &indent))
1250 return EXIT_FAILURE;
1251
1252 const mkvparser::Tracks* const tracks = segment->GetTracks();
1253 if (!tracks) {
1254 fprintf(stderr, "Could not get Tracks.\n");
1255 return EXIT_FAILURE;
1256 }
1257
1258 // If Cues are before the clusters output them first.
1259 if (options.output_cues) {
1260 const mkvparser::Cluster* cluster = segment->GetFirst();
1261 const mkvparser::Cues* const cues = segment->GetCues();
1262 if (cluster != NULL && cues != NULL) {
1263 if (cues->m_element_start < cluster->m_element_start) {
1264 if (!OutputCues(*segment, *tracks, options, out, &indent)) {
1265 return EXIT_FAILURE;
1266 }
1267 options.output_cues = false;
1268 }
1269 }
1270 }
1271
1272 if (options.output_clusters)
1273 fprintf(out, "%sClusters (count):%ld\n", indent.indent_str().c_str(),
1274 segment->GetCount());
1275
1276 int64_t clusters_size = 0;
1277 FrameStats stats;
1278 vp9_parser::Vp9HeaderParser parser;
1279 vp9_parser::Vp9LevelStats level_stats;
1280 const mkvparser::Cluster* cluster = segment->GetFirst();
1281 while (cluster != NULL && !cluster->EOS()) {
1282 if (!OutputCluster(*cluster, *tracks, options, out, reader.get(), &indent,
1283 &clusters_size, &stats, &parser, &level_stats))
1284 return EXIT_FAILURE;
1285 cluster = segment->GetNext(cluster);
1286 }
1287
1288 if (options.output_clusters_size)
1289 fprintf(out, "%sClusters (size):%" PRId64 "\n", indent.indent_str().c_str(),
1290 clusters_size);
1291
1292 if (options.output_cues)
1293 if (!OutputCues(*segment, *tracks, options, out, &indent))
1294 return EXIT_FAILURE;
1295
1296 // TODO(fgalligan): Add support for VP8.
1297 if (options.output_frame_stats &&
1298 stats.minimum_altref_distance != std::numeric_limits<int>::max()) {
1299 const double actual_fps =
1300 stats.frames /
1301 (segment->GetInfo()->GetDuration() / kNanosecondsPerSecond);
1302 const double displayed_fps =
1303 stats.displayed_frames /
1304 (segment->GetInfo()->GetDuration() / kNanosecondsPerSecond);
1305 fprintf(out, "\nActual fps:%g Displayed fps:%g\n", actual_fps,
1306 displayed_fps);
1307
1308 fprintf(out, "Minimum Altref Distance:%d at:%g seconds\n",
1309 stats.minimum_altref_distance,
1310 stats.min_altref_end_ns / kNanosecondsPerSecond);
1311
1312 // TODO(fgalligan): Add support for window duration other than 1 second.
1313 const double sec_end = stats.max_window_end_ns / kNanosecondsPerSecond;
1314 const double sec_start =
1315 stats.max_window_end_ns > kNanosecondsPerSecondi ? sec_end - 1.0 : 0.0;
1316 fprintf(out, "Maximum Window:%g-%g seconds Window fps:%" PRId64 "\n",
1317 sec_start, sec_end, stats.max_window_size);
1318 }
1319
1320 if (options.output_vp9_level) {
1321 level_stats.set_duration(segment->GetInfo()->GetDuration());
1322 const vp9_parser::Vp9Level level = level_stats.GetLevel();
1323 fprintf(out, "VP9 Level:%d\n", level);
1324 fprintf(
1325 out,
1326 "mlsr:%" PRId64 " mlps:%" PRId64 " mlpb:%" PRId64
1327 " abr:%g mcs:%g cr:%g mct:%d"
1328 " mad:%d mrf:%d\n",
1329 level_stats.GetMaxLumaSampleRate(), level_stats.GetMaxLumaPictureSize(),
1330 level_stats.GetMaxLumaPictureBreadth(), level_stats.GetAverageBitRate(),
1331 level_stats.GetMaxCpbSize(), level_stats.GetCompressionRatio(),
1332 level_stats.GetMaxColumnTiles(), level_stats.GetMinimumAltrefDistance(),
1333 level_stats.GetMaxReferenceFrames());
1334 }
1335 return EXIT_SUCCESS;
1336 }
1337