Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Low latency dash support staging #88

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
});
}