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

Adding support for hls EXT_X_PROGRAM_DATE_TIME #840

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions AUTHORS
Expand Up @@ -30,3 +30,4 @@ Richard Eklycke <richard@eklycke.se>
Sanil Raut <sr1990003@gmail.com>
Sergio Ammirata <sergio@ammirata.net>
The Chromium Authors <*@chromium.org>
cdnnow! <*@cdnnow.pro>
1 change: 1 addition & 0 deletions CONTRIBUTORS
Expand Up @@ -24,6 +24,7 @@

Alen Vrecko <alen.vrecko@gmail.com>
Anders Hasselqvist <anders.hasselqvist@gmail.com>
Artem Bogdanov <ykidia@gmail.com>
Bei Li <beil@google.com>
Chun-da Chen <capitalm.c@gmail.com>
Daniel Cantarín <canta@canta.com.ar>
Expand Down
7 changes: 7 additions & 0 deletions docs/source/options/hls_options.rst
Expand Up @@ -76,6 +76,13 @@ HLS options
The EXT-X-MEDIA-SEQUENCE documentation can be read here:
https://tools.ietf.org/html/rfc8216#section-4.3.3.2.


--hls_ext_x_program_date_time <time_offset_ms>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have a definitive answer myself right now. I am thinking whether it is a good idea to reuse --suggested_presentation_delay from DASH for the offset in HLS instead having another offset here.


Adds an EXT-X-PROGRAM-DATE-TIME tag to every Media Segment, with fixed UTC
timezone. Time offset is the difference in milliseconds between packaging time
and live event time.

--hls_only=0|1

Optional. Defaults to 0 if not specified. If it is set to 1, indicates the
Expand Down
4 changes: 4 additions & 0 deletions packager/app/hls_flags.cc
Expand Up @@ -30,3 +30,7 @@ DEFINE_int32(hls_media_sequence_number,
"EXT-X-MEDIA-SEQUENCE value, which allows continuous media "
"sequence across packager restarts. See #691 for more "
"information about the reasoning of this and its use cases.");
DEFINE_int32(hls_ext_x_program_date_time,
INT32_MIN,
"Enable generation of EXT-X-PROGRAM-DATE-TIME tag with "
"a given time offset in milliseconds");
1 change: 1 addition & 0 deletions packager/app/hls_flags.h
Expand Up @@ -14,5 +14,6 @@ DECLARE_string(hls_base_url);
DECLARE_string(hls_key_uri);
DECLARE_string(hls_playlist_type);
DECLARE_int32(hls_media_sequence_number);
DECLARE_int32(hls_ext_x_program_date_time);

#endif // PACKAGER_APP_HLS_FLAGS_H_
5 changes: 5 additions & 0 deletions packager/app/packager_main.cc
Expand Up @@ -490,6 +490,11 @@ base::Optional<PackagingParams> GetPackagingParams() {
hls_params.default_text_language = FLAGS_default_text_language;
hls_params.media_sequence_number = FLAGS_hls_media_sequence_number;

if (FLAGS_hls_ext_x_program_date_time != INT32_MIN) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the reason not to use 0 as default?

hls_params.add_ext_x_program_date_time = true;
hls_params.packaging_time_offset_ms = FLAGS_hls_ext_x_program_date_time;
}

TestParams& test_params = packaging_params.test_params;
test_params.dump_stream_info = FLAGS_dump_stream_info;
test_params.inject_fake_clock = FLAGS_use_fake_clock_for_muxer;
Expand Down
83 changes: 61 additions & 22 deletions packager/hls/base/media_playlist.cc
Expand Up @@ -12,9 +12,11 @@
#include <cmath>
#include <memory>

#include "packager/app/hls_flags.h"
#include "packager/base/logging.h"
#include "packager/base/strings/string_number_conversions.h"
#include "packager/base/strings/stringprintf.h"
#include "packager/base/time/time.h"
#include "packager/file/file.h"
#include "packager/hls/base/tag.h"
#include "packager/media/base/language_utils.h"
Expand Down Expand Up @@ -170,7 +172,8 @@ class SegmentInfoEntry : public HlsEntry {
bool use_byte_range,
uint64_t start_byte_offset,
uint64_t segment_file_size,
uint64_t previous_segment_end_offset);
uint64_t previous_segment_end_offset,
const base::Time wall_time);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pass by const reference to avoid copying, i.e. const base::Time& wall_time.


std::string ToString() override;
int64_t start_time() const { return start_time_; }
Expand All @@ -179,6 +182,10 @@ class SegmentInfoEntry : public HlsEntry {
duration_seconds_ = duration_seconds;
}

const base::Time wall_time() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Return a const reference to avoid copying, i.e.

