diff --git a/gyp_packager.py b/gyp_packager.py index e8b68aa4ce..077cdc0288 100755 --- a/gyp_packager.py +++ b/gyp_packager.py @@ -1,35 +1,34 @@ -#!/usr/bin/env python +#!/usr/bin/python # # Copyright 2014 Google Inc. All rights reserved. # # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file or at # https://developers.google.com/open-source/licenses/bsd -# -# This script is a wrapper for Packager that adds some support for how GYP -# is invoked by Packager. -# -# Build instructions: -# -# 1. Setup gyp: ./gyp_packager.py -# -# clang is not enabled by default, which can be enabled by overriding -# GYP_DEFINE environment variable, i.e. -# "GYP_DEFINES='clang=1' ./gyp_packager.py". -# -# Ninja is the default build system. User can also change to make by -# overriding GYP_GENERATORS to make, i.e. -# "GYP_GENERATORS='make' ./gyp_packager.py". -# -# 2. The first step generates the make files but does not start the -# build process. Ninja is the default build system. Refer to Ninja -# manual on how to do the build. -# -# Common syntaxes: ninja -C out/{Debug/Release} [Module] -# Module is optional. If not specified, build everything. -# -# Step 1 is only required if there is any gyp file change. Otherwise, you -# may just run ninja. +"""This script wraps gyp and sets up build environments. + +Build instructions: + +1. Setup gyp: ./gyp_packager.py or use gclient runhooks + +clang is enabled by default, which can be disabled by overriding +GYP_DEFINE environment variable, i.e. +"GYP_DEFINES='clang=0' gclient runhooks". + +Ninja is the default build system. User can also change to make by +overriding GYP_GENERATORS to make, i.e. +"GYP_GENERATORS='make' gclient runhooks". + +2. The first step generates the make files but does not start the +build process. Ninja is the default build system. Refer to Ninja +manual on how to do the build. + +Common syntaxes: ninja -C out/{Debug/Release} [Module] +Module is optional. If not specified, build everything. + +Step 1 is only required if there is any gyp file change. Otherwise, you +may just run ninja. +""" import os import sys @@ -37,11 +36,14 @@ import sys checkout_dir = os.path.dirname(os.path.realpath(__file__)) src_dir = os.path.join(checkout_dir, 'packager') +# Workaround the dynamic path. +# pylint: disable=g-import-not-at-top,g-bad-import-order + sys.path.insert(0, os.path.join(src_dir, 'build')) -import gyp_helper # Workaround the dynamic path. pylint: disable-msg=F0401 +import gyp_helper sys.path.insert(0, os.path.join(src_dir, 'tools', 'gyp', 'pylib')) -import gyp # Workaround the dynamic path. pylint: disable-msg=F0401 +import gyp if __name__ == '__main__': args = sys.argv[1:] @@ -58,12 +60,15 @@ if __name__ == '__main__': args.extend(['-I' + os.path.join(src_dir, 'build', 'common.gypi')]) # Set these default GYP_DEFINES if user does not set the value explicitly. - _DEFAULT_DEFINES = {'test_isolation_mode': 'noop', 'use_glib': 0, - 'use_openssl': 1, 'use_x11': 0, - 'linux_use_gold_binary': 0, 'linux_use_gold_flags': 0} + _DEFAULT_DEFINES = {'test_isolation_mode': 'noop', + 'use_glib': 0, + 'use_openssl': 1, + 'use_x11': 0, + 'linux_use_gold_binary': 0, + 'linux_use_gold_flags': 0} - gyp_defines = (os.environ['GYP_DEFINES'] if os.environ.get('GYP_DEFINES') - else '') + gyp_defines = (os.environ['GYP_DEFINES'] if os.environ.get('GYP_DEFINES') else + '') for key in _DEFAULT_DEFINES: if key not in gyp_defines: gyp_defines += ' {0}={1}'.format(key, _DEFAULT_DEFINES[key]) diff --git a/packager/app/mpd_generator.cc b/packager/app/mpd_generator.cc index b35028df62..86b718c517 100644 --- a/packager/app/mpd_generator.cc +++ b/packager/app/mpd_generator.cc @@ -11,6 +11,7 @@ #include "packager/base/strings/string_split.h" #include "packager/base/strings/stringprintf.h" #include "packager/mpd/util/mpd_writer.h" +#include "packager/version/version.h" namespace edash_packager { namespace { @@ -88,7 +89,9 @@ int MpdMain(int argc, char** argv) { ExitStatus status = CheckRequiredFlags(); if (status != kSuccess) { - google::ShowUsageWithFlags(argv[0]); + std::string version_string = + base::StringPrintf("mpd_generator version %s", kPackagerVersion); + google::ShowUsageWithFlags(version_string.c_str()); return status; } diff --git a/packager/app/packager_main.cc b/packager/app/packager_main.cc index a48b1b5b4c..eadbafb611 100644 --- a/packager/app/packager_main.cc +++ b/packager/app/packager_main.cc @@ -36,6 +36,7 @@ #include "packager/mpd/base/media_info.pb.h" #include "packager/mpd/base/mpd_builder.h" #include "packager/mpd/base/simple_mpd_notifier.h" +#include "packager/version/version.h" DEFINE_bool(use_fake_clock_for_muxer, false, @@ -45,37 +46,36 @@ DEFINE_bool(use_fake_clock_for_muxer, namespace { const char kUsage[] = - "Packager driver program. Sample Usage:\n" + "Packager driver program. Usage:\n\n" "%s [flags] ...\n" "stream_descriptor consists of comma separated field_name/value pairs:\n" "field_name=value,[field_name=value,]...\n" "Supported field names are as follows:\n" - " - input (in): Required input/source media file path or network stream " - "URL.\n" - " - stream_selector (stream): Required field with value 'audio', 'video', " - "or stream number (zero based).\n" - " - output (out): Required output file (single file) or initialization " - "file path (multiple file).\n" - " - segment_template (segment): Optional value which specifies the " - "naming pattern for the segment files, and that the stream should be " - "split into multiple files. Its presence should be consistent across " - "streams.\n" - " - bandwidth (bw): Optional value which contains a user-specified " - "content bit rate for the stream, in bits/sec. If specified, this value is " - "propagated to the $Bandwidth$ template parameter for segment names. " - "If not specified, its value may be estimated.\n" - " - language (lang): Optional value which contains a user-specified " - "language tag. If specified, this value overrides any language metadata " - "in the input track.\n" - " - output_format (format): Optional value which specifies the format " - "of the output files (MP4 or WebM). If not specified, it will be " - "derived from the file extension of the output file.\n"; + " - input (in): Required input/source media file path or network stream\n" + " URL.\n" + " - stream_selector (stream): Required field with value 'audio',\n" + " 'video', or stream number (zero based).\n" + " - output (out): Required output file (single file) or initialization\n" + " file path (multiple file).\n" + " - segment_template (segment): Optional value which specifies the\n" + " naming pattern for the segment files, and that the stream should be\n" + " split into multiple files. Its presence should be consistent across\n" + " streams.\n" + " - bandwidth (bw): Optional value which contains a user-specified\n" + " content bit rate for the stream, in bits/sec. If specified, this\n" + " value is propagated to the $Bandwidth$ template parameter for\n" + " segment names. If not specified, its value may be estimated.\n" + " - language (lang): Optional value which contains a user-specified\n" + " language tag. If specified, this value overrides any language\n" + " metadata in the input track.\n" + " - output_format (format): Optional value which specifies the format\n" + " of the output files (MP4 or WebM). If not specified, it will be\n" + " derived from the file extension of the output file.\n"; const char kMediaInfoSuffix[] = ".media_info"; enum ExitStatus { kSuccess = 0, - kNoArgument, kArgumentValidationFailed, kPackagingFailed, kInternalError, @@ -441,8 +441,10 @@ int PackagerMain(int argc, char** argv) { google::SetUsageMessage(base::StringPrintf(kUsage, argv[0])); google::ParseCommandLineFlags(&argc, &argv, true); if (argc < 2) { - google::ShowUsageWithFlags(argv[0]); - return kNoArgument; + std::string version_string = + base::StringPrintf("edash-packager version %s", kPackagerVersion); + google::ShowUsageWithFlags(version_string.c_str()); + return kSuccess; } if (!ValidateWidevineCryptoFlags() || !ValidateFixedCryptoFlags()) diff --git a/packager/app/packager_util.cc b/packager/app/packager_util.cc index 4b52f071dc..03373f86a4 100644 --- a/packager/app/packager_util.cc +++ b/packager/app/packager_util.cc @@ -25,6 +25,16 @@ #include "packager/mpd/base/mpd_builder.h" DEFINE_bool(dump_stream_info, false, "Dump demuxed stream info."); +DEFINE_bool(override_version_string, + false, + "Override packager version string in the generated outputs with " + "--test_version_string if it is set to true. Should be used for " + "testing only."); +DEFINE_string(test_version_string, + "", + "Packager version string for testing. Ignored if " + "--override_version_string is false. Should be used for testing " + "only."); namespace edash_packager { namespace media { @@ -146,6 +156,8 @@ bool GetMuxerOptions(MuxerOptions* muxer_options) { muxer_options->fragment_sap_aligned = FLAGS_fragment_sap_aligned; muxer_options->num_subsegments_per_sidx = FLAGS_num_subsegments_per_sidx; muxer_options->temp_dir = FLAGS_temp_dir; + if (FLAGS_override_version_string) + muxer_options->packager_version_string = FLAGS_test_version_string; return true; } @@ -158,6 +170,8 @@ bool GetMpdOptions(MpdOptions* mpd_options) { mpd_options->time_shift_buffer_depth = FLAGS_time_shift_buffer_depth; mpd_options->suggested_presentation_delay = FLAGS_suggested_presentation_delay; + if (FLAGS_override_version_string) + mpd_options->packager_version_string = FLAGS_test_version_string; return true; } diff --git a/packager/app/test/packager_app.py b/packager/app/test/packager_app.py index 41d56c93ad..26ffe0a73b 100644 --- a/packager/app/test/packager_app.py +++ b/packager/app/test/packager_app.py @@ -31,6 +31,13 @@ class PackagerApp(object): cmd = [self.binary, input_str, '--dump_stream_info'] return subprocess.check_output(cmd) + def Version(self): + output = subprocess.check_output([self.binary]) + # The output should of the form: + # edash-packager version xxx: Description... + # We consider everything before ':' part of version. + return output.split(':')[0] + def Package(self, streams, flags=None): if flags is None: flags = [] diff --git a/packager/app/test/packager_test.py b/packager/app/test/packager_test.py index bbe7d6a328..6d5340bdb4 100755 --- a/packager/app/test/packager_test.py +++ b/packager/app/test/packager_test.py @@ -38,6 +38,11 @@ class PackagerAppTest(unittest.TestCase): def testBuildingCode(self): self.assertEqual(0, self.packager.BuildSrc()) + def testVersion(self): + self.assertRegexpMatches( + self.packager.Version(), '^edash-packager version ' + '((?P[\w\.]+)-)?(?P[a-f\d]+)-(debug|release)$') + def testDumpStreamInfo(self): test_file = os.path.join(self.test_data_dir, 'bear-640x360.mp4') stream_info = self.packager.DumpStreamInfo(test_file) @@ -346,6 +351,10 @@ class PackagerAppTest(unittest.TestCase): # Use fake clock, so output can be compared. if use_fake_clock: flags.append('--use_fake_clock_for_muxer') + + # Override packager version string for testing. + flags += ['--override_version_string', '--test_version_string', + '--'] return flags def _CompareWithGold(self, test_output, golden_file_name): diff --git a/packager/app/test/testdata/bear-640x360-a-cenc-golden.mp4 b/packager/app/test/testdata/bear-640x360-a-cenc-golden.mp4 index 6735dbf7b5..830bf5f1e0 100644 Binary files a/packager/app/test/testdata/bear-640x360-a-cenc-golden.mp4 and b/packager/app/test/testdata/bear-640x360-a-cenc-golden.mp4 differ diff --git a/packager/app/test/testdata/bear-640x360-a-cenc-golden.mp4.media_info b/packager/app/test/testdata/bear-640x360-a-cenc-golden.mp4.media_info index 76efa5d606..2b9b668d29 100644 --- a/packager/app/test/testdata/bear-640x360-a-cenc-golden.mp4.media_info +++ b/packager/app/test/testdata/bear-640x360-a-cenc-golden.mp4.media_info @@ -1,4 +1,4 @@ -bandwidth: 128728 +bandwidth: 129127 audio_info { codec: "mp4a.40.2" sampling_frequency: 44100 @@ -8,11 +8,11 @@ audio_info { } init_range { begin: 0 - end: 816 + end: 954 } index_range { - begin: 817 - end: 884 + begin: 955 + end: 1022 } media_file_name: "place_holder" media_duration_seconds: 2.7631745 diff --git a/packager/app/test/testdata/bear-640x360-a-golden.mp4 b/packager/app/test/testdata/bear-640x360-a-golden.mp4 index 6de4821bab..d6a9bca499 100644 Binary files a/packager/app/test/testdata/bear-640x360-a-golden.mp4 and b/packager/app/test/testdata/bear-640x360-a-golden.mp4 differ diff --git a/packager/app/test/testdata/bear-640x360-a-live-cenc-golden-init.mp4 b/packager/app/test/testdata/bear-640x360-a-live-cenc-golden-init.mp4 index 936c0548b4..d575deabdf 100644 Binary files a/packager/app/test/testdata/bear-640x360-a-live-cenc-golden-init.mp4 and b/packager/app/test/testdata/bear-640x360-a-live-cenc-golden-init.mp4 differ diff --git a/packager/app/test/testdata/bear-640x360-a-live-cenc-rotation-golden-init.mp4 b/packager/app/test/testdata/bear-640x360-a-live-cenc-rotation-golden-init.mp4 index d4f5ef75be..a600f92454 100644 Binary files a/packager/app/test/testdata/bear-640x360-a-live-cenc-rotation-golden-init.mp4 and b/packager/app/test/testdata/bear-640x360-a-live-cenc-rotation-golden-init.mp4 differ diff --git a/packager/app/test/testdata/bear-640x360-a-live-golden-init.mp4 b/packager/app/test/testdata/bear-640x360-a-live-golden-init.mp4 index aae8e47b0d..1d6fa4d7cd 100644 Binary files a/packager/app/test/testdata/bear-640x360-a-live-golden-init.mp4 and b/packager/app/test/testdata/bear-640x360-a-live-golden-init.mp4 differ diff --git a/packager/app/test/testdata/bear-640x360-av-cenc-golden.mpd b/packager/app/test/testdata/bear-640x360-av-cenc-golden.mpd index c2581f9708..4a93b551c3 100644 --- a/packager/app/test/testdata/bear-640x360-av-cenc-golden.mpd +++ b/packager/app/test/testdata/bear-640x360-av-cenc-golden.mpd @@ -1,28 +1,29 @@ + - + AAAAMHBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAABAxMjM0NTY3ODkwMTIzNDU2 output_video.mp4 - - + + - + AAAAMHBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAABAxMjM0NTY3ODkwMTIzNDU2 output_audio.mp4 - - + + diff --git a/packager/app/test/testdata/bear-640x360-av-cenc-iop-golden.mpd b/packager/app/test/testdata/bear-640x360-av-cenc-iop-golden.mpd index cd8f6f195c..10667545aa 100644 --- a/packager/app/test/testdata/bear-640x360-av-cenc-iop-golden.mpd +++ b/packager/app/test/testdata/bear-640x360-av-cenc-iop-golden.mpd @@ -1,4 +1,5 @@ + @@ -6,10 +7,10 @@ AAAAMHBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAABAxMjM0NTY3ODkwMTIzNDU2 - + output_video.mp4 - - + + @@ -18,11 +19,11 @@ AAAAMHBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAABAxMjM0NTY3ODkwMTIzNDU2 - + output_audio.mp4 - - + + diff --git a/packager/app/test/testdata/bear-640x360-av-golden.mpd b/packager/app/test/testdata/bear-640x360-av-golden.mpd index 64553814d7..a5a8c9b634 100644 --- a/packager/app/test/testdata/bear-640x360-av-golden.mpd +++ b/packager/app/test/testdata/bear-640x360-av-golden.mpd @@ -1,20 +1,21 @@ + - + output_video.mp4 - - + + - + output_audio.mp4 - - + + diff --git a/packager/app/test/testdata/bear-640x360-av-live-cenc-golden.mpd b/packager/app/test/testdata/bear-640x360-av-live-cenc-golden.mpd index b69a174e60..836af9866d 100644 --- a/packager/app/test/testdata/bear-640x360-av-live-cenc-golden.mpd +++ b/packager/app/test/testdata/bear-640x360-av-live-cenc-golden.mpd @@ -1,4 +1,5 @@ + diff --git a/packager/app/test/testdata/bear-640x360-av-live-cenc-iop-golden.mpd b/packager/app/test/testdata/bear-640x360-av-live-cenc-iop-golden.mpd index e1f097e3a3..4dc639a9fd 100644 --- a/packager/app/test/testdata/bear-640x360-av-live-cenc-iop-golden.mpd +++ b/packager/app/test/testdata/bear-640x360-av-live-cenc-iop-golden.mpd @@ -1,4 +1,5 @@ + diff --git a/packager/app/test/testdata/bear-640x360-av-live-cenc-rotation-golden.mpd b/packager/app/test/testdata/bear-640x360-av-live-cenc-rotation-golden.mpd index 80a3fa0c3f..c438cd3b37 100644 --- a/packager/app/test/testdata/bear-640x360-av-live-cenc-rotation-golden.mpd +++ b/packager/app/test/testdata/bear-640x360-av-live-cenc-rotation-golden.mpd @@ -1,4 +1,5 @@ + diff --git a/packager/app/test/testdata/bear-640x360-av-live-cenc-rotation-iop-golden.mpd b/packager/app/test/testdata/bear-640x360-av-live-cenc-rotation-iop-golden.mpd index 18b72a84e9..1d38bb188c 100644 --- a/packager/app/test/testdata/bear-640x360-av-live-cenc-rotation-iop-golden.mpd +++ b/packager/app/test/testdata/bear-640x360-av-live-cenc-rotation-iop-golden.mpd @@ -1,4 +1,5 @@ + diff --git a/packager/app/test/testdata/bear-640x360-av-live-golden.mpd b/packager/app/test/testdata/bear-640x360-av-live-golden.mpd index 7d9a0d3e3a..94cf9ccc2b 100644 --- a/packager/app/test/testdata/bear-640x360-av-live-golden.mpd +++ b/packager/app/test/testdata/bear-640x360-av-live-golden.mpd @@ -1,4 +1,5 @@ + diff --git a/packager/app/test/testdata/bear-640x360-avt-golden.mpd b/packager/app/test/testdata/bear-640x360-avt-golden.mpd index d8308d3595..c0e04a7a91 100644 --- a/packager/app/test/testdata/bear-640x360-avt-golden.mpd +++ b/packager/app/test/testdata/bear-640x360-avt-golden.mpd @@ -1,4 +1,5 @@ + @@ -7,19 +8,19 @@ - + output_video.mp4 - - + + - + output_audio.mp4 - - + + diff --git a/packager/app/test/testdata/bear-640x360-hevc-v-cenc-golden.mp4 b/packager/app/test/testdata/bear-640x360-hevc-v-cenc-golden.mp4 index db71cd5c83..714ff7ac47 100644 Binary files a/packager/app/test/testdata/bear-640x360-hevc-v-cenc-golden.mp4 and b/packager/app/test/testdata/bear-640x360-hevc-v-cenc-golden.mp4 differ diff --git a/packager/app/test/testdata/bear-640x360-hevc-v-cenc-golden.mpd b/packager/app/test/testdata/bear-640x360-hevc-v-cenc-golden.mpd index 8e7c701a31..7dff2a0281 100644 --- a/packager/app/test/testdata/bear-640x360-hevc-v-cenc-golden.mpd +++ b/packager/app/test/testdata/bear-640x360-hevc-v-cenc-golden.mpd @@ -1,15 +1,16 @@ + - + AAAAMHBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAABAxMjM0NTY3ODkwMTIzNDU2 output_video.mp4 - - + + diff --git a/packager/app/test/testdata/bear-640x360-v-cenc-golden.mp4 b/packager/app/test/testdata/bear-640x360-v-cenc-golden.mp4 index 0fe9379ed5..160d14ebf4 100644 Binary files a/packager/app/test/testdata/bear-640x360-v-cenc-golden.mp4 and b/packager/app/test/testdata/bear-640x360-v-cenc-golden.mp4 differ diff --git a/packager/app/test/testdata/bear-640x360-v-cenc-golden.mp4.media_info b/packager/app/test/testdata/bear-640x360-v-cenc-golden.mp4.media_info index dd0670430d..d04cb8f287 100644 --- a/packager/app/test/testdata/bear-640x360-v-cenc-golden.mp4.media_info +++ b/packager/app/test/testdata/bear-640x360-v-cenc-golden.mp4.media_info @@ -1,4 +1,4 @@ -bandwidth: 885151 +bandwidth: 885555 video_info { codec: "avc1.64001e" width: 640 @@ -11,11 +11,11 @@ video_info { } init_range { begin: 0 - end: 940 + end: 1078 } index_range { - begin: 941 - end: 1008 + begin: 1079 + end: 1146 } media_file_name: "place_holder" media_duration_seconds: 2.7360666 diff --git a/packager/app/test/testdata/bear-640x360-v-golden.mp4 b/packager/app/test/testdata/bear-640x360-v-golden.mp4 index 0a4e7b3deb..793114711c 100644 Binary files a/packager/app/test/testdata/bear-640x360-v-golden.mp4 and b/packager/app/test/testdata/bear-640x360-v-golden.mp4 differ diff --git a/packager/app/test/testdata/bear-640x360-v-golden.mpd b/packager/app/test/testdata/bear-640x360-v-golden.mpd index ea6a74ba36..c374b9fd55 100644 --- a/packager/app/test/testdata/bear-640x360-v-golden.mpd +++ b/packager/app/test/testdata/bear-640x360-v-golden.mpd @@ -1,11 +1,12 @@ + - + output_0.mp4 - - + + diff --git a/packager/app/test/testdata/bear-640x360-v-live-cenc-golden-init.mp4 b/packager/app/test/testdata/bear-640x360-v-live-cenc-golden-init.mp4 index 8ee13870e3..8f9595cc31 100644 Binary files a/packager/app/test/testdata/bear-640x360-v-live-cenc-golden-init.mp4 and b/packager/app/test/testdata/bear-640x360-v-live-cenc-golden-init.mp4 differ diff --git a/packager/app/test/testdata/bear-640x360-v-live-cenc-rotation-golden-init.mp4 b/packager/app/test/testdata/bear-640x360-v-live-cenc-rotation-golden-init.mp4 index 735a1f464c..167a2e0060 100644 Binary files a/packager/app/test/testdata/bear-640x360-v-live-cenc-rotation-golden-init.mp4 and b/packager/app/test/testdata/bear-640x360-v-live-cenc-rotation-golden-init.mp4 differ diff --git a/packager/app/test/testdata/bear-640x360-v-live-golden-init.mp4 b/packager/app/test/testdata/bear-640x360-v-live-golden-init.mp4 index 80fadab713..68700f0fa1 100644 Binary files a/packager/app/test/testdata/bear-640x360-v-live-golden-init.mp4 and b/packager/app/test/testdata/bear-640x360-v-live-golden-init.mp4 differ diff --git a/packager/app/test/testdata/subtitle-english-vtt-golden.mpd b/packager/app/test/testdata/subtitle-english-vtt-golden.mpd index d6f20ee8c3..657d48f460 100644 --- a/packager/app/test/testdata/subtitle-english-vtt-golden.mpd +++ b/packager/app/test/testdata/subtitle-english-vtt-golden.mpd @@ -1,4 +1,5 @@ + diff --git a/packager/media/base/media_base.gyp b/packager/media/base/media_base.gyp index 08ceffb064..20a58a6200 100644 --- a/packager/media/base/media_base.gyp +++ b/packager/media/base/media_base.gyp @@ -84,6 +84,7 @@ '../../third_party/boringssl/boringssl.gyp:boringssl', '../../third_party/curl/curl.gyp:libcurl', '../../third_party/libxml/libxml.gyp:libxml', + '../../version/version.gyp:version', ], }, { diff --git a/packager/media/base/muxer_options.cc b/packager/media/base/muxer_options.cc index 1bc323a8ef..ed589780b2 100644 --- a/packager/media/base/muxer_options.cc +++ b/packager/media/base/muxer_options.cc @@ -5,6 +5,7 @@ // https://developers.google.com/open-source/licenses/bsd #include "packager/media/base/muxer_options.h" +#include "packager/version/version.h" namespace edash_packager { namespace media { @@ -16,7 +17,8 @@ MuxerOptions::MuxerOptions() segment_sap_aligned(false), fragment_sap_aligned(false), num_subsegments_per_sidx(0), - bandwidth(0) {} + bandwidth(0), + packager_version_string(kPackagerVersion) {} MuxerOptions::~MuxerOptions() {} } // namespace media diff --git a/packager/media/base/muxer_options.h b/packager/media/base/muxer_options.h index 315036b80c..093c8a6021 100644 --- a/packager/media/base/muxer_options.h +++ b/packager/media/base/muxer_options.h @@ -66,6 +66,9 @@ struct MuxerOptions { /// User-specified bit rate for the media stream. If zero, the muxer will /// attempt to estimate. uint32_t bandwidth; + + /// Specify the version string to be embedded in the output files. + std::string packager_version_string; }; } // namespace media diff --git a/packager/media/formats/mp4/box_definitions.cc b/packager/media/formats/mp4/box_definitions.cc index ee3eeb3d4b..f05e790aa1 100644 --- a/packager/media/formats/mp4/box_definitions.cc +++ b/packager/media/formats/mp4/box_definitions.cc @@ -63,6 +63,11 @@ bool IsIvSizeValid(size_t iv_size) { // bit(5) Reserved // 0 const uint8_t kDdtsExtraData[] = {0xe4, 0x7c, 0, 4, 0, 0x0f, 0}; +// ID3v2 header: http://id3.org/id3v2.4.0-structure +const uint32_t kID3v2HeaderSize = 10; +const char kID3v2Identifier[] = "ID3"; +const uint16_t kID3v2Version = 0x0400; // id3v2.4.0 + // Utility functions to check if the 64bit integers can fit in 32bit integer. bool IsFitIn32Bits(uint64_t a) { return a <= std::numeric_limits::max(); @@ -89,6 +94,36 @@ namespace edash_packager { namespace media { namespace mp4 { +namespace { + +TrackType FourCCToTrackType(FourCC fourcc) { + switch (fourcc) { + case FOURCC_VIDE: + return kVideo; + case FOURCC_SOUN: + return kAudio; + case FOURCC_TEXT: + return kText; + default: + return kInvalid; + } +} + +FourCC TrackTypeToFourCC(TrackType track_type) { + switch (track_type) { + case kVideo: + return FOURCC_VIDE; + case kAudio: + return FOURCC_SOUN; + case kText: + return FOURCC_TEXT; + default: + return FOURCC_NULL; + } +} + +} // namespace + FileType::FileType() : major_brand(FOURCC_NULL), minor_version(0) {} FileType::~FileType() {} FourCC FileType::BoxType() const { return FOURCC_FTYP; } @@ -967,44 +1002,37 @@ uint32_t Edit::ComputeSizeInternal() { return HeaderSize() + list.ComputeSize(); } -HandlerReference::HandlerReference() : type(kInvalid) {} +HandlerReference::HandlerReference() : handler_type(FOURCC_NULL) {} HandlerReference::~HandlerReference() {} FourCC HandlerReference::BoxType() const { return FOURCC_HDLR; } bool HandlerReference::ReadWriteInternal(BoxBuffer* buffer) { - FourCC hdlr_type = FOURCC_NULL; std::vector handler_name; if (!buffer->Reading()) { - if (type == kVideo) { - hdlr_type = FOURCC_VIDE; - handler_name.assign(kVideoHandlerName, - kVideoHandlerName + arraysize(kVideoHandlerName)); - } else if (type == kAudio) { - hdlr_type = FOURCC_SOUN; - handler_name.assign(kAudioHandlerName, - kAudioHandlerName + arraysize(kAudioHandlerName)); - } else if (type == kText) { - hdlr_type = FOURCC_TEXT; - handler_name.assign(kTextHandlerName, - kTextHandlerName + arraysize(kTextHandlerName)); - } else { - NOTIMPLEMENTED(); - return false; + switch (handler_type) { + case FOURCC_VIDE: + handler_name.assign(kVideoHandlerName, + kVideoHandlerName + arraysize(kVideoHandlerName)); + break; + case FOURCC_SOUN: + handler_name.assign(kAudioHandlerName, + kAudioHandlerName + arraysize(kAudioHandlerName)); + break; + case FOURCC_TEXT: + handler_name.assign(kTextHandlerName, + kTextHandlerName + arraysize(kTextHandlerName)); + break; + case FOURCC_ID32: + break; + default: + NOTIMPLEMENTED(); + return false; } } RCHECK(ReadWriteHeaderInternal(buffer) && buffer->IgnoreBytes(4) && // predefined. - buffer->ReadWriteFourCC(&hdlr_type)); - if (buffer->Reading()) { - // Note: for reading, remaining fields in box ignored. - if (hdlr_type == FOURCC_VIDE) { - type = kVideo; - } else if (hdlr_type == FOURCC_SOUN) { - type = kAudio; - } else { - type = kInvalid; - } - } else { + buffer->ReadWriteFourCC(&handler_type)); + if (!buffer->Reading()) { RCHECK(buffer->IgnoreBytes(12) && // reserved. buffer->ReadWriteVector(&handler_name, handler_name.size())); } @@ -1013,16 +1041,161 @@ bool HandlerReference::ReadWriteInternal(BoxBuffer* buffer) { uint32_t HandlerReference::ComputeSizeInternal() { uint32_t box_size = HeaderSize() + kFourCCSize + 16; // 16 bytes Reserved - if (type == kVideo) { - box_size += sizeof(kVideoHandlerName); - } else if (type == kAudio) { - box_size += sizeof(kAudioHandlerName); - } else { - box_size += sizeof(kTextHandlerName); + switch (handler_type) { + case FOURCC_VIDE: + box_size += sizeof(kVideoHandlerName); + break; + case FOURCC_SOUN: + box_size += sizeof(kAudioHandlerName); + break; + case FOURCC_TEXT: + box_size += sizeof(kTextHandlerName); + break; + case FOURCC_ID32: + break; + default: + NOTIMPLEMENTED(); } return box_size; } +bool Language::ReadWrite(BoxBuffer* buffer) { + if (buffer->Reading()) { + // Read language codes into temp first then use BitReader to read the + // values. ISO-639-2/T language code: unsigned int(5)[3] language (2 bytes). + std::vector temp; + RCHECK(buffer->ReadWriteVector(&temp, 2)); + + BitReader bit_reader(&temp[0], 2); + bit_reader.SkipBits(1); + char language[3]; + for (int i = 0; i < 3; ++i) { + CHECK(bit_reader.ReadBits(5, &language[i])); + language[i] += 0x60; + } + code.assign(language, 3); + } else { + // Set up default language if it is not set. + const char kUndefinedLanguage[] = "und"; + if (code.empty()) + code = kUndefinedLanguage; + DCHECK_EQ(code.size(), 3u); + + // Lang format: bit(1) pad, unsigned int(5)[3] language. + uint16_t lang = 0; + for (int i = 0; i < 3; ++i) + lang |= (code[i] - 0x60) << ((2 - i) * 5); + RCHECK(buffer->ReadWriteUInt16(&lang)); + } + return true; +} + +uint32_t Language::ComputeSize() const { + // ISO-639-2/T language code: unsigned int(5)[3] language (2 bytes). + return 2; +} + +bool PrivFrame::ReadWrite(BoxBuffer* buffer) { + FourCC fourcc = FOURCC_PRIV; + RCHECK(buffer->ReadWriteFourCC(&fourcc)); + if (fourcc != FOURCC_PRIV) { + VLOG(1) << "Skip unrecognized id3 frame during read: " + << FourCCToString(fourcc); + return true; + } + + uint32_t frame_size = owner.size() + 1 + value.size(); + // size should be encoded as synchsafe integer, which is not support here. + // We don't expect frame_size to be larger than 0x7F. Synchsafe integers less + // than 0x7F is encoded in the same way as normal integer. + DCHECK_LT(frame_size, 0x7Fu); + uint16_t flags = 0; + RCHECK(buffer->ReadWriteUInt32(&frame_size) && + buffer->ReadWriteUInt16(&flags)); + + if (buffer->Reading()) { + std::string str; + RCHECK(buffer->ReadWriteString(&str, frame_size)); + // |owner| is null terminated. + size_t pos = str.find('\0'); + RCHECK(pos < str.size()); + owner = str.substr(0, pos); + value = str.substr(pos + 1); + } else { + uint8_t byte = 0; // Null terminating byte between owner and value. + RCHECK(buffer->ReadWriteString(&owner, owner.size()) && + buffer->ReadWriteUInt8(&byte) && + buffer->ReadWriteString(&value, value.size())); + } + return true; +} + +uint32_t PrivFrame::ComputeSize() const { + if (owner.empty() && value.empty()) + return 0; + const uint32_t kFourCCSize = 4; + return kFourCCSize + sizeof(uint32_t) + sizeof(uint16_t) + owner.size() + 1 + + value.size(); +} + +ID3v2::ID3v2() {} +ID3v2::~ID3v2() {} + +FourCC ID3v2::BoxType() const { return FOURCC_ID32; } + +bool ID3v2::ReadWriteInternal(BoxBuffer* buffer) { + RCHECK(ReadWriteHeaderInternal(buffer) && + language.ReadWrite(buffer)); + + // Read/Write ID3v2 header + std::string id3v2_identifier = kID3v2Identifier; + uint16_t version = kID3v2Version; + // We only support PrivateFrame in ID3. + uint32_t data_size = private_frame.ComputeSize(); + // size should be encoded as synchsafe integer, which is not support here. + // We don't expect data_size to be larger than 0x7F. Synchsafe integers less + // than 0x7F is encoded in the same way as normal integer. + DCHECK_LT(data_size, 0x7Fu); + uint8_t flags = 0; + RCHECK(buffer->ReadWriteString(&id3v2_identifier, id3v2_identifier.size()) && + buffer->ReadWriteUInt16(&version) && + buffer->ReadWriteUInt8(&flags) && + buffer->ReadWriteUInt32(&data_size)); + + RCHECK(private_frame.ReadWrite(buffer)); + return true; +} + +uint32_t ID3v2::ComputeSizeInternal() { + uint32_t private_frame_size = private_frame.ComputeSize(); + // Skip ID3v2 box generation if there is no private frame. + return private_frame_size == 0 ? 0 : HeaderSize() + language.ComputeSize() + + kID3v2HeaderSize + + private_frame_size; +} + +Metadata::Metadata() {} +Metadata::~Metadata() {} + +FourCC Metadata::BoxType() const { + return FOURCC_META; +} + +bool Metadata::ReadWriteInternal(BoxBuffer* buffer) { + RCHECK(ReadWriteHeaderInternal(buffer) && + buffer->PrepareChildren() && + buffer->ReadWriteChild(&handler) && + buffer->TryReadWriteChild(&id3v2)); + return true; +} + +uint32_t Metadata::ComputeSizeInternal() { + uint32_t id3v2_size = id3v2.ComputeSize(); + // Skip metadata box generation if there is no metadata box. + return id3v2_size == 0 ? 0 + : HeaderSize() + handler.ComputeSize() + id3v2_size; +} + CodecConfigurationRecord::CodecConfigurationRecord() : box_type(FOURCC_NULL) {} CodecConfigurationRecord::~CodecConfigurationRecord() {} FourCC CodecConfigurationRecord::BoxType() const { @@ -1369,9 +1542,7 @@ uint32_t WVTTSampleEntry::ComputeSizeInternal() { } MediaHeader::MediaHeader() - : creation_time(0), modification_time(0), timescale(0), duration(0) { - language[0] = 0; -} + : creation_time(0), modification_time(0), timescale(0), duration(0) {} MediaHeader::~MediaHeader() {} FourCC MediaHeader::BoxType() const { return FOURCC_MDHD; } @@ -1382,43 +1553,17 @@ bool MediaHeader::ReadWriteInternal(BoxBuffer* buffer) { RCHECK(buffer->ReadWriteUInt64NBytes(&creation_time, num_bytes) && buffer->ReadWriteUInt64NBytes(&modification_time, num_bytes) && buffer->ReadWriteUInt32(×cale) && - buffer->ReadWriteUInt64NBytes(&duration, num_bytes)); - - if (buffer->Reading()) { - // Read language codes into temp first then use BitReader to read the - // values. ISO-639-2/T language code: unsigned int(5)[3] language (2 bytes). - std::vector temp; - RCHECK(buffer->ReadWriteVector(&temp, 2)); - - BitReader bit_reader(&temp[0], 2); - bit_reader.SkipBits(1); - for (int i = 0; i < 3; ++i) { - CHECK(bit_reader.ReadBits(5, &language[i])); - language[i] += 0x60; - } - language[3] = '\0'; - } else { - // Set up default language if it is not set. - const char kUndefinedLanguage[] = "und"; - if (language[0] == 0) - strcpy(language, kUndefinedLanguage); - - // Lang format: bit(1) pad, unsigned int(5)[3] language. - uint16_t lang = 0; - for (int i = 0; i < 3; ++i) - lang |= (language[i] - 0x60) << ((2 - i) * 5); - RCHECK(buffer->ReadWriteUInt16(&lang)); - } - - RCHECK(buffer->IgnoreBytes(2)); // predefined. + buffer->ReadWriteUInt64NBytes(&duration, num_bytes) && + language.ReadWrite(buffer) && + buffer->IgnoreBytes(2)); // predefined. return true; } uint32_t MediaHeader::ComputeSizeInternal() { version = IsFitIn32Bits(creation_time, modification_time, duration) ? 0 : 1; return HeaderSize() + sizeof(timescale) + - sizeof(uint32_t) * (1 + version) * 3 + 2 + // 2 bytes language. - 2; // 2 bytes predefined. + sizeof(uint32_t) * (1 + version) * 3 + language.ComputeSize() + + 2; // 2 bytes predefined. } VideoMediaHeader::VideoMediaHeader() @@ -1580,24 +1725,30 @@ FourCC Media::BoxType() const { return FOURCC_MDIA; } bool Media::ReadWriteInternal(BoxBuffer* buffer) { RCHECK(ReadWriteHeaderInternal(buffer) && buffer->PrepareChildren() && - buffer->ReadWriteChild(&header) && - buffer->ReadWriteChild(&handler)); + buffer->ReadWriteChild(&header)); if (buffer->Reading()) { + RCHECK(buffer->ReadWriteChild(&handler)); // Maddeningly, the HandlerReference box specifies how to parse the // SampleDescription box, making the latter the only box (of those that we // support) which cannot be parsed correctly on its own (or even with // information from its strict ancestor tree). We thus copy the handler type // to the sample description box *before* parsing it to provide this // information while parsing. - information.sample_table.description.type = handler.type; + information.sample_table.description.type = + FourCCToTrackType(handler.handler_type); } else { - DCHECK_EQ(information.sample_table.description.type, handler.type); + handler.handler_type = + TrackTypeToFourCC(information.sample_table.description.type); + RCHECK(handler.handler_type != FOURCC_NULL); + RCHECK(buffer->ReadWriteChild(&handler)); } RCHECK(buffer->ReadWriteChild(&information)); return true; } uint32_t Media::ComputeSizeInternal() { + handler.handler_type = + TrackTypeToFourCC(information.sample_table.description.type); return HeaderSize() + header.ComputeSize() + handler.ComputeSize() + information.ComputeSize(); } @@ -1702,6 +1853,7 @@ bool Movie::ReadWriteInternal(BoxBuffer* buffer) { RCHECK(ReadWriteHeaderInternal(buffer) && buffer->PrepareChildren() && buffer->ReadWriteChild(&header) && + buffer->TryReadWriteChild(&metadata) && buffer->TryReadWriteChild(&extends)); if (buffer->Reading()) { BoxReader* reader = buffer->reader(); @@ -1718,8 +1870,8 @@ bool Movie::ReadWriteInternal(BoxBuffer* buffer) { } uint32_t Movie::ComputeSizeInternal() { - uint32_t box_size = - HeaderSize() + header.ComputeSize() + extends.ComputeSize(); + uint32_t box_size = HeaderSize() + header.ComputeSize() + + metadata.ComputeSize() + extends.ComputeSize(); for (uint32_t i = 0; i < tracks.size(); ++i) box_size += tracks[i].ComputeSize(); for (uint32_t i = 0; i < pssh.size(); ++i) diff --git a/packager/media/formats/mp4/box_definitions.h b/packager/media/formats/mp4/box_definitions.h index a219002011..1cd56df23e 100644 --- a/packager/media/formats/mp4/box_definitions.h +++ b/packager/media/formats/mp4/box_definitions.h @@ -222,7 +222,41 @@ struct Edit : Box { struct HandlerReference : FullBox { DECLARE_BOX_METHODS(HandlerReference); - TrackType type; + FourCC handler_type; +}; + +struct Language { + bool ReadWrite(BoxBuffer* buffer); + uint32_t ComputeSize() const; + + std::string code; +}; + +/// Implemented per http://id3.org/id3v2.4.0-frames. +struct PrivFrame { + bool ReadWrite(BoxBuffer* buffer); + uint32_t ComputeSize() const; + + std::string owner; + std::string value; +}; + +/// Implemented per http://mp4ra.org/specs.html#id3v2 and +/// http://id3.org/id3v2.4.0-structure. +struct ID3v2 : FullBox { + DECLARE_BOX_METHODS(ID3v2); + + Language language; + + /// We only support PrivateFrame in ID3. Other frames are ignored. + PrivFrame private_frame; +}; + +struct Metadata : FullBox { + DECLARE_BOX_METHODS(Metadata); + + HandlerReference handler; + ID3v2 id3v2; }; struct CodecConfigurationRecord : Box { @@ -422,8 +456,7 @@ struct MediaHeader : FullBox { uint64_t modification_time; uint32_t timescale; uint64_t duration; - // 3-char language code + 1 null terminating char. - char language[4]; + Language language; }; struct VideoMediaHeader : FullBox { @@ -519,6 +552,7 @@ struct Movie : Box { DECLARE_BOX_METHODS(Movie); MovieHeader header; + Metadata metadata; // Used to hold version information. MovieExtends extends; std::vector tracks; std::vector pssh; diff --git a/packager/media/formats/mp4/box_definitions_comparison.h b/packager/media/formats/mp4/box_definitions_comparison.h index f830eadb28..6ba02840fb 100644 --- a/packager/media/formats/mp4/box_definitions_comparison.h +++ b/packager/media/formats/mp4/box_definitions_comparison.h @@ -180,7 +180,24 @@ inline bool operator==(const Edit& lhs, const Edit& rhs) { inline bool operator==(const HandlerReference& lhs, const HandlerReference& rhs) { - return lhs.type == rhs.type; + return lhs.handler_type == rhs.handler_type; +} + +inline bool operator==(const Language& lhs, + const Language& rhs) { + return lhs.code == rhs.code; +} + +inline bool operator==(const PrivFrame& lhs, const PrivFrame& rhs) { + return lhs.owner == rhs.owner && lhs.value == rhs.value; +} + +inline bool operator==(const ID3v2& lhs, const ID3v2& rhs) { + return lhs.language == rhs.language && lhs.private_frame == rhs.private_frame; +} + +inline bool operator==(const Metadata& lhs, const Metadata& rhs) { + return lhs.handler == rhs.handler && lhs.id3v2 == rhs.id3v2; } inline bool operator==(const CodecConfigurationRecord& lhs, @@ -252,7 +269,7 @@ inline bool operator==(const MediaHeader& lhs, const MediaHeader& rhs) { return lhs.creation_time == rhs.creation_time && lhs.modification_time == rhs.modification_time && lhs.timescale == rhs.timescale && lhs.duration == rhs.duration && - strcmp(lhs.language, rhs.language) == 0; + lhs.language == rhs.language; } inline bool operator==(const VideoMediaHeader& lhs, diff --git a/packager/media/formats/mp4/box_definitions_unittest.cc b/packager/media/formats/mp4/box_definitions_unittest.cc index 52ec0dd28b..06e79bbe67 100644 --- a/packager/media/formats/mp4/box_definitions_unittest.cc +++ b/packager/media/formats/mp4/box_definitions_unittest.cc @@ -286,11 +286,29 @@ class BoxDefinitionsTestGeneral : public testing::Test { void Modify(Edit* edts) { Modify(&edts->list); } - void Fill(HandlerReference* hdlr) { - hdlr->type = kSampleDescriptionTrackType; + void Fill(HandlerReference* hdlr) { hdlr->handler_type = FOURCC_VIDE; } + + void Modify(HandlerReference* hdlr) { hdlr->handler_type = FOURCC_SOUN; } + + void Fill(ID3v2* id3v2) { + id3v2->language.code = "eng"; + id3v2->private_frame.owner = "edash-packager"; + id3v2->private_frame.value = "version 1.2.0-debug"; } - void Modify(HandlerReference* hdlr) { hdlr->type = kAudio; } + void Modify(ID3v2* id3v2) { + id3v2->language.code = "fre"; + id3v2->private_frame.value = "version 1.3.1-release"; + } + + void Fill(Metadata* metadata) { + metadata->handler.handler_type = FOURCC_ID32; + Fill(&metadata->id3v2); + } + + void Modify(Metadata* metadata) { + Modify(&metadata->id3v2); + } void Fill(PixelAspectRatio* pasp) { pasp->h_spacing = 5; @@ -517,14 +535,14 @@ class BoxDefinitionsTestGeneral : public testing::Test { static_cast(std::numeric_limits::max()) + 1; mdhd->timescale = 50000; mdhd->duration = 250000; - strcpy(mdhd->language, "abc"); + mdhd->language.code = "abc"; mdhd->version = 1; } void Modify(MediaHeader* mdhd) { mdhd->creation_time = 2; mdhd->modification_time = std::numeric_limits::max(); - strcpy(mdhd->language, "und"); + mdhd->language.code = "und"; mdhd->version = 0; } @@ -633,6 +651,7 @@ class BoxDefinitionsTestGeneral : public testing::Test { void Fill(Movie* moov) { Fill(&moov->header); + Fill(&moov->metadata); Fill(&moov->extends); moov->tracks.resize(2); Fill(&moov->tracks[0]); @@ -854,6 +873,8 @@ class BoxDefinitionsTestGeneral : public testing::Test { Modify(&vttc->cue_payload); } + bool IsOptional(const ID3v2* box) { return true; } + bool IsOptional(const Metadata* box) { return true; } bool IsOptional(const SampleAuxiliaryInformationOffset* box) { return true; } bool IsOptional(const SampleAuxiliaryInformationSize* box) { return true; } bool IsOptional(const SampleEncryption* box) { return true; } @@ -881,76 +902,77 @@ class BoxDefinitionsTestGeneral : public testing::Test { scoped_ptr buffer_; }; -typedef testing::Types< - FileType, - SegmentType, - ProtectionSystemSpecificHeader, - SampleAuxiliaryInformationOffset, - SampleAuxiliaryInformationSize, - OriginalFormat, - SchemeType, - TrackEncryption, - SchemeInfo, - ProtectionSchemeInfo, - MovieHeader, - TrackHeader, - EditList, - Edit, - HandlerReference, - CodecConfigurationRecord, - PixelAspectRatio, - VideoSampleEntry, - ElementaryStreamDescriptor, - AudioSampleEntry, - WebVTTConfigurationBox, - WebVTTSourceLabelBox, - WVTTSampleEntry, - SampleDescription, - DecodingTimeToSample, - CompositionTimeToSample, - SampleToChunk, - SampleSize, - CompactSampleSize, - ChunkLargeOffset, - ChunkOffset, - SyncSample, - SampleTable, - MediaHeader, - VideoMediaHeader, - SoundMediaHeader, - SubtitleMediaHeader, - DataEntryUrl, - DataReference, - DataInformation, - MediaInformation, - Media, - Track, - MovieExtendsHeader, - TrackExtends, - MovieExtends, - Movie, - TrackFragmentDecodeTime, - MovieFragmentHeader, - TrackFragmentHeader> Boxes; - // GTEST support a maximum of 50 types in the template list, so we have to // break it into two groups. -typedef testing::Types< - TrackFragmentRun, - TrackFragment, - MovieFragment, - SegmentIndex, - SampleToGroup, - SampleGroupDescription, - CueSourceIDBox, - CueTimeBox, - CueIDBox, - CueSettingsBox, - CuePayloadBox, - VTTEmptyCueBox, - VTTAdditionalTextBox, - VTTCueBox, - DTSSpecific> Boxes2; +typedef testing::Types + Boxes; +typedef testing::Types + Boxes2; TYPED_TEST_CASE_P(BoxDefinitionsTestGeneral); @@ -1021,6 +1043,20 @@ INSTANTIATE_TYPED_TEST_CASE_P(BoxDefinitionTypedTests2, // Test other cases of box input. class BoxDefinitionsTest : public BoxDefinitionsTestGeneral {}; +TEST_F(BoxDefinitionsTest, MediaHandlerType) { + Media media; + Fill(&media); + // Clear handler type. When this box is written, it will derive handler type + // from sample table description. + media.handler.handler_type = FOURCC_NULL; + media.information.sample_table.description.type = kVideo; + media.Write(this->buffer_.get()); + + Media media_readback; + ASSERT_TRUE(ReadBack(&media_readback)); + ASSERT_EQ(FOURCC_VIDE, media_readback.handler.handler_type); +} + TEST_F(BoxDefinitionsTest, DTSSampleEntry) { AudioSampleEntry entry; entry.format = FOURCC_DTSE; diff --git a/packager/media/formats/mp4/fourccs.h b/packager/media/formats/mp4/fourccs.h index 02a1c9285c..a070d9b817 100644 --- a/packager/media/formats/mp4/fourccs.h +++ b/packager/media/formats/mp4/fourccs.h @@ -14,6 +14,8 @@ namespace mp4 { // TODO(rkuroiwa): Make these case sensitive. e.g. FOURCC_avc1. enum FourCC { FOURCC_NULL = 0, + FOURCC_ID32 = 0x49443332, + FOURCC_PRIV = 0x50524956, FOURCC_AVC1 = 0x61766331, FOURCC_AVCC = 0x61766343, FOURCC_BLOC = 0x626C6F63, diff --git a/packager/media/formats/mp4/mp4_media_parser.cc b/packager/media/formats/mp4/mp4_media_parser.cc index f0e8e8281e..cf1b658b45 100644 --- a/packager/media/formats/mp4/mp4_media_parser.cc +++ b/packager/media/formats/mp4/mp4_media_parser.cc @@ -327,7 +327,7 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) { RCHECK(desc_idx > 0); desc_idx -= 1; // BMFF descriptor index is one-based - if (track->media.handler.type == kAudio) { + if (samp_descr.type == kAudio) { RCHECK(!samp_descr.audio_entries.empty()); // It is not uncommon to find otherwise-valid files with incorrect sample @@ -428,7 +428,7 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) { duration, codec, AudioStreamInfo::GetCodecString(codec, audio_object_type), - track->media.header.language, + track->media.header.language.code, entry.samplesize, num_channels, sampling_frequency, @@ -439,7 +439,7 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) { is_encrypted)); } - if (track->media.handler.type == kVideo) { + if (samp_descr.type == kVideo) { RCHECK(!samp_descr.video_entries.empty()); if (desc_idx >= samp_descr.video_entries.size()) desc_idx = 0; @@ -527,8 +527,8 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) { DVLOG(1) << "is_video_track_encrypted_: " << is_encrypted; streams.push_back(new VideoStreamInfo( track->header.track_id, timescale, duration, video_codec, - codec_string, track->media.header.language, coded_width, coded_height, - pixel_width, pixel_height, + codec_string, track->media.header.language.code, coded_width, + coded_height, pixel_width, pixel_height, 0, // trick_play_rate nalu_length_size, vector_as_array(&entry.codec_config_record.data), entry.codec_config_record.data.size(), is_encrypted)); diff --git a/packager/media/formats/mp4/mp4_muxer.cc b/packager/media/formats/mp4/mp4_muxer.cc index 5aa11a0f21..52764393d2 100644 --- a/packager/media/formats/mp4/mp4_muxer.cc +++ b/packager/media/formats/mp4/mp4_muxer.cc @@ -187,13 +187,12 @@ void MP4Muxer::InitializeTrak(const StreamInfo* info, Track* trak) { trak->media.header.timescale = info->time_scale(); trak->media.header.duration = 0; if (!info->language().empty()) { - const size_t language_size = arraysize(trak->media.header.language) - 1; - if (info->language().size() != language_size) { + // ISO-639-2/T language code should be 3 characters.. + if (info->language().size() != 3) { LOG(WARNING) << "'" << info->language() << "' is not a valid ISO-639-2 " << "language code, ignoring."; } else { - memcpy(trak->media.header.language, info->language().c_str(), - language_size + 1); + trak->media.header.language.code = info->language(); } } } @@ -217,8 +216,6 @@ void MP4Muxer::GenerateVideoTrak(const VideoStreamInfo* video_info, trak->header.width = video_info->width() * sample_aspect_ratio * 0x10000; trak->header.height = video_info->height() * 0x10000; - trak->media.handler.type = kVideo; - VideoSampleEntry video; video.format = VideoCodecToFourCC(video_info->codec()); video.width = video_info->width(); @@ -241,7 +238,6 @@ void MP4Muxer::GenerateAudioTrak(const AudioStreamInfo* audio_info, InitializeTrak(audio_info, trak); trak->header.volume = 0x100; - trak->media.handler.type = kAudio; AudioSampleEntry audio; audio.format = AudioCodecToFourCC(audio_info->codec()); diff --git a/packager/media/formats/mp4/segmenter.cc b/packager/media/formats/mp4/segmenter.cc index 3ccd6320c5..6008c6cd16 100644 --- a/packager/media/formats/mp4/segmenter.cc +++ b/packager/media/formats/mp4/segmenter.cc @@ -238,6 +238,13 @@ Status Segmenter::Initialize(const std::vector& streams, // Use the reference stream's time scale as movie time scale. moov_->header.timescale = sidx_->timescale; moof_->header.sequence_number = 1; + + // Fill in version information. + moov_->metadata.handler.handler_type = FOURCC_ID32; + moov_->metadata.id3v2.language.code = "eng"; + moov_->metadata.id3v2.private_frame.owner = + "https://github.com/google/edash-packager"; + moov_->metadata.id3v2.private_frame.value = options_.packager_version_string; return DoInitialize(); } diff --git a/packager/media/formats/mp4/segmenter.h b/packager/media/formats/mp4/segmenter.h index 2bdb889050..db30db4aa2 100644 --- a/packager/media/formats/mp4/segmenter.h +++ b/packager/media/formats/mp4/segmenter.h @@ -13,6 +13,7 @@ #include "packager/base/memory/ref_counted.h" #include "packager/base/memory/scoped_ptr.h" #include "packager/media/base/status.h" +#include "packager/media/formats/mp4/box_definitions.h" namespace edash_packager { namespace media { @@ -30,11 +31,6 @@ namespace mp4 { class Fragmenter; -struct FileType; -struct Movie; -struct MovieFragment; -struct SegmentIndex; - /// This class defines the Segmenter which is responsible for organizing /// fragments into segments/subsegments and package them into a MP4 file. /// Inherited by MultiSegmentSegmenter and SingleSegmentSegmenter. diff --git a/packager/mpd/base/mpd_builder.cc b/packager/mpd/base/mpd_builder.cc index e1befa5bd3..6a25ed9707 100644 --- a/packager/mpd/base/mpd_builder.cc +++ b/packager/mpd/base/mpd_builder.cc @@ -494,7 +494,13 @@ xmlDocPtr MpdBuilder::GenerateMpd() { } DCHECK(doc); - xmlDocSetRootElement(doc.get(), mpd.Release()); + std::string version_string = + "Generated with https://github.com/google/edash-packager version " + + mpd_options_.packager_version_string; + xml::scoped_xml_ptr comment( + xmlNewDocComment(doc.get(), BAD_CAST version_string.c_str())); + xmlDocSetRootElement(doc.get(), comment.get()); + xmlAddSibling(comment.release(), mpd.Release()); return doc.release(); } diff --git a/packager/mpd/base/mpd_builder_unittest.cc b/packager/mpd/base/mpd_builder_unittest.cc index 19316c22e7..f4ca90a6bf 100644 --- a/packager/mpd/base/mpd_builder_unittest.cc +++ b/packager/mpd/base/mpd_builder_unittest.cc @@ -174,6 +174,8 @@ class DynamicMpdBuilderTest : public MpdBuilderTest { // current time. void SetUp() override { mpd_.availability_start_time_ = "2011-12-25T12:30:00"; + // Override packager version string for testing. + mpd_.mpd_options_.packager_version_string = "--"; } MpdOptions* mutable_mpd_options() { return &mpd_.mpd_options_; } @@ -1776,6 +1778,8 @@ TEST_F(StaticMpdBuilderTest, Text) { TEST_F(DynamicMpdBuilderTest, CheckMpdAttributes) { static const char kExpectedOutput[] = "\n" + "\n" " + +#include "packager/version/version.h" + namespace edash_packager { /// Defines Mpd Options. @@ -17,7 +21,8 @@ struct MpdOptions { // TODO(tinskip): Set min_buffer_time in unit tests rather than here. min_buffer_time(2.0), time_shift_buffer_depth(0), - suggested_presentation_delay(0) {} + suggested_presentation_delay(0), + packager_version_string(kPackagerVersion) {} ~MpdOptions() {}; @@ -26,6 +31,7 @@ struct MpdOptions { double min_buffer_time; double time_shift_buffer_depth; double suggested_presentation_delay; + std::string packager_version_string; }; } // namespace edash_packager diff --git a/packager/mpd/mpd.gyp b/packager/mpd/mpd.gyp index 484c89a737..cac66fef63 100644 --- a/packager/mpd/mpd.gyp +++ b/packager/mpd/mpd.gyp @@ -60,6 +60,7 @@ '../base/base.gyp:base', '../media/file/file.gyp:file', '../third_party/libxml/libxml.gyp:libxml', + '../version/version.gyp:version', 'media_info_proto', ], 'export_dependent_settings': [ diff --git a/packager/version/generate_version_string.py b/packager/version/generate_version_string.py new file mode 100755 index 0000000000..028b3a6af1 --- /dev/null +++ b/packager/version/generate_version_string.py @@ -0,0 +1,40 @@ +#!/usr/bin/python +# +# Copyright 2015 Google Inc. All rights reserved. +# +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file or at +# https://developers.google.com/open-source/licenses/bsd +"""This script is used to generate version string for packager.""" + +import subprocess + +# To support python version before 2.7, which does not have +# subprocess.check_output. +if 'check_output' not in dir(subprocess): + + def check_output_implementation(*popenargs, **kwargs): + """Implement check_output if it is not available.""" + if 'stdout' in kwargs: + raise ValueError('stdout argument not allowed, it will be overridden.') + process = subprocess.Popen(stdout=subprocess.PIPE, *popenargs, **kwargs) + output, unused_err = process.communicate() + retcode = process.poll() + if retcode: + cmd = kwargs.get('args') + if cmd is None: + cmd = popenargs[0] + raise subprocess.CalledProcessError(retcode, cmd) + return output + + subprocess.check_output = check_output_implementation + +if __name__ == '__main__': + version_tag = subprocess.check_output(['git', 'tag', '--points-at', 'HEAD' + ]).rstrip() + version_hash = subprocess.check_output(['git', 'rev-parse', '--short', 'HEAD' + ]).rstrip() + if version_tag: + print '{0}-{1}'.format(version_tag, version_hash) + else: + print version_hash diff --git a/packager/version/version.cc b/packager/version/version.cc new file mode 100644 index 0000000000..a39833a311 --- /dev/null +++ b/packager/version/version.cc @@ -0,0 +1,23 @@ +// Copyright 2015 Google Inc. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +#include "packager/version/version.h" + +namespace edash_packager { + +#if defined(PACKAGER_VERSION) +// PACKAGER_VERSION is generated in gyp file using script +// generate_version_string.py. +#if defined(NDEBUG) +const char kPackagerVersion[] = PACKAGER_VERSION "-release"; +#else +const char kPackagerVersion[] = PACKAGER_VERSION "-debug"; +#endif // #if defined(NDEBUG) +#else +const char kPackagerVersion[] = ""; +#endif // #if defined(PACKAGER_VERSION) + +} // namespace edash_packager diff --git a/packager/version/version.gyp b/packager/version/version.gyp new file mode 100644 index 0000000000..cedc703f32 --- /dev/null +++ b/packager/version/version.gyp @@ -0,0 +1,24 @@ +# Copyright 2015 Google Inc. All rights reserved. +# +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file or at +# https://developers.google.com/open-source/licenses/bsd + +{ + 'includes': [ + '../common.gypi', + ], + 'targets': [ + { + 'target_name': 'version', + 'type': '<(component)', + 'defines': [ + 'PACKAGER_VERSION="