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.
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
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
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
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
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"
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
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"
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
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"
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
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
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
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
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
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
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
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
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.