From 0cf0678db75972d8e3f5a59d7eb835b686ed4849 Mon Sep 17 00:00:00 2001 From: Joey Parrish Date: Thu, 26 Aug 2021 09:59:24 -0700 Subject: [PATCH] Restore "Low latency dash support staging (#88)" This reverts commit 2a4d3076539326d3b7e63085e151970593bee43e, restoring LL-DASH support in anticipation of v0.5.0. Change-Id: I688ea46180755d3f1076ed3b386be57d29583cf7 --- CHANGELOG.md | 2 + .../pipeline_low_latency_dash_config.yaml | 58 ++++++++++++++ run_end_to_end_tests.py | 14 +++- streamer/controller_node.py | 21 +++++- streamer/packager_node.py | 10 +++ streamer/pipeline_configuration.py | 24 ++++++ tests/tests.js | 75 +++++++++++++++++++ 7 files changed, 200 insertions(+), 4 deletions(-) create mode 100644 config_files/pipeline_low_latency_dash_config.yaml diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f6ab36..2f401c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ - Multi period support for DASH (https://github.com/google/shaka-streamer/issues/43) (https://github.com/google/shaka-streamer/pull/78) + - Add LL-DASH support + (https://github.com/google/shaka-streamer/pull/88) ## 0.4.0 (2021-08-26) diff --git a/config_files/pipeline_low_latency_dash_config.yaml b/config_files/pipeline_low_latency_dash_config.yaml new file mode 100644 index 0000000..6a93699 --- /dev/null +++ b/config_files/pipeline_low_latency_dash_config.yaml @@ -0,0 +1,58 @@ +# Copyright 2019 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is a sample pipeline configuration file for Shaka Streamer in live mode. +# Here you configure resolutions, manifest formats, segment size, and more. + +# Streaming mode. Can be live or vod. +streaming_mode: live + +# A list of resolutions to encode. +resolutions: + - 720p + - 480p + +# A list of channel layouts to encode. +channel_layouts: + - stereo + +# The codecs to encode with. +audio_codecs: + - aac +video_codecs: + - h264 + +# Manifest format must be DASH for LL-DASH streaming +manifest_format: + - dash + +# Length of each segment in seconds. +segment_size: 2 + +# Availability window, or the number of seconds a segment remains available. +availability_window: 300 + +# Presentation delay, or how far back from the edge the player should be. +presentation_delay: 0 + +# Update period, or how often the player should fetch a new manifest. +update_period: 8 + +# Stream in low latency dash mode, or chunked +low_latency_dash_mode: True + +# UTC timing values, or the global timing source used for segment time stamps. +utc_timings: + - scheme_id_uri: urn:mpeg:dash:utc:http-xsdate:2014 + value: https://akamai.com/?.iso \ No newline at end of file diff --git a/run_end_to_end_tests.py b/run_end_to_end_tests.py index 238d16f..ef99654 100755 --- a/run_end_to_end_tests.py +++ b/run_end_to_end_tests.py @@ -91,9 +91,17 @@ def dashStreamsReady(manifest_path): pattern = re.compile(r'') with open(manifest_path) as manifest_file: for representation in pattern.finditer(manifest_file.read()): - if not re.search(r'. + # Check for the availabilityTimeOffset attribute instead. + if not re.search(r'availabilityTimeOffset', representation.group()): + # This Representation does not have a availabilityTimeOffset yet, + # meaning the first chunk is not yet ready for playout. + return False + else: + if not re.search(r' bool: return self._pipeline_config.streaming_mode == StreamingMode.VOD + def is_low_latency_dash_mode(self) -> bool: + """Returns True if the pipeline is running in LL-DASH mode. + + :rtype: bool + """ + + return self._pipeline_config.low_latency_dash_mode + class VersionError(Exception): """A version error for one of Shaka Streamer's external dependencies. diff --git a/streamer/packager_node.py b/streamer/packager_node.py index 49006bb..86e8c09 100644 --- a/streamer/packager_node.py +++ b/streamer/packager_node.py @@ -160,6 +160,16 @@ def _setup_stream(self, stream: OutputStream) -> str: def _setup_manifest_format(self) -> List[str]: args: List[str] = [] if ManifestFormat.DASH in self._pipeline_config.manifest_format: + if self._pipeline_config.utc_timings: + args += [ + '--utc_timings', + ','.join(timing.scheme_id_uri + '=' + + timing.value for timing in self._pipeline_config.utc_timings) + ] + if self._pipeline_config.low_latency_dash_mode: + args += [ + '--low_latency_dash_mode=true', + ] if self._pipeline_config.streaming_mode == StreamingMode.VOD: args += [ '--generate_static_live_mpd', diff --git a/streamer/pipeline_configuration.py b/streamer/pipeline_configuration.py index 72379ab..8ce8aba 100644 --- a/streamer/pipeline_configuration.py +++ b/streamer/pipeline_configuration.py @@ -76,6 +76,17 @@ class EncryptionMode(enum.Enum): RAW = 'raw' """Raw key mode""" +class UtcTimingPair(configuration.Base): + """An object containing the attributes for a DASH MPD UTCTiming + element""" + + # TODO: Use an enum for scheme_id_uri to simplify the config input + scheme_id_uri = configuration.Field(str).cast() + """SchemeIdUri attribute to be used for the UTCTiming element""" + + value = configuration.Field(str).cast() + """Value attribute to be used for the UTCTiming element""" + class RawKeyConfig(configuration.Base): """An object representing a list of keys for Raw key encryption""" @@ -309,6 +320,19 @@ class PipelineConfig(configuration.Base): default=EncryptionConfig({})).cast() """Encryption settings.""" + # TODO: Generalize this to low_latency_mode once LL-HLS is supported by Packager + low_latency_dash_mode = configuration.Field(bool, default=False).cast() + """If true, stream in low latency mode for DASH.""" + + utc_timings = configuration.Field(List[UtcTimingPair]).cast() + """UTCTiming schemeIdUri and value pairs for the DASH MPD. + + If multiple UTCTiming pairs are provided for redundancy, + list the pairs in the order of preference. + + Must be set for LL-DASH streaming. + """ + def __init__(self, *args) -> None: diff --git a/tests/tests.js b/tests/tests.js index fbf66eb..a507c1e 100644 --- a/tests/tests.js +++ b/tests/tests.js @@ -147,6 +147,8 @@ describe('Shaka Streamer', () => { muxedTextTests(dashManifestUrl, '(dash)'); multiPeriodTests(dashManifestUrl, '(dash)'); + + lowLatencyDashTests(dashManifestUrl, '(dash)'); }); function errorTests() { @@ -378,6 +380,39 @@ function errorTests() { error_type: 'RuntimeError', })); }); + + it('fails when utc_timing is not set for low_latency_dash_mode', async () => { + const inputConfig = getBasicInputConfig(); + const pipelineConfig = { + low_latency_dash_mode: true, + streaming_mode: 'live', + }; + + await expectAsync(startStreamer(inputConfig, pipelineConfig)) + .toBeRejectedWith(jasmine.objectContaining({ + error_type: 'RuntimeError', + })); + }); + + it('fails when low_latency_dash_mode is set without a DASH manifest', async () => { + const inputConfig = getBasicInputConfig(); + const pipelineConfig = { + low_latency_dash_mode: true, + manifest_format: ['hls'], + streaming_mode: 'live', + utc_timings: [ + { + scheme_id_uri:'urn:mpeg:dash:utc:http-xsdate:2014', + value:'https://time.akamai.com/?.iso' + }, + ], + }; + + await expectAsync(startStreamer(inputConfig, pipelineConfig)) + .toBeRejectedWith(jasmine.objectContaining({ + error_type: 'RuntimeError', + })); + }); } function resolutionTests(manifestUrl, format) { @@ -1397,3 +1432,43 @@ function multiPeriodTests(manifestUrl, format) { expect(video.duration).toBeGreaterThan(1.9); }); } + +function lowLatencyDashTests(manifestUrl, format) { + it('can process LL-DASH streaming ' + format, async() => { + const inputConfigDict = { + 'inputs': [ + { + 'name': TEST_DIR + 'BigBuckBunny.1080p.mp4', + 'media_type': 'video', + // Keep this test short by only encoding 4s of content. + 'end_time': '0:04', + } + ], + }; + const pipelineConfigDict = { + 'streaming_mode': 'live', + 'resolutions': ['144p'], + 'video_codecs': ['h264'], + 'manifest_format': ['dash'], + 'segment_size': 2, + 'low_latency_dash_mode': true, + 'utc_timings': [ + { + 'scheme_id_uri':'urn:mpeg:dash:utc:http-xsdate:2014', + 'value':'https://time.akamai.com/?.iso' + }, + ], + }; + await startStreamer(inputConfigDict, pipelineConfigDict); + + // TODO(CaitlinO'Callaghan): fix so player loads and test passes + player.configure({ + streaming: { + lowLatencyMode: true, + inaccurateManifestTolerance: 0, + rebufferingGoal: 0.01, + } + }); + await player.load(manifestUrl); + }); +}