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 #979

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
3606211
Low lat dash control hooks to create a chunk for each media sample
CaitlinOCallaghan Jul 15, 2021
fdfb3d3
Setup class to handle low latency mp4 segments
CaitlinOCallaghan Jul 15, 2021
ea1537a
Handle chunks when they are finalized
CaitlinOCallaghan Jul 16, 2021
caa3fd3
Add 'availabilityTimeOffset'to DASH MPD for LL DASH streams
CaitlinOCallaghan Jul 16, 2021
735cea5
Add 'duration' to SegmentTemplate and remove SegmentTimeline for LL D…
CaitlinOCallaghan Jul 16, 2021
99b442a
Upload chunks via HTTP as they are created
CaitlinOCallaghan Jul 21, 2021
89bfc16
WIP: Manifest changes to pass LL DASH-IF validation
CaitlinOCallaghan Jul 21, 2021
465e7c6
HTTP correction - add line that was accidentally deleted
CaitlinOCallaghan Jul 21, 2021
ac7ed90
WIP: Clean up the writing of each chunk
CaitlinOCallaghan Jul 26, 2021
debf467
Clean up chunk writing
CaitlinOCallaghan Jul 26, 2021
ff5741d
Improve process of setting the 'duration' value for LL DASH MPD
CaitlinOCallaghan Jul 26, 2021
8048867
Finalize serviceDescription implementation to satisfy IF-DASH LL stan…
CaitlinOCallaghan Jul 26, 2021
fa956ac
Clean up code and logic
CaitlinOCallaghan Jul 27, 2021
7ea17dc
Improve MPD set segment duration implementation
CaitlinOCallaghan Jul 27, 2021
82ec563
Change file_name scope to calculate it once per seg rather than calcu…
CaitlinOCallaghan Jul 27, 2021
a064665
Better handle LL-DASH case within ChunkHandler and Segmenter
CaitlinOCallaghan Jul 27, 2021
3242a68
Clean up comments and naming
CaitlinOCallaghan Jul 28, 2021
cc550f9
Fix alignment
CaitlinOCallaghan Jul 30, 2021
1b7b348
Remove unneeded line
CaitlinOCallaghan Jul 30, 2021
f945484
Remove unnecessay steps when writing to ULL file
CaitlinOCallaghan Jul 30, 2021
9177469
Update segment file member var to be unique_ptr
CaitlinOCallaghan Aug 2, 2021
2ba03c0
Clarifying comment for availabilityTimeOffset calculation
CaitlinOCallaghan Aug 2, 2021
ffe41ff
Clarifying comment for --fragment_duration and --is_low_latency incom…
CaitlinOCallaghan Aug 2, 2021
9b6d056
HTTP file cleanup. Removed unnecessary changes.
CaitlinOCallaghan Aug 2, 2021
1faa487
Fix spacing
CaitlinOCallaghan Aug 2, 2021
d771a90
Write descriptions for low latency dash flags
CaitlinOCallaghan Aug 2, 2021
eb0d26a
Update '--is_low_latency_dash' description
CaitlinOCallaghan Aug 2, 2021
3c747ef
Typo fix
CaitlinOCallaghan Aug 3, 2021
8d0d908
Remove unnecessary log
CaitlinOCallaghan Aug 3, 2021
ae0fb68
Style fixes to satisfy clang-format lint
CaitlinOCallaghan Aug 3, 2021
58e652f
Improve comment regarding --is_low_latency and --fragment_duration in…
CaitlinOCallaghan Aug 6, 2021
3cb6884
Ensure --utc_timings is set if --is_low_latency
CaitlinOCallaghan Aug 6, 2021
1d78d21
Write SimpleMpdNotifierTest.NotifySegmentDuration test case
CaitlinOCallaghan Aug 6, 2021
06a92db
Write SimpleMpdNotifierTest.NotifyAvailabilityTimeOffset test case
CaitlinOCallaghan Aug 6, 2021
c49f264
Write PackagerTest.LowLatencyDashEnabledAndFragmentDurationSet test case
CaitlinOCallaghan Aug 10, 2021
4b54389
Write PackagerTest.LowLatencyDashEnabledAndUtcTimingNotSet test case
CaitlinOCallaghan Aug 10, 2021
a023b33
Write ChunkingHandlerTest.LowLatencyDash unit test
CaitlinOCallaghan Aug 10, 2021
0edf8fe
Write PeriodTest.LowLatencyDashMpdGetXml unit test
CaitlinOCallaghan Aug 11, 2021
00f7b07
Write MpdNotifyMuxerListenerTest.LowLatencyDash unit test
CaitlinOCallaghan Aug 11, 2021
efa1ca0
Write LowLatencySegmentTest.LowLatencySegmentTemplate unit test
CaitlinOCallaghan Aug 11, 2021
6f9fef3
Format code
CaitlinOCallaghan Aug 11, 2021
0e88f2d
Remove unnecessary line
CaitlinOCallaghan Aug 11, 2021
115657c
Specify value as float to pass 'Build and test win Debug shared' check
CaitlinOCallaghan Aug 11, 2021
29e2a32
Restore line that was removed previously - it is necessary to include
CaitlinOCallaghan Aug 11, 2021
c8443eb
Fixes to ChunkingHandlerTest.LowLatencyDash unittest
CaitlinOCallaghan Aug 11, 2021
5996805
Change availabilityTimeOffset test value
CaitlinOCallaghan Aug 11, 2021
6cfe1bb
Remove rounding on availabilityTimelineOffset for LL-DASH MPD
CaitlinOCallaghan Aug 11, 2021
98e2312
Add missing braket
CaitlinOCallaghan Aug 11, 2021
2791ca5
Change availabilityTimeOffset type from float to double
CaitlinOCallaghan Aug 11, 2021
7b7c348
Format fix
CaitlinOCallaghan Aug 12, 2021
c4540c9
LL-DASH flag name change
CaitlinOCallaghan Aug 17, 2021
1d7b941
Low Latency DASH web documentation
CaitlinOCallaghan Aug 12, 2021
46412b8
Cleanup
CaitlinOCallaghan Aug 13, 2021
03564a7
Account for LL-DASH name change
CaitlinOCallaghan Aug 17, 2021
95db305
Missing word fix
CaitlinOCallaghan Aug 17, 2021
8b168ec
Add open source examples that support LL-DASH
CaitlinOCallaghan Aug 17, 2021
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
5 changes: 5 additions & 0 deletions docs/source/options/dash_options.rst
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,8 @@ DASH options

