Skip to content

Commit

Permalink
Adding support for generation of the EXT_X_PROGRAM_DATE_TIME tag for …
Browse files Browse the repository at this point in the history
…HLS playlists, with fixed UTC timezone

Add a new hls option: --hls_ext_x_program_date_time <time_offset_ms>

When turned on this option will insert EXT_X_PROGRAM_DATE_TIME
in media playlists. Date time is computed from local time adjusted
by time_offset_ms.
  • Loading branch information
r0ro committed Sep 12, 2020
1 parent 5b9fd40 commit c6bc81f
Show file tree
Hide file tree
Showing 14 changed files with 300 additions and 80 deletions.
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>

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) {
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);

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() {
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) {
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;
}
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_);

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

0 comments on commit c6bc81f

Please sign in to comment.