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) {
|
||||
DCHECK(media_playlist);
|
||||
switch (media_playlist->stream_type()) {
|
||||
case MediaPlaylist::MediaPlaylistStreamType::kPlayListAudio: {
|
||||
case MediaPlaylist::MediaPlaylistStreamType::kAudio: {
|
||||
const std::string& group_id = media_playlist->group_id();
|
||||
audio_playlist_groups_[group_id].push_back(media_playlist);
|
||||
break;
|
||||
}
|
||||
case MediaPlaylist::MediaPlaylistStreamType::kPlayListVideo: {
|
||||
case MediaPlaylist::MediaPlaylistStreamType::kVideo: {
|
||||
video_playlists_.push_back(media_playlist);
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -64,7 +64,7 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistOneVideo) {
|
|||
MockMediaPlaylist mock_playlist(kVodPlaylist, "media1.m3u8", "somename",
|
||||
"somegroupid");
|
||||
mock_playlist.SetStreamTypeForTesting(
|
||||
MediaPlaylist::MediaPlaylistStreamType::kPlayListVideo);
|
||||
MediaPlaylist::MediaPlaylistStreamType::kVideo);
|
||||
mock_playlist.SetCodecForTesting(codec);
|
||||
EXPECT_CALL(mock_playlist, Bitrate()).WillOnce(Return(435889));
|
||||
EXPECT_CALL(mock_playlist, GetDisplayResolution(NotNull(), NotNull()))
|
||||
|
@ -95,7 +95,7 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistVideoAndAudio) {
|
|||
MockMediaPlaylist sd_video_playlist(kVodPlaylist, "sd.m3u8", "somename",
|
||||
"somegroupid");
|
||||
sd_video_playlist.SetStreamTypeForTesting(
|
||||
MediaPlaylist::MediaPlaylistStreamType::kPlayListVideo);
|
||||
MediaPlaylist::MediaPlaylistStreamType::kVideo);
|
||||
sd_video_playlist.SetCodecForTesting(sd_video_codec);
|
||||
EXPECT_CALL(sd_video_playlist, Bitrate())
|
||||
.Times(AtLeast(1))
|
||||
|
@ -111,7 +111,7 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistVideoAndAudio) {
|
|||
MockMediaPlaylist hd_video_playlist(kVodPlaylist, "hd.m3u8", "somename",
|
||||
"somegroupid");
|
||||
hd_video_playlist.SetStreamTypeForTesting(
|
||||
MediaPlaylist::MediaPlaylistStreamType::kPlayListVideo);
|
||||
MediaPlaylist::MediaPlaylistStreamType::kVideo);
|
||||
hd_video_playlist.SetCodecForTesting(hd_video_codec);
|
||||
EXPECT_CALL(hd_video_playlist, Bitrate())
|
||||
.Times(AtLeast(1))
|
||||
|
@ -131,7 +131,7 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistVideoAndAudio) {
|
|||
EXPECT_CALL(english_playlist, GetLanguage()).WillRepeatedly(Return("en"));
|
||||
EXPECT_CALL(english_playlist, GetNumChannels()).WillRepeatedly(Return(2));
|
||||
english_playlist.SetStreamTypeForTesting(
|
||||
MediaPlaylist::MediaPlaylistStreamType::kPlayListAudio);
|
||||
MediaPlaylist::MediaPlaylistStreamType::kAudio);
|
||||
english_playlist.SetCodecForTesting(audio_codec);
|
||||
EXPECT_CALL(english_playlist, Bitrate())
|
||||
.Times(AtLeast(1))
|
||||
|
@ -146,7 +146,7 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistVideoAndAudio) {
|
|||
EXPECT_CALL(spanish_playlist, GetLanguage()).WillRepeatedly(Return("es"));
|
||||
EXPECT_CALL(spanish_playlist, GetNumChannels()).WillRepeatedly(Return(5));
|
||||
spanish_playlist.SetStreamTypeForTesting(
|
||||
MediaPlaylist::MediaPlaylistStreamType::kPlayListAudio);
|
||||
MediaPlaylist::MediaPlaylistStreamType::kAudio);
|
||||
spanish_playlist.SetCodecForTesting(audio_codec);
|
||||
EXPECT_CALL(spanish_playlist, Bitrate())
|
||||
.Times(AtLeast(1))
|
||||
|
@ -187,7 +187,7 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistMultipleAudioGroups) {
|
|||
MockMediaPlaylist video_playlist(kVodPlaylist, "video.m3u8", "somename",
|
||||
"somegroupid");
|
||||
video_playlist.SetStreamTypeForTesting(
|
||||
MediaPlaylist::MediaPlaylistStreamType::kPlayListVideo);
|
||||
MediaPlaylist::MediaPlaylistStreamType::kVideo);
|
||||
video_playlist.SetCodecForTesting(video_codec);
|
||||
EXPECT_CALL(video_playlist, Bitrate())
|
||||
.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, GetNumChannels()).WillRepeatedly(Return(1));
|
||||
eng_lo_playlist.SetStreamTypeForTesting(
|
||||
MediaPlaylist::MediaPlaylistStreamType::kPlayListAudio);
|
||||
MediaPlaylist::MediaPlaylistStreamType::kAudio);
|
||||
eng_lo_playlist.SetCodecForTesting(audio_codec_lo);
|
||||
EXPECT_CALL(eng_lo_playlist, Bitrate())
|
||||
.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, GetNumChannels()).WillRepeatedly(Return(8));
|
||||
eng_hi_playlist.SetStreamTypeForTesting(
|
||||
MediaPlaylist::MediaPlaylistStreamType::kPlayListAudio);
|
||||
MediaPlaylist::MediaPlaylistStreamType::kAudio);
|
||||
eng_hi_playlist.SetCodecForTesting(audio_codec_hi);
|
||||
EXPECT_CALL(eng_hi_playlist, Bitrate())
|
||||
.Times(AtLeast(1))
|
||||
|
@ -262,7 +262,7 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistSameAudioGroupSameLanguage) {
|
|||
MockMediaPlaylist video_playlist(kVodPlaylist, "video.m3u8", "somename",
|
||||
"somegroupid");
|
||||
video_playlist.SetStreamTypeForTesting(
|
||||
MediaPlaylist::MediaPlaylistStreamType::kPlayListVideo);
|
||||
MediaPlaylist::MediaPlaylistStreamType::kVideo);
|
||||
video_playlist.SetCodecForTesting(video_codec);
|
||||
EXPECT_CALL(video_playlist, Bitrate())
|
||||
.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, GetNumChannels()).WillRepeatedly(Return(1));
|
||||
eng_lo_playlist.SetStreamTypeForTesting(
|
||||
MediaPlaylist::MediaPlaylistStreamType::kPlayListAudio);
|
||||
MediaPlaylist::MediaPlaylistStreamType::kAudio);
|
||||
eng_lo_playlist.SetCodecForTesting(audio_codec);
|
||||
EXPECT_CALL(eng_lo_playlist, Bitrate())
|
||||
.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, GetNumChannels()).WillRepeatedly(Return(8));
|
||||
eng_hi_playlist.SetStreamTypeForTesting(
|
||||
MediaPlaylist::MediaPlaylistStreamType::kPlayListAudio);
|
||||
MediaPlaylist::MediaPlaylistStreamType::kAudio);
|
||||
eng_hi_playlist.SetCodecForTesting(audio_codec);
|
||||
EXPECT_CALL(eng_hi_playlist, Bitrate())
|
||||
.Times(AtLeast(1))
|
||||
|
|
|
@ -59,11 +59,13 @@ std::string CreateExtXMap(const MediaInfo& media_info) {
|
|||
return ext_x_map;
|
||||
}
|
||||
|
||||
std::string CreatePlaylistHeader(const MediaInfo& media_info,
|
||||
uint32_t target_duration,
|
||||
HlsPlaylistType type,
|
||||
int media_sequence_number,
|
||||
int discontinuity_sequence_number) {
|
||||
std::string CreatePlaylistHeader(
|
||||
const MediaInfo& media_info,
|
||||
uint32_t target_duration,
|
||||
HlsPlaylistType type,
|
||||
MediaPlaylist::MediaPlaylistStreamType stream_type,
|
||||
int media_sequence_number,
|
||||
int discontinuity_sequence_number) {
|
||||
const std::string version = GetPackagerVersion();
|
||||
std::string version_line;
|
||||
if (!version.empty()) {
|
||||
|
@ -100,6 +102,10 @@ std::string CreatePlaylistHeader(const MediaInfo& media_info,
|
|||
default:
|
||||
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
|
||||
// segment and key info.
|
||||
|
@ -319,10 +325,10 @@ bool MediaPlaylist::SetMediaInfo(const MediaInfo& media_info) {
|
|||
}
|
||||
|
||||
if (media_info.has_video_info()) {
|
||||
stream_type_ = MediaPlaylistStreamType::kPlayListVideo;
|
||||
stream_type_ = MediaPlaylistStreamType::kVideo;
|
||||
codec_ = media_info.video_info().codec();
|
||||
} else if (media_info.has_audio_info()) {
|
||||
stream_type_ = MediaPlaylistStreamType::kPlayListAudio;
|
||||
stream_type_ = MediaPlaylistStreamType::kAudio;
|
||||
codec_ = media_info.audio_info().codec();
|
||||
} else {
|
||||
NOTIMPLEMENTED();
|
||||
|
@ -331,6 +337,7 @@ bool MediaPlaylist::SetMediaInfo(const MediaInfo& media_info) {
|
|||
|
||||
time_scale_ = time_scale;
|
||||
media_info_ = media_info;
|
||||
use_byte_range_ = !media_info_.has_segment_template();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -339,32 +346,45 @@ void MediaPlaylist::AddSegment(const std::string& file_name,
|
|||
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.";
|
||||
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,
|
||||
iter->start_byte_offset, iter->size);
|
||||
}
|
||||
|
||||
entries_.emplace_back(new SegmentInfoEntry(
|
||||
file_name, 0.0, 0.0, !media_info_.has_segment_template(),
|
||||
start_byte_offset, size, previous_segment_end_offset_));
|
||||
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;
|
||||
return;
|
||||
}
|
||||
return AddSegmentInfoEntry(file_name, start_time, duration, start_byte_offset,
|
||||
size);
|
||||
}
|
||||
|
||||
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(
|
||||
file_name, start_time_seconds, segment_duration_seconds,
|
||||
!media_info_.has_segment_template(), start_byte_offset, size,
|
||||
previous_segment_end_offset_));
|
||||
previous_segment_end_offset_ = start_byte_offset + size - 1;
|
||||
SlideWindow();
|
||||
void MediaPlaylist::AddKeyFrame(uint64_t timestamp,
|
||||
uint64_t start_byte_offset,
|
||||
uint64_t size) {
|
||||
if (stream_type_ != MediaPlaylistStreamType::kVideoIFramesOnly) {
|
||||
if (stream_type_ != MediaPlaylistStreamType::kVideo) {
|
||||
LOG(WARNING)
|
||||
<< "I-Frames Only playlist applies to video renditions only.";
|
||||
return;
|
||||
}
|
||||
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,
|
||||
|
@ -389,13 +409,24 @@ void MediaPlaylist::AddPlacementOpportunity() {
|
|||
}
|
||||
|
||||
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_) {
|
||||
SetTargetDuration(ceil(GetLongestSegmentDuration()));
|
||||
}
|
||||
|
||||
std::string header = CreatePlaylistHeader(
|
||||
media_info_, target_duration_, playlist_type_, media_sequence_number_,
|
||||
discontinuity_sequence_number_);
|
||||
media_info_, target_duration_, playlist_type_, stream_type_,
|
||||
media_sequence_number_, discontinuity_sequence_number_);
|
||||
|
||||
std::string body;
|
||||
for (const auto& entry : entries_)
|
||||
|
@ -472,6 +503,38 @@ bool MediaPlaylist::GetDisplayResolution(uint32_t* width,
|
|||
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() {
|
||||
DCHECK(!entries_.empty());
|
||||
if (time_shift_buffer_depth_ <= 0.0 ||
|
||||
|
|
|
@ -45,10 +45,11 @@ class HlsEntry {
|
|||
class MediaPlaylist {
|
||||
public:
|
||||
enum class MediaPlaylistStreamType {
|
||||
kPlaylistUnknown,
|
||||
kPlayListAudio,
|
||||
kPlayListVideo,
|
||||
kPlayListSubtitle,
|
||||
kUnknown,
|
||||
kAudio,
|
||||
kVideo,
|
||||
kVideoIFramesOnly,
|
||||
kSubtitle,
|
||||
};
|
||||
enum class EncryptionMethod {
|
||||
kNone, // No encryption, i.e. clear.
|
||||
|
@ -104,6 +105,16 @@ class MediaPlaylist {
|
|||
uint64_t start_byte_offset,
|
||||
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
|
||||
/// the key that can be fetched from |url|, until calling this again.
|
||||
/// @param method is the encryption method.
|
||||
|
@ -168,6 +179,12 @@ class MediaPlaylist {
|
|||
virtual bool GetDisplayResolution(uint32_t* width, uint32_t* height) const;
|
||||
|
||||
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
|
||||
// |sequence_number_| by the number of segments removed.
|
||||
void SlideWindow();
|
||||
|
@ -179,8 +196,9 @@ class MediaPlaylist {
|
|||
const std::string name_;
|
||||
const std::string group_id_;
|
||||
MediaInfo media_info_;
|
||||
MediaPlaylistStreamType stream_type_ =
|
||||
MediaPlaylistStreamType::kPlaylistUnknown;
|
||||
MediaPlaylistStreamType stream_type_ = MediaPlaylistStreamType::kUnknown;
|
||||
// Whether to use byte range for SegmentInfoEntry.
|
||||
bool use_byte_range_ = false;
|
||||
std::string codec_;
|
||||
int media_sequence_number_ = 0;
|
||||
bool inserted_discontinuity_tag_ = false;
|
||||
|
@ -201,6 +219,16 @@ class MediaPlaylist {
|
|||
|
||||
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);
|
||||
};
|
||||
|
||||
|
|
|
@ -771,5 +771,101 @@ TEST_F(EventMediaPlaylistTest, Basic) {
|
|||
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 shaka
|
||||
|
|
Loading…
Reference in New Issue