If enabled, allow adaptive switching between different codecs, if they have
the same language, media type (audio, video etc) and container type.

--low_latency_dash_mode

If enabled, LL-DASH streaming will be used,
reducing overall latency by decoupling latency from segment duration.
103 changes: 103 additions & 0 deletions docs/source/tutorials/low_latency.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
####################################
Low Latency DASH (LL-DASH) Streaming
####################################

************
Introduction
************

If ``--low_latency_dash_mode`` is enabled, low latency DASH (LL-DASH) packaging will be used.

This will reduce overall latency by ensuring that the media segments are chunk encoded and delivered via an aggregating response.
The combination of these features will ensure that overall latency can be decoupled from the segment duration.
For low latency to be achieved, the output of Shaka Packager must be combined with a delivery system which can chain together a set of aggregating responses, such as chunked transfer encoding under HTTP/1.1 or a HTTP/2 or HTTP/3 connection.
The output of Shaka Packager must be played with a DASH client that understands the availabilityTimeOffset MPD value.
Furthermore, the player should also understand the throughput estimation and ABR challenges that arise when operating in the low latency regime.

This tutorial covers LL-DASH packaging and uses features from the DASH, HTTP upload, and FFmpeg piping tutorials.
For more information on DASH, see :doc:`dash`; for HTTP upload, see :doc:`http_upload`;
for FFmpeg piping, see :doc:`ffmpeg_piping`;
for full documentation, see :doc:`/documentation`.

