Add version information in generated outputs

For mp4 outputs, a metadata box with version information is added to
moov box.

For mpd outputs, a text comment with version information is added in
the beginning of mpd file.

Issue #60

Change-Id: I783ba370781c0a8f77c910ff1172bad2e7edff75
This commit is contained in:
KongQun Yang 2015-12-21 15:10:17 -08:00
parent 9466582d9a
commit e0040a4910
51 changed files with 681 additions and 268 deletions

View File

@ -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])

View File

@ -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;
}

View File

@ -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] <stream_descriptor> ...\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 "
" - input (in): Required input/source media file path or network stream\n"
" 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 "
" - 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 "
"naming pattern for the segment files, and that the stream should be "
"split into multiple files. Its presence should be consistent across "
" - 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 "
"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 "
" - 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())

View File

@ -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;
}

View File

@ -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 = []

View File

@ -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<tag>[\w\.]+)-)?(?P<hash>[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',
'<tag>-<hash>-<test>']
return flags
def _CompareWithGold(self, test_output, golden_file_name):

View File

@ -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

View File

@ -1,28 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--Generated with https://github.com/google/edash-packager version <tag>-<hash>-<test>-->
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xlink="http://www.w3.org/1999/xlink" xsi:schemaLocation="urn:mpeg:dash:schema:mpd:2011 DASH-MPD.xsd" xmlns:cenc="urn:mpeg:cenc:2013" minBufferTime="PT2S" type="static" profiles="urn:mpeg:dash:profile:isoff-on-demand:2011" mediaPresentationDuration="PT2.763174533843994S">
<Period>
<AdaptationSet id="0" contentType="video" width="640" height="360" frameRate="30000/1001" subsegmentAlignment="true" par="16:9">
<Representation id="0" bandwidth="885151" codecs="avc1.64001e" mimeType="video/mp4" sar="1:1" width="640" height="360" frameRate="30000/1001">
<Representation id="0" bandwidth="885555" codecs="avc1.64001e" mimeType="video/mp4" sar="1:1" width="640" height="360" frameRate="30000/1001">
<ContentProtection value="cenc" schemeIdUri="urn:mpeg:dash:mp4protection:2011" cenc:default_KID="31323334-3536-3738-3930-313233343536"/>
<ContentProtection schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed">
<cenc:pssh>AAAAMHBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAABAxMjM0NTY3ODkwMTIzNDU2</cenc:pssh>
</ContentProtection>
<BaseURL>output_video.mp4</BaseURL>
<SegmentBase indexRange="941-1008" timescale="30000">
<Initialization range="0-940"/>
<SegmentBase indexRange="1079-1146" timescale="30000">
<Initialization range="0-1078"/>
</SegmentBase>
</Representation>
</AdaptationSet>
<AdaptationSet id="1" contentType="audio" subsegmentAlignment="true">
<Representation id="1" bandwidth="128728" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
<Representation id="1" bandwidth="129127" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
<ContentProtection value="cenc" schemeIdUri="urn:mpeg:dash:mp4protection:2011" cenc:default_KID="31323334-3536-3738-3930-313233343536"/>
<ContentProtection schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed">
<cenc:pssh>AAAAMHBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAABAxMjM0NTY3ODkwMTIzNDU2</cenc:pssh>
</ContentProtection>
<BaseURL>output_audio.mp4</BaseURL>
<SegmentBase indexRange="817-884" timescale="44100">
<Initialization range="0-816"/>
<SegmentBase indexRange="955-1022" timescale="44100">
<Initialization range="0-954"/>
</SegmentBase>
</Representation>
</AdaptationSet>

View File

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--Generated with https://github.com/google/edash-packager version <tag>-<hash>-<test>-->
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xlink="http://www.w3.org/1999/xlink" xsi:schemaLocation="urn:mpeg:dash:schema:mpd:2011 DASH-MPD.xsd" xmlns:cenc="urn:mpeg:cenc:2013" minBufferTime="PT2S" type="static" profiles="urn:mpeg:dash:profile:isoff-on-demand:2011" mediaPresentationDuration="PT2.763174533843994S">
<Period>
<AdaptationSet id="0" contentType="video" width="640" height="360" frameRate="30000/1001" subsegmentAlignment="true" par="16:9">
@ -6,10 +7,10 @@
<ContentProtection schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed">
<cenc:pssh>AAAAMHBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAABAxMjM0NTY3ODkwMTIzNDU2</cenc:pssh>
</ContentProtection>
<Representation id="0" bandwidth="885151" codecs="avc1.64001e" mimeType="video/mp4" sar="1:1" width="640" height="360" frameRate="30000/1001">
<Representation id="0" bandwidth="885555" codecs="avc1.64001e" mimeType="video/mp4" sar="1:1" width="640" height="360" frameRate="30000/1001">
<BaseURL>output_video.mp4</BaseURL>
<SegmentBase indexRange="941-1008" timescale="30000">
<Initialization range="0-940"/>
<SegmentBase indexRange="1079-1146" timescale="30000">
<Initialization range="0-1078"/>
</SegmentBase>
</Representation>
</AdaptationSet>
@ -18,11 +19,11 @@
<ContentProtection schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed">
<cenc:pssh>AAAAMHBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAABAxMjM0NTY3ODkwMTIzNDU2</cenc:pssh>
</ContentProtection>
<Representation id="1" bandwidth="128728" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
<Representation id="1" bandwidth="129127" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
<BaseURL>output_audio.mp4</BaseURL>
<SegmentBase indexRange="817-884" timescale="44100">
<Initialization range="0-816"/>
<SegmentBase indexRange="955-1022" timescale="44100">
<Initialization range="0-954"/>
</SegmentBase>
</Representation>
</AdaptationSet>

View File

@ -1,20 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--Generated with https://github.com/google/edash-packager version <tag>-<hash>-<test>-->
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xlink="http://www.w3.org/1999/xlink" xsi:schemaLocation="urn:mpeg:dash:schema:mpd:2011 DASH-MPD.xsd" xmlns:cenc="urn:mpeg:cenc:2013" minBufferTime="PT2S" type="static" profiles="urn:mpeg:dash:profile:isoff-on-demand:2011" mediaPresentationDuration="PT2.763174533843994S">
<Period>
<AdaptationSet id="0" contentType="video" width="640" height="360" frameRate="30000/1001" subsegmentAlignment="true" par="16:9">
<Representation id="0" bandwidth="881637" codecs="avc1.64001e" mimeType="video/mp4" sar="1:1" width="640" height="360" frameRate="30000/1001">
<Representation id="0" bandwidth="882040" codecs="avc1.64001e" mimeType="video/mp4" sar="1:1" width="640" height="360" frameRate="30000/1001">
<BaseURL>output_video.mp4</BaseURL>
<SegmentBase indexRange="677-744" timescale="30000">
<Initialization range="0-676"/>
<SegmentBase indexRange="815-882" timescale="30000">
<Initialization range="0-814"/>
</SegmentBase>
</Representation>
</AdaptationSet>
<AdaptationSet id="1" contentType="audio" subsegmentAlignment="true">
<Representation id="1" bandwidth="126087" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
<Representation id="1" bandwidth="126487" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
<BaseURL>output_audio.mp4</BaseURL>
<SegmentBase indexRange="611-678" timescale="44100">
<Initialization range="0-610"/>
<SegmentBase indexRange="749-816" timescale="44100">
<Initialization range="0-748"/>
</SegmentBase>
</Representation>
</AdaptationSet>

View File

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--Generated with https://github.com/google/edash-packager version <tag>-<hash>-<test>-->
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xlink="http://www.w3.org/1999/xlink" xsi:schemaLocation="urn:mpeg:dash:schema:mpd:2011 DASH-MPD.xsd" xmlns:cenc="urn:mpeg:cenc:2013" minBufferTime="PT2S" type="dynamic" profiles="urn:mpeg:dash:profile:isoff-live:2011" availabilityStartTime="place_holder" minimumUpdatePeriod="PT5S" timeShiftBufferDepth="PT1800S">
<Period start="PT0S">
<AdaptationSet id="0" contentType="video" width="640" height="360" frameRate="30000/1001" segmentAlignment="true" par="16:9">

View File

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--Generated with https://github.com/google/edash-packager version <tag>-<hash>-<test>-->
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xlink="http://www.w3.org/1999/xlink" xsi:schemaLocation="urn:mpeg:dash:schema:mpd:2011 DASH-MPD.xsd" xmlns:cenc="urn:mpeg:cenc:2013" minBufferTime="PT2S" type="dynamic" profiles="urn:mpeg:dash:profile:isoff-live:2011" availabilityStartTime="place_holder" minimumUpdatePeriod="PT5S" timeShiftBufferDepth="PT1800S">
<Period start="PT0S">
<AdaptationSet id="0" contentType="video" width="640" height="360" frameRate="30000/1001" segmentAlignment="true" par="16:9">

View File

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--Generated with https://github.com/google/edash-packager version <tag>-<hash>-<test>-->
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xlink="http://www.w3.org/1999/xlink" xsi:schemaLocation="urn:mpeg:dash:schema:mpd:2011 DASH-MPD.xsd" xmlns:cenc="urn:mpeg:cenc:2013" minBufferTime="PT2S" type="dynamic" profiles="urn:mpeg:dash:profile:isoff-live:2011" availabilityStartTime="place_holder" minimumUpdatePeriod="PT5S" timeShiftBufferDepth="PT1800S">
<Period start="PT0S">
<AdaptationSet id="0" contentType="video" width="640" height="360" frameRate="30000/1001" segmentAlignment="true" par="16:9">

View File

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--Generated with https://github.com/google/edash-packager version <tag>-<hash>-<test>-->
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xlink="http://www.w3.org/1999/xlink" xsi:schemaLocation="urn:mpeg:dash:schema:mpd:2011 DASH-MPD.xsd" xmlns:cenc="urn:mpeg:cenc:2013" minBufferTime="PT2S" type="dynamic" profiles="urn:mpeg:dash:profile:isoff-live:2011" availabilityStartTime="place_holder" minimumUpdatePeriod="PT5S" timeShiftBufferDepth="PT1800S">
<Period start="PT0S">
<AdaptationSet id="0" contentType="video" width="640" height="360" frameRate="30000/1001" segmentAlignment="true" par="16:9">

View File

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--Generated with https://github.com/google/edash-packager version <tag>-<hash>-<test>-->
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xlink="http://www.w3.org/1999/xlink" xsi:schemaLocation="urn:mpeg:dash:schema:mpd:2011 DASH-MPD.xsd" xmlns:cenc="urn:mpeg:cenc:2013" minBufferTime="PT2S" type="dynamic" profiles="urn:mpeg:dash:profile:isoff-live:2011" availabilityStartTime="place_holder" minimumUpdatePeriod="PT5S" timeShiftBufferDepth="PT1800S">
<Period start="PT0S">
<AdaptationSet id="0" contentType="video" width="640" height="360" frameRate="30000/1001" segmentAlignment="true" par="16:9">

View File

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--Generated with https://github.com/google/edash-packager version <tag>-<hash>-<test>-->
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xlink="http://www.w3.org/1999/xlink" xsi:schemaLocation="urn:mpeg:dash:schema:mpd:2011 DASH-MPD.xsd" xmlns:cenc="urn:mpeg:cenc:2013" minBufferTime="PT2S" type="static" profiles="urn:mpeg:dash:profile:isoff-on-demand:2011" mediaPresentationDuration="PT2.763174533843994S">
<Period>
<AdaptationSet id="0" contentType="text">
@ -7,19 +8,19 @@
</Representation>
</AdaptationSet>
<AdaptationSet id="1" contentType="video" width="640" height="360" frameRate="30000/1001" subsegmentAlignment="true" par="16:9">
<Representation id="1" bandwidth="881637" codecs="avc1.64001e" mimeType="video/mp4" sar="1:1" width="640" height="360" frameRate="30000/1001">
<Representation id="1" bandwidth="882040" codecs="avc1.64001e" mimeType="video/mp4" sar="1:1" width="640" height="360" frameRate="30000/1001">
<BaseURL>output_video.mp4</BaseURL>
<SegmentBase indexRange="677-744" timescale="30000">
<Initialization range="0-676"/>
<SegmentBase indexRange="815-882" timescale="30000">
<Initialization range="0-814"/>
</SegmentBase>
</Representation>
</AdaptationSet>
<AdaptationSet id="2" contentType="audio" subsegmentAlignment="true">
<Representation id="2" bandwidth="126087" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
<Representation id="2" bandwidth="126487" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
<BaseURL>output_audio.mp4</BaseURL>
<SegmentBase indexRange="611-678" timescale="44100">
<Initialization range="0-610"/>
<SegmentBase indexRange="749-816" timescale="44100">
<Initialization range="0-748"/>
</SegmentBase>
</Representation>
</AdaptationSet>

View File

@ -1,15 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--Generated with https://github.com/google/edash-packager version <tag>-<hash>-<test>-->
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xlink="http://www.w3.org/1999/xlink" xsi:schemaLocation="urn:mpeg:dash:schema:mpd:2011 DASH-MPD.xsd" xmlns:cenc="urn:mpeg:cenc:2013" minBufferTime="PT2S" type="static" profiles="urn:mpeg:dash:profile:isoff-on-demand:2011" mediaPresentationDuration="PT2.7694332599639893S">
<Period>
<AdaptationSet id="0" contentType="video" width="640" height="360" frameRate="30000/1001" subsegmentAlignment="true" par="16:9">
<Representation id="0" bandwidth="264624" codecs="hev1.1.6.L63.80" mimeType="video/mp4" sar="1:1" width="640" height="360" frameRate="30000/1001">
<Representation id="0" bandwidth="265022" codecs="hev1.1.6.L63.80" mimeType="video/mp4" sar="1:1" width="640" height="360" frameRate="30000/1001">
<ContentProtection value="cenc" schemeIdUri="urn:mpeg:dash:mp4protection:2011" cenc:default_KID="31323334-3536-3738-3930-313233343536"/>
<ContentProtection schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed">
<cenc:pssh>AAAAMHBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAABAxMjM0NTY3ODkwMTIzNDU2</cenc:pssh>
</ContentProtection>
<BaseURL>output_video.mp4</BaseURL>
<SegmentBase indexRange="2721-2764" timescale="30000">
<Initialization range="0-2720"/>
<SegmentBase indexRange="2859-2902" timescale="30000">
<Initialization range="0-2858"/>
</SegmentBase>
</Representation>
</AdaptationSet>

View File

@ -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

View File

@ -1,11 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--Generated with https://github.com/google/edash-packager version <tag>-<hash>-<test>-->
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xlink="http://www.w3.org/1999/xlink" xsi:schemaLocation="urn:mpeg:dash:schema:mpd:2011 DASH-MPD.xsd" xmlns:cenc="urn:mpeg:cenc:2013" minBufferTime="PT2S" type="static" profiles="urn:mpeg:dash:profile:isoff-on-demand:2011" mediaPresentationDuration="PT2.7360665798187256S">
<Period>
<AdaptationSet id="0" contentType="video" width="640" height="360" frameRate="30000/1001" subsegmentAlignment="true" par="16:9">
<Representation id="0" bandwidth="881637" codecs="avc1.64001e" mimeType="video/mp4" sar="1:1" width="640" height="360" frameRate="30000/1001">
<Representation id="0" bandwidth="882040" codecs="avc1.64001e" mimeType="video/mp4" sar="1:1" width="640" height="360" frameRate="30000/1001">
<BaseURL>output_0.mp4</BaseURL>
<SegmentBase indexRange="677-744" timescale="30000">
<Initialization range="0-676"/>
<SegmentBase indexRange="815-882" timescale="30000">
<Initialization range="0-814"/>
</SegmentBase>
</Representation>
</AdaptationSet>

View File

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--Generated with https://github.com/google/edash-packager version <tag>-<hash>-<test>-->
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xlink="http://www.w3.org/1999/xlink" xsi:schemaLocation="urn:mpeg:dash:schema:mpd:2011 DASH-MPD.xsd" xmlns:cenc="urn:mpeg:cenc:2013" minBufferTime="PT2S" type="static" profiles="urn:mpeg:dash:profile:isoff-on-demand:2011" mediaPresentationDuration="PT0S">
<Period>
<AdaptationSet id="0" contentType="text">

View File

@ -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',
],
},
{

View File

@ -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

View File

@ -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

View File

@ -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<uint32_t>::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<uint8_t> handler_name;
if (!buffer->Reading()) {
if (type == kVideo) {
hdlr_type = FOURCC_VIDE;
switch (handler_type) {
case FOURCC_VIDE:
handler_name.assign(kVideoHandlerName,
kVideoHandlerName + arraysize(kVideoHandlerName));
} else if (type == kAudio) {
hdlr_type = FOURCC_SOUN;
break;
case FOURCC_SOUN:
handler_name.assign(kAudioHandlerName,
kAudioHandlerName + arraysize(kAudioHandlerName));
} else if (type == kText) {
hdlr_type = FOURCC_TEXT;
break;
case FOURCC_TEXT:
handler_name.assign(kTextHandlerName,
kTextHandlerName + arraysize(kTextHandlerName));
} else {
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) {
switch (handler_type) {
case FOURCC_VIDE:
box_size += sizeof(kVideoHandlerName);
} else if (type == kAudio) {
break;
case FOURCC_SOUN:
box_size += sizeof(kAudioHandlerName);
} else {
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<uint8_t> 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,42 +1553,16 @@ bool MediaHeader::ReadWriteInternal(BoxBuffer* buffer) {
RCHECK(buffer->ReadWriteUInt64NBytes(&creation_time, num_bytes) &&
buffer->ReadWriteUInt64NBytes(&modification_time, num_bytes) &&
buffer->ReadWriteUInt32(&timescale) &&
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<uint8_t> 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.
sizeof(uint32_t) * (1 + version) * 3 + language.ComputeSize() +
2; // 2 bytes predefined.
}
@ -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)

View File

@ -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<Track> tracks;
std::vector<ProtectionSystemSpecificHeader> pssh;

View File

@ -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,

View File

@ -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<uint64_t>(std::numeric_limits<uint32_t>::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<uint32_t>::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,8 +902,9 @@ class BoxDefinitionsTestGeneral : public testing::Test {
scoped_ptr<BufferWriter> buffer_;
};
typedef testing::Types<
FileType,
// GTEST support a maximum of 50 types in the template list, so we have to
// break it into two groups.
typedef testing::Types<FileType,
SegmentType,
ProtectionSystemSpecificHeader,
SampleAuxiliaryInformationOffset,
@ -897,10 +919,13 @@ typedef testing::Types<
EditList,
Edit,
HandlerReference,
ID3v2,
Metadata,
CodecConfigurationRecord,
PixelAspectRatio,
VideoSampleEntry,
ElementaryStreamDescriptor,
DTSSpecific,
AudioSampleEntry,
WebVTTConfigurationBox,
WebVTTSourceLabelBox,
@ -914,8 +939,9 @@ typedef testing::Types<
ChunkLargeOffset,
ChunkOffset,
SyncSample,
SampleTable,
MediaHeader,
SampleTable>
Boxes;
typedef testing::Types<MediaHeader,
VideoMediaHeader,
SoundMediaHeader,
SubtitleMediaHeader,
@ -931,11 +957,7 @@ typedef testing::Types<
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<
TrackFragmentHeader,
TrackFragmentRun,
TrackFragment,
MovieFragment,
@ -949,8 +971,8 @@ typedef testing::Types<
CuePayloadBox,
VTTEmptyCueBox,
VTTAdditionalTextBox,
VTTCueBox,
DTSSpecific> Boxes2;
VTTCueBox>
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<Box> {};
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;

View File

@ -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,

View File

@ -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));

View File

@ -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());

View File

@ -238,6 +238,13 @@ Status Segmenter::Initialize(const std::vector<MediaStream*>& 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();
}

View File

@ -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.

View File

@ -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<xmlNode> comment(
xmlNewDocComment(doc.get(), BAD_CAST version_string.c_str()));
xmlDocSetRootElement(doc.get(), comment.get());
xmlAddSibling(comment.release(), mpd.Release());
return doc.release();
}

View File

@ -174,6 +174,8 @@ class DynamicMpdBuilderTest : public MpdBuilderTest<MpdBuilder::kDynamic> {
// 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 = "<tag>-<hash>-<test>";
}
MpdOptions* mutable_mpd_options() { return &mpd_.mpd_options_; }
@ -1776,6 +1778,8 @@ TEST_F(StaticMpdBuilderTest, Text) {
TEST_F(DynamicMpdBuilderTest, CheckMpdAttributes) {
static const char kExpectedOutput[] =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<!--Generated with https://github.com/google/edash-packager "
"version <tag>-<hash>-<test>-->\n"
"<MPD xmlns=\"urn:mpeg:dash:schema:mpd:2011\" "
"xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
"xmlns:xlink=\"http://www.w3.org/1999/xlink\" "

View File

@ -7,6 +7,10 @@
#ifndef MPD_BASE_MPD_OPTIONS_H_
#define MPD_BASE_MPD_OPTIONS_H_
#include <string>
#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

View File

@ -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': [

View File

@ -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

View File

@ -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

View File

@ -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="<!(python generate_version_string.py)"',
],
'sources': [
'version.cc',
'version.h',
],
},
],
}

View File

@ -0,0 +1,11 @@
// 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
namespace edash_packager {
extern const char kPackagerVersion[];
} // namespace edash_packager