[Ad Insertion] Support one file per Representation per Period
Allow the output file to contain template identifiers like $Number$, $Time$ etc. Unlike the actual template used in SegmentTemplate, the identifiers in output will be populated before pushing to the DASH manifest. Issue: #384 Change-Id: Ife1caadb6fccd32167fa1bc83fe2afcb2d2ad087
This commit is contained in:
parent
9feb2bf503
commit
6e4820eedc
|
@ -151,6 +151,7 @@ class PackagerAppTest(unittest.TestCase):
|
|||
drm_label=None,
|
||||
skip_encryption=None,
|
||||
bandwidth=None,
|
||||
split_content_on_ad_cues=False,
|
||||
test_file=None):
|
||||
"""Get a stream descriptor as a string.
|
||||
|
||||
|
@ -174,6 +175,8 @@ class PackagerAppTest(unittest.TestCase):
|
|||
skip_encryption: If set to true, the stream will not be encrypted.
|
||||
bandwidth: The expected bandwidth value that should be listed in the
|
||||
manifest.
|
||||
split_content_on_ad_cues: If set to true, the output file will be split
|
||||
into multiple files, with a total of NumAdCues + 1 files.
|
||||
test_file: Specify the input file to use. If the input file is not
|
||||
specify, a default file will be used.
|
||||
|
||||
|
@ -237,6 +240,9 @@ class PackagerAppTest(unittest.TestCase):
|
|||
segment_ext = GetSegmentedExtension(base_ext)
|
||||
seg_template = '%s-$Number$.%s' % (output_file_path, segment_ext)
|
||||
stream.Append('segment_template', seg_template)
|
||||
else:
|
||||
if split_content_on_ad_cues:
|
||||
output_file_path += '$Number$.' + base_ext
|
||||
else:
|
||||
output_file_path += '.' + base_ext
|
||||
stream.Append('output', output_file_path)
|
||||
|
@ -1016,6 +1022,14 @@ class PackagerFunctionalTest(PackagerAppTest):
|
|||
self._VerifyDecryption(self.output[0], 'bear-640x360-a-demuxed-golden.mp4')
|
||||
self._VerifyDecryption(self.output[1], 'bear-640x360-v-golden.mp4')
|
||||
|
||||
def testEncryptionAndAdCuesSplitContent(self):
|
||||
self.assertPackageSuccess(
|
||||
self._GetStreams(
|
||||
['audio', 'video'], hls=True, split_content_on_ad_cues=True),
|
||||
self._GetFlags(
|
||||
encryption=True, output_dash=True, output_hls=True, ad_cues='1.5'))
|
||||
self._CheckTestResults('encryption-and-ad-cues-split-content')
|
||||
|
||||
def testHlsAudioVideoTextWithAdCues(self):
|
||||
streams = [
|
||||
self._GetStream('audio',
|
||||
|
|
22
packager/app/test/testdata/encryption-and-ad-cues-split-content/bear-640x360-audio.m3u8
vendored
Normal file
22
packager/app/test/testdata/encryption-and-ad-cues-split-content/bear-640x360-audio.m3u8
vendored
Normal file
|
@ -0,0 +1,22 @@
|
|||
#EXTM3U
|
||||
#EXT-X-VERSION:6
|
||||
## Generated with https://github.com/google/shaka-packager version <tag>-<hash>-<test>
|
||||
#EXT-X-TARGETDURATION:2
|
||||
#EXT-X-PLAYLIST-TYPE:VOD
|
||||
#EXT-X-MAP:URI="bear-640x360-audio1.mp4",BYTERANGE="967@0"
|
||||
#EXT-X-KEY:METHOD=SAMPLE-AES-CTR,URI="data:text/plain;base64,MTIzNDU2Nzg5MDEyMzQ1Ng==",KEYFORMAT="identity"
|
||||
#EXTINF:1.022,
|
||||
#EXT-X-BYTERANGE:16655@1035
|
||||
bear-640x360-audio1.mp4
|
||||
#EXTINF:0.998,
|
||||
#EXT-X-BYTERANGE:16650
|
||||
bear-640x360-audio1.mp4
|
||||
#EXTINF:0.046,
|
||||
#EXT-X-BYTERANGE:1014
|
||||
bear-640x360-audio1.mp4
|
||||
#EXT-X-PLACEMENT-OPPORTUNITY
|
||||
#EXT-X-KEY:METHOD=SAMPLE-AES-CTR,URI="data:text/plain;base64,MTIzNDU2Nzg5MDEyMzQ1Ng==",KEYFORMAT="identity"
|
||||
#EXTINF:0.697,
|
||||
#EXT-X-BYTERANGE:9415@1011
|
||||
bear-640x360-audio2.mp4
|
||||
#EXT-X-ENDLIST
|
BIN
packager/app/test/testdata/encryption-and-ad-cues-split-content/bear-640x360-audio1.mp4
vendored
Normal file
BIN
packager/app/test/testdata/encryption-and-ad-cues-split-content/bear-640x360-audio1.mp4
vendored
Normal file
Binary file not shown.
BIN
packager/app/test/testdata/encryption-and-ad-cues-split-content/bear-640x360-audio2.mp4
vendored
Normal file
BIN
packager/app/test/testdata/encryption-and-ad-cues-split-content/bear-640x360-audio2.mp4
vendored
Normal file
Binary file not shown.
20
packager/app/test/testdata/encryption-and-ad-cues-split-content/bear-640x360-video-iframe.m3u8
vendored
Normal file
20
packager/app/test/testdata/encryption-and-ad-cues-split-content/bear-640x360-video-iframe.m3u8
vendored
Normal file
|
@ -0,0 +1,20 @@
|
|||
#EXTM3U
|
||||
#EXT-X-VERSION:6
|
||||
## Generated with https://github.com/google/shaka-packager version <tag>-<hash>-<test>
|
||||
#EXT-X-TARGETDURATION:2
|
||||
#EXT-X-PLAYLIST-TYPE:VOD
|
||||
#EXT-X-I-FRAMES-ONLY
|
||||
#EXT-X-MAP:URI="bear-640x360-video1.mp4",BYTERANGE="1091@0"
|
||||
#EXT-X-KEY:METHOD=SAMPLE-AES-CTR,URI="data:text/plain;base64,MTIzNDU2Nzg5MDEyMzQ1Ng==",KEYFORMAT="identity"
|
||||
#EXTINF:1.001,
|
||||
#EXT-X-BYTERANGE:15581@1147
|
||||
bear-640x360-video1.mp4
|
||||
#EXTINF:1.001,
|
||||
#EXT-X-BYTERANGE:18754@100460
|
||||
bear-640x360-video1.mp4
|
||||
#EXT-X-PLACEMENT-OPPORTUNITY
|
||||
#EXT-X-KEY:METHOD=SAMPLE-AES-CTR,URI="data:text/plain;base64,MTIzNDU2Nzg5MDEyMzQ1Ng==",KEYFORMAT="identity"
|
||||
#EXTINF:0.734,
|
||||
#EXT-X-BYTERANGE:20068@1135
|
||||
bear-640x360-video2.mp4
|
||||
#EXT-X-ENDLIST
|
19
packager/app/test/testdata/encryption-and-ad-cues-split-content/bear-640x360-video.m3u8
vendored
Normal file
19
packager/app/test/testdata/encryption-and-ad-cues-split-content/bear-640x360-video.m3u8
vendored
Normal file
|
@ -0,0 +1,19 @@
|
|||
#EXTM3U
|
||||
#EXT-X-VERSION:6
|
||||
## Generated with https://github.com/google/shaka-packager version <tag>-<hash>-<test>
|
||||
#EXT-X-TARGETDURATION:2
|
||||
#EXT-X-PLAYLIST-TYPE:VOD
|
||||
#EXT-X-MAP:URI="bear-640x360-video1.mp4",BYTERANGE="1091@0"
|
||||
#EXT-X-KEY:METHOD=SAMPLE-AES-CTR,URI="data:text/plain;base64,MTIzNDU2Nzg5MDEyMzQ1Ng==",KEYFORMAT="identity"
|
||||
#EXTINF:1.068,
|
||||
#EXT-X-BYTERANGE:99313@1147
|
||||
bear-640x360-video1.mp4
|
||||
#EXTINF:1.001,
|
||||
#EXT-X-BYTERANGE:122340
|
||||
bear-640x360-video1.mp4
|
||||
#EXT-X-PLACEMENT-OPPORTUNITY
|
||||
#EXT-X-KEY:METHOD=SAMPLE-AES-CTR,URI="data:text/plain;base64,MTIzNDU2Nzg5MDEyMzQ1Ng==",KEYFORMAT="identity"
|
||||
#EXTINF:0.801,
|
||||
#EXT-X-BYTERANGE:80067@1135
|
||||
bear-640x360-video2.mp4
|
||||
#EXT-X-ENDLIST
|
BIN
packager/app/test/testdata/encryption-and-ad-cues-split-content/bear-640x360-video1.mp4
vendored
Normal file
BIN
packager/app/test/testdata/encryption-and-ad-cues-split-content/bear-640x360-video1.mp4
vendored
Normal file
Binary file not shown.
BIN
packager/app/test/testdata/encryption-and-ad-cues-split-content/bear-640x360-video2.mp4
vendored
Normal file
BIN
packager/app/test/testdata/encryption-and-ad-cues-split-content/bear-640x360-video2.mp4
vendored
Normal file
Binary file not shown.
|
@ -0,0 +1,9 @@
|
|||
#EXTM3U
|
||||
## Generated with https://github.com/google/shaka-packager version <tag>-<hash>-<test>
|
||||
|
||||
#EXT-X-MEDIA:TYPE=AUDIO,URI="bear-640x360-audio.m3u8",GROUP-ID="default-audio-group",NAME="stream_0",AUTOSELECT=YES,CHANNELS="2"
|
||||
|
||||
#EXT-X-STREAM-INF:BANDWIDTH=1152419,CODECS="avc1.64001e,mp4a.40.2",RESOLUTION=640x360,AUDIO="default-audio-group"
|
||||
bear-640x360-video.m3u8
|
||||
|
||||
#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=218704,CODECS="avc1.64001e",RESOLUTION=640x360,URI="bear-640x360-video-iframe.m3u8"
|
|
@ -0,0 +1,58 @@
|
|||
<?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" 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" profiles="urn:mpeg:dash:profile:isoff-on-demand:2011" minBufferTime="PT2S" type="static" mediaPresentationDuration="PT2.8695333003997803S">
|
||||
<Period id="0" duration="PT2.0687333333333333S">
|
||||
<AdaptationSet id="0" contentType="video" width="640" height="360" frameRate="30000/1001" subsegmentAlignment="true" par="16:9">
|
||||
<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="977743" codecs="avc1.64001e" mimeType="video/mp4" sar="1:1">
|
||||
<BaseURL>bear-640x360-video1.mp4</BaseURL>
|
||||
<SegmentBase indexRange="1091-1146" timescale="30000">
|
||||
<Initialization range="0-1090"/>
|
||||
</SegmentBase>
|
||||
</Representation>
|
||||
</AdaptationSet>
|
||||
<AdaptationSet id="1" 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="1" bandwidth="174678" 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-audio1.mp4</BaseURL>
|
||||
<SegmentBase indexRange="967-1034" timescale="44100">
|
||||
<Initialization range="0-966"/>
|
||||
</SegmentBase>
|
||||
</Representation>
|
||||
</AdaptationSet>
|
||||
</Period>
|
||||
<Period id="1" duration="PT.8008000000000002S">
|
||||
<AdaptationSet id="0" contentType="video" width="640" height="360" frameRate="30000/1001" subsegmentAlignment="true" par="16:9">
|
||||
<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="799871" codecs="avc1.64001e" mimeType="video/mp4" sar="1:1">
|
||||
<BaseURL>bear-640x360-video2.mp4</BaseURL>
|
||||
<SegmentBase indexRange="1091-1134" timescale="30000" presentationTimeOffset="60059">
|
||||
<Initialization range="0-1090"/>
|
||||
</SegmentBase>
|
||||
</Representation>
|
||||
</AdaptationSet>
|
||||
<AdaptationSet id="1" 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="1" bandwidth="108126" 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-audio2.mp4</BaseURL>
|
||||
<SegmentBase indexRange="967-1010" timescale="44100" presentationTimeOffset="88288">
|
||||
<Initialization range="0-966"/>
|
||||
</SegmentBase>
|
||||
</Representation>
|
||||
</AdaptationSet>
|
||||
</Period>
|
||||
</MPD>
|
|
@ -9,15 +9,22 @@
|
|||
#include <algorithm>
|
||||
|
||||
#include "packager/media/base/media_sample.h"
|
||||
#include "packager/media/base/muxer_util.h"
|
||||
#include "packager/status_macros.h"
|
||||
|
||||
namespace shaka {
|
||||
namespace media {
|
||||
namespace {
|
||||
const bool kInitialEncryptionInfo = true;
|
||||
const int64_t kStartTime = 0;
|
||||
} // namespace
|
||||
|
||||
Muxer::Muxer(const MuxerOptions& options)
|
||||
: options_(options), cancelled_(false), clock_(NULL) {}
|
||||
Muxer::Muxer(const MuxerOptions& options) : options_(options) {
|
||||
// "$" is only allowed if the output file name is a template, which is used to
|
||||
// support one file per Representation per Period when there are Ad Cues.
|
||||
if (options_.output_file_name.find("$") != std::string::npos)
|
||||
output_file_template_ = options_.output_file_name;
|
||||
}
|
||||
|
||||
Muxer::~Muxer() {}
|
||||
|
||||
|
@ -39,16 +46,7 @@ Status Muxer::Process(std::unique_ptr<StreamData> stream_data) {
|
|||
switch (stream_data->stream_data_type) {
|
||||
case StreamDataType::kStreamInfo:
|
||||
streams_.push_back(std::move(stream_data->stream_info));
|
||||
if (muxer_listener_ && streams_.back()->is_encrypted()) {
|
||||
const EncryptionConfig& encryption_config =
|
||||
streams_.back()->encryption_config();
|
||||
muxer_listener_->OnEncryptionInfoReady(
|
||||
kInitialEncryptionInfo, encryption_config.protection_scheme,
|
||||
encryption_config.key_id, encryption_config.constant_iv,
|
||||
encryption_config.key_system_info);
|
||||
current_key_id_ = encryption_config.key_id;
|
||||
}
|
||||
return InitializeMuxer();
|
||||
return ReinitializeMuxer(kStartTime);
|
||||
case StreamDataType::kSegmentInfo: {
|
||||
const auto& segment_info = *stream_data->segment_info;
|
||||
if (muxer_listener_ && segment_info.is_encrypted) {
|
||||
|
@ -80,6 +78,12 @@ Status Muxer::Process(std::unique_ptr<StreamData> stream_data) {
|
|||
static_cast<int64_t>(time_in_seconds * time_scale);
|
||||
muxer_listener_->OnCueEvent(scaled_time,
|
||||
stream_data->cue_event->cue_data);
|
||||
|
||||
// Finalize and re-initialize Muxer to generate different content files.
|
||||
if (!output_file_template_.empty()) {
|
||||
RETURN_IF_ERROR(Finalize());
|
||||
RETURN_IF_ERROR(ReinitializeMuxer(scaled_time));
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
@ -91,5 +95,29 @@ Status Muxer::Process(std::unique_ptr<StreamData> stream_data) {
|
|||
return Status::OK;
|
||||
}
|
||||
|
||||
Status Muxer::OnFlushRequest(size_t input_stream_index) {
|
||||
return Finalize();
|
||||
}
|
||||
|
||||
Status Muxer::ReinitializeMuxer(int64_t timestamp) {
|
||||
if (muxer_listener_ && streams_.back()->is_encrypted()) {
|
||||
const EncryptionConfig& encryption_config =
|
||||
streams_.back()->encryption_config();
|
||||
muxer_listener_->OnEncryptionInfoReady(
|
||||
kInitialEncryptionInfo, encryption_config.protection_scheme,
|
||||
encryption_config.key_id, encryption_config.constant_iv,
|
||||
encryption_config.key_system_info);
|
||||
current_key_id_ = encryption_config.key_id;
|
||||
}
|
||||
if (!output_file_template_.empty()) {
|
||||
// Update |output_file_name| with an actual file name, which will be used by
|
||||
// the subclasses.
|
||||
options_.output_file_name =
|
||||
GetSegmentName(output_file_template_, timestamp, output_file_index_++,
|
||||
options_.bandwidth);
|
||||
}
|
||||
return InitializeMuxer();
|
||||
}
|
||||
|
||||
} // namespace media
|
||||
} // namespace shaka
|
||||
|
|
|
@ -63,7 +63,7 @@ class Muxer : public MediaHandler {
|
|||
/// @{
|
||||
Status InitializeInternal() override { return Status::OK; }
|
||||
Status Process(std::unique_ptr<StreamData> stream_data) override;
|
||||
Status OnFlushRequest(size_t input_stream_index) override { return Finalize(); }
|
||||
Status OnFlushRequest(size_t input_stream_index) override;
|
||||
/// @}
|
||||
|
||||
const MuxerOptions& options() const { return options_; }
|
||||
|
@ -72,6 +72,13 @@ class Muxer : public MediaHandler {
|
|||
base::Clock* clock() { return clock_; }
|
||||
|
||||
private:
|
||||
Muxer(const Muxer&) = delete;
|
||||
Muxer& operator=(const Muxer&) = delete;
|
||||
|
||||
// Re-initialize Muxer. Could be called on StreamInfo or CueEvent.
|
||||
// |timestamp| may be used to set the output file name.
|
||||
Status ReinitializeMuxer(int64_t timestamp);
|
||||
|
||||
// Initialize the muxer.
|
||||
virtual Status InitializeMuxer() = 0;
|
||||
|
||||
|
@ -92,14 +99,17 @@ class Muxer : public MediaHandler {
|
|||
std::vector<std::shared_ptr<const StreamInfo>> streams_;
|
||||
std::vector<uint8_t> current_key_id_;
|
||||
bool encryption_started_ = false;
|
||||
bool cancelled_;
|
||||
bool cancelled_ = false;
|
||||
|
||||
std::unique_ptr<MuxerListener> muxer_listener_;
|
||||
std::unique_ptr<ProgressListener> progress_listener_;
|
||||
// An external injected clock, can be NULL.
|
||||
base::Clock* clock_;
|
||||
base::Clock* clock_ = nullptr;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(Muxer);
|
||||
// In VOD single segment case with Ad Cues, |output_file_name| is allowed to
|
||||
// be a template. In this case, there will be NumAdCues + 1 files generated.
|
||||
std::string output_file_template_;
|
||||
size_t output_file_index_ = 0;
|
||||
};
|
||||
|
||||
} // namespace media
|
||||
|
|
|
@ -160,6 +160,26 @@ void SetMediaInfoMuxerOptions(const MuxerOptions& muxer_options,
|
|||
}
|
||||
}
|
||||
|
||||
// Adjust MediaInfo for compatibility comparison. MediaInfos are considered to
|
||||
// be compatible if codec and container are the same.
|
||||
MediaInfo GetCompatibleComparisonMediaInfo(const MediaInfo& media_info) {
|
||||
MediaInfo adjusted_media_info;
|
||||
adjusted_media_info.set_reference_time_scale(
|
||||
media_info.reference_time_scale());
|
||||
adjusted_media_info.set_container_type(media_info.container_type());
|
||||
if (media_info.has_video_info()) {
|
||||
*adjusted_media_info.mutable_video_info() = media_info.video_info();
|
||||
adjusted_media_info.mutable_video_info()->clear_frame_duration();
|
||||
}
|
||||
if (media_info.has_audio_info()) {
|
||||
*adjusted_media_info.mutable_audio_info() = media_info.audio_info();
|
||||
}
|
||||
if (media_info.has_text_info()) {
|
||||
*adjusted_media_info.mutable_text_info() = media_info.text_info();
|
||||
}
|
||||
return adjusted_media_info;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool GenerateMediaInfo(const MuxerOptions& muxer_options,
|
||||
|
@ -181,15 +201,9 @@ bool GenerateMediaInfo(const MuxerOptions& muxer_options,
|
|||
|
||||
bool IsMediaInfoCompatible(const MediaInfo& media_info1,
|
||||
const MediaInfo& media_info2) {
|
||||
return media_info1.reference_time_scale() ==
|
||||
media_info2.reference_time_scale() &&
|
||||
media_info1.container_type() == media_info2.container_type() &&
|
||||
MessageDifferencer::Equals(media_info1.video_info(),
|
||||
media_info2.video_info()) &&
|
||||
MessageDifferencer::Equals(media_info1.audio_info(),
|
||||
media_info2.audio_info()) &&
|
||||
MessageDifferencer::Equals(media_info1.text_info(),
|
||||
media_info2.text_info());
|
||||
return MessageDifferencer::Equals(
|
||||
GetCompatibleComparisonMediaInfo(media_info1),
|
||||
GetCompatibleComparisonMediaInfo(media_info2));
|
||||
}
|
||||
|
||||
bool SetVodInformation(const MuxerListener::MediaRanges& media_ranges,
|
||||
|
|
|
@ -219,6 +219,13 @@ Status ValidateStreamDescriptor(bool dump_stream_info,
|
|||
}
|
||||
}
|
||||
|
||||
if (stream.output.find('$') != std::string::npos) {
|
||||
// "$" is only allowed if the output file name is a template, which is
|
||||
// used to support one file per Representation per Period when there are
|
||||
// Ad Cues.
|
||||
RETURN_IF_ERROR(ValidateSegmentTemplate(stream.output));
|
||||
}
|
||||
|
||||
// There are some specifics that must be checked based on which format
|
||||
// we are writing to.
|
||||
const MediaContainerName output_format = GetOutputFormat(stream);
|
||||
|
|
Loading…
Reference in New Issue