*************
Documentation
*************

Getting started
===============

To enable LL-DASH mode, set the ``--low_latency_dash_mode`` flag to ``true``.

All HTTP requests will use chunked transfer encoding:
``Transfer-Encoding: chunked``.

.. note::

Only LL-DASH is supported. LL-HLS support is yet to come.

Synopsis
========

Here is a basic example of the LL-DASH support.
The LL-DASH setup borrows features from "FFmpeg piping" and "HTTP upload",
see :doc:`ffmpeg_piping` and :doc:`http_upload`.

Define UNIX pipe to connect ffmpeg with packager::

export PIPE=/tmp/bigbuckbunny.fifo
mkfifo ${PIPE}

Acquire and transcode RTMP stream::

ffmpeg -fflags nobuffer -threads 0 -y \
-i rtmp://184.72.239.149/vod/mp4:bigbuckbunny_450.mp4 \
-pix_fmt yuv420p -vcodec libx264 -preset:v superfast -acodec aac \
-f mpegts pipe: > ${PIPE}

Configure and run packager::

# Define upload URL
export UPLOAD_URL=http://localhost:6767/ll-dash

# Go
packager \
"input=${PIPE},stream=audio,init_segment=${UPLOAD_URL}_init.m4s,segment_template=${UPLOAD_URL}/bigbuckbunny-audio-aac-\$Number%04d\$.m4s" \
"input=${PIPE},stream=video,init_segment=${UPLOAD_URL}_init.m4s,segment_template=${UPLOAD_URL}/bigbuckbunny-video-h264-450-\$Number%04d\$.m4s" \
--io_block_size 65536 \
--segment_duration 2 \
--low_latency_dash_mode=true \
--utc_timings "urn:mpeg:dash:utc:http-xsdate:2014"="https://time.akamai.com/?iso" \
--mpd_output "${UPLOAD_URL}/bigbuckbunny.mpd" \


*************************
Low Latency Compatibility
*************************

For low latency to be achieved, the processes handling Shaka Packager's output, such as the server and player,
must support LL-DASH streaming.

Delivery Pipeline
=================
Shaka Packager will upload the LL-DASH content to the specified output via HTTP chunked transfer encoding.
The server must have the ability to handle this type of request. If using a proxy or shim for cloud authentication,
these services must also support HTTP chunked transfer encoding.

Examples of supporting content delivery systems:

* `AWS MediaStore <https://aws.amazon.com/mediastore/>`_
* `s3-upload-proxy <https://github.com/fsouza/s3-upload-proxy>`_
* `Streamline Low Latency DASH preview <https://github.com/streamlinevideo/low-latency-preview>`_
* `go-chunked-streaming-server <https://github.com/mjneil/go-chunked-streaming-server>`_

Player
======
The player must support LL-DASH playout.
LL-DASH requires the player to be able to interpret ``availabilityTimeOffset`` values from the DASH MPD.
The player should also recognize the the throughput estimation and ABR challenges that arise with low latency streaming.

Examples of supporting players:

