Add segment_list support for DASH on-demand profile
Configurable under flag --dash_force_segment_list, default to false. Note that DASH live profile is not supported right now.
This commit is contained in:
parent
fe9e26d5d9
commit
4686454a73
1
AUTHORS
1
AUTHORS
|
@ -16,6 +16,7 @@
|
|||
3Q GmbH <*@3qsdn.com>
|
||||
Alen Vrecko <alen.vrecko@gmail.com>
|
||||
Anders Hasselqvist <anders.hasselqvist@gmail.com>
|
||||
Audible <*@audible.com>
|
||||
Chun-da Chen <capitalm.c@gmail.com>
|
||||
Daniel Cantarín <canta@canta.com.ar>
|
||||
Dolby Laboratories <*@dolby.com>
|
||||
|
|
|
@ -43,6 +43,7 @@ Piotr Srebrny <srebrny.piotr@gmail.com>
|
|||
Qingquan Wang <wangqq1103@gmail.com>
|
||||
Richard Eklycke <richard@eklycke.se>
|
||||
Rintaro Kuroiwa <rkuroiwa@google.com>
|
||||
Samidh Talsania <talsania@audible.com>
|
||||
Sanil Raut <sr1990003@gmail.com>
|
||||
Sergio Ammirata <sergio@ammirata.net>
|
||||
Thomas Inskip <tinskip@google.com>
|
||||
|
|
|
@ -69,3 +69,9 @@ DEFINE_bool(include_mspr_pro_for_playready,
|
|||
"If enabled, PlayReady Object <mspr:pro> will be inserted into "
|
||||
"<ContentProtection ...> element alongside with <cenc:pssh> "
|
||||
"when using PlayReady protection system.");
|
||||
DEFINE_bool(dash_force_segment_list,
|
||||
false,
|
||||
"Uses SegmentList instead of SegmentBase. Use this if the "
|
||||
"content is huge and the total number of (sub)segment references "
|
||||
"is greater than what the sidx atom allows (65535). Currently "
|
||||
"this flag is only supported in DASH ondemand profile.");
|
||||
|
|
|
@ -23,5 +23,6 @@ DECLARE_bool(generate_dash_if_iop_compliant_mpd);
|
|||
DECLARE_bool(allow_approximate_segment_timeline);
|
||||
DECLARE_bool(allow_codec_switching);
|
||||
DECLARE_bool(include_mspr_pro_for_playready);
|
||||
DECLARE_bool(dash_force_segment_list);
|
||||
|
||||
#endif // APP_MPD_FLAGS_H_
|
||||
|
|
|
@ -451,6 +451,7 @@ base::Optional<PackagingParams> GetPackagingParams() {
|
|||
mpd_params.time_shift_buffer_depth = FLAGS_time_shift_buffer_depth;
|
||||
mpd_params.preserved_segments_outside_live_window =
|
||||
FLAGS_preserved_segments_outside_live_window;
|
||||
mpd_params.use_segment_list = FLAGS_dash_force_segment_list;
|
||||
|
||||
if (!FLAGS_utc_timings.empty()) {
|
||||
base::StringPairs pairs;
|
||||
|
|
|
@ -478,7 +478,8 @@ class PackagerAppTest(unittest.TestCase):
|
|||
default_language=None,
|
||||
segment_duration=1.0,
|
||||
use_fake_clock=True,
|
||||
allow_codec_switching=False):
|
||||
allow_codec_switching=False,
|
||||
dash_force_segment_list=False):
|
||||
flags = ['--single_threaded']
|
||||
|
||||
if not strip_parameter_set_nalus:
|
||||
|
@ -568,6 +569,10 @@ class PackagerAppTest(unittest.TestCase):
|
|||
if default_language:
|
||||
flags += ['--default_language', default_language]
|
||||
|
||||
if dash_force_segment_list:
|
||||
flags += ['--dash_force_segment_list']
|
||||
flags += ['--generate_sidx_in_media_segments=false']
|
||||
|
||||
flags.append('--segment_duration={0}'.format(segment_duration))
|
||||
|
||||
# Use fake clock, so output can be compared.
|
||||
|
@ -1518,7 +1523,7 @@ class PackagerFunctionalTest(PackagerAppTest):
|
|||
|
||||
def testEncryptionAndOutputMediaInfoAndMpdFromMediaInfo(self):
|
||||
self.assertPackageSuccess(
|
||||
# The order is not determinstic if there are more than one
|
||||
# The order is not deterministic if there are more than one
|
||||
# AdaptationSets, so only one is included here.
|
||||
self._GetStreams(['video']),
|
||||
self._GetFlags(encryption=True, output_media_info=True))
|
||||
|
@ -1526,6 +1531,15 @@ class PackagerFunctionalTest(PackagerAppTest):
|
|||
self._CheckTestResults(
|
||||
'encryption-and-output-media-info-and-mpd-from-media-info')
|
||||
|
||||
def testEncryptionAndOutputMediaInfoAndMpdFromMediaInfoSegmentList(self):
|
||||
self.assertPackageSuccess(
|
||||
# The order is not deterministic if there are more than one
|
||||
# AdaptationSets, so only one is included here.
|
||||
self._GetStreams(['audio']),
|
||||
self._GetFlags(encryption=True, output_media_info=True, dash_force_segment_list=True, output_dash=True))
|
||||
self._CheckTestResults(
|
||||
'encryption-and-output-media-info-and-mpd-from-media-info-segmentlist')
|
||||
|
||||
def testHlsSingleSegmentMp4Encrypted(self):
|
||||
self.assertPackageSuccess(
|
||||
self._GetStreams(['audio', 'video'], hls=True),
|
||||
|
|
Binary file not shown.
|
@ -0,0 +1,40 @@
|
|||
bandwidth: 133663
|
||||
audio_info {
|
||||
codec: "mp4a.40.2"
|
||||
sampling_frequency: 44100
|
||||
time_scale: 44100
|
||||
num_channels: 2
|
||||
decoder_config: "\022\020V\345\000"
|
||||
}
|
||||
init_range {
|
||||
begin: 0
|
||||
end: 1006
|
||||
}
|
||||
index_range {
|
||||
begin: 1007
|
||||
end: 1006
|
||||
}
|
||||
media_file_name: "bear-640x360-audio.mp4"
|
||||
media_duration_seconds: 2.76317453
|
||||
reference_time_scale: 44100
|
||||
container_type: CONTAINER_MP4
|
||||
protected_content {
|
||||
default_key_id: "1234567890123456"
|
||||
content_protection_entry {
|
||||
uuid: "1077efec-c0b2-4d02-ace3-3c1e52e2fb4b"
|
||||
pssh: "\000\000\0004pssh\001\000\000\000\020w\357\354\300\262M\002\254\343<\036R\342\373K\000\000\000\0011234567890123456\000\000\000\000"
|
||||
}
|
||||
protection_scheme: "cenc"
|
||||
}
|
||||
subsegment_ranges {
|
||||
begin: 1007
|
||||
end: 18034
|
||||
}
|
||||
subsegment_ranges {
|
||||
begin: 18035
|
||||
end: 34716
|
||||
}
|
||||
subsegment_ranges {
|
||||
begin: 34717
|
||||
end: 44575
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--Generated with https://github.com/google/shaka-packager version <tag>-<hash>-<test>-->
|
||||
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:mpeg:dash:schema:mpd:2011 DASH-MPD.xsd" xmlns:cenc="urn:mpeg:cenc:2013" profiles="urn:mpeg:dash:profile:isoff-on-demand:2011" minBufferTime="PT2S" type="static" mediaPresentationDuration="PT2.739954710006714S">
|
||||
<Period id="0">
|
||||
<AdaptationSet id="0" contentType="audio" subsegmentAlignment="true">
|
||||
<ContentProtection value="cenc" schemeIdUri="urn:mpeg:dash:mp4protection:2011" cenc:default_KID="31323334-3536-3738-3930-313233343536"/>
|
||||
<ContentProtection schemeIdUri="urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b">
|
||||
<cenc:pssh>AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA==</cenc:pssh>
|
||||
</ContentProtection>
|
||||
<Representation id="0" bandwidth="133663" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
|
||||
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
|
||||
<BaseURL>bear-640x360-audio.mp4</BaseURL>
|
||||
<SegmentList timescale="44100" duration="44100">
|
||||
<Initialization range="0-1006"/>
|
||||
<SegmentURL mediaRange="1007-18034"/>
|
||||
<SegmentURL mediaRange="18035-34716"/>
|
||||
<SegmentURL mediaRange="34717-44575"/>
|
||||
</SegmentList>
|
||||
</Representation>
|
||||
</AdaptationSet>
|
||||
</Period>
|
||||
</MPD>
|
|
@ -142,6 +142,7 @@ void MpdNotifyMuxerListener::OnMediaEnd(const MediaRanges& media_ranges,
|
|||
|
||||
DCHECK(media_info_);
|
||||
if (!internal::SetVodInformation(media_ranges, duration_seconds,
|
||||
mpd_notifier_->use_segment_list(),
|
||||
media_info_.get())) {
|
||||
LOG(ERROR) << "Failed to generate VOD information from input.";
|
||||
return;
|
||||
|
|
|
@ -68,6 +68,19 @@ class MpdNotifyMuxerListenerTest : public ::testing::TestWithParam<MpdType> {
|
|||
mpd_options.dash_profile = DashProfile::kOnDemand;
|
||||
// On-demand profile should be static.
|
||||
mpd_options.mpd_type = MpdType::kStatic;
|
||||
mpd_options.mpd_params.use_segment_list = false;
|
||||
notifier_.reset(new MockMpdNotifier(mpd_options));
|
||||
listener_.reset(
|
||||
new MpdNotifyMuxerListener(notifier_.get()));
|
||||
}
|
||||
|
||||
|
||||
void SetupForVodSegmentList() {
|
||||
MpdOptions mpd_options;
|
||||
mpd_options.dash_profile = DashProfile::kOnDemand;
|
||||
// On-demand profile should be static.
|
||||
mpd_options.mpd_type = MpdType::kStatic;
|
||||
mpd_options.mpd_params.use_segment_list = true;
|
||||
notifier_.reset(new MockMpdNotifier(mpd_options));
|
||||
listener_.reset(
|
||||
new MpdNotifyMuxerListener(notifier_.get()));
|
||||
|
@ -78,6 +91,7 @@ class MpdNotifyMuxerListenerTest : public ::testing::TestWithParam<MpdType> {
|
|||
mpd_options.dash_profile = DashProfile::kLive;
|
||||
// Live profile can be static or dynamic.
|
||||
mpd_options.mpd_type = GetParam();
|
||||
mpd_options.mpd_params.use_segment_list = false;
|
||||
notifier_.reset(new MockMpdNotifier(mpd_options));
|
||||
listener_.reset(new MpdNotifyMuxerListener(notifier_.get()));
|
||||
}
|
||||
|
@ -123,6 +137,28 @@ TEST_F(MpdNotifyMuxerListenerTest, VodClearContent) {
|
|||
FireOnMediaEndWithParams(GetDefaultOnMediaEndParams());
|
||||
}
|
||||
|
||||
|
||||
TEST_F(MpdNotifyMuxerListenerTest, VodClearContentSegmentList) {
|
||||
SetupForVodSegmentList();
|
||||
MuxerOptions muxer_options;
|
||||
SetDefaultMuxerOptions(&muxer_options);
|
||||
VideoStreamInfoParameters video_params = GetDefaultVideoStreamInfoParams();
|
||||
std::shared_ptr<StreamInfo> video_stream_info =
|
||||
CreateVideoStreamInfo(video_params);
|
||||
|
||||
EXPECT_CALL(*notifier_, NotifyNewContainer(_, _)).Times(0);
|
||||
listener_->OnMediaStart(muxer_options, *video_stream_info,
|
||||
kDefaultReferenceTimeScale,
|
||||
MuxerListener::kContainerMp4);
|
||||
::testing::Mock::VerifyAndClearExpectations(notifier_.get());
|
||||
|
||||
EXPECT_CALL(*notifier_, NotifyNewContainer(
|
||||
ExpectMediaInfoEq(kExpectedDefaultMediaInfoSubsegmentRange), _))
|
||||
.WillOnce(Return(true));
|
||||
EXPECT_CALL(*notifier_, Flush());
|
||||
FireOnMediaEndWithParams(GetDefaultOnMediaEndParams());
|
||||
}
|
||||
|
||||
// default_key_id and pssh are converted to string because when std::equal
|
||||
// compares a negative char and uint8_t > 127, it considers them not equal.
|
||||
MATCHER_P4(ProtectedContentEq, uuid, name, default_key_id, pssh, "") {
|
||||
|
@ -190,6 +226,49 @@ TEST_F(MpdNotifyMuxerListenerTest, VodEncryptedContent) {
|
|||
FireOnMediaEndWithParams(GetDefaultOnMediaEndParams());
|
||||
}
|
||||
|
||||
|
||||
TEST_F(MpdNotifyMuxerListenerTest, VodEncryptedContentSegmentList) {
|
||||
SetupForVodSegmentList();
|
||||
MuxerOptions muxer_options;
|
||||
SetDefaultMuxerOptions(&muxer_options);
|
||||
VideoStreamInfoParameters video_params = GetDefaultVideoStreamInfoParams();
|
||||
std::shared_ptr<StreamInfo> video_stream_info =
|
||||
CreateVideoStreamInfo(video_params);
|
||||
|
||||
const std::vector<uint8_t> default_key_id(
|
||||
kDefaultKeyId, kDefaultKeyId + arraysize(kDefaultKeyId) - 1);
|
||||
|
||||
const std::string kExpectedMediaInfo =
|
||||
std::string(kExpectedDefaultMediaInfoSubsegmentRange) +
|
||||
"protected_content {\n"
|
||||
" protection_scheme: 'cenc'\n"
|
||||
" content_protection_entry {\n"
|
||||
" uuid: '00010203-0405-0607-0809-0a0b0c0d0e0f'\n"
|
||||
" pssh: '" + std::string(kExpectedDefaultPsshBox) + "'\n"
|
||||
" }\n"
|
||||
" default_key_id: 'defaultkeyid'\n"
|
||||
" include_mspr_pro: 1\n"
|
||||
"}\n";
|
||||
|
||||
EXPECT_CALL(*notifier_, NotifyNewContainer(_, _)).Times(0);
|
||||
|
||||
std::vector<uint8_t> iv(kBogusIv, kBogusIv + arraysize(kBogusIv));
|
||||
listener_->OnEncryptionInfoReady(kInitialEncryptionInfo, FOURCC_cenc,
|
||||
default_key_id, iv,
|
||||
GetDefaultKeySystemInfo());
|
||||
|
||||
listener_->OnMediaStart(muxer_options, *video_stream_info,
|
||||
kDefaultReferenceTimeScale,
|
||||
MuxerListener::kContainerMp4);
|
||||
::testing::Mock::VerifyAndClearExpectations(notifier_.get());
|
||||
|
||||
EXPECT_CALL(*notifier_,
|
||||
NotifyNewContainer(ExpectMediaInfoEq(kExpectedMediaInfo), _))
|
||||
.WillOnce(Return(true));
|
||||
EXPECT_CALL(*notifier_, Flush());
|
||||
FireOnMediaEndWithParams(GetDefaultOnMediaEndParams());
|
||||
}
|
||||
|
||||
// Verify that calling OnSampleDurationReady() sets the frame duration in the
|
||||
// media info, and the media info gets passed to NotifyNewContainer() with
|
||||
// frame_duration == sample_duration.
|
||||
|
@ -223,6 +302,57 @@ TEST_F(MpdNotifyMuxerListenerTest, VodOnSampleDurationReady) {
|
|||
"container_type: 1\n"
|
||||
"media_file_name: 'test_output_file_name.mp4'\n"
|
||||
"media_duration_seconds: 10.5\n";
|
||||
|
||||
const uint32_t kReferenceTimeScale = 1111u; // Should match the protobuf.
|
||||
|
||||
EXPECT_CALL(*notifier_, NotifyNewContainer(_, _)).Times(0);
|
||||
listener_->OnMediaStart(muxer_options, *video_stream_info,
|
||||
kReferenceTimeScale, MuxerListener::kContainerMp4);
|
||||
listener_->OnSampleDurationReady(kSampleDuration);
|
||||
::testing::Mock::VerifyAndClearExpectations(notifier_.get());
|
||||
|
||||
EXPECT_CALL(*notifier_,
|
||||
NotifyNewContainer(ExpectMediaInfoEq(kExpectedMediaInfo), _))
|
||||
.WillOnce(Return(true));
|
||||
EXPECT_CALL(*notifier_, Flush());
|
||||
FireOnMediaEndWithParams(GetDefaultOnMediaEndParams());
|
||||
}
|
||||
|
||||
|
||||
TEST_F(MpdNotifyMuxerListenerTest, VodOnSampleDurationReadySegmentList) {
|
||||
SetupForVodSegmentList();
|
||||
MuxerOptions muxer_options;
|
||||
SetDefaultMuxerOptions(&muxer_options);
|
||||
VideoStreamInfoParameters video_params = GetDefaultVideoStreamInfoParams();
|
||||
std::shared_ptr<StreamInfo> video_stream_info =
|
||||
CreateVideoStreamInfo(video_params);
|
||||
const uint32_t kSampleDuration = 1234u;
|
||||
const char kExpectedMediaInfo[] =
|
||||
"video_info {\n"
|
||||
" frame_duration: 1234\n" // Should match the constant above.
|
||||
" codec: 'avc1.010101'\n"
|
||||
" width: 720\n"
|
||||
" height: 480\n"
|
||||
" time_scale: 10\n"
|
||||
" pixel_width: 1\n"
|
||||
" pixel_height: 1\n"
|
||||
"}\n"
|
||||
"init_range {\n"
|
||||
" begin: 0\n"
|
||||
" end: 120\n"
|
||||
"}\n"
|
||||
"index_range {\n"
|
||||
" begin: 121\n"
|
||||
" end: 221\n"
|
||||
"}\n"
|
||||
"reference_time_scale: 1111\n"
|
||||
"container_type: 1\n"
|
||||
"media_file_name: 'test_output_file_name.mp4'\n"
|
||||
"media_duration_seconds: 10.5\n"
|
||||
"subsegment_ranges {\n"
|
||||
" begin: 222\n"
|
||||
" end: 9999\n"
|
||||
"}\n";
|
||||
const uint32_t kReferenceTimeScale = 1111u; // Should match the protobuf.
|
||||
|
||||
EXPECT_CALL(*notifier_, NotifyNewContainer(_, _)).Times(0);
|
||||
|
@ -278,6 +408,44 @@ TEST_F(MpdNotifyMuxerListenerTest, VodOnNewSegment) {
|
|||
FireOnMediaEndWithParams(GetDefaultOnMediaEndParams());
|
||||
}
|
||||
|
||||
TEST_F(MpdNotifyMuxerListenerTest, VodOnNewSegmentSegmentList) {
|
||||
SetupForVodSegmentList();
|
||||
MuxerOptions muxer_options;
|
||||
SetDefaultMuxerOptions(&muxer_options);
|
||||
VideoStreamInfoParameters video_params = GetDefaultVideoStreamInfoParams();
|
||||
std::shared_ptr<StreamInfo> video_stream_info =
|
||||
CreateVideoStreamInfo(video_params);
|
||||
|
||||
const uint64_t kStartTime1 = 0u;
|
||||
const uint64_t kDuration1 = 1000u;
|
||||
const uint64_t kSegmentFileSize1 = 29812u;
|
||||
const uint64_t kStartTime2 = 1001u;
|
||||
const uint64_t kDuration2 = 3787u;
|
||||
const uint64_t kSegmentFileSize2 = 83743u;
|
||||
|
||||
EXPECT_CALL(*notifier_, NotifyNewContainer(_, _)).Times(0);
|
||||
EXPECT_CALL(*notifier_, NotifyNewSegment(_, _, _, _)).Times(0);
|
||||
listener_->OnMediaStart(muxer_options, *video_stream_info,
|
||||
kDefaultReferenceTimeScale,
|
||||
MuxerListener::kContainerMp4);
|
||||
listener_->OnNewSegment("", kStartTime1, kDuration1, kSegmentFileSize1);
|
||||
listener_->OnCueEvent(kStartTime2, "dummy cue data");
|
||||
listener_->OnNewSegment("", kStartTime2, kDuration2, kSegmentFileSize2);
|
||||
::testing::Mock::VerifyAndClearExpectations(notifier_.get());
|
||||
|
||||
InSequence s;
|
||||
EXPECT_CALL(*notifier_, NotifyNewContainer(
|
||||
ExpectMediaInfoEq(kExpectedDefaultMediaInfoSubsegmentRange), _))
|
||||
.WillOnce(Return(true));
|
||||
EXPECT_CALL(*notifier_,
|
||||
NotifyNewSegment(_, kStartTime1, kDuration1, kSegmentFileSize1));
|
||||
EXPECT_CALL(*notifier_, NotifyCueEvent(_, kStartTime2));
|
||||
EXPECT_CALL(*notifier_,
|
||||
NotifyNewSegment(_, kStartTime2, kDuration2, kSegmentFileSize2));
|
||||
EXPECT_CALL(*notifier_, Flush());
|
||||
FireOnMediaEndWithParams(GetDefaultOnMediaEndParams());
|
||||
}
|
||||
|
||||
// Verify the event handling with multiple files, i.e. multiple OnMediaStart and
|
||||
// OnMediaEnd calls.
|
||||
TEST_F(MpdNotifyMuxerListenerTest, VodMultipleFiles) {
|
||||
|
@ -341,6 +509,68 @@ TEST_F(MpdNotifyMuxerListenerTest, VodMultipleFiles) {
|
|||
FireOnMediaEndWithParams(GetDefaultOnMediaEndParams());
|
||||
}
|
||||
|
||||
|
||||
TEST_F(MpdNotifyMuxerListenerTest, VodMultipleFilesSegmentList) {
|
||||
SetupForVodSegmentList();
|
||||
MuxerOptions muxer_options1;
|
||||
SetDefaultMuxerOptions(&muxer_options1);
|
||||
muxer_options1.output_file_name = "test_output1.mp4";
|
||||
MuxerOptions muxer_options2 = muxer_options1;
|
||||
muxer_options2.output_file_name = "test_output2.mp4";
|
||||
|
||||
MediaInfo expected_media_info1 =
|
||||
ConvertToMediaInfo(kExpectedDefaultMediaInfoSubsegmentRange);
|
||||
expected_media_info1.set_media_file_name("test_output1.mp4");
|
||||
MediaInfo expected_media_info2 = expected_media_info1;
|
||||
expected_media_info2.set_media_file_name("test_output2.mp4");
|
||||
|
||||
VideoStreamInfoParameters video_params = GetDefaultVideoStreamInfoParams();
|
||||
std::shared_ptr<StreamInfo> video_stream_info =
|
||||
CreateVideoStreamInfo(video_params);
|
||||
|
||||
const uint64_t kStartTime1 = 0u;
|
||||
const uint64_t kDuration1 = 1000u;
|
||||
const uint64_t kSegmentFileSize1 = 29812u;
|
||||
const uint64_t kStartTime2 = 1001u;
|
||||
const uint64_t kDuration2 = 3787u;
|
||||
const uint64_t kSegmentFileSize2 = 83743u;
|
||||
|
||||
// Expectation for first file before OnMediaEnd.
|
||||
EXPECT_CALL(*notifier_, NotifyNewContainer(_, _)).Times(0);
|
||||
EXPECT_CALL(*notifier_, NotifyNewSegment(_, _, _, _)).Times(0);
|
||||
listener_->OnMediaStart(muxer_options1, *video_stream_info,
|
||||
kDefaultReferenceTimeScale,
|
||||
MuxerListener::kContainerMp4);
|
||||
listener_->OnNewSegment("", kStartTime1, kDuration1, kSegmentFileSize1);
|
||||
listener_->OnCueEvent(kStartTime2, "dummy cue data");
|
||||
::testing::Mock::VerifyAndClearExpectations(notifier_.get());
|
||||
|
||||
// Expectation for first file OnMediaEnd.
|
||||
InSequence s;
|
||||
EXPECT_CALL(*notifier_,
|
||||
NotifyNewContainer(EqualsProto(expected_media_info1), _))
|
||||
.WillOnce(Return(true));
|
||||
EXPECT_CALL(*notifier_,
|
||||
NotifyNewSegment(_, kStartTime1, kDuration1, kSegmentFileSize1));
|
||||
EXPECT_CALL(*notifier_, NotifyCueEvent(_, kStartTime2));
|
||||
EXPECT_CALL(*notifier_, Flush());
|
||||
FireOnMediaEndWithParams(GetDefaultOnMediaEndParams());
|
||||
|
||||
// Expectation for second file before OnMediaEnd.
|
||||
listener_->OnMediaStart(muxer_options2, *video_stream_info,
|
||||
kDefaultReferenceTimeScale,
|
||||
MuxerListener::kContainerMp4);
|
||||
listener_->OnNewSegment("", kStartTime2, kDuration2, kSegmentFileSize2);
|
||||
|
||||
// Expectation for second file OnMediaEnd.
|
||||
EXPECT_CALL(*notifier_,
|
||||
NotifyMediaInfoUpdate(_, EqualsProto(expected_media_info2)));
|
||||
EXPECT_CALL(*notifier_,
|
||||
NotifyNewSegment(_, kStartTime2, kDuration2, kSegmentFileSize2));
|
||||
EXPECT_CALL(*notifier_, Flush());
|
||||
FireOnMediaEndWithParams(GetDefaultOnMediaEndParams());
|
||||
}
|
||||
|
||||
// Live without key rotation. Note that OnEncryptionInfoReady() is called before
|
||||
// OnMediaStart() but no more calls.
|
||||
TEST_P(MpdNotifyMuxerListenerTest, LiveNoKeyRotation) {
|
||||
|
|
|
@ -25,11 +25,12 @@ namespace {
|
|||
const char kMediaInfoSuffix[] = ".media_info";
|
||||
|
||||
std::unique_ptr<MuxerListener> CreateMediaInfoDumpListenerInternal(
|
||||
const std::string& output) {
|
||||
const std::string& output,
|
||||
bool use_segment_list) {
|
||||
DCHECK(!output.empty());
|
||||
|
||||
std::unique_ptr<MuxerListener> listener(
|
||||
new VodMediaInfoDumpMuxerListener(output + kMediaInfoSuffix));
|
||||
new VodMediaInfoDumpMuxerListener(output + kMediaInfoSuffix, use_segment_list));
|
||||
return listener;
|
||||
}
|
||||
|
||||
|
@ -80,11 +81,13 @@ std::list<std::unique_ptr<MuxerListener>> CreateHlsListenersInternal(
|
|||
} // namespace
|
||||
|
||||
MuxerListenerFactory::MuxerListenerFactory(bool output_media_info,
|
||||
bool use_segment_list,
|
||||
MpdNotifier* mpd_notifier,
|
||||
hls::HlsNotifier* hls_notifier)
|
||||
: output_media_info_(output_media_info),
|
||||
mpd_notifier_(mpd_notifier),
|
||||
hls_notifier_(hls_notifier) {}
|
||||
hls_notifier_(hls_notifier),
|
||||
use_segment_list_(use_segment_list) {}
|
||||
|
||||
std::unique_ptr<MuxerListener> MuxerListenerFactory::CreateListener(
|
||||
const StreamData& stream) {
|
||||
|
@ -103,7 +106,8 @@ std::unique_ptr<MuxerListener> MuxerListenerFactory::CreateListener(
|
|||
new CombinedMuxerListener);
|
||||
if (output_media_info_) {
|
||||
combined_listener->AddListener(
|
||||
CreateMediaInfoDumpListenerInternal(stream.media_info_output));
|
||||
CreateMediaInfoDumpListenerInternal(stream.media_info_output,
|
||||
use_segment_list_));
|
||||
}
|
||||
|
||||
if (mpd_notifier_ && !stream.hls_only) {
|
||||
|
|
|
@ -58,11 +58,15 @@ class MuxerListenerFactory {
|
|||
/// Create a new muxer listener.
|
||||
/// @param output_media_info must be true for the combined listener to include
|
||||
/// a media info dump listener.
|
||||
/// @param use_segment_list is set when mpd_notifier_ is null and
|
||||
/// --output_media_info is set. If mpd_notifer is non-null, this value
|
||||
/// is the same as mpd_notifier->use_segment_list().
|
||||
/// @param mpd_notifer must be non-null for the combined listener to include a
|
||||
/// mpd listener.
|
||||
/// @param hls_notifier must be non-null for the combined listener to include
|
||||
/// an HLS listener.
|
||||
MuxerListenerFactory(bool output_media_info,
|
||||
bool use_segment_list,
|
||||
MpdNotifier* mpd_notifier,
|
||||
hls::HlsNotifier* hls_notifier);
|
||||
|
||||
|
@ -81,6 +85,9 @@ class MuxerListenerFactory {
|
|||
MpdNotifier* mpd_notifier_;
|
||||
hls::HlsNotifier* hls_notifier_;
|
||||
|
||||
/// This is set when mpd_notifier_ is NULL and --output_media_info is set.
|
||||
bool use_segment_list_;
|
||||
|
||||
// A counter to track which stream we are on.
|
||||
int stream_index_ = 0;
|
||||
};
|
||||
|
|
|
@ -251,6 +251,7 @@ bool IsMediaInfoCompatible(const MediaInfo& media_info1,
|
|||
|
||||
bool SetVodInformation(const MuxerListener::MediaRanges& media_ranges,
|
||||
float duration_seconds,
|
||||
bool use_segment_lists,
|
||||
MediaInfo* media_info) {
|
||||
DCHECK(media_info);
|
||||
|
||||
|
@ -270,6 +271,12 @@ bool SetVodInformation(const MuxerListener::MediaRanges& media_ranges,
|
|||
media_info->mutable_index_range());
|
||||
}
|
||||
|
||||
if (use_segment_lists) {
|
||||
for (const auto& range : media_ranges.subsegment_ranges) {
|
||||
SetRange(range.start, range.end, media_info->add_subsegment_ranges());
|
||||
}
|
||||
}
|
||||
|
||||
media_info->set_media_duration_seconds(duration_seconds);
|
||||
|
||||
return true;
|
||||
|
|
|
@ -42,6 +42,7 @@ bool IsMediaInfoCompatible(const MediaInfo& media_info1,
|
|||
/// @return true on success, false otherwise.
|
||||
bool SetVodInformation(const MuxerListener::MediaRanges& media_ranges,
|
||||
float duration_seconds,
|
||||
bool use_segment_list,
|
||||
MediaInfo* media_info);
|
||||
|
||||
/// @param protection_scheme specifies the protection scheme: 'cenc', 'cens',
|
||||
|
|
|
@ -43,6 +43,33 @@ const char kExpectedDefaultMediaInfo[] =
|
|||
"container_type: 1\n"
|
||||
"media_file_name: 'test_output_file_name.mp4'\n"
|
||||
"media_duration_seconds: 10.5\n";
|
||||
|
||||
const char kExpectedDefaultMediaInfoSubsegmentRange[] =
|
||||
"video_info {\n"
|
||||
" codec: 'avc1.010101'\n"
|
||||
" width: 720\n"
|
||||
" height: 480\n"
|
||||
" time_scale: 10\n"
|
||||
" pixel_width: 1\n"
|
||||
" pixel_height: 1\n"
|
||||
"}\n"
|
||||
"init_range {\n"
|
||||
" begin: 0\n"
|
||||
" end: 120\n"
|
||||
"}\n"
|
||||
"index_range {\n"
|
||||
" begin: 121\n"
|
||||
" end: 221\n"
|
||||
"}\n"
|
||||
"reference_time_scale: 1000\n"
|
||||
"container_type: 1\n"
|
||||
"media_file_name: 'test_output_file_name.mp4'\n"
|
||||
"media_duration_seconds: 10.5\n"
|
||||
"subsegment_ranges {\n"
|
||||
" begin: 222\n"
|
||||
" end: 9999\n"
|
||||
"}\n";
|
||||
|
||||
const uint32_t kDefaultReferenceTimeScale = 1000u;
|
||||
|
||||
// Struct that gets passed for to CreateVideoStreamInfo() to create a
|
||||
|
|
|
@ -22,8 +22,9 @@ namespace shaka {
|
|||
namespace media {
|
||||
|
||||
VodMediaInfoDumpMuxerListener::VodMediaInfoDumpMuxerListener(
|
||||
const std::string& output_file_path)
|
||||
: output_file_name_(output_file_path) {}
|
||||
const std::string& output_file_path, bool use_segment_list)
|
||||
: output_file_name_(output_file_path),
|
||||
use_segment_list_(use_segment_list) {}
|
||||
|
||||
VodMediaInfoDumpMuxerListener::~VodMediaInfoDumpMuxerListener() {}
|
||||
|
||||
|
@ -78,7 +79,7 @@ void VodMediaInfoDumpMuxerListener::OnMediaEnd(const MediaRanges& media_ranges,
|
|||
float duration_seconds) {
|
||||
DCHECK(media_info_);
|
||||
if (!internal::SetVodInformation(media_ranges, duration_seconds,
|
||||
media_info_.get())) {
|
||||
use_segment_list_, media_info_.get())) {
|
||||
LOG(ERROR) << "Failed to generate VOD information from input.";
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ namespace media {
|
|||
|
||||
class VodMediaInfoDumpMuxerListener : public MuxerListener {
|
||||
public:
|
||||
VodMediaInfoDumpMuxerListener(const std::string& output_file_name);
|
||||
VodMediaInfoDumpMuxerListener(const std::string& output_file_name, bool use_segment_list);
|
||||
~VodMediaInfoDumpMuxerListener() override;
|
||||
|
||||
/// @name MuxerListener implementation overrides.
|
||||
|
@ -63,6 +63,8 @@ class VodMediaInfoDumpMuxerListener : public MuxerListener {
|
|||
static bool WriteMediaInfoToFile(const MediaInfo& media_info,
|
||||
const std::string& output_file_path);
|
||||
|
||||
void set_use_segment_list(bool value) {use_segment_list_ = value;}
|
||||
|
||||
private:
|
||||
std::string output_file_name_;
|
||||
std::unique_ptr<MediaInfo> media_info_;
|
||||
|
@ -74,6 +76,8 @@ class VodMediaInfoDumpMuxerListener : public MuxerListener {
|
|||
std::vector<uint8_t> default_key_id_;
|
||||
std::vector<ProtectionSystemSpecificInfo> key_system_info_;
|
||||
|
||||
bool use_segment_list_ = false;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(VodMediaInfoDumpMuxerListener);
|
||||
};
|
||||
|
||||
|
|
|
@ -71,7 +71,12 @@ class VodMediaInfoDumpMuxerListenerTest : public ::testing::Test {
|
|||
DLOG(INFO) << "Created temp file: " << temp_file_path_.value();
|
||||
|
||||
listener_.reset(new VodMediaInfoDumpMuxerListener(temp_file_path_
|
||||
.AsUTF8Unsafe()));
|
||||
.AsUTF8Unsafe(),false));
|
||||
|
||||
}
|
||||
|
||||
void SetSegmentListFlag() {
|
||||
listener_->set_use_segment_list(true);
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
|
@ -228,6 +233,7 @@ TEST_F(VodMediaInfoDumpMuxerListenerTest, CheckPixelWidthAndHeightSet) {
|
|||
"container_type: 1\n"
|
||||
"media_file_name: 'test_output_file_name.mp4'\n"
|
||||
"media_duration_seconds: 10.5\n";
|
||||
|
||||
EXPECT_THAT(temp_file_path_.AsUTF8Unsafe(),
|
||||
FileContentEqualsProto(kExpectedProtobufOutput));
|
||||
}
|
||||
|
@ -274,5 +280,47 @@ TEST_F(VodMediaInfoDumpMuxerListenerTest, CheckBandwidth) {
|
|||
FileContentEqualsProto(kExpectedProtobufOutput));
|
||||
}
|
||||
|
||||
// Equivalent tests with segment list flag on which writes subsegment ranges
|
||||
// to media info files
|
||||
|
||||
TEST_F(VodMediaInfoDumpMuxerListenerTest, UnencryptedStream_Normal_SegmentList) {
|
||||
SetSegmentListFlag();
|
||||
std::shared_ptr<StreamInfo> stream_info =
|
||||
CreateVideoStreamInfo(GetDefaultVideoStreamInfoParams());
|
||||
|
||||
FireOnMediaStartWithDefaultMuxerOptions(*stream_info, !kEnableEncryption);
|
||||
OnMediaEndParameters media_end_param = GetDefaultOnMediaEndParams();
|
||||
FireOnMediaEndWithParams(media_end_param);
|
||||
|
||||
const char kExpectedProtobufOutput[] =
|
||||
"bandwidth: 0\n"
|
||||
"video_info {\n"
|
||||
" codec: 'avc1.010101'\n"
|
||||
" width: 720\n"
|
||||
" height: 480\n"
|
||||
" time_scale: 10\n"
|
||||
" pixel_width: 1\n"
|
||||
" pixel_height: 1\n"
|
||||
"}\n"
|
||||
"init_range {\n"
|
||||
" begin: 0\n"
|
||||
" end: 120\n"
|
||||
"}\n"
|
||||
"index_range {\n"
|
||||
" begin: 121\n"
|
||||
" end: 221\n"
|
||||
"}\n"
|
||||
"reference_time_scale: 1000\n"
|
||||
"container_type: 1\n"
|
||||
"media_file_name: 'test_output_file_name.mp4'\n"
|
||||
"media_duration_seconds: 10.5\n"
|
||||
"subsegment_ranges {\n"
|
||||
" begin: 222\n"
|
||||
" end: 9999\n"
|
||||
"}\n";
|
||||
EXPECT_THAT(temp_file_path_.AsUTF8Unsafe(),
|
||||
FileContentEqualsProto(kExpectedProtobufOutput));
|
||||
}
|
||||
|
||||
} // namespace media
|
||||
} // namespace shaka
|
||||
|
|
|
@ -172,6 +172,7 @@ message MediaInfo {
|
|||
optional Range init_range = 6;
|
||||
optional Range index_range = 7;
|
||||
optional string media_file_name = 8;
|
||||
repeated Range subsegment_ranges = 23;
|
||||
// END VOD only.
|
||||
|
||||
// VOD and static LIVE.
|
||||
|
|
|
@ -113,6 +113,11 @@ class MpdNotifier {
|
|||
/// @return The mpd type for this object.
|
||||
MpdType mpd_type() const { return mpd_options_.mpd_type; }
|
||||
|
||||
/// @return The value of dash_force_segment_list flag
|
||||
bool use_segment_list() const {
|
||||
return mpd_options_.mpd_params.use_segment_list;
|
||||
}
|
||||
|
||||
private:
|
||||
const MpdOptions mpd_options_;
|
||||
|
||||
|
|
|
@ -265,7 +265,9 @@ base::Optional<xml::XmlNode> Representation::GetXml() {
|
|||
}
|
||||
|
||||
if (HasVODOnlyFields(media_info_) &&
|
||||
!representation.AddVODOnlyInfo(media_info_)) {
|
||||
!representation.AddVODOnlyInfo(
|
||||
media_info_, mpd_options_.mpd_params.use_segment_list,
|
||||
mpd_options_.mpd_params.target_segment_duration)) {
|
||||
LOG(ERROR) << "Failed to add VOD info.";
|
||||
return base::nullopt;
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include <gflags/gflags.h>
|
||||
#include <libxml/tree.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <set>
|
||||
|
||||
|
@ -376,57 +377,83 @@ bool RepresentationXmlNode::AddAudioInfo(const AudioInfo& audio_info) {
|
|||
AddAudioSamplingRateInfo(audio_info);
|
||||
}
|
||||
|
||||
bool RepresentationXmlNode::AddVODOnlyInfo(const MediaInfo& media_info) {
|
||||
const bool use_segment_list_text =
|
||||
bool RepresentationXmlNode::AddVODOnlyInfo(const MediaInfo& media_info,
|
||||
bool use_segment_list,
|
||||
double target_segment_duration) {
|
||||
const bool use_single_segment_url_with_media =
|
||||
media_info.has_text_info() && media_info.has_presentation_time_offset();
|
||||
|
||||
if (media_info.has_media_file_url() && !use_segment_list_text) {
|
||||
if (media_info.has_media_file_url() && !use_single_segment_url_with_media) {
|
||||
XmlNode base_url("BaseURL");
|
||||
base_url.SetContent(media_info.media_file_url());
|
||||
|
||||
RCHECK(AddChild(std::move(base_url)));
|
||||
}
|
||||
|
||||
const bool need_segment_base =
|
||||
media_info.has_index_range() || media_info.has_init_range() ||
|
||||
(media_info.has_reference_time_scale() && !media_info.has_text_info());
|
||||
DCHECK(!need_segment_base || !use_segment_list_text);
|
||||
const bool need_segment_base_or_list =
|
||||
use_segment_list || media_info.has_index_range() ||
|
||||
media_info.has_init_range() ||
|
||||
(media_info.has_reference_time_scale() && !media_info.has_text_info()) ||
|
||||
use_single_segment_url_with_media;
|
||||
|
||||
if (need_segment_base || use_segment_list_text) {
|
||||
XmlNode child(need_segment_base ? "SegmentBase" : "SegmentList");
|
||||
if (media_info.has_index_range()) {
|
||||
RCHECK(child.SetStringAttribute("indexRange",
|
||||
RangeToString(media_info.index_range())));
|
||||
}
|
||||
|
||||
if (media_info.has_reference_time_scale()) {
|
||||
RCHECK(child.SetIntegerAttribute("timescale",
|
||||
media_info.reference_time_scale()));
|
||||
}
|
||||
|
||||
if (media_info.has_presentation_time_offset()) {
|
||||
RCHECK(child.SetIntegerAttribute("presentationTimeOffset",
|
||||
media_info.presentation_time_offset()));
|
||||
}
|
||||
|
||||
if (media_info.has_init_range()) {
|
||||
XmlNode initialization("Initialization");
|
||||
RCHECK(initialization.SetStringAttribute(
|
||||
"range", RangeToString(media_info.init_range())));
|
||||
|
||||
RCHECK(child.AddChild(std::move(initialization)));
|
||||
}
|
||||
|
||||
if (use_segment_list_text) {
|
||||
XmlNode media_url("SegmentURL");
|
||||
RCHECK(
|
||||
media_url.SetStringAttribute("media", media_info.media_file_url()));
|
||||
RCHECK(child.AddChild(std::move(media_url)));
|
||||
}
|
||||
|
||||
RCHECK(AddChild(std::move(child)));
|
||||
if (!need_segment_base_or_list) {
|
||||
return true;
|
||||
}
|
||||
|
||||
XmlNode child(use_segment_list || use_single_segment_url_with_media
|
||||
? "SegmentList"
|
||||
: "SegmentBase");
|
||||
|
||||
// Forcing SegmentList for longer audio causes sidx atom to not be
|
||||
// generated, therefore indexRange is not added to MPD if flag is set.
|
||||
if (media_info.has_index_range() && !use_segment_list) {
|
||||
RCHECK(child.SetStringAttribute("indexRange",
|
||||
RangeToString(media_info.index_range())));
|
||||
}
|
||||
|
||||
if (media_info.has_reference_time_scale()) {
|
||||
RCHECK(child.SetIntegerAttribute("timescale",
|
||||
media_info.reference_time_scale()));
|
||||
|
||||
if (use_segment_list && !use_single_segment_url_with_media) {
|
||||
const uint64_t duration_seconds = static_cast<uint64_t>(
|
||||
floor(target_segment_duration * media_info.reference_time_scale()));
|
||||
RCHECK(child.SetIntegerAttribute("duration", duration_seconds));
|
||||
}
|
||||
}
|
||||
|
||||
if (media_info.has_presentation_time_offset()) {
|
||||
RCHECK(child.SetIntegerAttribute("presentationTimeOffset",
|
||||
media_info.presentation_time_offset()));
|
||||
}
|
||||
|
||||
if (media_info.has_init_range()) {
|
||||
XmlNode initialization("Initialization");
|
||||
RCHECK(initialization.SetStringAttribute(
|
||||
"range", RangeToString(media_info.init_range())));
|
||||
|
||||
RCHECK(child.AddChild(std::move(initialization)));
|
||||
}
|
||||
|
||||
if (use_single_segment_url_with_media) {
|
||||
XmlNode media_url("SegmentURL");
|
||||
RCHECK(media_url.SetStringAttribute("media", media_info.media_file_url()));
|
||||
RCHECK(child.AddChild(std::move(media_url)));
|
||||
}
|
||||
|
||||
// Since the SegmentURLs here do not have a @media element,
|
||||
// BaseURL element is mapped to the @media attribute.
|
||||
if (use_segment_list) {
|
||||
for (const Range& subsegment_range : media_info.subsegment_ranges()) {
|
||||
XmlNode subsegment("SegmentURL");
|
||||
RCHECK(subsegment.SetStringAttribute("mediaRange",
|
||||
RangeToString(subsegment_range)));
|
||||
|
||||
RCHECK(child.AddChild(std::move(subsegment)));
|
||||
}
|
||||
}
|
||||
|
||||
RCHECK(AddChild(std::move(child)));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -205,8 +205,15 @@ class RepresentationXmlNode : public RepresentationBaseXmlNode {
|
|||
/// Adds fields that are specific to VOD. This ignores @a media_info fields
|
||||
/// for Live.
|
||||
/// @param media_info is a MediaInfo with VOD information.
|
||||
/// @param use_segment_list is a param that instructs the xml writer to
|
||||
/// use SegmentList instead of SegmentBase.
|
||||
/// @param target_segment_duration is a param that specifies the target
|
||||
// duration of media segments. This is only used when use_segment_list
|
||||
// is true.
|
||||
/// @return true on success, false otherwise.
|
||||
bool AddVODOnlyInfo(const MediaInfo& media_info) WARN_UNUSED_RESULT;
|
||||
bool AddVODOnlyInfo(const MediaInfo& media_info,
|
||||
bool use_segment_list,
|
||||
double target_segment_duration) WARN_UNUSED_RESULT;
|
||||
|
||||
/// @param segment_infos is a set of SegmentInfos. This method assumes that
|
||||
/// SegmentInfos are sorted by its start time.
|
||||
|
|
|
@ -15,11 +15,13 @@
|
|||
#include "packager/base/strings/string_util.h"
|
||||
#include "packager/mpd/base/segment_info.h"
|
||||
#include "packager/mpd/base/xml/xml_node.h"
|
||||
#include "packager/mpd/test/mpd_builder_test_helper.h"
|
||||
#include "packager/mpd/test/xml_compare.h"
|
||||
|
||||
DECLARE_bool(segment_template_constant_duration);
|
||||
DECLARE_bool(dash_add_last_segment_number_when_needed);
|
||||
|
||||
|
||||
using ::testing::ElementsAre;
|
||||
|
||||
namespace shaka {
|
||||
|
@ -543,5 +545,175 @@ TEST_F(LiveSegmentTimelineTest, LastSegmentNumberSupplementalProperty) {
|
|||
FLAGS_dash_add_last_segment_number_when_needed = false;
|
||||
}
|
||||
|
||||
// Creating a separate Test Suite for RepresentationXmlNode::AddVODOnlyInfo
|
||||
class OnDemandVODSegmentTest : public ::testing::Test {
|
||||
};
|
||||
|
||||
TEST_F(OnDemandVODSegmentTest, SegmentBase) {
|
||||
const char kTestMediaInfo[] =
|
||||
"audio_info {\n"
|
||||
" codec: 'mp4a.40.2'\n"
|
||||
" sampling_frequency: 44100\n"
|
||||
" time_scale: 44100\n"
|
||||
" num_channels: 2\n"
|
||||
"}\n"
|
||||
"init_range {\n"
|
||||
" begin: 0\n"
|
||||
" end: 863\n"
|
||||
"}\n"
|
||||
"index_range {\n"
|
||||
" begin: 864\n"
|
||||
" end: 931\n"
|
||||
"}\n"
|
||||
"media_file_url: 'encrypted_audio.mp4'\n"
|
||||
"media_duration_seconds: 24.009434\n"
|
||||
"reference_time_scale: 44100\n"
|
||||
"presentation_time_offset: 100\n";
|
||||
|
||||
const MediaInfo media_info = ConvertToMediaInfo(kTestMediaInfo);
|
||||
|
||||
RepresentationXmlNode representation;
|
||||
ASSERT_TRUE(representation.AddVODOnlyInfo(media_info, false, 100));
|
||||
EXPECT_THAT(representation,
|
||||
XmlNodeEqual("<Representation>"
|
||||
"<BaseURL>encrypted_audio.mp4</BaseURL>"
|
||||
"<SegmentBase indexRange=\"864-931\" "
|
||||
"timescale=\"44100\" presentationTimeOffset=\"100\">"
|
||||
"<Initialization range=\"0-863\"/>"
|
||||
"</SegmentBase>"
|
||||
"</Representation>"));
|
||||
}
|
||||
|
||||
TEST_F(OnDemandVODSegmentTest, TextInfoBaseUrl) {
|
||||
const char kTextMediaInfo[] =
|
||||
"text_info {\n"
|
||||
" codec: 'ttml'\n"
|
||||
" language: 'en'\n"
|
||||
" type: SUBTITLE\n"
|
||||
"}\n"
|
||||
"media_duration_seconds: 35\n"
|
||||
"bandwidth: 1000\n"
|
||||
"media_file_url: 'subtitle.xml'\n"
|
||||
"container_type: CONTAINER_TEXT\n";
|
||||
|
||||
const MediaInfo media_info = ConvertToMediaInfo(kTextMediaInfo);
|
||||
|
||||
RepresentationXmlNode representation;
|
||||
ASSERT_TRUE(representation.AddVODOnlyInfo(media_info, false, 100));
|
||||
EXPECT_THAT(representation, XmlNodeEqual("<Representation>"
|
||||
"<BaseURL>subtitle.xml</BaseURL>"
|
||||
"</Representation>"));
|
||||
}
|
||||
|
||||
TEST_F(OnDemandVODSegmentTest, TextInfoWithPresentationOffset) {
|
||||
const char kTextMediaInfo[] =
|
||||
"text_info {\n"
|
||||
" codec: 'ttml'\n"
|
||||
" language: 'en'\n"
|
||||
" type: SUBTITLE\n"
|
||||
"}\n"
|
||||
"media_duration_seconds: 35\n"
|
||||
"bandwidth: 1000\n"
|
||||
"media_file_url: 'subtitle.xml'\n"
|
||||
"container_type: CONTAINER_TEXT\n"
|
||||
"presentation_time_offset: 100\n";
|
||||
|
||||
const MediaInfo media_info = ConvertToMediaInfo(kTextMediaInfo);
|
||||
|
||||
RepresentationXmlNode representation;
|
||||
ASSERT_TRUE(representation.AddVODOnlyInfo(media_info, false, 100));
|
||||
|
||||
EXPECT_THAT(representation,
|
||||
XmlNodeEqual("<Representation>"
|
||||
"<SegmentList presentationTimeOffset=\"100\">"
|
||||
"<SegmentURL media=\"subtitle.xml\"/>"
|
||||
"</SegmentList>"
|
||||
"</Representation>"));
|
||||
}
|
||||
|
||||
TEST_F(OnDemandVODSegmentTest, SegmentListWithoutUrls) {
|
||||
const char kTestMediaInfo[] =
|
||||
"audio_info {\n"
|
||||
" codec: 'mp4a.40.2'\n"
|
||||
" sampling_frequency: 44100\n"
|
||||
" time_scale: 44100\n"
|
||||
" num_channels: 2\n"
|
||||
"}\n"
|
||||
"init_range {\n"
|
||||
" begin: 0\n"
|
||||
" end: 863\n"
|
||||
"}\n"
|
||||
"index_range {\n"
|
||||
" begin: 864\n"
|
||||
" end: 931\n"
|
||||
"}\n"
|
||||
"media_file_url: 'encrypted_audio.mp4'\n"
|
||||
"media_duration_seconds: 24.009434\n"
|
||||
"reference_time_scale: 44100\n"
|
||||
"presentation_time_offset: 100\n";
|
||||
|
||||
const MediaInfo media_info = ConvertToMediaInfo(kTestMediaInfo);
|
||||
|
||||
RepresentationXmlNode representation;
|
||||
ASSERT_TRUE(representation.AddVODOnlyInfo(media_info, true, 100));
|
||||
|
||||
EXPECT_THAT(
|
||||
representation,
|
||||
XmlNodeEqual("<Representation>"
|
||||
"<BaseURL>encrypted_audio.mp4</BaseURL>"
|
||||
"<SegmentList timescale=\"44100\" duration=\"4410000\" "
|
||||
"presentationTimeOffset=\"100\">"
|
||||
"<Initialization range=\"0-863\"/>"
|
||||
"</SegmentList>"
|
||||
"</Representation>"));
|
||||
}
|
||||
|
||||
TEST_F(OnDemandVODSegmentTest, SegmentUrlWithMediaRanges) {
|
||||
const char kTextMediaInfo[] =
|
||||
"audio_info {\n"
|
||||
" codec: 'mp4a.40.2'\n"
|
||||
" sampling_frequency: 44100\n"
|
||||
" time_scale: 44100\n"
|
||||
" num_channels: 2\n"
|
||||
"}\n"
|
||||
"init_range {\n"
|
||||
" begin: 0\n"
|
||||
" end: 863\n"
|
||||
"}\n"
|
||||
"index_range {\n"
|
||||
" begin: 864\n"
|
||||
" end: 931\n"
|
||||
"}\n"
|
||||
"media_file_url: 'encrypted_audio.mp4'\n"
|
||||
"media_duration_seconds: 24.009434\n"
|
||||
"reference_time_scale: 44100\n"
|
||||
"presentation_time_offset: 100\n"
|
||||
"subsegment_ranges {\n"
|
||||
" begin: 932\n"
|
||||
" end: 9999\n"
|
||||
"}\n"
|
||||
"subsegment_ranges {\n"
|
||||
" begin: 10000\n"
|
||||
" end: 11000\n"
|
||||
"}\n";
|
||||
|
||||
const MediaInfo media_info = ConvertToMediaInfo(kTextMediaInfo);
|
||||
|
||||
RepresentationXmlNode representation;
|
||||
ASSERT_TRUE(representation.AddVODOnlyInfo(media_info, true, 100));
|
||||
|
||||
EXPECT_THAT(
|
||||
representation,
|
||||
XmlNodeEqual("<Representation>"
|
||||
"<BaseURL>encrypted_audio.mp4</BaseURL>"
|
||||
"<SegmentList timescale=\"44100\" duration=\"4410000\" "
|
||||
"presentationTimeOffset=\"100\">"
|
||||
"<Initialization range=\"0-863\"/>"
|
||||
"<SegmentURL mediaRange=\"932-9999\"/>"
|
||||
"<SegmentURL mediaRange=\"10000-11000\"/>"
|
||||
"</SegmentList>"
|
||||
"</Representation>"));
|
||||
}
|
||||
|
||||
} // namespace xml
|
||||
} // namespace shaka
|
||||
|
|
|
@ -87,6 +87,10 @@ struct MpdParams {
|
|||
/// <ContentProtection ...> element alongside with <cenc:pssh>
|
||||
/// when using PlayReady protection system.
|
||||
bool include_mspr_pro = true;
|
||||
/// Uses SegmentList instead of SegmentBase. Use this if the
|
||||
/// content is huge and the total number of (sub)segment references
|
||||
/// is greater than what the sidx atom allows (65535).
|
||||
bool use_segment_list = false;
|
||||
};
|
||||
|
||||
} // namespace shaka
|
||||
|
|
|
@ -367,10 +367,11 @@ Status ValidateParams(const PackagingParams& packaging_params,
|
|||
|
||||
if (on_demand_dash_profile &&
|
||||
!packaging_params.mpd_params.mpd_output.empty() &&
|
||||
!packaging_params.mp4_output_params.generate_sidx_in_media_segments) {
|
||||
!packaging_params.mp4_output_params.generate_sidx_in_media_segments &&
|
||||
!packaging_params.mpd_params.use_segment_list) {
|
||||
return Status(error::UNIMPLEMENTED,
|
||||
"--generate_sidx_in_media_segments is required for DASH "
|
||||
"on-demand profile (not using segment_template).");
|
||||
"on-demand profile (not using segment_template or segment list).");
|
||||
}
|
||||
|
||||
return Status::OK;
|
||||
|
@ -937,8 +938,9 @@ Status Packager::Initialize(
|
|||
}
|
||||
|
||||
media::MuxerListenerFactory muxer_listener_factory(
|
||||
packaging_params.output_media_info, internal->mpd_notifier.get(),
|
||||
internal->hls_notifier.get());
|
||||
packaging_params.output_media_info,
|
||||
packaging_params.mpd_params.use_segment_list,
|
||||
internal->mpd_notifier.get(), internal->hls_notifier.get());
|
||||
|
||||
RETURN_IF_ERROR(media::CreateAllJobs(
|
||||
streams_for_jobs, packaging_params, internal->mpd_notifier.get(),
|
||||
|
|
Loading…
Reference in New Issue