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:
Samidh 2021-05-25 15:08:58 -04:00 committed by GitHub
parent fe9e26d5d9
commit 4686454a73
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 693 additions and 57 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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