* `Shaka Player <https://github.com/google/shaka-player>`_
* `dash.js <https://github.com/Dash-Industry-Forum/dash.js>`_
* `Streamline Low Latency DASH preview <https://github.com/streamlinevideo/low-latency-preview>`_
1 change: 1 addition & 0 deletions docs/source/tutorials/tutorials.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ Tutorials
ads.rst
ffmpeg_piping.rst
http_upload.rst
low_latency.rst
8 changes: 8 additions & 0 deletions packager/app/mpd_flags.cc
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,11 @@ DEFINE_bool(dash_force_segment_list,
"content is huge and the total number of (sub)segment references "
"is greater than what the sidx atom allows (65535). Currently "
"this flag is only supported in DASH ondemand profile.");
DEFINE_bool(
low_latency_dash_mode,
false,
"If enabled, LL-DASH streaming will be used, "
"reducing overall latency by decoupling latency from segment duration. "
"Please see "
"https://google.github.io/shaka-packager/html/tutorials/low_latency.html "
"for more information.");
1 change: 1 addition & 0 deletions packager/app/mpd_flags.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,6 @@ DECLARE_bool(allow_approximate_segment_timeline);
DECLARE_bool(allow_codec_switching);
DECLARE_bool(include_mspr_pro_for_playready);
DECLARE_bool(dash_force_segment_list);
DECLARE_bool(low_latency_dash_mode);

