Skip to content

Commit

Permalink
Low latency dash support staging (#88)
Browse files Browse the repository at this point in the history
Support LL-DASH streaming.  Dependent on upcoming Packager release (v2.6).
  • Loading branch information
CaitlinOCallaghan authored Aug 25, 2021
1 parent 5a5b53d commit 4fc6d19
Show file tree
Hide file tree
Showing 6 changed files with 197 additions and 4 deletions.
58 changes: 58 additions & 0 deletions config_files/pipeline_low_latency_dash_config.yaml
Original file line number Diff line number Diff line change
@@ -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
14 changes: 11 additions & 3 deletions run_end_to_end_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,17 @@ def dashStreamsReady(manifest_path):
pattern = re.compile(r'<Representation.*?((\n).*?)*?Representation>')
with open(manifest_path) as manifest_file:
for representation in pattern.finditer(manifest_file.read()):
if not re.search(r'<S t', representation.group()):
# This Representation has no segments.
return False
if controller.is_low_latency_dash_mode():
# LL-DASH manifests do not contain the segment reference tag <S>.
# 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'<S t', representation.group()):
# This Representation has no segments.
return False

return True

Expand Down
21 changes: 20 additions & 1 deletion streamer/controller_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
from streamer.node_base import NodeBase, ProcessStatus
from streamer.output_stream import AudioOutputStream, OutputStream, TextOutputStream, VideoOutputStream
from streamer.packager_node import PackagerNode
from streamer.pipeline_configuration import PipelineConfig, StreamingMode
from streamer.pipeline_configuration import ManifestFormat, PipelineConfig, StreamingMode
from streamer.transcoder_node import TranscoderNode
from streamer.periodconcat_node import PeriodConcatNode
import streamer.subprocessWindowsPatch # side-effects only
Expand Down Expand Up @@ -143,6 +143,17 @@ def start(self, output_location: str,
raise RuntimeError(
'Multiperiod input list support is incompatible with HTTP outputs.')

if self._pipeline_config.low_latency_dash_mode:
# Check some restrictions on LL-DASH packaging.
if ManifestFormat.DASH not in self._pipeline_config.manifest_format:
raise RuntimeError(
'low_latency_dash_mode is only compatible with DASH ouputs. ' +
'manifest_format must include DASH')

if not self._pipeline_config.utc_timings:
raise RuntimeError(
'For low_latency_dash_mode, the utc_timings must be set.')

# Note that we remove the trailing slash from the output location, because
# otherwise GCS would create a subdirectory whose name is "".
output_location = output_location.rstrip('/')
Expand Down Expand Up @@ -289,6 +300,14 @@ def is_vod(self) -> 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.
Expand Down
10 changes: 10 additions & 0 deletions streamer/packager_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
24 changes: 24 additions & 0 deletions streamer/pipeline_configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"""

Expand Down Expand Up @@ -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:

Expand Down
74 changes: 74 additions & 0 deletions tests/tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,8 @@ describe('Shaka Streamer', () => {
muxedTextTests(dashManifestUrl, '(dash)');

multiPeriodTests(dashManifestUrl, '(dash)');

lowLatencyDashTests(dashManifestUrl, '(dash)');
});

function errorTests() {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -1396,4 +1431,43 @@ function multiPeriodTests(manifestUrl, format) {
// Be more tolerant with float comparison, (D > 1.9 * length) instead of (D == 2 * length).
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);
});
}

0 comments on commit 4fc6d19

Please sign in to comment.