const base::Time& wall_time() const { return wall_time_; }

return wall_time_;
}

private:
SegmentInfoEntry(const SegmentInfoEntry&) = delete;
SegmentInfoEntry& operator=(const SegmentInfoEntry&) = delete;
Expand All @@ -190,6 +197,7 @@ class SegmentInfoEntry : public HlsEntry {
const uint64_t start_byte_offset_;
const uint64_t segment_file_size_;
const uint64_t previous_segment_end_offset_;
const base::Time wall_time_;
};

SegmentInfoEntry::SegmentInfoEntry(const std::string& file_name,
Expand All @@ -198,15 +206,17 @@ SegmentInfoEntry::SegmentInfoEntry(const std::string& file_name,
bool use_byte_range,
uint64_t start_byte_offset,
uint64_t segment_file_size,
uint64_t previous_segment_end_offset)
uint64_t previous_segment_end_offset,
const base::Time wall_time)
: HlsEntry(HlsEntry::EntryType::kExtInf),
file_name_(file_name),
start_time_(start_time),
duration_seconds_(duration_seconds),
use_byte_range_(use_byte_range),
start_byte_offset_(start_byte_offset),
segment_file_size_(segment_file_size),
previous_segment_end_offset_(previous_segment_end_offset) {}
previous_segment_end_offset_(previous_segment_end_offset),
wall_time_(wall_time) {}

std::string SegmentInfoEntry::ToString() {
std::string result = base::StringPrintf("#EXTINF:%.3f,", duration_seconds_);
Expand Down Expand Up @@ -408,7 +418,8 @@ void MediaPlaylist::AddSegment(const std::string& file_name,
int64_t start_time,
int64_t duration,
uint64_t start_byte_offset,
uint64_t size) {
uint64_t size,
const base::Time reference_time) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since it only changes on discontinuity, it is probably a good idea to provide it in a separate function instead of in AddSegment.

if (stream_type_ == MediaPlaylistStreamType::kVideoIFramesOnly) {
if (key_frames_.empty())
return;
Expand All @@ -423,13 +434,13 @@ void MediaPlaylist::AddSegment(const std::string& file_name,
: std::next(iter)->timestamp;
AddSegmentInfoEntry(file_name, iter->timestamp,
next_timestamp - iter->timestamp,
iter->start_byte_offset, iter->size);
iter->start_byte_offset, iter->size, reference_time);
}
key_frames_.clear();
return;
}
return AddSegmentInfoEntry(file_name, start_time, duration, start_byte_offset,
size);
size, reference_time);
}

void MediaPlaylist::AddKeyFrame(int64_t timestamp,
Expand Down Expand Up @@ -477,8 +488,34 @@ bool MediaPlaylist::WriteToFile(const std::string& file_path) {
media_info_, target_duration_, hls_params_.playlist_type, stream_type_,
media_sequence_number_, discontinuity_sequence_number_);

for (const auto& entry : entries_)
bool need_ext_date_time = hls_params_.add_ext_x_program_date_time;

for (const auto& entry : entries_) {
switch (entry->type()) {
case HlsEntry::EntryType::kExtInf:
if (need_ext_date_time) {
SegmentInfoEntry* segment_info =
reinterpret_cast<SegmentInfoEntry*>(entry.get());
base::Time::Exploded time;
segment_info->wall_time().UTCExplode(&time);
base::StringAppendF(&content,
"#EXT-X-PROGRAM-DATE-TIME:"
"%4d-%02d-%02dT%02d:%02d:%02d.%03dZ\n",
time.year, time.month, time.day_of_month,
time.hour, time.minute, time.second,
time.millisecond);
need_ext_date_time = false;
}
break;
case HlsEntry::EntryType::kExtDiscontinuity:
// we need to add a new program data time after a discontinuity
need_ext_date_time = hls_params_.add_ext_x_program_date_time;
break;
default:
break;
Comment on lines +495 to +515
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indent by two extra characters.

}
base::StringAppendF(&content, "%s\n", entry->ToString().c_str());
}

if (hls_params_.playlist_type == HlsPlaylistType::kVod) {
content += "#EXT-X-ENDLIST\n";
Expand Down Expand Up @@ -581,14 +618,15 @@ void MediaPlaylist::AddSegmentInfoEntry(const std::string& segment_file_name,
int64_t start_time,
int64_t duration,
uint64_t start_byte_offset,
uint64_t size) {
uint64_t size,
base::Time reference_time) {
if (time_scale_ == 0) {
LOG(WARNING) << "Timescale is not set and the duration for " << duration
<< " cannot be calculated. The output will be wrong.";

entries_.emplace_back(new SegmentInfoEntry(
segment_file_name, 0.0, 0.0, use_byte_range_, start_byte_offset, size,
previous_segment_end_offset_));
previous_segment_end_offset_, reference_time));
return;
}

Expand All @@ -605,26 +643,27 @@ void MediaPlaylist::AddSegmentInfoEntry(const std::string& segment_file_name,
std::max(longest_segment_duration_seconds_, segment_duration_seconds);
bandwidth_estimator_.AddBlock(size, segment_duration_seconds);
current_buffer_depth_ += segment_duration_seconds;

if (!entries_.empty() &&
entries_.back()->type() == HlsEntry::EntryType::kExtInf) {
const SegmentInfoEntry* segment_info =
static_cast<SegmentInfoEntry*>(entries_.back().get());
if (segment_info->start_time() > start_time) {
LOG(WARNING)
<< "Insert a discontinuity tag after the segment with start time "
<< segment_info->start_time() << " as the next segment starts at "
<< start_time << ".";
entries_.emplace_back(new DiscontinuityEntry());
}
const base::Time wall_time = reference_time + base::TimeDelta::FromSeconds(start_time / time_scale_);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can defer the wall_time computation to MediaPlaylist::WriteToFile when writing the EXT-X-PROGRAM-DATE-TIME, then SegmentInfoEntry constructor does not need to be changed.


if (!entries_.empty() && start_time < last_segment_start_time_) {
LOG(WARNING)
<< "Insert a discontinuity tag after the segment with start time "
<< last_segment_start_time_ << " as the next segment starts at "
<< start_time << ".";
entries_.emplace_back(new DiscontinuityEntry());
}

last_segment_start_time_ = start_time;
entries_.emplace_back(new SegmentInfoEntry(
segment_file_name, start_time, segment_duration_seconds, use_byte_range_,
start_byte_offset, size, previous_segment_end_offset_));
start_byte_offset, size, previous_segment_end_offset_, wall_time));
previous_segment_end_offset_ = start_byte_offset + size - 1;
}