#endif // APP_MPD_FLAGS_H_
3 changes: 3 additions & 0 deletions packager/app/packager_main.cc
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,7 @@ base::Optional<PackagingParams> GetPackagingParams() {
ChunkingParams& chunking_params = packaging_params.chunking_params;
chunking_params.segment_duration_in_seconds = FLAGS_segment_duration;
chunking_params.subsegment_duration_in_seconds = FLAGS_fragment_duration;
chunking_params.low_latency_dash_mode = FLAGS_low_latency_dash_mode;
chunking_params.segment_sap_aligned = FLAGS_segment_sap_aligned;
chunking_params.subsegment_sap_aligned = FLAGS_fragment_sap_aligned;

Expand Down Expand Up @@ -435,6 +436,7 @@ base::Optional<PackagingParams> GetPackagingParams() {
mp4_params.generate_sidx_in_media_segments =
FLAGS_generate_sidx_in_media_segments;
mp4_params.include_pssh_in_stream = FLAGS_mp4_include_pssh_in_stream;
mp4_params.low_latency_dash_mode = FLAGS_low_latency_dash_mode;

packaging_params.transport_stream_timestamp_offset_ms =
FLAGS_transport_stream_timestamp_offset_ms;
Expand Down Expand Up @@ -474,6 +476,7 @@ base::Optional<PackagingParams> GetPackagingParams() {
FLAGS_allow_approximate_segment_timeline;
mpd_params.allow_codec_switching = FLAGS_allow_codec_switching;
mpd_params.include_mspr_pro = FLAGS_include_mspr_pro_for_playready;
mpd_params.low_latency_dash_mode = FLAGS_low_latency_dash_mode;

HlsParams& hls_params = packaging_params.hls_params;
if (!GetHlsPlaylistType(FLAGS_hls_playlist_type, &hls_params.playlist_type)) {
Expand Down
1 change: 1 addition & 0 deletions packager/file/file.cc
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ File* File::CreateInternalFile(const char* file_name, const char* mode) {
base::StringPiece real_file_name;
const FileTypeInfo* file_type = GetFileTypeInfo(file_name, &real_file_name);
DCHECK(file_type);
// Calls constructor for the derived File class.
return file_type->factory_function(real_file_name.data(), mode);
}

Expand Down
2 changes: 1 addition & 1 deletion packager/file/http_file.cc
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ void HttpFile::SetupRequest() {
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &CurlWriteCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA,
method_ == HttpMethod::kPut ? nullptr : &download_cache_);
method_ == HttpMethod::kGet ? &download_cache_ : nullptr);
if (method_ != HttpMethod::kGet) {
curl_easy_setopt(curl, CURLOPT_READFUNCTION, &CurlReadCallback);
curl_easy_setopt(curl, CURLOPT_READDATA, &upload_cache_);
Expand Down
2 changes: 2 additions & 0 deletions packager/media/base/media_handler.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ struct CueEvent {

struct SegmentInfo {
bool is_subsegment = false;
bool is_chunk = false;
bool is_final_chunk_in_seg = false;
bool is_encrypted = false;
int64_t start_timestamp = -1;
int64_t duration = 0;
Expand Down
24 changes: 23 additions & 1 deletion packager/media/chunking/chunking_handler.cc
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,23 @@ Status ChunkingHandler::OnMediaSample(
started_new_segment = true;
}
}
if (!started_new_segment && IsSubsegmentEnabled()) {

// This handles the LL-DASH case.
// On each media sample, which is the basis for a chunk,
// we must increment the current_subsegment_index_
// in order to hit FinalizeSegment() within Segmenter.
if (!started_new_segment && chunking_params_.low_latency_dash_mode) {
current_subsegment_index_++;

RETURN_IF_ERROR(EndSubsegmentIfStarted());
subsegment_start_time_ = timestamp;
}

// Here, a subsegment refers to a fragment that is within a segment.
// This fragment size can be set with the 'fragment_duration' cmd arg.
// This is NOT for the LL-DASH case.
if (!started_new_segment && IsSubsegmentEnabled() &&
!chunking_params_.low_latency_dash_mode) {
const bool can_start_new_subsegment =
sample->is_key_frame() || !chunking_params_.subsegment_sap_aligned;
if (can_start_new_subsegment) {
Expand Down Expand Up @@ -151,6 +167,10 @@ Status ChunkingHandler::EndSegmentIfStarted() const {
auto segment_info = std::make_shared<SegmentInfo>();
segment_info->start_timestamp = segment_start_time_.value();
segment_info->duration = max_segment_time_ - segment_start_time_.value();
if (chunking_params_.low_latency_dash_mode) {
segment_info->is_chunk = true;
segment_info->is_final_chunk_in_seg = true;
}
return DispatchSegmentInfo(kStreamIndex, std::move(segment_info));
}

Expand All @@ -163,6 +183,8 @@ Status ChunkingHandler::EndSubsegmentIfStarted() const {
subsegment_info->duration =
max_segment_time_ - subsegment_start_time_.value();
subsegment_info->is_subsegment = true;
if (chunking_params_.low_latency_dash_mode)
subsegment_info->is_chunk = true;
return DispatchSegmentInfo(kStreamIndex, std::move(subsegment_info));
}

Expand Down
43 changes: 43 additions & 0 deletions packager/media/chunking/chunking_handler_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -207,5 +207,48 @@ TEST_F(ChunkingHandlerTest, CueEvent) {
kDuration, !kEncrypted, _)));
}

TEST_F(ChunkingHandlerTest, LowLatencyDash) {
ChunkingParams chunking_params;
chunking_params.low_latency_dash_mode = true;
chunking_params.segment_duration_in_seconds = 1;
SetUpChunkingHandler(1, chunking_params);

// Each completed segment will contain 2 chunks
const int64_t kChunkDurationInMs = 500;
const int64_t kSegmentDurationInMs = 1000;

ASSERT_OK(Process(StreamData::FromStreamInfo(
kStreamIndex, GetVideoStreamInfo(kTimeScale1))));

for (int i = 0; i < 4; ++i) {
ASSERT_OK(Process(StreamData::FromMediaSample(
kStreamIndex, GetMediaSample(i * kChunkDurationInMs, kChunkDurationInMs,
kKeyFrame))));
}

// NOTE: Each MediaSample will create a chunk, dispatching SegmentInfo
EXPECT_THAT(
GetOutputStreamDataVector(),
ElementsAre(
IsStreamInfo(kStreamIndex, kTimeScale1, !kEncrypted, _),
// Chunk 1 for segment 1
IsMediaSample(kStreamIndex, 0, kChunkDurationInMs, !kEncrypted, _),
IsSegmentInfo(kStreamIndex, 0, kChunkDurationInMs, kIsSubsegment,
!kEncrypted),
// Chunk 2 for segment 1
IsMediaSample(kStreamIndex, kChunkDurationInMs, kChunkDurationInMs,
!kEncrypted, _),
IsSegmentInfo(kStreamIndex, 0, 2 * kChunkDurationInMs, !kIsSubsegment,
!kEncrypted),
// Chunk 1 for segment 2
IsMediaSample(kStreamIndex, kSegmentDurationInMs, kChunkDurationInMs,
!kEncrypted, _),
IsSegmentInfo(kStreamIndex, kSegmentDurationInMs, kChunkDurationInMs,
kIsSubsegment, !kEncrypted),
// Chunk 2 for segment 2
IsMediaSample(kStreamIndex, kSegmentDurationInMs + kChunkDurationInMs,
kChunkDurationInMs, !kEncrypted, _)));
}

} // namespace media
} // namespace shaka
12 changes: 12 additions & 0 deletions packager/media/event/combined_muxer_listener.cc
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,24 @@ void CombinedMuxerListener::OnMediaStart(const MuxerOptions& muxer_options,
}
}

void CombinedMuxerListener::OnAvailabilityOffsetReady() {
for (auto& listener : muxer_listeners_) {
listener->OnAvailabilityOffsetReady();
}
}

void CombinedMuxerListener::OnSampleDurationReady(int32_t sample_duration) {
for (auto& listener : muxer_listeners_) {
listener->OnSampleDurationReady(sample_duration);
}
}

void CombinedMuxerListener::OnSegmentDurationReady() {
for (auto& listener : muxer_listeners_) {
listener->OnSegmentDurationReady();
}
}

void CombinedMuxerListener::OnMediaEnd(const MediaRanges& media_ranges,
float duration_seconds) {
for (auto& listener : muxer_listeners_) {
Expand Down
2 changes: 2 additions & 0 deletions packager/media/event/combined_muxer_listener.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ class CombinedMuxerListener : public MuxerListener {
const StreamInfo& stream_info,
int32_t time_scale,
ContainerType container_type) override;
void OnAvailabilityOffsetReady() override;
void OnSampleDurationReady(int32_t sample_duration) override;
void OnSegmentDurationReady() override;
void OnMediaEnd(const MediaRanges& media_ranges,
float duration_seconds) override;
void OnNewSegment(const std::string& file_name,
Expand Down
10 changes: 10 additions & 0 deletions packager/media/event/mpd_notify_muxer_listener.cc
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,11 @@ void MpdNotifyMuxerListener::OnMediaStart(const MuxerOptions& muxer_options,
}
}

// Record the availability time offset for LL-DASH manifests.
void MpdNotifyMuxerListener::OnAvailabilityOffsetReady() {
mpd_notifier_->NotifyAvailabilityTimeOffset(notification_id_.value());
}

// Record the sample duration in the media info for VOD so that OnMediaEnd, all
// the information is in the media info.
void MpdNotifyMuxerListener::OnSampleDurationReady(int32_t sample_duration) {
Expand All @@ -127,6 +132,11 @@ void MpdNotifyMuxerListener::OnSampleDurationReady(int32_t sample_duration) {
media_info_->mutable_video_info()->set_frame_duration(sample_duration);
}

// Record the segment duration for LL-DASH manifests.
void MpdNotifyMuxerListener::OnSegmentDurationReady() {
mpd_notifier_->NotifySegmentDuration(notification_id_.value());
}

void MpdNotifyMuxerListener::OnMediaEnd(const MediaRanges& media_ranges,
float duration_seconds) {
if (mpd_notifier_->dash_profile() == DashProfile::kLive) {
Expand Down
2 changes: 2 additions & 0 deletions packager/media/event/mpd_notify_muxer_listener.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@ class MpdNotifyMuxerListener : public MuxerListener {
const StreamInfo& stream_info,
int32_t time_scale,
ContainerType container_type) override;
void OnAvailabilityOffsetReady() override;
void OnSampleDurationReady(int32_t sample_duration) override;
void OnSegmentDurationReady() override;
void OnMediaEnd(const MediaRanges& media_ranges,
float duration_seconds) override;
void OnNewSegment(const std::string& file_name,
Expand Down
Loading