Fix small bugs in HLS playlist generation

- Media Playlist that is non-VERSION 1 must have EXT-X-VERSION.
- Attribute for EXTINF should be CODECS instead of CODEC.
- BANDWIDTH attribute expects bitrate (not bytes per second).
- Removed unnecessary check if MediaInfo has media_info_name, in
  SimpleHlsNotifier::NotifyNewStream().

Change-Id: Ia63cfa59e5e2ec24bbf1b784164e6e41176fc589
This commit is contained in:
Rintaro Kuroiwa 2016-04-16 15:58:47 -07:00
parent a80e16bab0
commit 2756902934
7 changed files with 27 additions and 32 deletions

View File

@ -125,7 +125,7 @@ bool MasterPlaylist::WriteMasterPlaylist(const std::string& base_url,
const std::string& audio_codec = audio_playlists.front()->codec(); const std::string& audio_codec = audio_playlists.front()->codec();
base::StringAppendF( base::StringAppendF(
&video_output, &video_output,
"#EXT-X-STREAM-INF:AUDIO=\"%s\",CODEC=\"%s\",BANDWIDTH=%" PRIu64 "\n" "#EXT-X-STREAM-INF:AUDIO=\"%s\",CODECS=\"%s\",BANDWIDTH=%" PRIu64 "\n"
"%s\n", "%s\n",
group_id.c_str(), (video_codec + "," + audio_codec).c_str(), group_id.c_str(), (video_codec + "," + audio_codec).c_str(),
video_bitrate + max_audio_bitrate, video_bitrate + max_audio_bitrate,
@ -138,7 +138,7 @@ bool MasterPlaylist::WriteMasterPlaylist(const std::string& base_url,
const std::string& video_codec = video_playlist->codec(); const std::string& video_codec = video_playlist->codec();
const uint64_t video_bitrate = video_playlist->Bitrate(); const uint64_t video_bitrate = video_playlist->Bitrate();
base::StringAppendF(&video_output, base::StringAppendF(&video_output,
"#EXT-X-STREAM-INF:CODEC=\"%s\",BANDWIDTH=%" PRIu64 "#EXT-X-STREAM-INF:CODECS=\"%s\",BANDWIDTH=%" PRIu64
"\n%s\n", "\n%s\n",
video_codec.c_str(), video_bitrate, video_codec.c_str(), video_bitrate,
(base_url + video_playlist->file_name()).c_str()); (base_url + video_playlist->file_name()).c_str());

View File

@ -82,7 +82,7 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistOneVideo) {
const std::string expected = const std::string expected =
"#EXTM3U\n" "#EXTM3U\n"
"#EXT-X-STREAM-INF:CODEC=\"avc1\",BANDWIDTH=435889\n" "#EXT-X-STREAM-INF:CODECS=\"avc1\",BANDWIDTH=435889\n"
"http://myplaylistdomain.com/media1.m3u8\n"; "http://myplaylistdomain.com/media1.m3u8\n";
ASSERT_EQ(expected, actual); ASSERT_EQ(expected, actual);
@ -152,11 +152,11 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistVideoAndAudio) {
"#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"audiogroup\",NAME=\"espanol\"," "#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"audiogroup\",NAME=\"espanol\","
"URI=\"http://playlists.org/spa.m3u8\"\n" "URI=\"http://playlists.org/spa.m3u8\"\n"
"#EXT-X-STREAM-INF:AUDIO=\"audiogroup\"," "#EXT-X-STREAM-INF:AUDIO=\"audiogroup\","
"CODEC=\"sdvideocodec,audiocodec\"," "CODECS=\"sdvideocodec,audiocodec\","
"BANDWIDTH=360000\n" "BANDWIDTH=360000\n"
"http://playlists.org/sd.m3u8\n" "http://playlists.org/sd.m3u8\n"
"#EXT-X-STREAM-INF:AUDIO=\"audiogroup\"," "#EXT-X-STREAM-INF:AUDIO=\"audiogroup\","
"CODEC=\"hdvideocodec,audiocodec\"," "CODECS=\"hdvideocodec,audiocodec\","
"BANDWIDTH=760000\n" "BANDWIDTH=760000\n"
"http://playlists.org/hd.m3u8\n"; "http://playlists.org/hd.m3u8\n";
@ -214,11 +214,11 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistMultipleAudioGroups) {
"#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"audio_lo\",NAME=\"english_lo\"," "#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"audio_lo\",NAME=\"english_lo\","
"URI=\"http://anydomain.com/eng_lo.m3u8\"\n" "URI=\"http://anydomain.com/eng_lo.m3u8\"\n"
"#EXT-X-STREAM-INF:AUDIO=\"audio_hi\"," "#EXT-X-STREAM-INF:AUDIO=\"audio_hi\","
"CODEC=\"videocodec,audiocodec_hi\"," "CODECS=\"videocodec,audiocodec_hi\","
"BANDWIDTH=400000\n" "BANDWIDTH=400000\n"
"http://anydomain.com/video.m3u8\n" "http://anydomain.com/video.m3u8\n"
"#EXT-X-STREAM-INF:AUDIO=\"audio_lo\"," "#EXT-X-STREAM-INF:AUDIO=\"audio_lo\","
"CODEC=\"videocodec,audiocodec_lo\"," "CODECS=\"videocodec,audiocodec_lo\","
"BANDWIDTH=350000\n" "BANDWIDTH=350000\n"
"http://anydomain.com/video.m3u8\n"; "http://anydomain.com/video.m3u8\n";

View File

@ -169,15 +169,16 @@ void MediaPlaylist::AddSegment(const std::string& file_name,
return; return;
} }
const double segment_duration = static_cast<double>(duration) / time_scale_; const double segment_duration_seconds =
if (segment_duration > longest_segment_duration_) static_cast<double>(duration) / time_scale_;
longest_segment_duration_ = segment_duration; if (segment_duration_seconds > longest_segment_duration_)
longest_segment_duration_ = segment_duration_seconds;
total_duration_in_seconds_ += segment_duration; total_duration_in_seconds_ += segment_duration_seconds;
total_segments_size_ += size; total_segments_size_ += size;
++total_num_segments_; ++total_num_segments_;
entries_.push_back(new SegmentInfoEntry(file_name, segment_duration)); entries_.push_back(new SegmentInfoEntry(file_name, segment_duration_seconds));
} }
// TODO(rkuroiwa): This works for single key format but won't work for multiple // TODO(rkuroiwa): This works for single key format but won't work for multiple
@ -264,7 +265,9 @@ bool MediaPlaylist::WriteToFile(media::File* file) {
SetTargetDuration(ceil(GetLongestSegmentDuration())); SetTargetDuration(ceil(GetLongestSegmentDuration()));
} }
// EXTINF with floating point duration requires version 4.
std::string header = base::StringPrintf("#EXTM3U\n" std::string header = base::StringPrintf("#EXTM3U\n"
"#EXT-X-VERSION:4\n"
"#EXT-X-TARGETDURATION:%d\n", "#EXT-X-TARGETDURATION:%d\n",
target_duration_); target_duration_);
std::string body; std::string body;
@ -298,7 +301,8 @@ uint64_t MediaPlaylist::Bitrate() const {
return 0; return 0;
if (total_segments_size_ == 0) if (total_segments_size_ == 0)
return 0; return 0;
return total_segments_size_ / total_duration_in_seconds_; const int kBytesToBits = 8;
return total_segments_size_ * kBytesToBits / total_duration_in_seconds_;
} }
double MediaPlaylist::GetLongestSegmentDuration() const { double MediaPlaylist::GetLongestSegmentDuration() const {

View File

@ -128,7 +128,7 @@ class MediaPlaylist {
/// If bitrate is specified in MediaInfo then it will use that value. /// If bitrate is specified in MediaInfo then it will use that value.
/// Otherwise, it is calculated from the duration and the size of the /// Otherwise, it is calculated from the duration and the size of the
/// segments added to this object. /// segments added to this object.
/// @return the bitrate of this MediaPlaylist. /// @return the bitrate (in bits per second) of this MediaPlaylist.
virtual uint64_t Bitrate() const; virtual uint64_t Bitrate() const;
/// @return the longest segments duration. This will return 0 if no /// @return the longest segments duration. This will return 0 if no
@ -157,6 +157,7 @@ class MediaPlaylist {
double longest_segment_duration_ = 0.0; double longest_segment_duration_ = 0.0;
uint32_t time_scale_ = 0; uint32_t time_scale_ = 0;
// The sum of the size of the segments listed in this playlist (in bytes).
uint64_t total_segments_size_ = 0; uint64_t total_segments_size_ = 0;
double total_duration_in_seconds_ = 0.0; double total_duration_in_seconds_ = 0.0;
int total_num_segments_; int total_num_segments_;

View File

@ -113,6 +113,7 @@ TEST_F(MediaPlaylistTest, WriteToFile) {
ASSERT_TRUE(media_playlist_.SetMediaInfo(valid_video_media_info_)); ASSERT_TRUE(media_playlist_.SetMediaInfo(valid_video_media_info_));
const std::string kExpectedOutput = const std::string kExpectedOutput =
"#EXTM3U\n" "#EXTM3U\n"
"#EXT-X-VERSION:4\n"
"#EXT-X-TARGETDURATION:0\n"; "#EXT-X-TARGETDURATION:0\n";
MockFile file; MockFile file;
@ -142,8 +143,8 @@ TEST_F(MediaPlaylistTest, GetBitrateFromSegments) {
// 20 seconds, 5MB. // 20 seconds, 5MB.
media_playlist_.AddSegment("file2.ts", 1800000, 5000000); media_playlist_.AddSegment("file2.ts", 1800000, 5000000);
// 200KB per second. // 200KB per second which is 1600K bits / sec.
EXPECT_EQ(200000u, media_playlist_.Bitrate()); EXPECT_EQ(1600000u, media_playlist_.Bitrate());
} }
TEST_F(MediaPlaylistTest, GetLongestSegmentDuration) { TEST_F(MediaPlaylistTest, GetLongestSegmentDuration) {
@ -165,6 +166,7 @@ TEST_F(MediaPlaylistTest, SetTargetDuration) {
EXPECT_TRUE(media_playlist_.SetTargetDuration(20)); EXPECT_TRUE(media_playlist_.SetTargetDuration(20));
const std::string kExpectedOutput = const std::string kExpectedOutput =
"#EXTM3U\n" "#EXTM3U\n"
"#EXT-X-VERSION:4\n"
"#EXT-X-TARGETDURATION:20\n"; "#EXT-X-TARGETDURATION:20\n";
MockFile file; MockFile file;
@ -188,6 +190,7 @@ TEST_F(MediaPlaylistTest, WriteToFileWithSegments) {
media_playlist_.AddSegment("file2.ts", 2700000, 5000000); media_playlist_.AddSegment("file2.ts", 2700000, 5000000);
const std::string kExpectedOutput = const std::string kExpectedOutput =
"#EXTM3U\n" "#EXTM3U\n"
"#EXT-X-VERSION:4\n"
"#EXT-X-TARGETDURATION:30\n" "#EXT-X-TARGETDURATION:30\n"
"#EXTINF:10.000\n" "#EXTINF:10.000\n"
"file1.ts\n" "file1.ts\n"
@ -214,6 +217,7 @@ TEST_F(MediaPlaylistTest, WriteToFileWithEncryptionInfo) {
media_playlist_.AddSegment("file2.ts", 2700000, 5000000); media_playlist_.AddSegment("file2.ts", 2700000, 5000000);
const std::string kExpectedOutput = const std::string kExpectedOutput =
"#EXTM3U\n" "#EXTM3U\n"
"#EXT-X-VERSION:4\n"
"#EXT-X-TARGETDURATION:30\n" "#EXT-X-TARGETDURATION:30\n"
"#EXT-X-KEY:METHOD=SAMPLE-AES," "#EXT-X-KEY:METHOD=SAMPLE-AES,"
"URI=\"http://example.com\",IV=0x12345678,KEYFORMATVERSIONS=\"1/2/4\"," "URI=\"http://example.com\",IV=0x12345678,KEYFORMATVERSIONS=\"1/2/4\","
@ -243,6 +247,7 @@ TEST_F(MediaPlaylistTest, WriteToFileWithEncryptionInfoEmptyIv) {
media_playlist_.AddSegment("file2.ts", 2700000, 5000000); media_playlist_.AddSegment("file2.ts", 2700000, 5000000);
const std::string kExpectedOutput = const std::string kExpectedOutput =
"#EXTM3U\n" "#EXTM3U\n"
"#EXT-X-VERSION:4\n"
"#EXT-X-TARGETDURATION:30\n" "#EXT-X-TARGETDURATION:30\n"
"#EXT-X-KEY:METHOD=SAMPLE-AES," "#EXT-X-KEY:METHOD=SAMPLE-AES,"
"URI=\"http://example.com\",KEYFORMAT=\"com.widevine\"\n" "URI=\"http://example.com\",KEYFORMAT=\"com.widevine\"\n"
@ -270,6 +275,7 @@ TEST_F(MediaPlaylistTest, RemoveOldestSegment) {
const std::string kExpectedOutput = const std::string kExpectedOutput =
"#EXTM3U\n" "#EXTM3U\n"
"#EXT-X-VERSION:4\n"
"#EXT-X-TARGETDURATION:30\n" "#EXT-X-TARGETDURATION:30\n"
"#EXTINF:30.000\n" "#EXTINF:30.000\n"
"file2.ts\n"; "file2.ts\n";

View File

@ -58,12 +58,6 @@ bool SimpleHlsNotifier::NotifyNewStream(const MediaInfo& media_info,
const std::string& group_id, const std::string& group_id,
uint32_t* stream_id) { uint32_t* stream_id) {
DCHECK(stream_id); DCHECK(stream_id);
if (!media_info.has_media_file_name()) {
LOG(ERROR) << "MediaInfo.media_file_name is required to generate a Media "
"Playlist name.";
return false;
}
*stream_id = sequence_number_.GetNext(); *stream_id = sequence_number_.GetNext();
scoped_ptr<MediaPlaylist> media_playlist = scoped_ptr<MediaPlaylist> media_playlist =

View File

@ -93,8 +93,6 @@ TEST_F(SimpleHlsNotifierTest, NotifyNewStream) {
InjectMediaPlaylistFactory(factory.Pass()); InjectMediaPlaylistFactory(factory.Pass());
EXPECT_TRUE(notifier_.Init()); EXPECT_TRUE(notifier_.Init());
MediaInfo media_info; MediaInfo media_info;
media_info.set_media_file_name("media_file.mp4");
media_info.mutable_video_info()->set_codec("videocodec");
uint32_t stream_id; uint32_t stream_id;
EXPECT_TRUE(notifier_.NotifyNewStream(media_info, "video_playlist.m3u8", EXPECT_TRUE(notifier_.NotifyNewStream(media_info, "video_playlist.m3u8",
"name", "groupid", &stream_id)); "name", "groupid", &stream_id));
@ -126,8 +124,6 @@ TEST_F(SimpleHlsNotifierTest, NotifyNewSegment) {
InjectMediaPlaylistFactory(factory.Pass()); InjectMediaPlaylistFactory(factory.Pass());
EXPECT_TRUE(notifier_.Init()); EXPECT_TRUE(notifier_.Init());
MediaInfo media_info; MediaInfo media_info;
media_info.set_media_file_name("media_file.mp4");
media_info.mutable_video_info()->set_codec("videocodec");
uint32_t stream_id; uint32_t stream_id;
EXPECT_TRUE(notifier_.NotifyNewStream(media_info, "playlist.m3u8", "name", EXPECT_TRUE(notifier_.NotifyNewStream(media_info, "playlist.m3u8", "name",
"groupid", &stream_id)); "groupid", &stream_id));
@ -159,8 +155,6 @@ TEST_F(SimpleHlsNotifierTest, NotifyEncryptionUpdate) {
InjectMediaPlaylistFactory(factory.Pass()); InjectMediaPlaylistFactory(factory.Pass());
EXPECT_TRUE(notifier_.Init()); EXPECT_TRUE(notifier_.Init());
MediaInfo media_info; MediaInfo media_info;
media_info.set_media_file_name("media_file.mp4");
media_info.mutable_video_info()->set_codec("videocodec");
uint32_t stream_id; uint32_t stream_id;
EXPECT_TRUE(notifier_.NotifyNewStream(media_info, "playlist.m3u8", "name", EXPECT_TRUE(notifier_.NotifyNewStream(media_info, "playlist.m3u8", "name",
"groupid", &stream_id)); "groupid", &stream_id));
@ -225,8 +219,6 @@ TEST_F(SimpleHlsNotifierTest, MultipleKeyIdsInPssh) {
InjectMediaPlaylistFactory(factory.Pass()); InjectMediaPlaylistFactory(factory.Pass());
EXPECT_TRUE(notifier_.Init()); EXPECT_TRUE(notifier_.Init());
MediaInfo media_info; MediaInfo media_info;
media_info.set_media_file_name("media_file.mp4");
media_info.mutable_video_info()->set_codec("videocodec");
uint32_t stream_id; uint32_t stream_id;
EXPECT_TRUE(notifier_.NotifyNewStream(media_info, "playlist.m3u8", "name", EXPECT_TRUE(notifier_.NotifyNewStream(media_info, "playlist.m3u8", "name",
"groupid", &stream_id)); "groupid", &stream_id));
@ -297,8 +289,6 @@ TEST_F(SimpleHlsNotifierTest, NotifyEncryptionUpdateEmptyIv) {
InjectMediaPlaylistFactory(factory.Pass()); InjectMediaPlaylistFactory(factory.Pass());
EXPECT_TRUE(notifier_.Init()); EXPECT_TRUE(notifier_.Init());
MediaInfo media_info; MediaInfo media_info;
media_info.set_media_file_name("media_file.mp4");
media_info.mutable_video_info()->set_codec("videocodec");
uint32_t stream_id; uint32_t stream_id;
EXPECT_TRUE(notifier_.NotifyNewStream(media_info, "playlist.m3u8", "name", EXPECT_TRUE(notifier_.NotifyNewStream(media_info, "playlist.m3u8", "name",
"groupid", &stream_id)); "groupid", &stream_id));