uint64_t MediaPlaylist::LastSegmentStartTime() const {
return last_segment_start_time_;
}

void MediaPlaylist::AdjustLastSegmentInfoEntryDuration(int64_t next_timestamp) {
if (time_scale_ == 0)
return;
Expand Down
17 changes: 14 additions & 3 deletions packager/hls/base/media_playlist.h
Expand Up @@ -13,6 +13,7 @@
#include <vector>

#include "packager/base/macros.h"
#include "packager/base/time/time.h"
#include "packager/hls/public/hls_params.h"
#include "packager/mpd/base/bandwidth_estimator.h"
#include "packager/mpd/base/media_info.pb.h"
Expand Down Expand Up @@ -112,11 +113,14 @@ class MediaPlaylist {
/// @param start_byte_offset is the offset of where the subsegment starts.
/// This must be 0 if the whole segment is a subsegment.
/// @param size is size in bytes.
/// @param reference_time reference time for wall clock time associated
/// to segments generation start.
virtual void AddSegment(const std::string& file_name,
int64_t start_time,
int64_t duration,
uint64_t start_byte_offset,
uint64_t size);
uint64_t size,
base::Time reference_time);

/// Keyframes must be added in order. It is also called before the containing
/// segment being called.
Expand Down Expand Up @@ -176,6 +180,9 @@ class MediaPlaylist {
/// segments have been added.
virtual double GetLongestSegmentDuration() const;

/// @return the start time of the last added segment
virtual uint64_t LastSegmentStartTime() const;

/// Set the target duration of this MediaPlaylist.
/// In other words this is the value for EXT-X-TARGETDURATION.
/// If this is not called before calling Write(), it will estimate the best
Expand Down Expand Up @@ -227,7 +234,8 @@ class MediaPlaylist {
int64_t start_time,
int64_t duration,
uint64_t start_byte_offset,
uint64_t size);
uint64_t size,
const base::Time reference_time);
// Adjust the duration of the last SegmentInfoEntry to end on
// |next_timestamp|.
void AdjustLastSegmentInfoEntryDuration(int64_t next_timestamp);
Expand Down Expand Up @@ -276,7 +284,10 @@ class MediaPlaylist {
// Once a file is actually removed, it is removed from the list.
std::list<std::string> segments_to_be_removed_;

// Used by kVideoIFrameOnly playlists to track the i-frames (key frames).
// Store last segment start_time to be able to detect discontinuities
int64_t last_segment_start_time_ = 0;

// Used by kVideoIFrameOnly playlists to track the i-frames (key frames).
struct KeyFrameInfo {
int64_t timestamp;
uint64_t start_byte_offset;
Expand Down