Skip to content

Commit

Permalink
[HLS] Fixing AdCues handling in iframe playlists
Browse files Browse the repository at this point in the history
Previously for the last iframe in a segment, we wait for the next
segment to arrive before writing the EXTINF tag. If an Ad Cue comes
in before the next segment, the EXT-X-PLACEMENT_OPPORTUNITY tag would
be inserted before the iframe in previous segment.

Fixes #378, #396.

Change-Id: I1ede72a4d4edca94781c7b05bc25397d67916d1a
  • Loading branch information
kqyang committed May 22, 2018
1 parent 14caaf1 commit cf40acc
Show file tree
Hide file tree
Showing 18 changed files with 112 additions and 49 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
#EXT-X-TARGETDURATION:2
#EXT-X-PLAYLIST-TYPE:VOD
#EXT-X-I-FRAMES-ONLY
#EXT-X-KEY:METHOD=SAMPLE-AES,URI="data:text/plain;base64,MTIzNDU2Nzg5MDEyMzQ1Ng==",IV=0x3334353637383930,KEYFORMAT="identity"
#EXTINF:1.001,
#EXT-X-BYTERANGE:15604@376
bear-640x360-ac3-video-1.ts
#EXT-X-DISCONTINUITY
#EXT-X-KEY:METHOD=SAMPLE-AES,URI="data:text/plain;base64,MTIzNDU2Nzg5MDEyMzQ1Ng==",IV=0x3334353637383930,KEYFORMAT="identity"
#EXTINF:1.001,
#EXT-X-BYTERANGE:18236@376
bear-640x360-ac3-video-2.ts
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
#EXT-X-TARGETDURATION:2
#EXT-X-PLAYLIST-TYPE:VOD
#EXT-X-I-FRAMES-ONLY
#EXT-X-KEY:METHOD=SAMPLE-AES,URI="data:text/plain;base64,MTIzNDU2Nzg5MDEyMzQ1Ng==",IV=0x3334353637383930,KEYFORMAT="identity"
#EXTINF:1.001,
#EXT-X-BYTERANGE:15604@376
bear-640x360-video-1.ts
#EXT-X-DISCONTINUITY
#EXT-X-KEY:METHOD=SAMPLE-AES,URI="data:text/plain;base64,MTIzNDU2Nzg5MDEyMzQ1Ng==",IV=0x3334353637383930,KEYFORMAT="identity"
#EXTINF:1.001,
#EXT-X-BYTERANGE:18236@376
bear-640x360-video-2.ts
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
#EXT-X-TARGETDURATION:2
#EXT-X-PLAYLIST-TYPE:VOD
#EXT-X-I-FRAMES-ONLY
#EXT-X-KEY:METHOD=SAMPLE-AES,URI="data:text/plain;base64,MTIzNDU2Nzg5MDEyMzQ1Ng==",IV=0x3334353637383930,KEYFORMAT="identity"
#EXTINF:1.001,
#EXT-X-BYTERANGE:15604@376
bear-640x360-ac3-video-1.ts
#EXT-X-DISCONTINUITY
#EXT-X-KEY:METHOD=SAMPLE-AES,URI="data:text/plain;base64,MTIzNDU2Nzg5MDEyMzQ1Ng==",IV=0x3334353637383930,KEYFORMAT="identity"
#EXTINF:1.001,
#EXT-X-BYTERANGE:18236@376
bear-640x360-ac3-video-2.ts
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,6 @@ bear-640x360-video-1.ts
#EXTINF:1.001,
#EXT-X-BYTERANGE:18236@376
bear-640x360-video-2.ts
#EXTINF:0.667,
#EXT-X-BYTERANGE:19928@376
bear-640x360-video-3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@
#EXT-X-STREAM-INF:BANDWIDTH=1217518,CODECS="avc1.64001e,mp4a.40.2",RESOLUTION=640x360,AUDIO="default-audio-group"
bear-640x360-video.m3u8

