Support I-Frames only playlist in MediaPlaylist
Issue: #287 Change-Id: I204774fd87cf9a159a4448ba3457db5ab17f7889
This commit is contained in:
parent
4194f0746e
commit
6d0a6bb120
|
@ -76,12 +76,12 @@ MasterPlaylist::~MasterPlaylist() {}
|
||||||
void MasterPlaylist::AddMediaPlaylist(MediaPlaylist* media_playlist) {
|
void MasterPlaylist::AddMediaPlaylist(MediaPlaylist* media_playlist) {
|
||||||
DCHECK(media_playlist);
|
DCHECK(media_playlist);
|
||||||
switch (media_playlist->stream_type()) {
|
switch (media_playlist->stream_type()) {
|
||||||
case MediaPlaylist::MediaPlaylistStreamType::kPlayListAudio: {
|
case MediaPlaylist::MediaPlaylistStreamType::kAudio: {
|
||||||
const std::string& group_id = media_playlist->group_id();
|
const std::string& group_id = media_playlist->group_id();
|
||||||
audio_playlist_groups_[group_id].push_back(media_playlist);
|
audio_playlist_groups_[group_id].push_back(media_playlist);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case MediaPlaylist::MediaPlaylistStreamType::kPlayListVideo: {
|
case MediaPlaylist::MediaPlaylistStreamType::kVideo: {
|
||||||
video_playlists_.push_back(media_playlist);
|
video_playlists_.push_back(media_playlist);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,7 +64,7 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistOneVideo) {
|
||||||
MockMediaPlaylist mock_playlist(kVodPlaylist, "media1.m3u8", "somename",
|
MockMediaPlaylist mock_playlist(kVodPlaylist, "media1.m3u8", "somename",
|
||||||
"somegroupid");
|
"somegroupid");
|
||||||
mock_playlist.SetStreamTypeForTesting(
|
mock_playlist.SetStreamTypeForTesting(
|
||||||
MediaPlaylist::MediaPlaylistStreamType::kPlayListVideo);
|
MediaPlaylist::MediaPlaylistStreamType::kVideo);
|
||||||
mock_playlist.SetCodecForTesting(codec);
|
mock_playlist.SetCodecForTesting(codec);
|
||||||
EXPECT_CALL(mock_playlist, Bitrate()).WillOnce(Return(435889));
|
EXPECT_CALL(mock_playlist, Bitrate()).WillOnce(Return(435889));
|
||||||
EXPECT_CALL(mock_playlist, GetDisplayResolution(NotNull(), NotNull()))
|
EXPECT_CALL(mock_playlist, GetDisplayResolution(NotNull(), NotNull()))
|
||||||
|
@ -95,7 +95,7 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistVideoAndAudio) {
|
||||||
MockMediaPlaylist sd_video_playlist(kVodPlaylist, "sd.m3u8", "somename",
|
MockMediaPlaylist sd_video_playlist(kVodPlaylist, "sd.m3u8", "somename",
|
||||||
"somegroupid");
|
"somegroupid");
|
||||||
sd_video_playlist.SetStreamTypeForTesting(
|
sd_video_playlist.SetStreamTypeForTesting(
|
||||||
MediaPlaylist::MediaPlaylistStreamType::kPlayListVideo);
|
MediaPlaylist::MediaPlaylistStreamType::kVideo);
|
||||||
sd_video_playlist.SetCodecForTesting(sd_video_codec);
|
sd_video_playlist.SetCodecForTesting(sd_video_codec);
|
||||||
EXPECT_CALL(sd_video_playlist, Bitrate())
|
EXPECT_CALL(sd_video_playlist, Bitrate())
|
||||||
.Times(AtLeast(1))
|
.Times(AtLeast(1))
|
||||||
|
@ -111,7 +111,7 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistVideoAndAudio) {
|
||||||
MockMediaPlaylist hd_video_playlist(kVodPlaylist, "hd.m3u8", "somename",
|
MockMediaPlaylist hd_video_playlist(kVodPlaylist, "hd.m3u8", "somename",
|
||||||
"somegroupid");
|
"somegroupid");
|
||||||
hd_video_playlist.SetStreamTypeForTesting(
|
hd_video_playlist.SetStreamTypeForTesting(
|
||||||
MediaPlaylist::MediaPlaylistStreamType::kPlayListVideo);
|
MediaPlaylist::MediaPlaylistStreamType::kVideo);
|
||||||
hd_video_playlist.SetCodecForTesting(hd_video_codec);
|
hd_video_playlist.SetCodecForTesting(hd_video_codec);
|
||||||
EXPECT_CALL(hd_video_playlist, Bitrate())
|
EXPECT_CALL(hd_video_playlist, Bitrate())
|
||||||
.Times(AtLeast(1))
|
.Times(AtLeast(1))
|
||||||
|
@ -131,7 +131,7 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistVideoAndAudio) {
|
||||||
EXPECT_CALL(english_playlist, GetLanguage()).WillRepeatedly(Return("en"));
|
EXPECT_CALL(english_playlist, GetLanguage()).WillRepeatedly(Return("en"));
|
||||||
EXPECT_CALL(english_playlist, GetNumChannels()).WillRepeatedly(Return(2));
|
EXPECT_CALL(english_playlist, GetNumChannels()).WillRepeatedly(Return(2));
|
||||||
english_playlist.SetStreamTypeForTesting(
|
english_playlist.SetStreamTypeForTesting(
|
||||||
MediaPlaylist::MediaPlaylistStreamType::kPlayListAudio);
|
MediaPlaylist::MediaPlaylistStreamType::kAudio);
|
||||||
english_playlist.SetCodecForTesting(audio_codec);
|
english_playlist.SetCodecForTesting(audio_codec);
|
||||||
EXPECT_CALL(english_playlist, Bitrate())
|
EXPECT_CALL(english_playlist, Bitrate())
|
||||||
.Times(AtLeast(1))
|
.Times(AtLeast(1))
|
||||||
|
@ -146,7 +146,7 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistVideoAndAudio) {
|
||||||
EXPECT_CALL(spanish_playlist, GetLanguage()).WillRepeatedly(Return("es"));
|
EXPECT_CALL(spanish_playlist, GetLanguage()).WillRepeatedly(Return("es"));
|
||||||
EXPECT_CALL(spanish_playlist, GetNumChannels()).WillRepeatedly(Return(5));
|
EXPECT_CALL(spanish_playlist, GetNumChannels()).WillRepeatedly(Return(5));
|
||||||
spanish_playlist.SetStreamTypeForTesting(
|
spanish_playlist.SetStreamTypeForTesting(
|
||||||
MediaPlaylist::MediaPlaylistStreamType::kPlayListAudio);
|
MediaPlaylist::MediaPlaylistStreamType::kAudio);
|
||||||
spanish_playlist.SetCodecForTesting(audio_codec);
|
spanish_playlist.SetCodecForTesting(audio_codec);
|
||||||
EXPECT_CALL(spanish_playlist, Bitrate())
|
EXPECT_CALL(spanish_playlist, Bitrate())
|
||||||
.Times(AtLeast(1))
|
.Times(AtLeast(1))
|
||||||
|
@ -187,7 +187,7 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistMultipleAudioGroups) {
|
||||||
MockMediaPlaylist video_playlist(kVodPlaylist, "video.m3u8", "somename",
|
MockMediaPlaylist video_playlist(kVodPlaylist, "video.m3u8", "somename",
|
||||||
"somegroupid");
|
"somegroupid");
|
||||||
video_playlist.SetStreamTypeForTesting(
|
video_playlist.SetStreamTypeForTesting(
|
||||||
MediaPlaylist::MediaPlaylistStreamType::kPlayListVideo);
|
MediaPlaylist::MediaPlaylistStreamType::kVideo);
|
||||||
video_playlist.SetCodecForTesting(video_codec);
|
video_playlist.SetCodecForTesting(video_codec);
|
||||||
EXPECT_CALL(video_playlist, Bitrate())
|
EXPECT_CALL(video_playlist, Bitrate())
|
||||||
.Times(AtLeast(1))
|
.Times(AtLeast(1))
|
||||||
|
@ -205,7 +205,7 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistMultipleAudioGroups) {
|
||||||
EXPECT_CALL(eng_lo_playlist, GetLanguage()).WillRepeatedly(Return("en"));
|
EXPECT_CALL(eng_lo_playlist, GetLanguage()).WillRepeatedly(Return("en"));
|
||||||
EXPECT_CALL(eng_lo_playlist, GetNumChannels()).WillRepeatedly(Return(1));
|
EXPECT_CALL(eng_lo_playlist, GetNumChannels()).WillRepeatedly(Return(1));
|
||||||
eng_lo_playlist.SetStreamTypeForTesting(
|
eng_lo_playlist.SetStreamTypeForTesting(
|
||||||
MediaPlaylist::MediaPlaylistStreamType::kPlayListAudio);
|
MediaPlaylist::MediaPlaylistStreamType::kAudio);
|
||||||
eng_lo_playlist.SetCodecForTesting(audio_codec_lo);
|
eng_lo_playlist.SetCodecForTesting(audio_codec_lo);
|
||||||
EXPECT_CALL(eng_lo_playlist, Bitrate())
|
EXPECT_CALL(eng_lo_playlist, Bitrate())
|
||||||
.Times(AtLeast(1))
|
.Times(AtLeast(1))
|
||||||
|
@ -221,7 +221,7 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistMultipleAudioGroups) {
|
||||||
EXPECT_CALL(eng_hi_playlist, GetLanguage()).WillRepeatedly(Return("en"));
|
EXPECT_CALL(eng_hi_playlist, GetLanguage()).WillRepeatedly(Return("en"));
|
||||||
EXPECT_CALL(eng_hi_playlist, GetNumChannels()).WillRepeatedly(Return(8));
|
EXPECT_CALL(eng_hi_playlist, GetNumChannels()).WillRepeatedly(Return(8));
|
||||||
eng_hi_playlist.SetStreamTypeForTesting(
|
eng_hi_playlist.SetStreamTypeForTesting(
|
||||||
MediaPlaylist::MediaPlaylistStreamType::kPlayListAudio);
|
MediaPlaylist::MediaPlaylistStreamType::kAudio);
|
||||||
eng_hi_playlist.SetCodecForTesting(audio_codec_hi);
|
eng_hi_playlist.SetCodecForTesting(audio_codec_hi);
|
||||||
EXPECT_CALL(eng_hi_playlist, Bitrate())
|
EXPECT_CALL(eng_hi_playlist, Bitrate())
|
||||||
.Times(AtLeast(1))
|
.Times(AtLeast(1))
|
||||||
|
@ -262,7 +262,7 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistSameAudioGroupSameLanguage) {
|
||||||
MockMediaPlaylist video_playlist(kVodPlaylist, "video.m3u8", "somename",
|
MockMediaPlaylist video_playlist(kVodPlaylist, "video.m3u8", "somename",
|
||||||
"somegroupid");
|
"somegroupid");
|
||||||
video_playlist.SetStreamTypeForTesting(
|
video_playlist.SetStreamTypeForTesting(
|
||||||
MediaPlaylist::MediaPlaylistStreamType::kPlayListVideo);
|
MediaPlaylist::MediaPlaylistStreamType::kVideo);
|
||||||
video_playlist.SetCodecForTesting(video_codec);
|
video_playlist.SetCodecForTesting(video_codec);
|
||||||
EXPECT_CALL(video_playlist, Bitrate())
|
EXPECT_CALL(video_playlist, Bitrate())
|
||||||
.Times(AtLeast(1))
|
.Times(AtLeast(1))
|
||||||
|
@ -279,7 +279,7 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistSameAudioGroupSameLanguage) {
|
||||||
EXPECT_CALL(eng_lo_playlist, GetLanguage()).WillRepeatedly(Return("en"));
|
EXPECT_CALL(eng_lo_playlist, GetLanguage()).WillRepeatedly(Return("en"));
|
||||||
EXPECT_CALL(eng_lo_playlist, GetNumChannels()).WillRepeatedly(Return(1));
|
EXPECT_CALL(eng_lo_playlist, GetNumChannels()).WillRepeatedly(Return(1));
|
||||||
eng_lo_playlist.SetStreamTypeForTesting(
|
eng_lo_playlist.SetStreamTypeForTesting(
|
||||||
MediaPlaylist::MediaPlaylistStreamType::kPlayListAudio);
|
MediaPlaylist::MediaPlaylistStreamType::kAudio);
|
||||||
eng_lo_playlist.SetCodecForTesting(audio_codec);
|
eng_lo_playlist.SetCodecForTesting(audio_codec);
|
||||||
EXPECT_CALL(eng_lo_playlist, Bitrate())
|
EXPECT_CALL(eng_lo_playlist, Bitrate())
|
||||||
.Times(AtLeast(1))
|
.Times(AtLeast(1))
|
||||||
|
@ -294,7 +294,7 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistSameAudioGroupSameLanguage) {
|
||||||
EXPECT_CALL(eng_hi_playlist, GetLanguage()).WillRepeatedly(Return("en"));
|
EXPECT_CALL(eng_hi_playlist, GetLanguage()).WillRepeatedly(Return("en"));
|
||||||
EXPECT_CALL(eng_hi_playlist, GetNumChannels()).WillRepeatedly(Return(8));
|
EXPECT_CALL(eng_hi_playlist, GetNumChannels()).WillRepeatedly(Return(8));
|
||||||
eng_hi_playlist.SetStreamTypeForTesting(
|
eng_hi_playlist.SetStreamTypeForTesting(
|
||||||
MediaPlaylist::MediaPlaylistStreamType::kPlayListAudio);
|
MediaPlaylist::MediaPlaylistStreamType::kAudio);
|
||||||
eng_hi_playlist.SetCodecForTesting(audio_codec);
|
eng_hi_playlist.SetCodecForTesting(audio_codec);
|
||||||
EXPECT_CALL(eng_hi_playlist, Bitrate())
|
EXPECT_CALL(eng_hi_playlist, Bitrate())
|
||||||
.Times(AtLeast(1))
|
.Times(AtLeast(1))
|
||||||
|
|
|
@ -59,9 +59,11 @@ std::string CreateExtXMap(const MediaInfo& media_info) {
|
||||||
return ext_x_map;
|
return ext_x_map;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string CreatePlaylistHeader(const MediaInfo& media_info,
|
std::string CreatePlaylistHeader(
|
||||||
|
const MediaInfo& media_info,
|
||||||
uint32_t target_duration,
|
uint32_t target_duration,
|
||||||
HlsPlaylistType type,
|
HlsPlaylistType type,
|
||||||
|
MediaPlaylist::MediaPlaylistStreamType stream_type,
|
||||||
int media_sequence_number,
|
int media_sequence_number,
|
||||||
int discontinuity_sequence_number) {
|
int discontinuity_sequence_number) {
|
||||||
const std::string version = GetPackagerVersion();
|
const std::string version = GetPackagerVersion();
|
||||||
|
@ -100,6 +102,10 @@ std::string CreatePlaylistHeader(const MediaInfo& media_info,
|
||||||
default:
|
default:
|
||||||
NOTREACHED() << "Unexpected MediaPlaylistType " << static_cast<int>(type);
|
NOTREACHED() << "Unexpected MediaPlaylistType " << static_cast<int>(type);
|
||||||
}
|
}
|
||||||
|
if (stream_type ==
|
||||||
|
MediaPlaylist::MediaPlaylistStreamType::kVideoIFramesOnly) {
|
||||||
|
base::StringAppendF(&header, "#EXT-X-I-FRAMES-ONLY\n");
|
||||||
|
}
|
||||||
|
|
||||||
// Put EXT-X-MAP at the end since the rest of the playlist is about the
|
// Put EXT-X-MAP at the end since the rest of the playlist is about the
|
||||||
// segment and key info.
|
// segment and key info.
|
||||||
|
@ -319,10 +325,10 @@ bool MediaPlaylist::SetMediaInfo(const MediaInfo& media_info) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (media_info.has_video_info()) {
|
if (media_info.has_video_info()) {
|
||||||
stream_type_ = MediaPlaylistStreamType::kPlayListVideo;
|
stream_type_ = MediaPlaylistStreamType::kVideo;
|
||||||
codec_ = media_info.video_info().codec();
|
codec_ = media_info.video_info().codec();
|
||||||
} else if (media_info.has_audio_info()) {
|
} else if (media_info.has_audio_info()) {
|
||||||
stream_type_ = MediaPlaylistStreamType::kPlayListAudio;
|
stream_type_ = MediaPlaylistStreamType::kAudio;
|
||||||
codec_ = media_info.audio_info().codec();
|
codec_ = media_info.audio_info().codec();
|
||||||
} else {
|
} else {
|
||||||
NOTIMPLEMENTED();
|
NOTIMPLEMENTED();
|
||||||
|
@ -331,6 +337,7 @@ bool MediaPlaylist::SetMediaInfo(const MediaInfo& media_info) {
|
||||||
|
|
||||||
time_scale_ = time_scale;
|
time_scale_ = time_scale;
|
||||||
media_info_ = media_info;
|
media_info_ = media_info;
|
||||||
|
use_byte_range_ = !media_info_.has_segment_template();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -339,32 +346,45 @@ void MediaPlaylist::AddSegment(const std::string& file_name,
|
||||||
uint64_t duration,
|
uint64_t duration,
|
||||||
uint64_t start_byte_offset,
|
uint64_t start_byte_offset,
|
||||||
uint64_t size) {
|
uint64_t size) {
|
||||||
if (time_scale_ == 0) {
|
if (stream_type_ == MediaPlaylistStreamType::kVideoIFramesOnly) {
|
||||||
LOG(WARNING) << "Timescale is not set and the duration for " << duration
|
if (key_frames_.empty())
|
||||||
<< " cannot be calculated. The output will be wrong.";
|
|
||||||
|
|
||||||
entries_.emplace_back(new SegmentInfoEntry(
|
|
||||||
file_name, 0.0, 0.0, !media_info_.has_segment_template(),
|
|
||||||
start_byte_offset, size, previous_segment_end_offset_));
|
|
||||||
return;
|
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,
|
||||||
|
iter->start_byte_offset, iter->size);
|
||||||
}
|
}
|
||||||
|
|
||||||
const double start_time_seconds =
|
key_frames_.erase(key_frames_.begin(), std::prev(key_frames_.end()));
|
||||||
static_cast<double>(start_time) / time_scale_;
|
KeyFrameInfo& key_frame = key_frames_.front();
|
||||||
const double segment_duration_seconds =
|
key_frame.segment_file_name = file_name;
|
||||||
static_cast<double>(duration) / time_scale_;
|
key_frame.duration = start_time + duration - key_frame.timestamp;
|
||||||
if (segment_duration_seconds > longest_segment_duration_)
|
return;
|
||||||
longest_segment_duration_ = segment_duration_seconds;
|
}
|
||||||
|
return AddSegmentInfoEntry(file_name, start_time, duration, start_byte_offset,
|
||||||
|
size);
|
||||||
|
}
|
||||||
|
|
||||||
const int kBitsInByte = 8;
|
void MediaPlaylist::AddKeyFrame(uint64_t timestamp,
|
||||||
const uint64_t bitrate = kBitsInByte * size / segment_duration_seconds;
|
uint64_t start_byte_offset,
|
||||||
max_bitrate_ = std::max(max_bitrate_, bitrate);
|
uint64_t size) {
|
||||||
entries_.emplace_back(new SegmentInfoEntry(
|
if (stream_type_ != MediaPlaylistStreamType::kVideoIFramesOnly) {
|
||||||
file_name, start_time_seconds, segment_duration_seconds,
|
if (stream_type_ != MediaPlaylistStreamType::kVideo) {
|
||||||
!media_info_.has_segment_template(), start_byte_offset, size,
|
LOG(WARNING)
|
||||||
previous_segment_end_offset_));
|
<< "I-Frames Only playlist applies to video renditions only.";
|
||||||
previous_segment_end_offset_ = start_byte_offset + size - 1;
|
return;
|
||||||
SlideWindow();
|
}
|
||||||
|
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});
|
||||||
}
|
}
|
||||||
|
|
||||||
void MediaPlaylist::AddEncryptionInfo(MediaPlaylist::EncryptionMethod method,
|
void MediaPlaylist::AddEncryptionInfo(MediaPlaylist::EncryptionMethod method,
|
||||||
|
@ -389,13 +409,24 @@ void MediaPlaylist::AddPlacementOpportunity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MediaPlaylist::WriteToFile(const std::string& file_path) {
|
bool MediaPlaylist::WriteToFile(const std::string& file_path) {
|
||||||
|
if (!key_frames_.empty() && 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_) {
|
if (!target_duration_set_) {
|
||||||
SetTargetDuration(ceil(GetLongestSegmentDuration()));
|
SetTargetDuration(ceil(GetLongestSegmentDuration()));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string header = CreatePlaylistHeader(
|
std::string header = CreatePlaylistHeader(
|
||||||
media_info_, target_duration_, playlist_type_, media_sequence_number_,
|
media_info_, target_duration_, playlist_type_, stream_type_,
|
||||||
discontinuity_sequence_number_);
|
media_sequence_number_, discontinuity_sequence_number_);
|
||||||
|
|
||||||
std::string body;
|
std::string body;
|
||||||
for (const auto& entry : entries_)
|
for (const auto& entry : entries_)
|
||||||
|
@ -472,6 +503,38 @@ bool MediaPlaylist::GetDisplayResolution(uint32_t* width,
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MediaPlaylist::AddSegmentInfoEntry(const std::string& segment_file_name,
|
||||||
|
uint64_t start_time,
|
||||||
|
uint64_t duration,
|
||||||
|
uint64_t start_byte_offset,
|
||||||
|
uint64_t size) {
|
||||||
|
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_));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const double start_time_seconds =
|
||||||
|
static_cast<double>(start_time) / time_scale_;
|
||||||
|
const double segment_duration_seconds =
|
||||||
|
static_cast<double>(duration) / time_scale_;
|
||||||
|
if (segment_duration_seconds > longest_segment_duration_)
|
||||||
|
longest_segment_duration_ = segment_duration_seconds;
|
||||||
|
|
||||||
|
const int kBitsInByte = 8;
|
||||||
|
const uint64_t bitrate = kBitsInByte * size / segment_duration_seconds;
|
||||||
|
max_bitrate_ = std::max(max_bitrate_, bitrate);
|
||||||
|
entries_.emplace_back(new SegmentInfoEntry(
|
||||||
|
segment_file_name, start_time_seconds, segment_duration_seconds,
|
||||||
|
use_byte_range_, start_byte_offset, size, previous_segment_end_offset_));
|
||||||
|
previous_segment_end_offset_ = start_byte_offset + size - 1;
|
||||||
|
SlideWindow();
|
||||||
|
}
|
||||||
|
|
||||||
void MediaPlaylist::SlideWindow() {
|
void MediaPlaylist::SlideWindow() {
|
||||||
DCHECK(!entries_.empty());
|
DCHECK(!entries_.empty());
|
||||||
if (time_shift_buffer_depth_ <= 0.0 ||
|
if (time_shift_buffer_depth_ <= 0.0 ||
|
||||||
|
|
|
@ -45,10 +45,11 @@ class HlsEntry {
|
||||||
class MediaPlaylist {
|
class MediaPlaylist {
|
||||||
public:
|
public:
|
||||||
enum class MediaPlaylistStreamType {
|
enum class MediaPlaylistStreamType {
|
||||||
kPlaylistUnknown,
|
kUnknown,
|
||||||
kPlayListAudio,
|
kAudio,
|
||||||
kPlayListVideo,
|
kVideo,
|
||||||
kPlayListSubtitle,
|
kVideoIFramesOnly,
|
||||||
|
kSubtitle,
|
||||||
};
|
};
|
||||||
enum class EncryptionMethod {
|
enum class EncryptionMethod {
|
||||||
kNone, // No encryption, i.e. clear.
|
kNone, // No encryption, i.e. clear.
|
||||||
|
@ -104,6 +105,16 @@ class MediaPlaylist {
|
||||||
uint64_t start_byte_offset,
|
uint64_t start_byte_offset,
|
||||||
uint64_t size);
|
uint64_t size);
|
||||||
|
|
||||||
|
/// Keyframes must be added in order. It is also called before the containing
|
||||||
|
/// segment being called.
|
||||||
|
/// @param timestamp is the timestamp of the key frame in timescale of the
|
||||||
|
/// media.
|
||||||
|
/// @param start_byte_offset is the offset of where the key frame starts.
|
||||||
|
/// @param size is size in bytes.
|
||||||
|
virtual void AddKeyFrame(uint64_t timestamp,
|
||||||
|
uint64_t start_byte_offset,
|
||||||
|
uint64_t size);
|
||||||
|
|
||||||
/// All segments added after calling this method must be decryptable with
|
/// All segments added after calling this method must be decryptable with
|
||||||
/// the key that can be fetched from |url|, until calling this again.
|
/// the key that can be fetched from |url|, until calling this again.
|
||||||
/// @param method is the encryption method.
|
/// @param method is the encryption method.
|
||||||
|
@ -168,6 +179,12 @@ class MediaPlaylist {
|
||||||
virtual bool GetDisplayResolution(uint32_t* width, uint32_t* height) const;
|
virtual bool GetDisplayResolution(uint32_t* width, uint32_t* height) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
// Add a SegmentInfoEntry (#EXTINF).
|
||||||
|
void AddSegmentInfoEntry(const std::string& segment_file_name,
|
||||||
|
uint64_t start_time,
|
||||||
|
uint64_t duration,
|
||||||
|
uint64_t start_byte_offset,
|
||||||
|
uint64_t size);
|
||||||
// Remove elements from |entries_| for live profile. Increments
|
// Remove elements from |entries_| for live profile. Increments
|
||||||
// |sequence_number_| by the number of segments removed.
|
// |sequence_number_| by the number of segments removed.
|
||||||
void SlideWindow();
|
void SlideWindow();
|
||||||
|
@ -179,8 +196,9 @@ class MediaPlaylist {
|
||||||
const std::string name_;
|
const std::string name_;
|
||||||
const std::string group_id_;
|
const std::string group_id_;
|
||||||
MediaInfo media_info_;
|
MediaInfo media_info_;
|
||||||
MediaPlaylistStreamType stream_type_ =
|
MediaPlaylistStreamType stream_type_ = MediaPlaylistStreamType::kUnknown;
|
||||||
MediaPlaylistStreamType::kPlaylistUnknown;
|
// Whether to use byte range for SegmentInfoEntry.
|
||||||
|
bool use_byte_range_ = false;
|
||||||
std::string codec_;
|
std::string codec_;
|
||||||
int media_sequence_number_ = 0;
|
int media_sequence_number_ = 0;
|
||||||
bool inserted_discontinuity_tag_ = false;
|
bool inserted_discontinuity_tag_ = false;
|
||||||
|
@ -201,6 +219,16 @@ class MediaPlaylist {
|
||||||
|
|
||||||
std::list<std::unique_ptr<HlsEntry>> entries_;
|
std::list<std::unique_ptr<HlsEntry>> entries_;
|
||||||
|
|
||||||
|
// Used by kVideoIFrameOnly playlists to track the i-frames (key frames).
|
||||||
|
struct KeyFrameInfo {
|
||||||
|
uint64_t timestamp;
|
||||||
|
uint64_t start_byte_offset;
|
||||||
|
uint64_t size;
|
||||||
|
uint64_t duration;
|
||||||
|
std::string segment_file_name;
|
||||||
|
};
|
||||||
|
std::list<KeyFrameInfo> key_frames_;
|
||||||
|
|
||||||
DISALLOW_COPY_AND_ASSIGN(MediaPlaylist);
|
DISALLOW_COPY_AND_ASSIGN(MediaPlaylist);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -771,5 +771,101 @@ TEST_F(EventMediaPlaylistTest, Basic) {
|
||||||
ASSERT_FILE_STREQ(kMemoryFilePath, kExpectedOutput);
|
ASSERT_FILE_STREQ(kMemoryFilePath, kExpectedOutput);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class IFrameMediaPlaylistTest : public MediaPlaylistTest {};
|
||||||
|
|
||||||
|
TEST_F(IFrameMediaPlaylistTest, MediaPlaylistType) {
|
||||||
|
ASSERT_TRUE(media_playlist_.SetMediaInfo(valid_video_media_info_));
|
||||||
|
EXPECT_EQ(MediaPlaylist::MediaPlaylistStreamType::kVideo,
|
||||||
|
media_playlist_.stream_type());
|
||||||
|
media_playlist_.AddKeyFrame(0, 1000, 2345);
|
||||||
|
// Playlist stream type is updated to I-Frames only after seeing
|
||||||
|
// |AddKeyFrame|.
|
||||||
|
EXPECT_EQ(MediaPlaylist::MediaPlaylistStreamType::kVideoIFramesOnly,
|
||||||
|
media_playlist_.stream_type());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(IFrameMediaPlaylistTest, SingleSegment) {
|
||||||
|
valid_video_media_info_.set_media_file_name("file.mp4");
|
||||||
|
valid_video_media_info_.mutable_init_range()->set_begin(0);
|
||||||
|
valid_video_media_info_.mutable_init_range()->set_end(500);
|
||||||
|
|
||||||
|
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("file.mp4", 0, 10 * kTimeScale, kZeroByteOffset,
|
||||||
|
kMBytes);
|
||||||
|
media_playlist_.AddKeyFrame(11 * kTimeScale, kMBytes + 1000, 2345);
|
||||||
|
media_playlist_.AddKeyFrame(15 * kTimeScale, kMBytes + 3345, 12345);
|
||||||
|
media_playlist_.AddSegment("file.mp4", 10 * kTimeScale, 10 * kTimeScale,
|
||||||
|
1001000, 2 * 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:9\n"
|
||||||
|
"#EXT-X-PLAYLIST-TYPE:VOD\n"
|
||||||
|
"#EXT-X-I-FRAMES-ONLY\n"
|
||||||
|
"#EXT-X-MAP:URI=\"file.mp4\",BYTERANGE=\"501@0\"\n"
|
||||||
|
"#EXTINF:2.000,\n"
|
||||||
|
"#EXT-X-BYTERANGE:2345@1000\n"
|
||||||
|
"file.mp4\n"
|
||||||
|
"#EXTINF:9.000,\n"
|
||||||
|
"#EXT-X-BYTERANGE:6345@5000\n"
|
||||||
|
"file.mp4\n"
|
||||||
|
"#EXTINF:4.000,\n"
|
||||||
|
"#EXT-X-BYTERANGE:2345@1001000\n"
|
||||||
|
"file.mp4\n"
|
||||||
|
"#EXTINF:5.000,\n"
|
||||||
|
"#EXT-X-BYTERANGE:12345\n"
|
||||||
|
"file.mp4\n"
|
||||||
|
"#EXT-X-ENDLIST\n";
|
||||||
|
|
||||||
|
const char kMemoryFilePath[] = "memory://media.m3u8";
|
||||||
|
EXPECT_TRUE(media_playlist_.WriteToFile(kMemoryFilePath));
|
||||||
|
ASSERT_FILE_STREQ(kMemoryFilePath, kExpectedOutput);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(IFrameMediaPlaylistTest, MultiSegment) {
|
||||||
|
valid_video_media_info_.set_reference_time_scale(90000);
|
||||||
|
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_.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"
|
||||||
|
"#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 hls
|
} // namespace hls
|
||||||
} // namespace shaka
|
} // namespace shaka
|
||||||
|
|
Loading…
Reference in New Issue