Integrate CueAlignmentHandler
Also changed ChunkingHandler to be one-one handler. Issue: #355 Change-Id: Ie98a96bcc0ddded347699c9f333f604826976d11
This commit is contained in:
parent
e685c8a63a
commit
e1bb27f130
|
@ -806,8 +806,8 @@ class PackagerFunctionalTest(PackagerAppTest):
|
|||
self.assertPackageSuccess(
|
||||
self._GetStreams(['audio', 'video']),
|
||||
self._GetFlags(encryption=True, ad_cues='1.5'))
|
||||
self._DiffGold(self.output[0], 'bear-640x360-a-cenc-golden.mp4')
|
||||
self._DiffGold(self.output[1], 'bear-640x360-v-cenc-golden.mp4')
|
||||
self._DiffGold(self.output[0], 'bear-640x360-a-cenc-ad_cues-golden.mp4')
|
||||
self._DiffGold(self.output[1], 'bear-640x360-v-cenc-ad_cues-golden.mp4')
|
||||
self._DiffGold(self.mpd_output, 'bear-640x360-av-cenc-ad_cues-golden.mpd')
|
||||
self._VerifyDecryption(self.output[0], 'bear-640x360-a-demuxed-golden.mp4')
|
||||
self._VerifyDecryption(self.output[1], 'bear-640x360-v-golden.mp4')
|
||||
|
@ -1088,10 +1088,10 @@ class PackagerFunctionalTest(PackagerAppTest):
|
|||
self.assertPackageSuccess(
|
||||
self._GetStreams(['audio', 'video'], hls=True),
|
||||
self._GetFlags(encryption=True, output_hls=True, ad_cues='1.5'))
|
||||
self._DiffGold(self.output[0], 'bear-640x360-a-cenc-golden.mp4')
|
||||
self._DiffGold(self.output[1], 'bear-640x360-v-cenc-golden.mp4')
|
||||
self._DiffGold(self.output[0], 'bear-640x360-a-cenc-ad_cues-golden.mp4')
|
||||
self._DiffGold(self.output[1], 'bear-640x360-v-cenc-ad_cues-golden.mp4')
|
||||
self._DiffGold(self.hls_master_playlist_output,
|
||||
'bear-640x360-av-mp4-master-cenc-golden.m3u8')
|
||||
'bear-640x360-av-mp4-master-cenc-ad_cues-golden.m3u8')
|
||||
self._DiffGold(
|
||||
os.path.join(self.tmp_dir, 'audio.m3u8'),
|
||||
'bear-640x360-a-mp4-cenc-ad_cues-golden.m3u8')
|
||||
|
@ -1146,8 +1146,8 @@ class PackagerFunctionalTest(PackagerAppTest):
|
|||
self.assertPackageSuccess(
|
||||
self._GetStreams(['audio', 'video'], live=True),
|
||||
self._GetFlags(generate_static_mpd=True, ad_cues='1.5'))
|
||||
self._DiffLiveGold(self.output[0], 'bear-640x360-a-live-golden')
|
||||
self._DiffLiveGold(self.output[1], 'bear-640x360-v-live-golden')
|
||||
self._DiffLiveGold(self.output[0], 'bear-640x360-a-live-ad_cues-golden')
|
||||
self._DiffLiveGold(self.output[1], 'bear-640x360-v-live-ad_cues-golden')
|
||||
self._DiffGold(self.mpd_output,
|
||||
'bear-640x360-av-live-static-ad_cues-golden.mpd')
|
||||
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -7,7 +7,7 @@
|
|||
<ContentProtection schemeIdUri="urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b">
|
||||
<cenc:pssh>AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA==</cenc:pssh>
|
||||
</ContentProtection>
|
||||
<Representation id="0" bandwidth="79930" codecs="opus" mimeType="audio/mp4" audioSamplingRate="48000">
|
||||
<Representation id="0" bandwidth="81568" codecs="opus" mimeType="audio/mp4" audioSamplingRate="48000">
|
||||
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
|
||||
<BaseURL>output_audio.mp4</BaseURL>
|
||||
<SegmentBase indexRange="975-1042" timescale="1000000">
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<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.736S">
|
||||
<Period id="0">
|
||||
<AdaptationSet id="0" contentType="audio" subsegmentAlignment="true">
|
||||
<Representation id="0" bandwidth="75444" codecs="opus" mimeType="audio/webm" audioSamplingRate="48000">
|
||||
<Representation id="0" bandwidth="76531" codecs="opus" mimeType="audio/webm" audioSamplingRate="48000">
|
||||
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
|
||||
<BaseURL>output_audio.webm</BaseURL>
|
||||
<SegmentBase indexRange="323-371" timescale="1000000">
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,4 +1,4 @@
|
|||
bandwidth: 129185
|
||||
bandwidth: 129162
|
||||
audio_info {
|
||||
codec: "mp4a.40.2"
|
||||
sampling_frequency: 44100
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -3,12 +3,12 @@
|
|||
## Generated with https://github.com/google/shaka-packager version <tag>-<hash>-<test>
|
||||
#EXT-X-TARGETDURATION:2
|
||||
#EXT-X-PLAYLIST-TYPE:VOD
|
||||
#EXTINF:0.952,
|
||||
#EXTINF:0.975,
|
||||
output_audio-1.ts
|
||||
#EXT-X-DISCONTINUITY
|
||||
#EXT-X-KEY:METHOD=SAMPLE-AES,URI="data:text/plain;base64,MTIzNDU2Nzg5MDEyMzQ1Ng==",IV=0x3334353637383930,KEYFORMAT="identity"
|
||||
#EXTINF:0.998,
|
||||
output_audio-2.ts
|
||||
#EXTINF:0.813,
|
||||
#EXTINF:0.789,
|
||||
output_audio-3.ts
|
||||
#EXT-X-ENDLIST
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -3,9 +3,9 @@
|
|||
## Generated with https://github.com/google/shaka-packager version <tag>-<hash>-<test>
|
||||
#EXT-X-TARGETDURATION:2
|
||||
#EXT-X-PLAYLIST-TYPE:EVENT
|
||||
#EXTINF:0.952,
|
||||
#EXTINF:0.975,
|
||||
output_audio-1.ts
|
||||
#EXTINF:0.998,
|
||||
output_audio-2.ts
|
||||
#EXTINF:0.813,
|
||||
#EXTINF:0.789,
|
||||
output_audio-3.ts
|
||||
|
|
|
@ -3,12 +3,12 @@
|
|||
## Generated with https://github.com/google/shaka-packager version <tag>-<hash>-<test>
|
||||
#EXT-X-TARGETDURATION:2
|
||||
#EXT-X-PLAYLIST-TYPE:VOD
|
||||
#EXTINF:0.952,
|
||||
#EXTINF:0.975,
|
||||
output_audio-1.ts
|
||||
#EXT-X-DISCONTINUITY
|
||||
#EXT-X-KEY:METHOD=SAMPLE-AES,URI="skd://www.license.com/getkey?KeyId=31323334-3536-3738-3930-313233343536",KEYFORMATVERSIONS="1",KEYFORMAT="com.apple.streamingkeydelivery"
|
||||
#EXTINF:0.998,
|
||||
output_audio-2.ts
|
||||
#EXTINF:0.813,
|
||||
#EXTINF:0.789,
|
||||
output_audio-3.ts
|
||||
#EXT-X-ENDLIST
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -3,10 +3,10 @@
|
|||
## Generated with https://github.com/google/shaka-packager version <tag>-<hash>-<test>
|
||||
#EXT-X-TARGETDURATION:2
|
||||
#EXT-X-PLAYLIST-TYPE:VOD
|
||||
#EXTINF:0.952,
|
||||
#EXTINF:0.975,
|
||||
output_audio-1.ts
|
||||
#EXTINF:0.998,
|
||||
output_audio-2.ts
|
||||
#EXTINF:0.813,
|
||||
#EXTINF:0.789,
|
||||
output_audio-3.ts
|
||||
#EXT-X-ENDLIST
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -4,9 +4,9 @@
|
|||
#EXT-X-TARGETDURATION:2
|
||||
#EXT-X-MEDIA-SEQUENCE:1
|
||||
#EXT-X-DISCONTINUITY-SEQUENCE:1
|
||||
#EXT-X-KEY:METHOD=SAMPLE-AES,URI="data:text/plain;base64,MTIzNDU2Nzg5MDEyMzQ1Ng==",IV=0x3334353637383930,KEYFORMAT="identity"
|
||||
#EXT-X-KEY:METHOD=SAMPLE-AES,URI="data:text/plain;base64,MjM0NTY3ODkwMTIzNDU2MQ==",IV=0x3334353637383930,KEYFORMAT="identity"
|
||||
#EXTINF:0.998,
|
||||
output_audio-2.ts
|
||||
#EXT-X-KEY:METHOD=SAMPLE-AES,URI="data:text/plain;base64,MjM0NTY3ODkwMTIzNDU2MQ==",IV=0x3334353637383930,KEYFORMAT="identity"
|
||||
#EXTINF:0.813,
|
||||
#EXT-X-KEY:METHOD=SAMPLE-AES,URI="data:text/plain;base64,MzQ1Njc4OTAxMjM0NTYxMg==",IV=0x3334353637383930,KEYFORMAT="identity"
|
||||
#EXTINF:0.789,
|
||||
output_audio-3.ts
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -5,5 +5,5 @@
|
|||
#EXT-X-MEDIA-SEQUENCE:1
|
||||
#EXTINF:0.998,
|
||||
output_audio-2.ts
|
||||
#EXTINF:0.813,
|
||||
#EXTINF:0.789,
|
||||
output_audio-3.ts
|
||||
|
|
|
@ -5,14 +5,17 @@
|
|||
#EXT-X-PLAYLIST-TYPE:VOD
|
||||
#EXT-X-MAP:URI="output_audio.mp4",BYTERANGE="967@0"
|
||||
#EXT-X-KEY:METHOD=SAMPLE-AES-CTR,URI="data:text/plain;base64,MTIzNDU2Nzg5MDEyMzQ1Ng==",KEYFORMAT="identity"
|
||||
#EXTINF:0.998,
|
||||
#EXT-X-BYTERANGE:16279@1035
|
||||
#EXTINF:1.022,
|
||||
#EXT-X-BYTERANGE:16655@1047
|
||||
output_audio.mp4
|
||||
#EXTINF:0.998,
|
||||
#EXT-X-BYTERANGE:16674
|
||||
#EXT-X-BYTERANGE:16650
|
||||
output_audio.mp4
|
||||
#EXTINF:0.046,
|
||||
#EXT-X-BYTERANGE:1014
|
||||
output_audio.mp4
|
||||
#EXT-X-PLACEMENT-OPPORTUNITY
|
||||
#EXTINF:0.766,
|
||||
#EXT-X-BYTERANGE:10632
|
||||
#EXTINF:0.697,
|
||||
#EXT-X-BYTERANGE:9415
|
||||
output_audio.mp4
|
||||
#EXT-X-ENDLIST
|
||||
|
|
|
@ -5,13 +5,13 @@
|
|||
#EXT-X-PLAYLIST-TYPE:VOD
|
||||
#EXT-X-MAP:URI="output_audio.mp4",BYTERANGE="967@0"
|
||||
#EXT-X-KEY:METHOD=SAMPLE-AES-CTR,URI="data:text/plain;base64,MTIzNDU2Nzg5MDEyMzQ1Ng==",KEYFORMAT="identity"
|
||||
#EXTINF:0.998,
|
||||
#EXT-X-BYTERANGE:16279@1035
|
||||
#EXTINF:1.022,
|
||||
#EXT-X-BYTERANGE:16655@1035
|
||||
output_audio.mp4
|
||||
#EXTINF:0.998,
|
||||
#EXT-X-BYTERANGE:16674
|
||||
#EXT-X-BYTERANGE:16650
|
||||
output_audio.mp4
|
||||
#EXTINF:0.766,
|
||||
#EXT-X-BYTERANGE:10632
|
||||
#EXTINF:0.743,
|
||||
#EXT-X-BYTERANGE:10272
|
||||
output_audio.mp4
|
||||
#EXT-X-ENDLIST
|
||||
|
|
|
@ -4,10 +4,10 @@
|
|||
#EXT-X-TARGETDURATION:2
|
||||
#EXT-X-PLAYLIST-TYPE:VOD
|
||||
#EXT-X-MAP:URI="audio-init.mp4"
|
||||
#EXTINF:0.998,
|
||||
#EXTINF:1.022,
|
||||
audio-1.m4s
|
||||
#EXTINF:0.998,
|
||||
audio-2.m4s
|
||||
#EXTINF:0.766,
|
||||
#EXTINF:0.743,
|
||||
audio-3.m4s
|
||||
#EXT-X-ENDLIST
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -7,8 +7,8 @@
|
|||
output_audio-1.ts
|
||||
#EXT-X-DISCONTINUITY
|
||||
#EXT-X-KEY:METHOD=SAMPLE-AES,URI="data:text/plain;base64,MTIzNDU2Nzg5MDEyMzQ1Ng==",IV=0x3334353637383930,KEYFORMAT="identity"
|
||||
#EXTINF:0.975,
|
||||
#EXTINF:1.010,
|
||||
output_audio-2.ts
|
||||
#EXTINF:0.836,
|
||||
#EXTINF:0.801,
|
||||
output_audio-3.ts
|
||||
#EXT-X-ENDLIST
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -5,8 +5,8 @@
|
|||
#EXT-X-PLAYLIST-TYPE:VOD
|
||||
#EXTINF:0.975,
|
||||
output_audio-1.ts
|
||||
#EXTINF:0.975,
|
||||
#EXTINF:1.010,
|
||||
output_audio-2.ts
|
||||
#EXTINF:0.836,
|
||||
#EXTINF:0.801,
|
||||
output_audio-3.ts
|
||||
#EXT-X-ENDLIST
|
||||
|
|
|
@ -7,10 +7,10 @@
|
|||
#EXTINF:0.975,
|
||||
#EXT-X-BYTERANGE:23728@794
|
||||
output_audio.mp4
|
||||
#EXTINF:0.975,
|
||||
#EXT-X-BYTERANGE:23730
|
||||
#EXTINF:1.010,
|
||||
#EXT-X-BYTERANGE:24574
|
||||
output_audio.mp4
|
||||
#EXTINF:0.836,
|
||||
#EXT-X-BYTERANGE:20354
|
||||
#EXTINF:0.801,
|
||||
#EXT-X-BYTERANGE:19510
|
||||
output_audio.mp4
|
||||
#EXT-X-ENDLIST
|
||||
|
|
|
@ -3,5 +3,5 @@
|
|||
|
||||
#EXT-X-MEDIA:TYPE=AUDIO,URI="audio.m3u8",GROUP-ID="default-audio-group",NAME="stream_0",AUTOSELECT=YES,CHANNELS="2"
|
||||
|
||||
#EXT-X-STREAM-INF:BANDWIDTH=1242703,CODECS="avc1.64001e,ac-3",RESOLUTION=640x360,AUDIO="default-audio-group"
|
||||
#EXT-X-STREAM-INF:BANDWIDTH=1242861,CODECS="avc1.64001e,ac-3",RESOLUTION=640x360,AUDIO="default-audio-group"
|
||||
video.m3u8
|
||||
|
|
|
@ -3,5 +3,5 @@
|
|||
|
||||
#EXT-X-MEDIA:TYPE=AUDIO,URI="audio.m3u8",GROUP-ID="default-audio-group",NAME="stream_0",AUTOSELECT=YES,CHANNELS="2"
|
||||
|
||||
#EXT-X-STREAM-INF:BANDWIDTH=1168277,CODECS="avc1.64001e,ac-3",RESOLUTION=640x360,AUDIO="default-audio-group"
|
||||
#EXT-X-STREAM-INF:BANDWIDTH=1168319,CODECS="avc1.64001e,ac-3",RESOLUTION=640x360,AUDIO="default-audio-group"
|
||||
video.m3u8
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
<ContentProtection schemeIdUri="urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b">
|
||||
<cenc:pssh>AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA==</cenc:pssh>
|
||||
</ContentProtection>
|
||||
<Representation id="1" bandwidth="129185" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
|
||||
<Representation id="1" bandwidth="129162" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
|
||||
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
|
||||
<BaseURL>output_audio.mp4</BaseURL>
|
||||
<SegmentBase indexRange="967-1034" timescale="44100">
|
||||
|
|
|
@ -2,57 +2,57 @@
|
|||
<!--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.73607S">
|
||||
<Period id="0" duration="PT2.002S">
|
||||
<AdaptationSet id="0" contentType="video" width="640" height="360" frameRate="30000/1001" subsegmentAlignment="true" par="16:9">
|
||||
<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="885590" codecs="avc1.64001e" mimeType="video/mp4" sar="1:1">
|
||||
<Representation id="0" bandwidth="129651" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
|
||||
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
|
||||
<BaseURL>output_audio.mp4</BaseURL>
|
||||
<SegmentBase indexRange="967-1046" timescale="44100">
|
||||
<Initialization range="0-966"/>
|
||||
</SegmentBase>
|
||||
</Representation>
|
||||
</AdaptationSet>
|
||||
<AdaptationSet id="2" 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="1" bandwidth="885590" codecs="avc1.64001e" mimeType="video/mp4" sar="1:1">
|
||||
<BaseURL>output_video.mp4</BaseURL>
|
||||
<SegmentBase indexRange="1091-1158" timescale="30000">
|
||||
<Initialization range="0-1090"/>
|
||||
</SegmentBase>
|
||||
</Representation>
|
||||
</AdaptationSet>
|
||||
<AdaptationSet id="2" contentType="audio" subsegmentAlignment="true">
|
||||
</Period>
|
||||
<Period id="1" duration="PT0.734067S">
|
||||
<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="1" bandwidth="129185" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
|
||||
<Representation id="0" bandwidth="129651" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
|
||||
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
|
||||
<BaseURL>output_audio.mp4</BaseURL>
|
||||
<SegmentBase indexRange="967-1034" timescale="44100">
|
||||
<SegmentBase indexRange="967-1046" timescale="44100" presentationTimeOffset="91230">
|
||||
<Initialization range="0-966"/>
|
||||
</SegmentBase>
|
||||
</Representation>
|
||||
</AdaptationSet>
|
||||
</Period>
|
||||
<Period id="1" duration="PT0.734067S">
|
||||
<AdaptationSet id="0" contentType="video" width="640" height="360" frameRate="30000/1001" subsegmentAlignment="true" par="16:9">
|
||||
<AdaptationSet id="2" 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="885590" codecs="avc1.64001e" mimeType="video/mp4" sar="1:1">
|
||||
<Representation id="1" bandwidth="885590" codecs="avc1.64001e" mimeType="video/mp4" sar="1:1">
|
||||
<BaseURL>output_video.mp4</BaseURL>
|
||||
<SegmentBase indexRange="1091-1158" timescale="30000" presentationTimeOffset="62061">
|
||||
<Initialization range="0-1090"/>
|
||||
</SegmentBase>
|
||||
</Representation>
|
||||
</AdaptationSet>
|
||||
<AdaptationSet id="2" 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="129185" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
|
||||
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
|
||||
<BaseURL>output_audio.mp4</BaseURL>
|
||||
<SegmentBase indexRange="967-1034" timescale="44100" presentationTimeOffset="91230">
|
||||
<Initialization range="0-966"/>
|
||||
</SegmentBase>
|
||||
</Representation>
|
||||
</AdaptationSet>
|
||||
</Period>
|
||||
</MPD>
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
<ContentProtection schemeIdUri="urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b">
|
||||
<cenc:pssh>AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA==</cenc:pssh>
|
||||
</ContentProtection>
|
||||
<Representation id="1" bandwidth="129185" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
|
||||
<Representation id="1" bandwidth="129162" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
|
||||
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
|
||||
<BaseURL>output_audio.mp4</BaseURL>
|
||||
<SegmentBase indexRange="967-1034" timescale="44100">
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
<ContentProtection schemeIdUri="urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b">
|
||||
<cenc:pssh>AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA==</cenc:pssh>
|
||||
</ContentProtection>
|
||||
<Representation id="1" bandwidth="129035" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
|
||||
<Representation id="1" bandwidth="129012" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
|
||||
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
|
||||
<BaseURL>output_audio.mp4</BaseURL>
|
||||
<SegmentBase indexRange="915-982" timescale="44100">
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
</Representation>
|
||||
</AdaptationSet>
|
||||
<AdaptationSet id="1" contentType="audio" subsegmentAlignment="true">
|
||||
<Representation id="1" bandwidth="129185" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
|
||||
<Representation id="1" bandwidth="129162" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
|
||||
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
|
||||
<ContentProtection value="cenc" schemeIdUri="urn:mpeg:dash:mp4protection:2011" cenc:default_KID="31323334-3536-3738-3930-313233343536"/>
|
||||
<ContentProtection schemeIdUri="urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b">
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
<ContentProtection schemeIdUri="urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b">
|
||||
<cenc:pssh>AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA==</cenc:pssh>
|
||||
</ContentProtection>
|
||||
<Representation id="1" bandwidth="129185" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
|
||||
<Representation id="1" bandwidth="129162" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
|
||||
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
|
||||
<BaseURL>output_audio.mp4</BaseURL>
|
||||
<SegmentBase indexRange="967-1034" timescale="44100">
|
||||
|
|
|
@ -21,12 +21,13 @@
|
|||
<ContentProtection schemeIdUri="urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b">
|
||||
<cenc:pssh>AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA==</cenc:pssh>
|
||||
</ContentProtection>
|
||||
<Representation id="1" bandwidth="124859" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
|
||||
<Representation id="1" bandwidth="124634" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
|
||||
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
|
||||
<SegmentTemplate timescale="44100" initialization="output_audio-init.mp4" media="output_audio-$Number$.m4s" startNumber="1">
|
||||
<SegmentTimeline>
|
||||
<S t="0" d="44032" r="1"/>
|
||||
<S t="88064" d="33792"/>
|
||||
<S t="0" d="45056"/>
|
||||
<S t="45056" d="44032"/>
|
||||
<S t="89088" d="32768"/>
|
||||
</SegmentTimeline>
|
||||
</SegmentTemplate>
|
||||
</Representation>
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
</Representation>
|
||||
</AdaptationSet>
|
||||
<AdaptationSet id="1" contentType="audio" segmentAlignment="true">
|
||||
<Representation id="1" bandwidth="124859" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
|
||||
<Representation id="1" bandwidth="124634" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
|
||||
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
|
||||
<ContentProtection value="cenc" schemeIdUri="urn:mpeg:dash:mp4protection:2011" cenc:default_KID="31323334-3536-3738-3930-313233343536"/>
|
||||
<ContentProtection schemeIdUri="urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b">
|
||||
|
@ -25,8 +25,9 @@
|
|||
</ContentProtection>
|
||||
<SegmentTemplate timescale="44100" initialization="output_audio-init.mp4" media="output_audio-$Number$.m4s" startNumber="1">
|
||||
<SegmentTimeline>
|
||||
<S t="0" d="44032" r="1"/>
|
||||
<S t="88064" d="33792"/>
|
||||
<S t="0" d="45056"/>
|
||||
<S t="45056" d="44032"/>
|
||||
<S t="89088" d="32768"/>
|
||||
</SegmentTimeline>
|
||||
</SegmentTemplate>
|
||||
</Representation>
|
||||
|
|
|
@ -17,12 +17,13 @@
|
|||
<AdaptationSet id="1" contentType="audio" segmentAlignment="true">
|
||||
<ContentProtection value="cenc" schemeIdUri="urn:mpeg:dash:mp4protection:2011"/>
|
||||
<ContentProtection schemeIdUri="urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b"/>
|
||||
<Representation id="1" bandwidth="125808" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
|
||||
<Representation id="1" bandwidth="125598" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
|
||||
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
|
||||
<SegmentTemplate timescale="44100" initialization="output_audio-init.mp4" media="output_audio-$Number$.m4s" startNumber="1">
|
||||
<SegmentTimeline>
|
||||
<S t="0" d="44032" r="1"/>
|
||||
<S t="88064" d="33792"/>
|
||||
<S t="0" d="45056"/>
|
||||
<S t="45056" d="44032"/>
|
||||
<S t="89088" d="32768"/>
|
||||
</SegmentTimeline>
|
||||
</SegmentTemplate>
|
||||
</Representation>
|
||||
|
|
|
@ -17,12 +17,13 @@
|
|||
<AdaptationSet id="1" contentType="audio" segmentAlignment="true">
|
||||
<ContentProtection value="cenc" schemeIdUri="urn:mpeg:dash:mp4protection:2011"/>
|
||||
<ContentProtection schemeIdUri="urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b"/>
|
||||
<Representation id="1" bandwidth="125337" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
|
||||
<Representation id="1" bandwidth="125122" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
|
||||
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
|
||||
<SegmentTemplate timescale="44100" initialization="output_audio-init.mp4" media="output_audio-$Number$.m4s" startNumber="1">
|
||||
<SegmentTimeline>
|
||||
<S t="0" d="44032" r="1"/>
|
||||
<S t="88064" d="33792"/>
|
||||
<S t="0" d="45056"/>
|
||||
<S t="45056" d="44032"/>
|
||||
<S t="89088" d="32768"/>
|
||||
</SegmentTimeline>
|
||||
</SegmentTemplate>
|
||||
</Representation>
|
||||
|
|
|
@ -15,14 +15,15 @@
|
|||
</Representation>
|
||||
</AdaptationSet>
|
||||
<AdaptationSet id="1" contentType="audio" segmentAlignment="true">
|
||||
<Representation id="1" bandwidth="125808" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
|
||||
<Representation id="1" bandwidth="125598" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
|
||||
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
|
||||
<ContentProtection value="cenc" schemeIdUri="urn:mpeg:dash:mp4protection:2011"/>
|
||||
<ContentProtection schemeIdUri="urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b"/>
|
||||
<SegmentTemplate timescale="44100" initialization="output_audio-init.mp4" media="output_audio-$Number$.m4s" startNumber="1">
|
||||
<SegmentTimeline>
|
||||
<S t="0" d="44032" r="1"/>
|
||||
<S t="88064" d="33792"/>
|
||||
<S t="0" d="45056"/>
|
||||
<S t="45056" d="44032"/>
|
||||
<S t="89088" d="32768"/>
|
||||
</SegmentTimeline>
|
||||
</SegmentTemplate>
|
||||
</Representation>
|
||||
|
|
|
@ -13,12 +13,13 @@
|
|||
</Representation>
|
||||
</AdaptationSet>
|
||||
<AdaptationSet id="1" contentType="audio" segmentAlignment="true">
|
||||
<Representation id="1" bandwidth="122544" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
|
||||
<Representation id="1" bandwidth="122308" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
|
||||
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
|
||||
<SegmentTemplate timescale="44100" initialization="output_audio-init.mp4" media="output_audio-$Number$.m4s" startNumber="1">
|
||||
<SegmentTimeline>
|
||||
<S t="0" d="44032" r="1"/>
|
||||
<S t="88064" d="33792"/>
|
||||
<S t="0" d="45056"/>
|
||||
<S t="45056" d="44032"/>
|
||||
<S t="89088" d="32768"/>
|
||||
</SegmentTimeline>
|
||||
</SegmentTemplate>
|
||||
</Representation>
|
||||
|
|
|
@ -12,27 +12,19 @@
|
|||
</Representation>
|
||||
</AdaptationSet>
|
||||
<AdaptationSet id="1" contentType="audio" segmentAlignment="true">
|
||||
<Representation id="1" bandwidth="131035" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
|
||||
<Representation id="1" bandwidth="143117" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
|
||||
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
|
||||
<SegmentTemplate timescale="44100" initialization="output_audio-init.mp4" media="output_audio-$Number$.m4s" startNumber="1">
|
||||
<SegmentTimeline>
|
||||
<S t="0" d="44032" r="1"/>
|
||||
<S t="0" d="45056"/>
|
||||
<S t="45056" d="44032"/>
|
||||
<S t="89088" d="2048"/>
|
||||
</SegmentTimeline>
|
||||
</SegmentTemplate>
|
||||
</Representation>
|
||||
</AdaptationSet>
|
||||
</Period>
|
||||
<Period id="1" duration="PT0.734067S">
|
||||
<AdaptationSet id="1" contentType="audio" segmentAlignment="true">
|
||||
<Representation id="1" bandwidth="108486" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
|
||||
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
|
||||
<SegmentTemplate timescale="44100" presentationTimeOffset="91230" initialization="output_audio-init.mp4" media="output_audio-$Number$.m4s" startNumber="3">
|
||||
<SegmentTimeline>
|
||||
<S t="88064" d="33792"/>
|
||||
</SegmentTimeline>
|
||||
</SegmentTemplate>
|
||||
</Representation>
|
||||
</AdaptationSet>
|
||||
<AdaptationSet id="0" contentType="video" width="640" height="360" frameRate="30000/1001" segmentAlignment="true" par="16:9">
|
||||
<Representation id="0" bandwidth="869044" codecs="avc1.64001e" mimeType="video/mp4" sar="1:1">
|
||||
<SegmentTemplate timescale="30000" presentationTimeOffset="62061" initialization="output_video-init.mp4" media="output_video-$Number$.m4s" startNumber="3">
|
||||
|
@ -42,5 +34,15 @@
|
|||
</SegmentTemplate>
|
||||
</Representation>
|
||||
</AdaptationSet>
|
||||
<AdaptationSet id="1" contentType="audio" segmentAlignment="true">
|
||||
<Representation id="1" bandwidth="105634" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
|
||||
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
|
||||
<SegmentTemplate timescale="44100" presentationTimeOffset="91230" initialization="output_audio-init.mp4" media="output_audio-$Number$.m4s" startNumber="4">
|
||||
<SegmentTimeline>
|
||||
<S t="91136" d="30720"/>
|
||||
</SegmentTimeline>
|
||||
</SegmentTemplate>
|
||||
</Representation>
|
||||
</AdaptationSet>
|
||||
</Period>
|
||||
</MPD>
|
||||
|
|
|
@ -13,12 +13,13 @@
|
|||
</Representation>
|
||||
</AdaptationSet>
|
||||
<AdaptationSet id="1" contentType="audio" segmentAlignment="true">
|
||||
<Representation id="1" bandwidth="122544" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
|
||||
<Representation id="1" bandwidth="122308" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
|
||||
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
|
||||
<SegmentTemplate timescale="44100" initialization="output_audio-init.mp4" media="output_audio-$Number$.m4s" startNumber="1">
|
||||
<SegmentTimeline>
|
||||
<S t="0" d="44032" r="1"/>
|
||||
<S t="88064" d="33792"/>
|
||||
<S t="0" d="45056"/>
|
||||
<S t="45056" d="44032"/>
|
||||
<S t="89088" d="32768"/>
|
||||
</SegmentTimeline>
|
||||
</SegmentTemplate>
|
||||
</Representation>
|
||||
|
|
7
packager/app/test/testdata/bear-640x360-av-mp4-master-cenc-ad_cues-golden.m3u8
vendored
Normal file
7
packager/app/test/testdata/bear-640x360-av-mp4-master-cenc-ad_cues-golden.m3u8
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
#EXTM3U
|
||||
## Generated with https://github.com/google/shaka-packager version <tag>-<hash>-<test>
|
||||
|
||||
#EXT-X-MEDIA:TYPE=AUDIO,URI="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"
|
||||
video.m3u8
|
|
@ -3,5 +3,5 @@
|
|||
|
||||
#EXT-X-MEDIA:TYPE=AUDIO,URI="audio.m3u8",GROUP-ID="default-audio-group",NAME="stream_0",AUTOSELECT=YES,CHANNELS="2"
|
||||
|
||||
#EXT-X-STREAM-INF:BANDWIDTH=1111340,CODECS="avc1.64001e,mp4a.40.2",RESOLUTION=640x360,AUDIO="default-audio-group"
|
||||
#EXT-X-STREAM-INF:BANDWIDTH=1111147,CODECS="avc1.64001e,mp4a.40.2",RESOLUTION=640x360,AUDIO="default-audio-group"
|
||||
video.m3u8
|
||||
|
|
|
@ -3,5 +3,5 @@
|
|||
|
||||
#EXT-X-MEDIA:TYPE=AUDIO,URI="audio/audio.m3u8",GROUP-ID="default-audio-group",NAME="stream_0",AUTOSELECT=YES,CHANNELS="2"
|
||||
|
||||
#EXT-X-STREAM-INF:BANDWIDTH=1105163,CODECS="avc1.64001e,mp4a.40.2",RESOLUTION=640x360,AUDIO="default-audio-group"
|
||||
#EXT-X-STREAM-INF:BANDWIDTH=1105129,CODECS="avc1.64001e,mp4a.40.2",RESOLUTION=640x360,AUDIO="default-audio-group"
|
||||
video/video.m3u8
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
<ContentProtection schemeIdUri="urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b">
|
||||
<cenc:pssh>AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA==</cenc:pssh>
|
||||
</ContentProtection>
|
||||
<Representation id="2" bandwidth="129185" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
|
||||
<Representation id="2" bandwidth="129162" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
|
||||
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
|
||||
<BaseURL>output_audio.mp4</BaseURL>
|
||||
<SegmentBase indexRange="967-1034" timescale="44100">
|
||||
|
|
|
@ -38,7 +38,7 @@
|
|||
<ContentProtection schemeIdUri="urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b">
|
||||
<cenc:pssh>AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA==</cenc:pssh>
|
||||
</ContentProtection>
|
||||
<Representation id="3" bandwidth="129185" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
|
||||
<Representation id="3" bandwidth="129162" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
|
||||
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
|
||||
<BaseURL>output_audio.mp4</BaseURL>
|
||||
<SegmentBase indexRange="967-1034" timescale="44100">
|
||||
|
|
Binary file not shown.
|
@ -8,10 +8,10 @@
|
|||
#EXTINF:1.010,
|
||||
#EXT-X-BYTERANGE:24460@977
|
||||
output_audio.mp4
|
||||
#EXTINF:0.975,
|
||||
#EXT-X-BYTERANGE:23899
|
||||
#EXTINF:1.010,
|
||||
#EXT-X-BYTERANGE:24747
|
||||
output_audio.mp4
|
||||
#EXTINF:0.766,
|
||||
#EXT-X-BYTERANGE:18811
|
||||
#EXTINF:0.731,
|
||||
#EXT-X-BYTERANGE:17963
|
||||
output_audio.mp4
|
||||
#EXT-X-ENDLIST
|
||||
|
|
|
@ -3,5 +3,5 @@
|
|||
|
||||
#EXT-X-MEDIA:TYPE=AUDIO,URI="audio.m3u8",GROUP-ID="default-audio-group",NAME="stream_0",AUTOSELECT=YES,CHANNELS="2"
|
||||
|
||||
#EXT-X-STREAM-INF:BANDWIDTH=1174135,CODECS="avc1.64001e,ec-3",RESOLUTION=640x360,AUDIO="default-audio-group"
|
||||
#EXT-X-STREAM-INF:BANDWIDTH=1174212,CODECS="avc1.64001e,ec-3",RESOLUTION=640x360,AUDIO="default-audio-group"
|
||||
video.m3u8
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -49,7 +49,7 @@ bool MediaHandler::ValidateOutputStreamIndex(size_t stream_index) const {
|
|||
return stream_index < num_input_streams_;
|
||||
}
|
||||
|
||||
Status MediaHandler::Dispatch(std::unique_ptr<StreamData> stream_data) {
|
||||
Status MediaHandler::Dispatch(std::unique_ptr<StreamData> stream_data) const {
|
||||
size_t output_stream_index = stream_data->stream_index;
|
||||
auto handler_it = output_handlers_.find(output_stream_index);
|
||||
if (handler_it == output_handlers_.end()) {
|
||||
|
|
|
@ -185,43 +185,54 @@ class MediaHandler {
|
|||
|
||||
/// Dispatch the stream data to downstream handlers. Note that
|
||||
/// stream_data.stream_index should be the output stream index.
|
||||
Status Dispatch(std::unique_ptr<StreamData> stream_data);
|
||||
Status Dispatch(std::unique_ptr<StreamData> stream_data) const;
|
||||
|
||||
/// Dispatch the stream info to downstream handlers.
|
||||
Status DispatchStreamInfo(
|
||||
size_t stream_index, std::shared_ptr<const StreamInfo> stream_info) {
|
||||
return Dispatch(StreamData::FromStreamInfo(stream_index, stream_info));
|
||||
size_t stream_index,
|
||||
std::shared_ptr<const StreamInfo> stream_info) const {
|
||||
return Dispatch(
|
||||
StreamData::FromStreamInfo(stream_index, std::move(stream_info)));
|
||||
}
|
||||
|
||||
/// Dispatch the media sample to downstream handlers.
|
||||
Status DispatchMediaSample(
|
||||
size_t stream_index, std::shared_ptr<const MediaSample> media_sample) {
|
||||
return Dispatch(StreamData::FromMediaSample(stream_index, media_sample));
|
||||
size_t stream_index,
|
||||
std::shared_ptr<const MediaSample> media_sample) const {
|
||||
return Dispatch(
|
||||
StreamData::FromMediaSample(stream_index, std::move(media_sample)));
|
||||
}
|
||||
|
||||
/// Dispatch the text sample to downsream handlers.
|
||||
// DispatchTextSample should only be override for testing.
|
||||
Status DispatchTextSample(
|
||||
size_t stream_index, std::shared_ptr<const TextSample> text_sample) {
|
||||
return Dispatch(StreamData::FromTextSample(stream_index, text_sample));
|
||||
size_t stream_index,
|
||||
std::shared_ptr<const TextSample> text_sample) const {
|
||||
return Dispatch(
|
||||
StreamData::FromTextSample(stream_index, std::move(text_sample)));
|
||||
}
|
||||
|
||||
/// Dispatch the segment info to downstream handlers.
|
||||
Status DispatchSegmentInfo(
|
||||
size_t stream_index, std::shared_ptr<const SegmentInfo> segment_info) {
|
||||
return Dispatch(StreamData::FromSegmentInfo(stream_index, segment_info));
|
||||
size_t stream_index,
|
||||
std::shared_ptr<const SegmentInfo> segment_info) const {
|
||||
return Dispatch(
|
||||
StreamData::FromSegmentInfo(stream_index, std::move(segment_info)));
|
||||
}
|
||||
|
||||
/// Dispatch the scte35 event to downstream handlers.
|
||||
Status DispatchScte35Event(size_t stream_index,
|
||||
std::shared_ptr<const Scte35Event> scte35_event) {
|
||||
return Dispatch(StreamData::FromScte35Event(stream_index, scte35_event));
|
||||
Status DispatchScte35Event(
|
||||
size_t stream_index,
|
||||
std::shared_ptr<const Scte35Event> scte35_event) const {
|
||||
return Dispatch(
|
||||
StreamData::FromScte35Event(stream_index, std::move(scte35_event)));
|
||||
}
|
||||
|
||||
/// Dispatch the cue event to downstream handlers.
|
||||
Status DispatchCueEvent(size_t stream_index,
|
||||
std::shared_ptr<const CueEvent> cue_event) {
|
||||
return Dispatch(StreamData::FromCueEvent(stream_index, cue_event));
|
||||
std::shared_ptr<const CueEvent> cue_event) const {
|
||||
return Dispatch(
|
||||
StreamData::FromCueEvent(stream_index, std::move(cue_event)));
|
||||
}
|
||||
|
||||
/// Flush the downstream connected at the specified output stream index.
|
||||
|
|
|
@ -9,46 +9,38 @@
|
|||
#include <algorithm>
|
||||
|
||||
#include "packager/base/logging.h"
|
||||
#include "packager/base/threading/platform_thread.h"
|
||||
#include "packager/media/base/media_sample.h"
|
||||
|
||||
namespace shaka {
|
||||
namespace media {
|
||||
namespace {
|
||||
int64_t kThreadIdUnset = -1;
|
||||
const size_t kStreamIndex = 0;
|
||||
} // namespace
|
||||
|
||||
ChunkingHandler::ChunkingHandler(const ChunkingParams& chunking_params)
|
||||
: chunking_params_(chunking_params),
|
||||
thread_id_(kThreadIdUnset),
|
||||
media_sample_comparator_(this),
|
||||
cached_media_sample_stream_data_(media_sample_comparator_) {
|
||||
: chunking_params_(chunking_params) {
|
||||
CHECK_NE(chunking_params.segment_duration_in_seconds, 0u);
|
||||
}
|
||||
|
||||
ChunkingHandler::~ChunkingHandler() {}
|
||||
|
||||
Status ChunkingHandler::InitializeInternal() {
|
||||
segment_info_.resize(num_input_streams());
|
||||
subsegment_info_.resize(num_input_streams());
|
||||
time_scales_.resize(num_input_streams());
|
||||
last_sample_end_timestamps_.resize(num_input_streams());
|
||||
num_cached_samples_.resize(num_input_streams());
|
||||
if (num_input_streams() != 1 || next_output_stream_index() != 1) {
|
||||
return Status(error::INVALID_ARGUMENT,
|
||||
"Expects exactly one input and one output.");
|
||||
}
|
||||
return Status::OK;
|
||||
}
|
||||
|
||||
Status ChunkingHandler::Process(std::unique_ptr<StreamData> stream_data) {
|
||||
switch (stream_data->stream_data_type) {
|
||||
case StreamDataType::kStreamInfo:
|
||||
return OnStreamInfo(stream_data->stream_index, stream_data->stream_info);
|
||||
case StreamDataType::kScte35Event:
|
||||
return OnScte35Event(stream_data->stream_index,
|
||||
stream_data->scte35_event);
|
||||
return OnStreamInfo(std::move(stream_data->stream_info));
|
||||
case StreamDataType::kCueEvent:
|
||||
return OnCueEvent(std::move(stream_data->cue_event));
|
||||
case StreamDataType::kSegmentInfo:
|
||||
VLOG(3) << "Droppping existing segment info.";
|
||||
return Status::OK;
|
||||
case StreamDataType::kMediaSample:
|
||||
return OnMediaSample(std::move(stream_data));
|
||||
return OnMediaSample(std::move(stream_data->media_sample));
|
||||
default:
|
||||
VLOG(3) << "Stream data type "
|
||||
<< static_cast<int>(stream_data->stream_data_type) << " ignored.";
|
||||
|
@ -57,289 +49,103 @@ Status ChunkingHandler::Process(std::unique_ptr<StreamData> stream_data) {
|
|||
}
|
||||
|
||||
Status ChunkingHandler::OnFlushRequest(size_t input_stream_index) {
|
||||
// Process all cached samples.
|
||||
while (!cached_media_sample_stream_data_.empty()) {
|
||||
Status status =
|
||||
ProcessMediaSampleStreamData(*cached_media_sample_stream_data_.top());
|
||||
if (!status.ok())
|
||||
return status;
|
||||
--num_cached_samples_[cached_media_sample_stream_data_.top()->stream_index];
|
||||
cached_media_sample_stream_data_.pop();
|
||||
}
|
||||
if (segment_info_[input_stream_index]) {
|
||||
auto& segment_info = segment_info_[input_stream_index];
|
||||
if (segment_info->start_timestamp != -1) {
|
||||
segment_info->duration = last_sample_end_timestamps_[input_stream_index] -
|
||||
segment_info->start_timestamp;
|
||||
Status status =
|
||||
DispatchSegmentInfo(input_stream_index, std::move(segment_info));
|
||||
if (!status.ok())
|
||||
return status;
|
||||
}
|
||||
}
|
||||
const size_t output_stream_index = input_stream_index;
|
||||
return FlushDownstream(output_stream_index);
|
||||
Status status = EndSegmentIfStarted();
|
||||
if (!status.ok())
|
||||
return status;
|
||||
return FlushDownstream(kStreamIndex);
|
||||
}
|
||||
|
||||
Status ChunkingHandler::OnStreamInfo(uint64_t stream_index,
|
||||
std::shared_ptr<const StreamInfo> info) {
|
||||
// Make sure the inputs come from the same thread.
|
||||
const int64_t thread_id =
|
||||
static_cast<int64_t>(base::PlatformThread::CurrentId());
|
||||
|
||||
int64_t expected = kThreadIdUnset;
|
||||
if (!thread_id_.compare_exchange_strong(expected, thread_id) &&
|
||||
expected != thread_id) {
|
||||
return Status(error::CHUNKING_ERROR,
|
||||
"Inputs should come from the same thread.");
|
||||
}
|
||||
|
||||
const auto time_scale = info->time_scale();
|
||||
time_scales_[stream_index] = time_scale;
|
||||
|
||||
// The video stream is treated as the main stream. If there is only one
|
||||
// stream, it is the main stream.
|
||||
const bool is_main_stream =
|
||||
main_stream_index_ == kInvalidStreamIndex &&
|
||||
(info->stream_type() == kStreamVideo || num_input_streams() == 1);
|
||||
if (is_main_stream) {
|
||||
main_stream_index_ = stream_index;
|
||||
segment_duration_ =
|
||||
chunking_params_.segment_duration_in_seconds * time_scale;
|
||||
subsegment_duration_ =
|
||||
chunking_params_.subsegment_duration_in_seconds * time_scale;
|
||||
} else if (info->stream_type() == kStreamVideo) {
|
||||
return Status(error::CHUNKING_ERROR,
|
||||
"Only one video stream is allowed per chunking handler.");
|
||||
}
|
||||
|
||||
return DispatchStreamInfo(stream_index, std::move(info));
|
||||
Status ChunkingHandler::OnStreamInfo(std::shared_ptr<const StreamInfo> info) {
|
||||
time_scale_ = info->time_scale();
|
||||
segment_duration_ =
|
||||
chunking_params_.segment_duration_in_seconds * time_scale_;
|
||||
subsegment_duration_ =
|
||||
chunking_params_.subsegment_duration_in_seconds * time_scale_;
|
||||
return DispatchStreamInfo(kStreamIndex, std::move(info));
|
||||
}
|
||||
|
||||
Status ChunkingHandler::OnScte35Event(
|
||||
uint64_t stream_index,
|
||||
std::shared_ptr<const Scte35Event> event) {
|
||||
if (stream_index == main_stream_index_) {
|
||||
scte35_events_.push(std::move(event));
|
||||
} else {
|
||||
VLOG(3) << "Dropping scte35 event from non main stream.";
|
||||
}
|
||||
|
||||
return Status::OK;
|
||||
Status ChunkingHandler::OnCueEvent(std::shared_ptr<const CueEvent> event) {
|
||||
Status status = EndSegmentIfStarted();
|
||||
if (!status.ok())
|
||||
return status;
|
||||
// Force start new segment after cue event.
|
||||
segment_start_time_ = base::nullopt;
|
||||
return DispatchCueEvent(kStreamIndex, std::move(event));
|
||||
}
|
||||
|
||||
Status ChunkingHandler::OnMediaSample(std::unique_ptr<StreamData> stream_data) {
|
||||
DCHECK_EQ(StreamDataType::kMediaSample, stream_data->stream_data_type);
|
||||
Status ChunkingHandler::OnMediaSample(
|
||||
std::shared_ptr<const MediaSample> sample) {
|
||||
DCHECK_NE(time_scale_, 0u) << "kStreamInfo should arrive before kMediaSample";
|
||||
|
||||
const size_t stream_index = stream_data->stream_index;
|
||||
DCHECK_NE(time_scales_[stream_index], 0u)
|
||||
<< "kStreamInfo should arrive before kMediaSample";
|
||||
|
||||
if (stream_index != main_stream_index_ &&
|
||||
!stream_data->media_sample->is_key_frame()) {
|
||||
return Status(error::CHUNKING_ERROR,
|
||||
"All non video samples should be key frames.");
|
||||
}
|
||||
// The streams are expected to be roughly synchronized, so we don't expect
|
||||
// to see a lot of samples from one stream but no samples from another
|
||||
// stream.
|
||||
// The value is kind of arbitrary here. For a 24fps video, it is ~40s.
|
||||
const size_t kMaxCachedSamplesPerStream = 1000u;
|
||||
if (num_cached_samples_[stream_index] >= kMaxCachedSamplesPerStream) {
|
||||
LOG(ERROR) << "Streams are not synchronized:";
|
||||
for (size_t i = 0; i < num_cached_samples_.size(); ++i)
|
||||
LOG(ERROR) << " [Stream " << i << "] " << num_cached_samples_[i];
|
||||
return Status(error::CHUNKING_ERROR, "Streams are not synchronized.");
|
||||
}
|
||||
|
||||
cached_media_sample_stream_data_.push(std::move(stream_data));
|
||||
++num_cached_samples_[stream_index];
|
||||
|
||||
// If we have cached samples from every stream, the first sample in
|
||||
// |cached_media_samples_stream_data_| is guaranteed to be the earliest
|
||||
// sample. Extract and process that sample.
|
||||
if (std::all_of(num_cached_samples_.begin(), num_cached_samples_.end(),
|
||||
[](size_t num_samples) { return num_samples > 0; })) {
|
||||
while (true) {
|
||||
const size_t top_stream_index =
|
||||
cached_media_sample_stream_data_.top()->stream_index;
|
||||
Status status =
|
||||
ProcessMediaSampleStreamData(*cached_media_sample_stream_data_.top());
|
||||
if (!status.ok())
|
||||
return status;
|
||||
cached_media_sample_stream_data_.pop();
|
||||
if (--num_cached_samples_[top_stream_index] == 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
return Status::OK;
|
||||
}
|
||||
|
||||
Status ChunkingHandler::ProcessMainMediaSample(const MediaSample* sample) {
|
||||
const bool is_key_frame = sample->is_key_frame();
|
||||
const int64_t timestamp = sample->dts();
|
||||
const int64_t time_scale = time_scales_[main_stream_index_];
|
||||
const double dts_in_seconds = static_cast<double>(sample->dts()) / time_scale;
|
||||
|
||||
// Check if we need to terminate the current (sub)segment.
|
||||
bool new_segment = false;
|
||||
bool new_subsegment = false;
|
||||
std::shared_ptr<CueEvent> cue_event;
|
||||
if (is_key_frame || !chunking_params_.segment_sap_aligned) {
|
||||
bool started_new_segment = false;
|
||||
const bool can_start_new_segment =
|
||||
sample->is_key_frame() || !chunking_params_.segment_sap_aligned;
|
||||
if (can_start_new_segment) {
|
||||
const int64_t segment_index = timestamp / segment_duration_;
|
||||
if (segment_index != current_segment_index_) {
|
||||
if (!segment_start_time_ || segment_index != current_segment_index_) {
|
||||
current_segment_index_ = segment_index;
|
||||
// Reset subsegment index.
|
||||
current_subsegment_index_ = 0;
|
||||
new_segment = true;
|
||||
}
|
||||
// We use 'while' instead of 'if' to make sure to pop off multiple SCTE35
|
||||
// events that may be very close to each other.
|
||||
while (!scte35_events_.empty() &&
|
||||
scte35_events_.top()->start_time_in_seconds <= dts_in_seconds) {
|
||||
// For simplicity, don't change |current_segment_index_|.
|
||||
current_subsegment_index_ = 0;
|
||||
new_segment = true;
|
||||
|
||||
cue_event = std::make_shared<CueEvent>();
|
||||
cue_event->time_in_seconds =
|
||||
static_cast<double>(sample->pts()) / time_scale;
|
||||
cue_event->cue_data = scte35_events_.top()->cue_data;
|
||||
LOG(INFO) << "Chunked at " << dts_in_seconds << " seconds for Ad Cue.";
|
||||
|
||||
scte35_events_.pop();
|
||||
Status status = EndSegmentIfStarted();
|
||||
if (!status.ok())
|
||||
return status;
|
||||
segment_start_time_ = timestamp;
|
||||
subsegment_start_time_ = timestamp;
|
||||
started_new_segment = true;
|
||||
}
|
||||
}
|
||||
if (!new_segment && subsegment_duration_ > 0 &&
|
||||
(is_key_frame || !chunking_params_.subsegment_sap_aligned)) {
|
||||
const int64_t subsegment_index =
|
||||
(timestamp - segment_info_[main_stream_index_]->start_timestamp) /
|
||||
subsegment_duration_;
|
||||
if (subsegment_index != current_subsegment_index_) {
|
||||
current_subsegment_index_ = subsegment_index;
|
||||
new_subsegment = true;
|
||||
if (!started_new_segment && IsSubsegmentEnabled()) {
|
||||
const bool can_start_new_subsegment =
|
||||
sample->is_key_frame() || !chunking_params_.subsegment_sap_aligned;
|
||||
if (can_start_new_subsegment) {
|
||||
const int64_t subsegment_index =
|
||||
(timestamp - segment_start_time_.value()) / subsegment_duration_;
|
||||
if (subsegment_index != current_subsegment_index_) {
|
||||
current_subsegment_index_ = subsegment_index;
|
||||
|
||||
Status status = EndSubsegmentIfStarted();
|
||||
if (!status.ok())
|
||||
return status;
|
||||
subsegment_start_time_ = timestamp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Status status;
|
||||
if (new_segment) {
|
||||
status.Update(DispatchSegmentInfoForAllStreams());
|
||||
segment_info_[main_stream_index_]->start_timestamp = timestamp;
|
||||
|
||||
if (cue_event)
|
||||
status.Update(DispatchCueEventForAllStreams(std::move(cue_event)));
|
||||
}
|
||||
if (subsegment_duration_ > 0 && (new_segment || new_subsegment)) {
|
||||
status.Update(DispatchSubsegmentInfoForAllStreams());
|
||||
subsegment_info_[main_stream_index_]->start_timestamp = timestamp;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
Status ChunkingHandler::ProcessMediaSampleStreamData(
|
||||
const StreamData& media_sample_stream_data) {
|
||||
const size_t stream_index = media_sample_stream_data.stream_index;
|
||||
const auto sample = std::move(media_sample_stream_data.media_sample);
|
||||
|
||||
if (stream_index == main_stream_index_) {
|
||||
Status status = ProcessMainMediaSample(sample.get());
|
||||
if (!status.ok())
|
||||
return status;
|
||||
}
|
||||
|
||||
VLOG(3) << "Stream index: " << stream_index << " "
|
||||
<< "Sample ts: " << sample->dts() << " "
|
||||
<< " duration: " << sample->duration()
|
||||
<< " scale: " << time_scales_[stream_index] << "\n"
|
||||
<< " scale: " << time_scales_[main_stream_index_]
|
||||
<< (segment_info_[stream_index] ? " dispatch " : " discard ");
|
||||
VLOG(3) << "Sample ts: " << timestamp << " "
|
||||
<< " duration: " << sample->duration() << " scale: " << time_scale_
|
||||
<< (segment_start_time_ ? " dispatch " : " discard ");
|
||||
// Discard samples before segment start. If the segment has started,
|
||||
// |segment_info_[stream_index]| won't be null.
|
||||
if (!segment_info_[stream_index])
|
||||
// |segment_start_time_| won't be null.
|
||||
if (!segment_start_time_)
|
||||
return Status::OK;
|
||||
if (segment_info_[stream_index]->start_timestamp == -1)
|
||||
segment_info_[stream_index]->start_timestamp = sample->dts();
|
||||
if (subsegment_info_[stream_index] &&
|
||||
subsegment_info_[stream_index]->start_timestamp == -1) {
|
||||
subsegment_info_[stream_index]->start_timestamp = sample->dts();
|
||||
}
|
||||
last_sample_end_timestamps_[stream_index] =
|
||||
sample->dts() + sample->duration();
|
||||
return DispatchMediaSample(stream_index, std::move(sample));
|
||||
last_sample_end_timestamp_ = timestamp + sample->duration();
|
||||
return DispatchMediaSample(kStreamIndex, std::move(sample));
|
||||
}
|
||||
|
||||
Status ChunkingHandler::DispatchSegmentInfoForAllStreams() {
|
||||
Status status;
|
||||
for (size_t i = 0; i < segment_info_.size() && status.ok(); ++i) {
|
||||
if (segment_info_[i] && segment_info_[i]->start_timestamp != -1) {
|
||||
segment_info_[i]->duration =
|
||||
last_sample_end_timestamps_[i] - segment_info_[i]->start_timestamp;
|
||||
status.Update(DispatchSegmentInfo(i, std::move(segment_info_[i])));
|
||||
}
|
||||
segment_info_[i].reset(new SegmentInfo);
|
||||
subsegment_info_[i].reset();
|
||||
}
|
||||
return status;
|
||||
Status ChunkingHandler::EndSegmentIfStarted() const {
|
||||
if (!segment_start_time_)
|
||||
return Status::OK;
|
||||
|
||||
auto segment_info = std::make_shared<SegmentInfo>();
|
||||
segment_info->start_timestamp = segment_start_time_.value();
|
||||
segment_info->duration =
|
||||
last_sample_end_timestamp_ - segment_start_time_.value();
|
||||
return DispatchSegmentInfo(kStreamIndex, std::move(segment_info));
|
||||
}
|
||||
|
||||
Status ChunkingHandler::DispatchSubsegmentInfoForAllStreams() {
|
||||
Status status;
|
||||
for (size_t i = 0; i < subsegment_info_.size() && status.ok(); ++i) {
|
||||
if (subsegment_info_[i] && subsegment_info_[i]->start_timestamp != -1) {
|
||||
subsegment_info_[i]->duration =
|
||||
last_sample_end_timestamps_[i] - subsegment_info_[i]->start_timestamp;
|
||||
status.Update(DispatchSegmentInfo(i, std::move(subsegment_info_[i])));
|
||||
}
|
||||
subsegment_info_[i].reset(new SegmentInfo);
|
||||
subsegment_info_[i]->is_subsegment = true;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
Status ChunkingHandler::EndSubsegmentIfStarted() const {
|
||||
if (!subsegment_start_time_)
|
||||
return Status::OK;
|
||||
|
||||
Status ChunkingHandler::DispatchCueEventForAllStreams(
|
||||
std::shared_ptr<CueEvent> cue_event) {
|
||||
Status status;
|
||||
for (size_t i = 0; i < segment_info_.size() && status.ok(); ++i) {
|
||||
status.Update(DispatchCueEvent(i, cue_event));
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
ChunkingHandler::MediaSampleTimestampGreater::MediaSampleTimestampGreater(
|
||||
const ChunkingHandler* const chunking_handler)
|
||||
: chunking_handler_(chunking_handler) {}
|
||||
|
||||
bool ChunkingHandler::MediaSampleTimestampGreater::operator()(
|
||||
const std::unique_ptr<StreamData>& lhs,
|
||||
const std::unique_ptr<StreamData>& rhs) const {
|
||||
DCHECK(lhs);
|
||||
DCHECK(rhs);
|
||||
return GetSampleTimeInSeconds(*lhs) > GetSampleTimeInSeconds(*rhs);
|
||||
}
|
||||
|
||||
double ChunkingHandler::MediaSampleTimestampGreater::GetSampleTimeInSeconds(
|
||||
const StreamData& media_sample_stream_data) const {
|
||||
const size_t stream_index = media_sample_stream_data.stream_index;
|
||||
const auto& sample = media_sample_stream_data.media_sample;
|
||||
DCHECK(sample);
|
||||
// Order main samples by left boundary and non main samples by mid-point. This
|
||||
// ensures non main samples are properly chunked, i.e. if the portion of the
|
||||
// sample in the next chunk is bigger than the portion of the sample in the
|
||||
// previous chunk, the sample is placed in the next chunk.
|
||||
const uint64_t timestamp =
|
||||
stream_index == chunking_handler_->main_stream_index_
|
||||
? sample->dts()
|
||||
: (sample->dts() + sample->duration() / 2);
|
||||
return static_cast<double>(timestamp) /
|
||||
chunking_handler_->time_scales_[stream_index];
|
||||
}
|
||||
|
||||
bool ChunkingHandler::Scte35EventTimestampGreater::operator()(
|
||||
const std::shared_ptr<const Scte35Event>& lhs,
|
||||
const std::shared_ptr<const Scte35Event>& rhs) const {
|
||||
DCHECK(lhs);
|
||||
DCHECK(rhs);
|
||||
return lhs->start_time_in_seconds > rhs->start_time_in_seconds;
|
||||
auto subsegment_info = std::make_shared<SegmentInfo>();
|
||||
subsegment_info->start_timestamp = subsegment_start_time_.value();
|
||||
subsegment_info->duration =
|
||||
last_sample_end_timestamp_ - subsegment_start_time_.value();
|
||||
subsegment_info->is_subsegment = true;
|
||||
return DispatchSegmentInfo(kStreamIndex, std::move(subsegment_info));
|
||||
}
|
||||
|
||||
} // namespace media
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include <queue>
|
||||
|
||||
#include "packager/base/logging.h"
|
||||
#include "packager/base/optional.h"
|
||||
#include "packager/media/base/media_handler.h"
|
||||
#include "packager/media/public/chunking_params.h"
|
||||
|
||||
|
@ -19,9 +20,7 @@ namespace media {
|
|||
|
||||
/// ChunkingHandler splits the samples into segments / subsegments based on the
|
||||
/// specified chunking params.
|
||||
/// This handler is a multi-in multi-out handler. If more than one input is
|
||||
/// provided, there should be one and only one video stream; also, all inputs
|
||||
/// should come from the same thread and are synchronized.
|
||||
/// This handler is a one-in one-out handler.
|
||||
/// There can be multiple chunking handler running in different threads or even
|
||||
/// different processes, we use the "consistent chunking algorithm" to make sure
|
||||
/// the chunks in different streams are aligned without explicit communcating
|
||||
|
@ -36,17 +35,11 @@ namespace media {
|
|||
/// 2. Chunk only at the consistent chunkable boundary
|
||||
///
|
||||
/// This algorithm will make sure the chunks from different video streams are
|
||||
/// aligned if they have aligned GoPs. However, this algorithm will only work
|
||||
/// for video streams. To be able to chunk non video streams at similar
|
||||
/// positions as video streams, ChunkingHandler is designed to accept one video
|
||||
/// input and multiple non video inputs, the non video inputs are chunked when
|
||||
/// the video input is chunked. If the inputs are synchronized - which is true
|
||||
/// if the inputs come from the same demuxer, the video and non video chunks
|
||||
/// are aligned.
|
||||
/// aligned if they have aligned GoPs.
|
||||
class ChunkingHandler : public MediaHandler {
|
||||
public:
|
||||
explicit ChunkingHandler(const ChunkingParams& chunking_params);
|
||||
~ChunkingHandler() override;
|
||||
~ChunkingHandler() override = default;
|
||||
|
||||
protected:
|
||||
/// @name MediaHandler implementation overrides.
|
||||
|
@ -62,84 +55,34 @@ class ChunkingHandler : public MediaHandler {
|
|||
ChunkingHandler(const ChunkingHandler&) = delete;
|
||||
ChunkingHandler& operator=(const ChunkingHandler&) = delete;
|
||||
|
||||
Status OnStreamInfo(uint64_t stream_index,
|
||||
std::shared_ptr<const StreamInfo> info);
|
||||
Status OnScte35Event(uint64_t stream_index,
|
||||
std::shared_ptr<const Scte35Event> event);
|
||||
Status OnStreamInfo(std::shared_ptr<const StreamInfo> info);
|
||||
Status OnCueEvent(std::shared_ptr<const CueEvent> event);
|
||||
Status OnMediaSample(std::shared_ptr<const MediaSample> sample);
|
||||
|
||||
Status OnMediaSample(std::unique_ptr<StreamData> stream_data);
|
||||
Status EndSegmentIfStarted() const;
|
||||
Status EndSubsegmentIfStarted() const;
|
||||
|
||||
// Processes main media sample and apply chunking if needed.
|
||||
Status ProcessMainMediaSample(const MediaSample* sample);
|
||||
|
||||
// Processes and dispatches media sample.
|
||||
Status ProcessMediaSampleStreamData(const StreamData& media_data);
|
||||
|
||||
// The (sub)segments are aligned and dispatched together.
|
||||
Status DispatchSegmentInfoForAllStreams();
|
||||
Status DispatchSubsegmentInfoForAllStreams();
|
||||
Status DispatchCueEventForAllStreams(std::shared_ptr<CueEvent> cue_event);
|
||||
bool IsSubsegmentEnabled() {
|
||||
return subsegment_duration_ > 0 &&
|
||||
subsegment_duration_ != segment_duration_;
|
||||
}
|
||||
|
||||
const ChunkingParams chunking_params_;
|
||||
|
||||
// The inputs are expected to come from the same thread.
|
||||
std::atomic<int64_t> thread_id_;
|
||||
|
||||
// The video stream is the main stream; if there is only one stream, it is the
|
||||
// main stream. The chunking is based on the main stream.
|
||||
const size_t kInvalidStreamIndex = static_cast<size_t>(-1);
|
||||
size_t main_stream_index_ = kInvalidStreamIndex;
|
||||
// Segment and subsegment duration in main stream's time scale.
|
||||
// Segment and subsegment duration in stream's time scale.
|
||||
int64_t segment_duration_ = 0;
|
||||
int64_t subsegment_duration_ = 0;
|
||||
|
||||
class MediaSampleTimestampGreater {
|
||||
public:
|
||||
explicit MediaSampleTimestampGreater(
|
||||
const ChunkingHandler* const chunking_handler);
|
||||
|
||||
// Comparison operator. Used by |media_samples_| priority queue below to
|
||||
// sort the media samples.
|
||||
bool operator()(const std::unique_ptr<StreamData>& lhs,
|
||||
const std::unique_ptr<StreamData>& rhs) const;
|
||||
|
||||
private:
|
||||
double GetSampleTimeInSeconds(
|
||||
const StreamData& media_sample_stream_data) const;
|
||||
|
||||
const ChunkingHandler* const chunking_handler_ = nullptr;
|
||||
};
|
||||
MediaSampleTimestampGreater media_sample_comparator_;
|
||||
// Caches media samples and sort the samples.
|
||||
std::priority_queue<std::unique_ptr<StreamData>,
|
||||
std::vector<std::unique_ptr<StreamData>>,
|
||||
MediaSampleTimestampGreater>
|
||||
cached_media_sample_stream_data_;
|
||||
// Tracks number of cached samples in input streams.
|
||||
std::vector<size_t> num_cached_samples_;
|
||||
|
||||
// Current segment index, useful to determine where to do chunking.
|
||||
int64_t current_segment_index_ = -1;
|
||||
// Current subsegment index, useful to determine where to do chunking.
|
||||
int64_t current_subsegment_index_ = -1;
|
||||
|
||||
std::vector<std::shared_ptr<SegmentInfo>> segment_info_;
|
||||
std::vector<std::shared_ptr<SegmentInfo>> subsegment_info_;
|
||||
std::vector<uint32_t> time_scales_;
|
||||
base::Optional<int64_t> segment_start_time_;
|
||||
base::Optional<int64_t> subsegment_start_time_;
|
||||
uint32_t time_scale_ = 0;
|
||||
// The end timestamp of the last dispatched sample.
|
||||
std::vector<int64_t> last_sample_end_timestamps_;
|
||||
|
||||
struct Scte35EventTimestampGreater {
|
||||
bool operator()(const std::shared_ptr<const Scte35Event>& lhs,
|
||||
const std::shared_ptr<const Scte35Event>& rhs) const;
|
||||
};
|
||||
// Captures all incoming SCTE35 events to identify chunking points. Events
|
||||
// will be removed from this queue one at a time as soon as the correct
|
||||
// chunking point is identified in the incoming samples.
|
||||
std::priority_queue<std::shared_ptr<const Scte35Event>,
|
||||
std::vector<std::shared_ptr<const Scte35Event>>,
|
||||
Scte35EventTimestampGreater>
|
||||
scte35_events_;
|
||||
int64_t last_sample_end_timestamp_ = 0;
|
||||
};
|
||||
|
||||
} // namespace media
|
||||
|
|
|
@ -18,12 +18,10 @@ using ::testing::IsEmpty;
|
|||
namespace shaka {
|
||||
namespace media {
|
||||
namespace {
|
||||
const size_t kStreamIndex0 = 0;
|
||||
const size_t kStreamIndex1 = 1;
|
||||
const size_t kStreamIndex = 0;
|
||||
const uint32_t kTimeScale0 = 800;
|
||||
const uint32_t kTimeScale1 = 1000;
|
||||
const int64_t kDuration0 = 200;
|
||||
const int64_t kDuration1 = 300;
|
||||
const int64_t kDuration = 300;
|
||||
const bool kKeyFrame = true;
|
||||
const bool kIsSubsegment = true;
|
||||
const bool kEncrypted = true;
|
||||
|
@ -57,34 +55,34 @@ TEST_F(ChunkingHandlerTest, AudioNoSubsegmentsThenFlush) {
|
|||
SetUpChunkingHandler(1, chunking_params);
|
||||
|
||||
ASSERT_OK(Process(StreamData::FromStreamInfo(
|
||||
kStreamIndex0, GetAudioStreamInfo(kTimeScale0))));
|
||||
kStreamIndex, GetAudioStreamInfo(kTimeScale0))));
|
||||
EXPECT_THAT(
|
||||
GetOutputStreamDataVector(),
|
||||
ElementsAre(IsStreamInfo(kStreamIndex0, kTimeScale0, !kEncrypted)));
|
||||
ElementsAre(IsStreamInfo(kStreamIndex, kTimeScale0, !kEncrypted)));
|
||||
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
ClearOutputStreamDataVector();
|
||||
ASSERT_OK(Process(StreamData::FromMediaSample(
|
||||
kStreamIndex0, GetMediaSample(i * kDuration1, kDuration1, kKeyFrame))));
|
||||
kStreamIndex, GetMediaSample(i * kDuration, kDuration, kKeyFrame))));
|
||||
// One output stream_data except when i == 3, which also has SegmentInfo.
|
||||
if (i == 3) {
|
||||
EXPECT_THAT(GetOutputStreamDataVector(),
|
||||
ElementsAre(IsSegmentInfo(kStreamIndex0, 0, kDuration1 * 3,
|
||||
ElementsAre(IsSegmentInfo(kStreamIndex, 0, kDuration * 3,
|
||||
!kIsSubsegment, !kEncrypted),
|
||||
IsMediaSample(kStreamIndex0, i * kDuration1,
|
||||
kDuration1, !kEncrypted)));
|
||||
IsMediaSample(kStreamIndex, i * kDuration,
|
||||
kDuration, !kEncrypted)));
|
||||
} else {
|
||||
EXPECT_THAT(GetOutputStreamDataVector(),
|
||||
ElementsAre(IsMediaSample(kStreamIndex0, i * kDuration1,
|
||||
kDuration1, !kEncrypted)));
|
||||
ElementsAre(IsMediaSample(kStreamIndex, i * kDuration,
|
||||
kDuration, !kEncrypted)));
|
||||
}
|
||||
}
|
||||
|
||||
ClearOutputStreamDataVector();
|
||||
ASSERT_OK(OnFlushRequest(kStreamIndex0));
|
||||
ASSERT_OK(OnFlushRequest(kStreamIndex));
|
||||
EXPECT_THAT(
|
||||
GetOutputStreamDataVector(),
|
||||
ElementsAre(IsSegmentInfo(kStreamIndex0, kDuration1 * 3, kDuration1 * 2,
|
||||
ElementsAre(IsSegmentInfo(kStreamIndex, kDuration * 3, kDuration * 2,
|
||||
!kIsSubsegment, !kEncrypted)));
|
||||
}
|
||||
|
||||
|
@ -95,25 +93,24 @@ TEST_F(ChunkingHandlerTest, AudioWithSubsegments) {
|
|||
SetUpChunkingHandler(1, chunking_params);
|
||||
|
||||
ASSERT_OK(Process(StreamData::FromStreamInfo(
|
||||
kStreamIndex0, GetAudioStreamInfo(kTimeScale0))));
|
||||
kStreamIndex, GetAudioStreamInfo(kTimeScale0))));
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
ASSERT_OK(Process(StreamData::FromMediaSample(
|
||||
kStreamIndex0, GetMediaSample(i * kDuration1, kDuration1, kKeyFrame))));
|
||||
kStreamIndex, GetMediaSample(i * kDuration, kDuration, kKeyFrame))));
|
||||
}
|
||||
EXPECT_THAT(
|
||||
GetOutputStreamDataVector(),
|
||||
ElementsAre(
|
||||
IsStreamInfo(kStreamIndex0, kTimeScale0, !kEncrypted),
|
||||
IsMediaSample(kStreamIndex0, 0, kDuration1, !kEncrypted),
|
||||
IsMediaSample(kStreamIndex0, kDuration1, kDuration1, !kEncrypted),
|
||||
IsSegmentInfo(kStreamIndex0, 0, kDuration1 * 2, kIsSubsegment,
|
||||
IsStreamInfo(kStreamIndex, kTimeScale0, !kEncrypted),
|
||||
IsMediaSample(kStreamIndex, 0, kDuration, !kEncrypted),
|
||||
IsMediaSample(kStreamIndex, kDuration, kDuration, !kEncrypted),
|
||||
IsSegmentInfo(kStreamIndex, 0, kDuration * 2, kIsSubsegment,
|
||||
!kEncrypted),
|
||||
IsMediaSample(kStreamIndex0, 2 * kDuration1, kDuration1, !kEncrypted),
|
||||
IsSegmentInfo(kStreamIndex0, 0, kDuration1 * 3, !kIsSubsegment,
|
||||
IsMediaSample(kStreamIndex, 2 * kDuration, kDuration, !kEncrypted),
|
||||
IsSegmentInfo(kStreamIndex, 0, kDuration * 3, !kIsSubsegment,
|
||||
!kEncrypted),
|
||||
IsMediaSample(kStreamIndex0, 3 * kDuration1, kDuration1, !kEncrypted),
|
||||
IsMediaSample(kStreamIndex0, 4 * kDuration1, kDuration1,
|
||||
!kEncrypted)));
|
||||
IsMediaSample(kStreamIndex, 3 * kDuration, kDuration, !kEncrypted),
|
||||
IsMediaSample(kStreamIndex, 4 * kDuration, kDuration, !kEncrypted)));
|
||||
}
|
||||
|
||||
TEST_F(ChunkingHandlerTest, VideoAndSubsegmentAndNonzeroStart) {
|
||||
|
@ -123,193 +120,79 @@ TEST_F(ChunkingHandlerTest, VideoAndSubsegmentAndNonzeroStart) {
|
|||
SetUpChunkingHandler(1, chunking_params);
|
||||
|
||||
ASSERT_OK(Process(StreamData::FromStreamInfo(
|
||||
kStreamIndex0, GetVideoStreamInfo(kTimeScale1))));
|
||||
kStreamIndex, GetVideoStreamInfo(kTimeScale1))));
|
||||
const int64_t kVideoStartTimestamp = 12345;
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
// Alternate key frame.
|
||||
const bool is_key_frame = (i % 2) == 1;
|
||||
ASSERT_OK(Process(StreamData::FromMediaSample(
|
||||
kStreamIndex0, GetMediaSample(kVideoStartTimestamp + i * kDuration1,
|
||||
kDuration1, is_key_frame))));
|
||||
kStreamIndex, GetMediaSample(kVideoStartTimestamp + i * kDuration,
|
||||
kDuration, is_key_frame))));
|
||||
}
|
||||
EXPECT_THAT(
|
||||
GetOutputStreamDataVector(),
|
||||
ElementsAre(
|
||||
IsStreamInfo(kStreamIndex0, kTimeScale1, !kEncrypted),
|
||||
IsStreamInfo(kStreamIndex, kTimeScale1, !kEncrypted),
|
||||
// The first samples @ kStartTimestamp is discarded - not key frame.
|
||||
IsMediaSample(kStreamIndex0, kVideoStartTimestamp + kDuration1,
|
||||
kDuration1, !kEncrypted),
|
||||
IsMediaSample(kStreamIndex0, kVideoStartTimestamp + kDuration1 * 2,
|
||||
kDuration1, !kEncrypted),
|
||||
IsMediaSample(kStreamIndex, kVideoStartTimestamp + kDuration,
|
||||
kDuration, !kEncrypted),
|
||||
IsMediaSample(kStreamIndex, kVideoStartTimestamp + kDuration * 2,
|
||||
kDuration, !kEncrypted),
|
||||
// The next segment boundary 13245 / 1000 != 12645 / 1000.
|
||||
IsSegmentInfo(kStreamIndex0, kVideoStartTimestamp + kDuration1,
|
||||
kDuration1 * 2, !kIsSubsegment, !kEncrypted),
|
||||
IsMediaSample(kStreamIndex0, kVideoStartTimestamp + kDuration1 * 3,
|
||||
kDuration1, !kEncrypted),
|
||||
IsMediaSample(kStreamIndex0, kVideoStartTimestamp + kDuration1 * 4,
|
||||
kDuration1, !kEncrypted),
|
||||
// The subsegment has duration kDuration1 * 2 since it can only
|
||||
IsSegmentInfo(kStreamIndex, kVideoStartTimestamp + kDuration,
|
||||
kDuration * 2, !kIsSubsegment, !kEncrypted),
|
||||
IsMediaSample(kStreamIndex, kVideoStartTimestamp + kDuration * 3,
|
||||
kDuration, !kEncrypted),
|
||||
IsMediaSample(kStreamIndex, kVideoStartTimestamp + kDuration * 4,
|
||||
kDuration, !kEncrypted),
|
||||
// The subsegment has duration kDuration * 2 since it can only
|
||||
// terminate before key frame.
|
||||
IsSegmentInfo(kStreamIndex0, kVideoStartTimestamp + kDuration1 * 3,
|
||||
kDuration1 * 2, kIsSubsegment, !kEncrypted),
|
||||
IsMediaSample(kStreamIndex0, kVideoStartTimestamp + kDuration1 * 5,
|
||||
kDuration1, !kEncrypted)));
|
||||
IsSegmentInfo(kStreamIndex, kVideoStartTimestamp + kDuration * 3,
|
||||
kDuration * 2, kIsSubsegment, !kEncrypted),
|
||||
IsMediaSample(kStreamIndex, kVideoStartTimestamp + kDuration * 5,
|
||||
kDuration, !kEncrypted)));
|
||||
}
|
||||
|
||||
TEST_F(ChunkingHandlerTest, AudioAndVideo) {
|
||||
ChunkingParams chunking_params;
|
||||
chunking_params.segment_duration_in_seconds = 1;
|
||||
chunking_params.subsegment_duration_in_seconds = 0.3;
|
||||
SetUpChunkingHandler(2, chunking_params);
|
||||
|
||||
ASSERT_OK(Process(StreamData::FromStreamInfo(
|
||||
kStreamIndex0, GetAudioStreamInfo(kTimeScale0))));
|
||||
ASSERT_OK(Process(StreamData::FromStreamInfo(
|
||||
kStreamIndex1, GetVideoStreamInfo(kTimeScale1))));
|
||||
EXPECT_THAT(
|
||||
GetOutputStreamDataVector(),
|
||||
ElementsAre(IsStreamInfo(kStreamIndex0, kTimeScale0, !kEncrypted),
|
||||
IsStreamInfo(kStreamIndex1, kTimeScale1, !kEncrypted)));
|
||||
ClearOutputStreamDataVector();
|
||||
|
||||
// Equivalent to 12345 in video timescale.
|
||||
const int64_t kAudioStartTimestamp = 9876;
|
||||
const int64_t kVideoStartTimestamp = 12345;
|
||||
// Burst of audio and video samples. They will be properly ordered.
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
ASSERT_OK(Process(StreamData::FromMediaSample(
|
||||
kStreamIndex0, GetMediaSample(kAudioStartTimestamp + kDuration0 * i,
|
||||
kDuration0, true))));
|
||||
}
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
// Alternate key frame.
|
||||
const bool is_key_frame = (i % 2) == 1;
|
||||
ASSERT_OK(Process(StreamData::FromMediaSample(
|
||||
kStreamIndex1, GetMediaSample(kVideoStartTimestamp + kDuration1 * i,
|
||||
kDuration1, is_key_frame))));
|
||||
}
|
||||
|
||||
EXPECT_THAT(
|
||||
GetOutputStreamDataVector(),
|
||||
ElementsAre(
|
||||
// The first samples @ kStartTimestamp is discarded - not key frame.
|
||||
IsMediaSample(kStreamIndex1, kVideoStartTimestamp + kDuration1,
|
||||
kDuration1, !kEncrypted),
|
||||
IsMediaSample(kStreamIndex0, kAudioStartTimestamp + kDuration0,
|
||||
kDuration0, !kEncrypted),
|
||||
IsMediaSample(kStreamIndex1, kVideoStartTimestamp + kDuration1 * 2,
|
||||
kDuration1, !kEncrypted),
|
||||
IsMediaSample(kStreamIndex0, kAudioStartTimestamp + kDuration0 * 2,
|
||||
kDuration0, !kEncrypted),
|
||||
IsMediaSample(kStreamIndex0, kAudioStartTimestamp + kDuration0 * 3,
|
||||
kDuration0, !kEncrypted),
|
||||
// The audio segment is terminated together with video stream.
|
||||
IsSegmentInfo(kStreamIndex0, kAudioStartTimestamp + kDuration0,
|
||||
kDuration0 * 3, !kIsSubsegment, !kEncrypted),
|
||||
// The next segment boundary 13245 / 1000 != 12645 / 1000.
|
||||
IsSegmentInfo(kStreamIndex1, kVideoStartTimestamp + kDuration1,
|
||||
kDuration1 * 2, !kIsSubsegment, !kEncrypted),
|
||||
IsMediaSample(kStreamIndex1, kVideoStartTimestamp + kDuration1 * 3,
|
||||
kDuration1, !kEncrypted),
|
||||
IsMediaSample(kStreamIndex0, kAudioStartTimestamp + kDuration0 * 4,
|
||||
kDuration0, !kEncrypted)));
|
||||
ClearOutputStreamDataVector();
|
||||
|
||||
// The side comments below show the equivalent timestamp in video timescale.
|
||||
// The audio and video are made ~aligned.
|
||||
ASSERT_OK(Process(StreamData::FromMediaSample(
|
||||
kStreamIndex0,
|
||||
GetMediaSample(kAudioStartTimestamp + kDuration0 * 5, kDuration0,
|
||||
true)))); // 13595
|
||||
ASSERT_OK(Process(StreamData::FromMediaSample(
|
||||
kStreamIndex1,
|
||||
GetMediaSample(kVideoStartTimestamp + kDuration1 * 5, kDuration1,
|
||||
true)))); // 13845
|
||||
ASSERT_OK(Process(StreamData::FromMediaSample(
|
||||
kStreamIndex0,
|
||||
GetMediaSample(kAudioStartTimestamp + kDuration0 * 6, kDuration0,
|
||||
true)))); // 13845
|
||||
// This expectation are separated from the expectation above because
|
||||
// ElementsAre supports at most 10 elements.
|
||||
EXPECT_THAT(
|
||||
GetOutputStreamDataVector(),
|
||||
ElementsAre(
|
||||
IsMediaSample(kStreamIndex1, kVideoStartTimestamp + kDuration1 * 4,
|
||||
kDuration1, !kEncrypted),
|
||||
IsMediaSample(kStreamIndex0, kAudioStartTimestamp + kDuration0 * 5,
|
||||
kDuration0, !kEncrypted),
|
||||
// Audio is terminated along with video below.
|
||||
IsSegmentInfo(kStreamIndex0, kAudioStartTimestamp + kDuration0 * 4,
|
||||
kDuration0 * 2, kIsSubsegment, !kEncrypted),
|
||||
// The subsegment has duration kDuration1 * 2 since it can only
|
||||
// terminate before key frame.
|
||||
IsSegmentInfo(kStreamIndex1, kVideoStartTimestamp + kDuration1 * 3,
|
||||
kDuration1 * 2, kIsSubsegment, !kEncrypted),
|
||||
IsMediaSample(kStreamIndex1, kVideoStartTimestamp + kDuration1 * 5,
|
||||
kDuration1, !kEncrypted)));
|
||||
|
||||
ClearOutputStreamDataVector();
|
||||
ASSERT_OK(OnFlushRequest(kStreamIndex0));
|
||||
EXPECT_THAT(
|
||||
GetOutputStreamDataVector(),
|
||||
ElementsAre(
|
||||
IsMediaSample(kStreamIndex0, kAudioStartTimestamp + kDuration0 * 6,
|
||||
kDuration0, !kEncrypted),
|
||||
IsSegmentInfo(kStreamIndex0, kAudioStartTimestamp + kDuration0 * 4,
|
||||
kDuration0 * 3, !kIsSubsegment, !kEncrypted)));
|
||||
|
||||
ClearOutputStreamDataVector();
|
||||
ASSERT_OK(OnFlushRequest(kStreamIndex1));
|
||||
EXPECT_THAT(GetOutputStreamDataVector(),
|
||||
ElementsAre(IsSegmentInfo(
|
||||
kStreamIndex1, kVideoStartTimestamp + kDuration1 * 3,
|
||||
kDuration1 * 3, !kIsSubsegment, !kEncrypted)));
|
||||
|
||||
// Flush again will do nothing.
|
||||
ClearOutputStreamDataVector();
|
||||
ASSERT_OK(OnFlushRequest(kStreamIndex0));
|
||||
ASSERT_OK(OnFlushRequest(kStreamIndex1));
|
||||
EXPECT_THAT(GetOutputStreamDataVector(), IsEmpty());
|
||||
}
|
||||
|
||||
TEST_F(ChunkingHandlerTest, Scte35Event) {
|
||||
TEST_F(ChunkingHandlerTest, CueEvent) {
|
||||
ChunkingParams chunking_params;
|
||||
chunking_params.segment_duration_in_seconds = 1;
|
||||
chunking_params.subsegment_duration_in_seconds = 0.5;
|
||||
SetUpChunkingHandler(1, chunking_params);
|
||||
|
||||
ASSERT_OK(Process(StreamData::FromStreamInfo(
|
||||
kStreamIndex0, GetVideoStreamInfo(kTimeScale1))));
|
||||
kStreamIndex, GetVideoStreamInfo(kTimeScale1))));
|
||||
|
||||
const int64_t kVideoStartTimestamp = 12345;
|
||||
const double kScte35TimeInSeconds =
|
||||
static_cast<double>(kVideoStartTimestamp + kDuration1) / kTimeScale1;
|
||||
const double kCueTimeInSeconds =
|
||||
static_cast<double>(kVideoStartTimestamp + kDuration) / kTimeScale1;
|
||||
|
||||
auto scte35_event = std::make_shared<Scte35Event>();
|
||||
scte35_event->start_time_in_seconds = kScte35TimeInSeconds;
|
||||
ASSERT_OK(Process(StreamData::FromScte35Event(kStreamIndex0, scte35_event)));
|
||||
auto cue_event = std::make_shared<CueEvent>();
|
||||
cue_event->time_in_seconds = kCueTimeInSeconds;
|
||||
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
const bool is_key_frame = true;
|
||||
ASSERT_OK(Process(StreamData::FromMediaSample(
|
||||
kStreamIndex0, GetMediaSample(kVideoStartTimestamp + i * kDuration1,
|
||||
kDuration1, is_key_frame))));
|
||||
kStreamIndex, GetMediaSample(kVideoStartTimestamp + i * kDuration,
|
||||
kDuration, is_key_frame))));
|
||||
if (i == 0) {
|
||||
ASSERT_OK(Process(StreamData::FromCueEvent(kStreamIndex, cue_event)));
|
||||
}
|
||||
}
|
||||
|
||||
EXPECT_THAT(
|
||||
GetOutputStreamDataVector(),
|
||||
ElementsAre(
|
||||
IsStreamInfo(kStreamIndex0, kTimeScale1, !kEncrypted),
|
||||
IsMediaSample(kStreamIndex0, kVideoStartTimestamp, kDuration1,
|
||||
IsStreamInfo(kStreamIndex, kTimeScale1, !kEncrypted),
|
||||
IsMediaSample(kStreamIndex, kVideoStartTimestamp, kDuration,
|
||||
!kEncrypted),
|
||||
// A new segment is created due to the existance of Cue.
|
||||
IsSegmentInfo(kStreamIndex0, kVideoStartTimestamp, kDuration1,
|
||||
IsSegmentInfo(kStreamIndex, kVideoStartTimestamp, kDuration,
|
||||
!kIsSubsegment, !kEncrypted),
|
||||
IsCueEvent(kStreamIndex0, kScte35TimeInSeconds),
|
||||
IsMediaSample(kStreamIndex0, kVideoStartTimestamp + kDuration1 * 1,
|
||||
kDuration1, !kEncrypted),
|
||||
IsMediaSample(kStreamIndex0, kVideoStartTimestamp + kDuration1 * 2,
|
||||
kDuration1, !kEncrypted)));
|
||||
IsCueEvent(kStreamIndex, kCueTimeInSeconds),
|
||||
IsMediaSample(kStreamIndex, kVideoStartTimestamp + kDuration * 1,
|
||||
kDuration, !kEncrypted),
|
||||
IsMediaSample(kStreamIndex, kVideoStartTimestamp + kDuration * 2,
|
||||
kDuration, !kEncrypted)));
|
||||
}
|
||||
|
||||
} // namespace media
|
||||
|
|
|
@ -20,7 +20,18 @@ double TimeInSeconds(const StreamInfo& info, const StreamData& data) {
|
|||
switch (data.stream_data_type) {
|
||||
case StreamDataType::kMediaSample:
|
||||
time_scale = info.time_scale();
|
||||
scaled_time = data.media_sample->pts();
|
||||
if (info.stream_type() == kStreamAudio) {
|
||||
// Return the start time for video and mid-point for audio, so that for
|
||||
// an audio sample, if the portion of the sample after the cue point is
|
||||
// bigger than the portion of the sample before the cue point, the
|
||||
// sample is placed after the cue.
|
||||
// It does not matter for text samples as text samples will be cut at
|
||||
// cue point.
|
||||
scaled_time =
|
||||
data.media_sample->pts() + data.media_sample->duration() / 2;
|
||||
} else {
|
||||
scaled_time = data.media_sample->pts();
|
||||
}
|
||||
break;
|
||||
case StreamDataType::kTextSample:
|
||||
// Text is always in MS but the stream info time scale is 0.
|
||||
|
@ -80,7 +91,7 @@ Status CueAlignmentHandler::OnFlushRequest(size_t stream_index) {
|
|||
if (!stream_state.samples.empty()) {
|
||||
LOG(WARNING) << "Unexpected data seen on stream " << i;
|
||||
while (!stream_state.samples.empty()) {
|
||||
Status status(Dispatch(std::move(stream_state.samples.front())));
|
||||
Status status = Dispatch(std::move(stream_state.samples.front()));
|
||||
if (!status.ok())
|
||||
return status;
|
||||
stream_state.samples.pop_front();
|
||||
|
@ -141,7 +152,7 @@ Status CueAlignmentHandler::OnSample(std::unique_ptr<StreamData> sample) {
|
|||
|
||||
// Accept the sample. This will output it if it comes before the hint point or
|
||||
// will cache it if it comes after the hint point.
|
||||
Status status(AcceptSample(std::move(sample), &stream_state));
|
||||
Status status = AcceptSample(std::move(sample), &stream_state);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
@ -180,6 +191,8 @@ Status CueAlignmentHandler::UseNewSyncPoint(
|
|||
// No stream should be so out of sync with the others that they would
|
||||
// still be working on an old cue.
|
||||
if (stream_state.cue) {
|
||||
// TODO(kqyang): Could this happen for text when there are no text samples
|
||||
// between the two cues?
|
||||
LOG(ERROR) << "Found two cue events that are too close together. One at "
|
||||
<< stream_state.cue->time_in_seconds << " and the other at "
|
||||
<< new_sync->time_in_seconds;
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
#include "packager/media/base/muxer_options.h"
|
||||
#include "packager/media/base/muxer_util.h"
|
||||
#include "packager/media/chunking/chunking_handler.h"
|
||||
#include "packager/media/chunking/cue_alignment_handler.h"
|
||||
#include "packager/media/crypto/encryption_handler.h"
|
||||
#include "packager/media/demuxer/demuxer.h"
|
||||
#include "packager/media/event/muxer_listener_factory.h"
|
||||
|
@ -55,6 +56,7 @@ namespace shaka {
|
|||
using media::Demuxer;
|
||||
using media::KeySource;
|
||||
using media::MuxerOptions;
|
||||
using media::SyncPointQueue;
|
||||
|
||||
namespace media {
|
||||
namespace {
|
||||
|
@ -375,8 +377,15 @@ std::shared_ptr<MediaHandler> CreateEncryptionHandler(
|
|||
Status CreateMp4ToMp4TextJob(const StreamDescriptor& stream,
|
||||
const PackagingParams& packaging_params,
|
||||
std::unique_ptr<MuxerListener> muxer_listener,
|
||||
SyncPointQueue* sync_points,
|
||||
MuxerFactory* muxer_factory,
|
||||
std::shared_ptr<OriginHandler>* root) {
|
||||
// TODO(kqyang): This need to be integrated back to media pipeline since we
|
||||
// may want to get not only text streams from the demuxer, in which case, the
|
||||
// same demuxer should be used to get all streams instead of having a demuxer
|
||||
// specifically for text.
|
||||
// TODO(kqyang): Support Cue Alignment if |sync_points| is not null.
|
||||
|
||||
Status status;
|
||||
std::shared_ptr<Demuxer> demuxer;
|
||||
|
||||
|
@ -385,14 +394,15 @@ Status CreateMp4ToMp4TextJob(const StreamDescriptor& stream,
|
|||
demuxer->SetLanguageOverride(stream.stream_selector, stream.language);
|
||||
}
|
||||
|
||||
std::shared_ptr<MediaHandler> chunker(
|
||||
new ChunkingHandler(packaging_params.chunking_params));
|
||||
auto chunker =
|
||||
std::make_shared<ChunkingHandler>(packaging_params.chunking_params);
|
||||
std::shared_ptr<Muxer> muxer =
|
||||
muxer_factory->CreateMuxer(GetOutputFormat(stream), stream);
|
||||
muxer->SetMuxerListener(std::move(muxer_listener));
|
||||
|
||||
status.Update(chunker->AddHandler(std::move(muxer)));
|
||||
status.Update(demuxer->SetHandler(stream.stream_selector, chunker));
|
||||
status.Update(
|
||||
demuxer->SetHandler(stream.stream_selector, std::move(chunker)));
|
||||
|
||||
return status;
|
||||
}
|
||||
|
@ -400,7 +410,10 @@ Status CreateMp4ToMp4TextJob(const StreamDescriptor& stream,
|
|||
Status CreateHlsTextJob(const StreamDescriptor& stream,
|
||||
const PackagingParams& packaging_params,
|
||||
std::unique_ptr<MuxerListener> muxer_listener,
|
||||
SyncPointQueue* sync_points,
|
||||
JobManager* job_manager) {
|
||||
// TODO(kqyang): Support Cue Alignment if |sync_points| is not null.
|
||||
|
||||
DCHECK(muxer_listener);
|
||||
DCHECK(job_manager);
|
||||
|
||||
|
@ -421,9 +434,8 @@ Status CreateHlsTextJob(const StreamDescriptor& stream,
|
|||
MuxerOptions muxer_options = CreateMuxerOptions(stream, packaging_params);
|
||||
muxer_options.bandwidth = stream.bandwidth ? stream.bandwidth : 256;
|
||||
|
||||
std::shared_ptr<WebVttSegmentedOutputHandler> output(
|
||||
new WebVttSegmentedOutputHandler(muxer_options,
|
||||
std::move(muxer_listener)));
|
||||
auto output = std::make_shared<WebVttSegmentedOutputHandler>(
|
||||
muxer_options, std::move(muxer_listener));
|
||||
|
||||
std::unique_ptr<FileReader> reader;
|
||||
Status open_status = FileReader::Open(stream.input, &reader);
|
||||
|
@ -431,10 +443,9 @@ Status CreateHlsTextJob(const StreamDescriptor& stream,
|
|||
return open_status;
|
||||
}
|
||||
|
||||
std::shared_ptr<OriginHandler> parser(
|
||||
new WebVttParser(std::move(reader), stream.language));
|
||||
std::shared_ptr<MediaHandler> segmenter(
|
||||
new WebVttSegmenter(segment_length_in_ms));
|
||||
auto parser =
|
||||
std::make_shared<WebVttParser>(std::move(reader), stream.language);
|
||||
auto segmenter = std::make_shared<WebVttSegmenter>(segment_length_in_ms);
|
||||
|
||||
// Build in reverse to allow us to move the pointers.
|
||||
Status status;
|
||||
|
@ -451,8 +462,11 @@ Status CreateHlsTextJob(const StreamDescriptor& stream,
|
|||
Status CreateWebVttToMp4TextJob(const StreamDescriptor& stream,
|
||||
const PackagingParams& packaging_params,
|
||||
std::unique_ptr<MuxerListener> muxer_listener,
|
||||
SyncPointQueue* sync_points,
|
||||
MuxerFactory* muxer_factory,
|
||||
std::shared_ptr<OriginHandler>* root) {
|
||||
// TODO(kqyang): Support Cue Alignment if |sync_points| is not null.
|
||||
|
||||
Status status;
|
||||
std::unique_ptr<FileReader> reader;
|
||||
status = FileReader::Open(stream.input, &reader);
|
||||
|
@ -461,11 +475,11 @@ Status CreateWebVttToMp4TextJob(const StreamDescriptor& stream,
|
|||
return status;
|
||||
}
|
||||
|
||||
std::shared_ptr<OriginHandler> parser(
|
||||
new WebVttParser(std::move(reader), stream.language));
|
||||
std::shared_ptr<MediaHandler> text_to_mp4(new WebVttToMp4Handler);
|
||||
std::shared_ptr<MediaHandler> chunker(
|
||||
new ChunkingHandler(packaging_params.chunking_params));
|
||||
auto parser =
|
||||
std::make_shared<WebVttParser>(std::move(reader), stream.language);
|
||||
auto text_to_mp4 = std::make_shared<WebVttToMp4Handler>();
|
||||
auto chunker =
|
||||
std::make_shared<ChunkingHandler>(packaging_params.chunking_params);
|
||||
std::shared_ptr<Muxer> muxer =
|
||||
muxer_factory->CreateMuxer(GetOutputFormat(stream), stream);
|
||||
muxer->SetMuxerListener(std::move(muxer_listener));
|
||||
|
@ -482,6 +496,7 @@ Status CreateWebVttToMp4TextJob(const StreamDescriptor& stream,
|
|||
Status CreateTextJobs(
|
||||
const std::vector<std::reference_wrapper<const StreamDescriptor>>& streams,
|
||||
const PackagingParams& packaging_params,
|
||||
SyncPointQueue* sync_points,
|
||||
MuxerListenerFactory* muxer_listener_factory,
|
||||
MuxerFactory* muxer_factory,
|
||||
MpdNotifier* mpd_notifier,
|
||||
|
@ -498,15 +513,15 @@ Status CreateTextJobs(
|
|||
|
||||
switch (DetermineContainerFromFileName(stream.input)) {
|
||||
case CONTAINER_WEBVTT:
|
||||
status.Update(CreateWebVttToMp4TextJob(stream, packaging_params,
|
||||
std::move(muxer_listener),
|
||||
muxer_factory, &root));
|
||||
status.Update(CreateWebVttToMp4TextJob(
|
||||
stream, packaging_params, std::move(muxer_listener), sync_points,
|
||||
muxer_factory, &root));
|
||||
break;
|
||||
|
||||
case CONTAINER_MOV:
|
||||
status.Update(CreateMp4ToMp4TextJob(stream, packaging_params,
|
||||
std::move(muxer_listener),
|
||||
muxer_factory, &root));
|
||||
status.Update(CreateMp4ToMp4TextJob(
|
||||
stream, packaging_params, std::move(muxer_listener), sync_points,
|
||||
muxer_factory, &root));
|
||||
break;
|
||||
|
||||
default:
|
||||
|
@ -543,8 +558,9 @@ Status CreateTextJobs(
|
|||
// If we are outputting to HLS, then create the HLS test pipeline that
|
||||
// will create segmented text output.
|
||||
if (hls_listener) {
|
||||
Status status = CreateHlsTextJob(stream, packaging_params,
|
||||
std::move(hls_listener), job_manager);
|
||||
Status status =
|
||||
CreateHlsTextJob(stream, packaging_params, std::move(hls_listener),
|
||||
sync_points, job_manager);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
@ -592,6 +608,7 @@ Status CreateAudioVideoJobs(
|
|||
const std::vector<std::reference_wrapper<const StreamDescriptor>>& streams,
|
||||
const PackagingParams& packaging_params,
|
||||
KeySource* encryption_key_source,
|
||||
SyncPointQueue* sync_points,
|
||||
MuxerListenerFactory* muxer_listener_factory,
|
||||
MuxerFactory* muxer_factory,
|
||||
JobManager* job_manager) {
|
||||
|
@ -601,14 +618,14 @@ Status CreateAudioVideoJobs(
|
|||
|
||||
// Demuxers are shared among all streams with the same input.
|
||||
std::shared_ptr<Demuxer> demuxer;
|
||||
// Chunkers can be shared among all streams with the same input (except for
|
||||
// WVM files), which allows samples from the same input to be synced when
|
||||
// doing chunking.
|
||||
std::shared_ptr<MediaHandler> chunker;
|
||||
bool is_wvm_file = false;
|
||||
// When |sync_points| is not null, there should be one CueAlignmentHandler per
|
||||
// input. All CueAlignmentHandler shares the same |sync_points|, which allows
|
||||
// sync points / cues to be aligned across streams, whether they are from the
|
||||
// same input or not.
|
||||
std::shared_ptr<CueAlignmentHandler> cue_aligner;
|
||||
// Replicators are shared among all streams with the same input and stream
|
||||
// selector.
|
||||
std::shared_ptr<MediaHandler> replicator;
|
||||
std::shared_ptr<Replicator> replicator;
|
||||
|
||||
std::string previous_input;
|
||||
std::string previous_selector;
|
||||
|
@ -624,15 +641,8 @@ Status CreateAudioVideoJobs(
|
|||
|
||||
job_manager->Add("RemuxJob", demuxer);
|
||||
|
||||
// Share chunkers among all streams with the same input except for WVM
|
||||
// file, which may contain multiple video files and the samples may not be
|
||||
// interleaved either.
|
||||
is_wvm_file =
|
||||
DetermineContainerFromFileName(stream.input) == CONTAINER_WVM;
|
||||
if (!is_wvm_file) {
|
||||
chunker =
|
||||
std::make_shared<ChunkingHandler>(packaging_params.chunking_params);
|
||||
}
|
||||
if (sync_points)
|
||||
cue_aligner = std::make_shared<CueAlignmentHandler>(sync_points);
|
||||
}
|
||||
|
||||
if (!stream.language.empty()) {
|
||||
|
@ -651,16 +661,8 @@ Status CreateAudioVideoJobs(
|
|||
}
|
||||
|
||||
if (new_stream) {
|
||||
std::shared_ptr<MediaHandler> ad_cue_generator;
|
||||
if (!packaging_params.ad_cue_generator_params.cue_points.empty()) {
|
||||
ad_cue_generator = std::make_shared<AdCueGenerator>(
|
||||
packaging_params.ad_cue_generator_params);
|
||||
}
|
||||
|
||||
if (is_wvm_file) {
|
||||
chunker =
|
||||
std::make_shared<ChunkingHandler>(packaging_params.chunking_params);
|
||||
}
|
||||
auto chunker =
|
||||
std::make_shared<ChunkingHandler>(packaging_params.chunking_params);
|
||||
|
||||
std::shared_ptr<MediaHandler> encryptor = CreateEncryptionHandler(
|
||||
packaging_params, stream, encryption_key_source);
|
||||
|
@ -668,10 +670,9 @@ Status CreateAudioVideoJobs(
|
|||
replicator = std::make_shared<Replicator>();
|
||||
|
||||
Status status;
|
||||
if (ad_cue_generator) {
|
||||
status.Update(
|
||||
demuxer->SetHandler(stream.stream_selector, ad_cue_generator));
|
||||
status.Update(ad_cue_generator->AddHandler(chunker));
|
||||
if (cue_aligner) {
|
||||
status.Update(demuxer->SetHandler(stream.stream_selector, cue_aligner));
|
||||
status.Update(cue_aligner->AddHandler(chunker));
|
||||
} else {
|
||||
status.Update(demuxer->SetHandler(stream.stream_selector, chunker));
|
||||
}
|
||||
|
@ -729,6 +730,7 @@ Status CreateAllJobs(const std::vector<StreamDescriptor>& stream_descriptors,
|
|||
const PackagingParams& packaging_params,
|
||||
MpdNotifier* mpd_notifier,
|
||||
KeySource* encryption_key_source,
|
||||
SyncPointQueue* sync_points,
|
||||
MuxerListenerFactory* muxer_listener_factory,
|
||||
MuxerFactory* muxer_factory,
|
||||
JobManager* job_manager) {
|
||||
|
@ -758,11 +760,11 @@ Status CreateAllJobs(const std::vector<StreamDescriptor>& stream_descriptors,
|
|||
media::StreamDescriptorCompareFn);
|
||||
|
||||
Status status;
|
||||
status.Update(CreateTextJobs(text_streams, packaging_params,
|
||||
status.Update(CreateTextJobs(text_streams, packaging_params, sync_points,
|
||||
muxer_listener_factory, muxer_factory,
|
||||
mpd_notifier, job_manager));
|
||||
status.Update(CreateAudioVideoJobs(
|
||||
audio_video_streams, packaging_params, encryption_key_source,
|
||||
audio_video_streams, packaging_params, encryption_key_source, sync_points,
|
||||
muxer_listener_factory, muxer_factory, job_manager));
|
||||
|
||||
if (!status.ok()) {
|
||||
|
@ -783,6 +785,7 @@ struct Packager::PackagerInternal {
|
|||
std::unique_ptr<KeySource> encryption_key_source;
|
||||
std::unique_ptr<MpdNotifier> mpd_notifier;
|
||||
std::unique_ptr<hls::HlsNotifier> hls_notifier;
|
||||
std::unique_ptr<SyncPointQueue> sync_points;
|
||||
BufferCallbackParams buffer_callback_params;
|
||||
media::JobManager job_manager;
|
||||
};
|
||||
|
@ -854,6 +857,11 @@ Status Packager::Initialize(
|
|||
internal->hls_notifier.reset(new hls::SimpleHlsNotifier(hls_params));
|
||||
}
|
||||
|
||||
if (!packaging_params.ad_cue_generator_params.cue_points.empty()) {
|
||||
internal->sync_points.reset(
|
||||
new SyncPointQueue(packaging_params.ad_cue_generator_params));
|
||||
}
|
||||
|
||||
std::vector<StreamDescriptor> streams_for_jobs;
|
||||
|
||||
for (const StreamDescriptor& descriptor : stream_descriptors) {
|
||||
|
@ -896,8 +904,8 @@ Status Packager::Initialize(
|
|||
|
||||
Status status = media::CreateAllJobs(
|
||||
streams_for_jobs, packaging_params, internal->mpd_notifier.get(),
|
||||
internal->encryption_key_source.get(), &muxer_listener_factory,
|
||||
&muxer_factory, &internal->job_manager);
|
||||
internal->encryption_key_source.get(), internal->sync_points.get(),
|
||||
&muxer_listener_factory, &muxer_factory, &internal->job_manager);
|
||||
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
|
|
Loading…
Reference in New Issue