#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=145742,CODECS="avc1.64001e",RESOLUTION=640x360,URI="bear-640x360-video-iframe.m3u8"
#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=238897,CODECS="avc1.64001e",RESOLUTION=640x360,URI="bear-640x360-video-iframe.m3u8"
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@
#EXT-X-VERSION:6
## Generated with https://github.com/google/shaka-packager version <tag>-<hash>-<test>
#EXT-X-TARGETDURATION:2
#EXT-X-MEDIA-SEQUENCE:1
#EXT-X-DISCONTINUITY-SEQUENCE:1
#EXT-X-I-FRAMES-ONLY
#EXT-X-KEY:METHOD=SAMPLE-AES,URI="data:text/plain;base64,MjM0NTY3ODkwMTIzNDU2MQ==",IV=0x3334353637383930,KEYFORMAT="identity"
#EXTINF:1.001,
#EXT-X-BYTERANGE:15604@376
bear-640x360-video-1.ts
#EXT-X-KEY:METHOD=SAMPLE-AES,URI="data:text/plain;base64,MzQ1Njc4OTAxMjM0NTYxMg==",IV=0x3334353637383930,KEYFORMAT="identity"
#EXTINF:1.001,
#EXT-X-BYTERANGE:18236@376
bear-640x360-video-2.ts
#EXT-X-KEY:METHOD=SAMPLE-AES,URI="data:text/plain;base64,MzQ1Njc4OTAxMjM0NTYxMg==",IV=0x3334353637383930,KEYFORMAT="identity"
#EXTINF:0.667,
#EXT-X-BYTERANGE:19928@376
bear-640x360-video-3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@
#EXT-X-STREAM-INF:BANDWIDTH=1217518,CODECS="avc1.64001e,mp4a.40.2",RESOLUTION=640x360,AUDIO="default-audio-group"
bear-640x360-video.m3u8

#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=145742,CODECS="avc1.64001e",RESOLUTION=640x360,URI="bear-640x360-video-iframe.m3u8"
#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=238897,CODECS="avc1.64001e",RESOLUTION=640x360,URI="bear-640x360-video-iframe.m3u8"
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@
#EXT-X-VERSION:6
## Generated with https://github.com/google/shaka-packager version <tag>-<hash>-<test>
#EXT-X-TARGETDURATION:2
#EXT-X-MEDIA-SEQUENCE:1
#EXT-X-I-FRAMES-ONLY
#EXTINF:1.001,
#EXT-X-BYTERANGE:15604@376
bear-640x360-video-1.ts
#EXTINF:1.001,
#EXT-X-BYTERANGE:18236@376
bear-640x360-video-2.ts
#EXTINF:0.667,
#EXT-X-BYTERANGE:19928@376
bear-640x360-video-3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@
#EXT-X-STREAM-INF:BANDWIDTH=1217518,CODECS="avc1.64001e,mp4a.40.2",RESOLUTION=640x360,AUDIO="default-audio-group"
bear-640x360-video.m3u8

#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=145742,CODECS="avc1.64001e",RESOLUTION=640x360,URI="bear-640x360-video-iframe.m3u8"
#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=238897,CODECS="avc1.64001e",RESOLUTION=640x360,URI="bear-640x360-video-iframe.m3u8"
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
#EXT-X-TARGETDURATION:2
#EXT-X-PLAYLIST-TYPE:VOD
#EXT-X-I-FRAMES-ONLY
#EXT-X-KEY:METHOD=SAMPLE-AES,URI="skd://www.license.com/getkey?KeyId=31323334-3536-3738-3930-313233343536",KEYFORMATVERSIONS="1",KEYFORMAT="com.apple.streamingkeydelivery"
#EXTINF:1.001,
#EXT-X-BYTERANGE:15604@376
bear-640x360-video-1.ts
#EXT-X-DISCONTINUITY
#EXT-X-KEY:METHOD=SAMPLE-AES,URI="skd://www.license.com/getkey?KeyId=31323334-3536-3738-3930-313233343536",KEYFORMATVERSIONS="1",KEYFORMAT="com.apple.streamingkeydelivery"
#EXTINF:1.001,
#EXT-X-BYTERANGE:18236@376
bear-640x360-video-2.ts
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
#EXT-X-TARGETDURATION:2
#EXT-X-PLAYLIST-TYPE:VOD
#EXT-X-I-FRAMES-ONLY
#EXT-X-KEY:METHOD=SAMPLE-AES,URI="data:text/plain;base64,MTIzNDU2Nzg5MDEyMzQ1Ng==",IV=0x3334353637383930,KEYFORMAT="identity"
#EXTINF:1.000,
#EXT-X-BYTERANGE:940@376
sintel-1024x436-video-1.ts
#EXT-X-DISCONTINUITY
#EXT-X-KEY:METHOD=SAMPLE-AES,URI="data:text/plain;base64,MTIzNDU2Nzg5MDEyMzQ1Ng==",IV=0x3334353637383930,KEYFORMAT="identity"
#EXTINF:1.000,
#EXT-X-BYTERANGE:376@376
sintel-1024x436-video-2.ts
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
#EXT-X-TARGETDURATION:2
#EXT-X-PLAYLIST-TYPE:VOD
#EXT-X-I-FRAMES-ONLY
#EXT-X-KEY:METHOD=SAMPLE-AES,URI="data:text/plain;base64,MTIzNDU2Nzg5MDEyMzQ1Ng==",IV=0x3334353637383930,KEYFORMAT="identity"
#EXTINF:1.001,
#EXT-X-BYTERANGE:15604@376
bear-640x360-video-1.ts
#EXT-X-DISCONTINUITY
#EXT-X-KEY:METHOD=SAMPLE-AES,URI="data:text/plain;base64,MTIzNDU2Nzg5MDEyMzQ1Ng==",IV=0x3334353637383930,KEYFORMAT="identity"
#EXTINF:1.001,
#EXT-X-BYTERANGE:18236@376
bear-640x360-video-2.ts
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
#EXT-X-TARGETDURATION:2
#EXT-X-PLAYLIST-TYPE:VOD
#EXT-X-I-FRAMES-ONLY
#EXT-X-KEY:METHOD=SAMPLE-AES,URI="data:text/plain;base64,MTIzNDU2Nzg5MDEyMzQ1Ng==",IV=0x3334353637383930,KEYFORMAT="identity"
#EXTINF:1.001,
#EXT-X-BYTERANGE:15604@376
bear-640x360-ec3-video-1.ts
#EXT-X-DISCONTINUITY
#EXT-X-KEY:METHOD=SAMPLE-AES,URI="data:text/plain;base64,MTIzNDU2Nzg5MDEyMzQ1Ng==",IV=0x3334353637383930,KEYFORMAT="identity"
#EXTINF:1.001,
#EXT-X-BYTERANGE:18236@376
bear-640x360-ec3-video-2.ts
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@
#EXTINF:1.001,
#EXT-X-BYTERANGE:15581@80
bear-640x360-video-1.m4s
#EXT-X-PLACEMENT-OPPORTUNITY
#EXTINF:1.001,
#EXT-X-BYTERANGE:18221@80
bear-640x360-video-2.m4s
#EXT-X-PLACEMENT-OPPORTUNITY
#EXTINF:0.734,
#EXT-X-BYTERANGE:19663@80
bear-640x360-video-3.m4s
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@
#EXTINF:1.001,
#EXT-X-BYTERANGE:15581@1159
bear-640x360-video.mp4
#EXT-X-PLACEMENT-OPPORTUNITY
#EXTINF:1.001,
#EXT-X-BYTERANGE:18754@100472
bear-640x360-video.mp4
#EXT-X-PLACEMENT-OPPORTUNITY
#EXTINF:0.734,
#EXT-X-BYTERANGE:20068@222812
bear-640x360-video.mp4
Expand Down
61 changes: 33 additions & 28 deletions packager/hls/base/media_playlist.cc
Original file line number Diff line number Diff line change
Expand Up @@ -151,14 +151,15 @@ class SegmentInfoEntry : public HlsEntry {
std::string ToString() override;
double start_time() const { return start_time_; }
double duration() const { return duration_; }
void set_duration(double duration) { duration_ = duration; }

private:
SegmentInfoEntry(const SegmentInfoEntry&) = delete;
SegmentInfoEntry& operator=(const SegmentInfoEntry&) = delete;

const std::string file_name_;
const double start_time_;
const double duration_;
double duration_;
const bool use_byte_range_;
const uint64_t start_byte_offset_;
const uint64_t segment_file_size_;
Expand Down Expand Up @@ -379,20 +380,20 @@ void MediaPlaylist::AddSegment(const std::string& file_name,
if (stream_type_ == MediaPlaylistStreamType::kVideoIFramesOnly) {
if (key_frames_.empty())
return;
// Skip the last entry as the duration of the key frames are defined by the
// next key frame, which we don't know yet.
for (auto iter = key_frames_.begin(); iter != std::prev(key_frames_.end());
++iter) {
const std::string& segment_file_name =
iter->segment_file_name.empty() ? file_name : iter->segment_file_name;
AddSegmentInfoEntry(segment_file_name, iter->timestamp, iter->duration,

AdjustLastSegmentInfoEntryDuration(key_frames_.front().timestamp);

for (auto iter = key_frames_.begin(); iter != key_frames_.end(); ++iter) {
// Last entry duration may be adjusted later when the next iframe becomes
// available.
const uint64_t next_timestamp = std::next(iter) == key_frames_.end()
? (start_time + duration)
: std::next(iter)->timestamp;
AddSegmentInfoEntry(file_name, iter->timestamp,
next_timestamp - iter->timestamp,
iter->start_byte_offset, iter->size);
}

key_frames_.erase(key_frames_.begin(), std::prev(key_frames_.end()));
KeyFrameInfo& key_frame = key_frames_.front();
key_frame.segment_file_name = file_name;
key_frame.duration = start_time + duration - key_frame.timestamp;
key_frames_.clear();
return;
}
return AddSegmentInfoEntry(file_name, start_time, duration, start_byte_offset,
Expand All @@ -411,9 +412,6 @@ void MediaPlaylist::AddKeyFrame(uint64_t timestamp,
stream_type_ = MediaPlaylistStreamType::kVideoIFramesOnly;
use_byte_range_ = true;
}
if (!key_frames_.empty()) {
key_frames_.back().duration = timestamp - key_frames_.back().timestamp;
}
key_frames_.push_back({timestamp, start_byte_offset, size});
}

Expand All @@ -439,18 +437,6 @@ void MediaPlaylist::AddPlacementOpportunity() {
}

bool MediaPlaylist::WriteToFile(const std::string& file_path) {
if (!key_frames_.empty() &&
hls_params_.playlist_type == HlsPlaylistType::kVod) {
// Flush remaining key frames. This assumes |WriteToFile| is only called
// once at the end of the file in VOD.
CHECK_EQ(key_frames_.size(), 1u);
const KeyFrameInfo& key_frame = key_frames_.front();
AddSegmentInfoEntry(key_frame.segment_file_name, key_frame.timestamp,
key_frame.duration, key_frame.start_byte_offset,
key_frame.size);
key_frames_.clear();
}

if (!target_duration_set_) {
SetTargetDuration(ceil(GetLongestSegmentDuration()));
}
Expand Down Expand Up @@ -548,6 +534,25 @@ void MediaPlaylist::AddSegmentInfoEntry(const std::string& segment_file_name,
SlideWindow();
}

void MediaPlaylist::AdjustLastSegmentInfoEntryDuration(
uint64_t next_timestamp) {
if (time_scale_ == 0)
return;

const double scaled_next_timestamp =
static_cast<double>(next_timestamp) / time_scale_;

for (auto iter = entries_.rbegin(); iter != entries_.rend(); ++iter) {
if (iter->get()->type() == HlsEntry::EntryType::kExtInf) {
SegmentInfoEntry* segment_info =
reinterpret_cast<SegmentInfoEntry*>(iter->get());
segment_info->set_duration(scaled_next_timestamp -
segment_info->start_time());
break;
}
}
}

void MediaPlaylist::SlideWindow() {
DCHECK(!entries_.empty());
if (hls_params_.time_shift_buffer_depth <= 0.0 ||
Expand Down
4 changes: 3 additions & 1 deletion packager/hls/base/media_playlist.h
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,9 @@ class MediaPlaylist {
uint64_t duration,
uint64_t start_byte_offset,
uint64_t size);
// Adjust the duration of the last SegmentInfoEntry to end on
// |next_timestamp|.
void AdjustLastSegmentInfoEntryDuration(uint64_t next_timestamp);
// Remove elements from |entries_| for live profile. Increments
// |sequence_number_| by the number of segments removed.
void SlideWindow();
Expand Down Expand Up @@ -231,7 +234,6 @@ class MediaPlaylist {
uint64_t timestamp;
uint64_t start_byte_offset;
uint64_t size;
uint64_t duration;
std::string segment_file_name;
};
std::list<KeyFrameInfo> key_frames_;
Expand Down
45 changes: 44 additions & 1 deletion packager/hls/base/media_playlist_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -811,7 +811,7 @@ TEST_F(IFrameMediaPlaylistTest, SingleSegment) {
"#EXT-X-VERSION:6\n"
"## Generated with https://github.com/google/shaka-packager version "
"test\n"
"#EXT-X-TARGETDURATION:9\n"
"#EXT-X-TARGETDURATION:8\n"
"#EXT-X-PLAYLIST-TYPE:VOD\n"
"#EXT-X-I-FRAMES-ONLY\n"
"#EXT-X-MAP:URI=\"file.mp4\",BYTERANGE=\"501@0\"\n"
Expand Down Expand Up @@ -875,6 +875,49 @@ TEST_F(IFrameMediaPlaylistTest, MultiSegment) {
ASSERT_FILE_STREQ(kMemoryFilePath, kExpectedOutput);
}

TEST_F(IFrameMediaPlaylistTest, MultiSegmentWithPlacementOpportunity) {
valid_video_media_info_.set_reference_time_scale(90000);
valid_video_media_info_.set_segment_template_url("file$Number$.ts");
ASSERT_TRUE(media_playlist_->SetMediaInfo(valid_video_media_info_));

media_playlist_->AddKeyFrame(0, 1000, 2345);
media_playlist_->AddKeyFrame(2 * kTimeScale, 5000, 6345);
media_playlist_->AddSegment("file1.ts", 0, 10 * kTimeScale, kZeroByteOffset,
kMBytes);
media_playlist_->AddPlacementOpportunity();
media_playlist_->AddKeyFrame(11 * kTimeScale, 1000, 2345);
media_playlist_->AddKeyFrame(15 * kTimeScale, 3345, 12345);
media_playlist_->AddSegment("file2.ts", 10 * kTimeScale, 30 * kTimeScale,
kZeroByteOffset, 5 * kMBytes);

const char kExpectedOutput[] =
"#EXTM3U\n"
"#EXT-X-VERSION:6\n"
"## Generated with https://github.com/google/shaka-packager version "
"test\n"
"#EXT-X-TARGETDURATION:25\n"
"#EXT-X-PLAYLIST-TYPE:VOD\n"
"#EXT-X-I-FRAMES-ONLY\n"
"#EXTINF:2.000,\n"
"#EXT-X-BYTERANGE:2345@1000\n"
"file1.ts\n"
"#EXTINF:9.000,\n"
"#EXT-X-BYTERANGE:6345@5000\n"
"file1.ts\n"
"#EXT-X-PLACEMENT-OPPORTUNITY\n"
"#EXTINF:4.000,\n"
"#EXT-X-BYTERANGE:2345@1000\n"
"file2.ts\n"
"#EXTINF:25.000,\n"
"#EXT-X-BYTERANGE:12345\n"
"file2.ts\n"
"#EXT-X-ENDLIST\n";

const char kMemoryFilePath[] = "memory://media.m3u8";
EXPECT_TRUE(media_playlist_->WriteToFile(kMemoryFilePath));
ASSERT_FILE_STREQ(kMemoryFilePath, kExpectedOutput);
}

namespace {
const int kNumPreservedSegmentsOutsideLiveWindow = 3;
const int kMaxNumSegmentsAvailable =
Expand Down

0 comments on commit cf40acc

Please sign in to comment.