Support EXT-X-I-FRAME-STREAM-INF in master playlist

Issue: #287

Change-Id: I07fdfa095fe1797b3aa091d48798a2b5fbb4dfbe
This commit is contained in:
KongQun Yang 2018-02-02 14:45:52 -08:00
parent c991490e82
commit 1750357024
2 changed files with 100 additions and 35 deletions

View File

@ -145,42 +145,58 @@ std::list<Variant> BuildVariants(
return merged; return merged;
} }
void BuildVideoTag(const MediaPlaylist& playlist, void BuildStreamInfTag(const MediaPlaylist& playlist,
uint64_t max_audio_bitrate, const Variant& variant,
const std::string& audio_codec,
const std::string* audio_group_id,
const std::string* text_group_id,
const std::string& base_url, const std::string& base_url,
std::string* out) { std::string* out) {
DCHECK(out); DCHECK(out);
const uint64_t bitrate = playlist.Bitrate() + max_audio_bitrate; const uint64_t bitrate = playlist.Bitrate() + variant.audio_bitrate;
uint32_t width; uint32_t width;
uint32_t height; uint32_t height;
CHECK(playlist.GetDisplayResolution(&width, &height)); CHECK(playlist.GetDisplayResolution(&width, &height));
std::string codecs = playlist.codec(); std::string codecs = playlist.codec();
if (!audio_codec.empty()) { if (!variant.audio_codec.empty()) {
base::StringAppendF(&codecs, ",%s", audio_codec.c_str()); base::StringAppendF(&codecs, ",%s", variant.audio_codec.c_str());
} }
Tag tag("#EXT-X-STREAM-INF", out); std::string tag_name;
switch (playlist.stream_type()) {
case MediaPlaylist::MediaPlaylistStreamType::kVideo:
tag_name = "#EXT-X-STREAM-INF";
break;
case MediaPlaylist::MediaPlaylistStreamType::kVideoIFramesOnly:
tag_name = "#EXT-X-I-FRAME-STREAM-INF";
break;
default:
NOTREACHED() << "Cannot build STREAM-INFO tag for type "
<< static_cast<int>(playlist.stream_type());
break;
}
Tag tag(tag_name, out);
tag.AddNumber("BANDWIDTH", bitrate); tag.AddNumber("BANDWIDTH", bitrate);
tag.AddQuotedString("CODECS", codecs); tag.AddQuotedString("CODECS", codecs);
tag.AddNumberPair("RESOLUTION", width, 'x', height); tag.AddNumberPair("RESOLUTION", width, 'x', height);
if (audio_group_id) { if (variant.audio_group_id) {
tag.AddQuotedString("AUDIO", *audio_group_id); tag.AddQuotedString("AUDIO", *variant.audio_group_id);
} }
if (text_group_id) { if (variant.text_group_id) {
tag.AddQuotedString("SUBTITLES", *text_group_id); tag.AddQuotedString("SUBTITLES", *variant.text_group_id);
} }
if (playlist.stream_type() ==
MediaPlaylist::MediaPlaylistStreamType::kVideoIFramesOnly) {
tag.AddQuotedString("URI", base_url + playlist.file_name());
out->append("\n");
} else {
base::StringAppendF(out, "\n%s%s\n", base_url.c_str(), base::StringAppendF(out, "\n%s%s\n", base_url.c_str(),
playlist.file_name().c_str()); playlist.file_name().c_str());
}
} }
// Need to pass in |group_id| as it may have changed to a new default when // Need to pass in |group_id| as it may have changed to a new default when
@ -297,6 +313,9 @@ void AppendPlaylists(const std::string& default_language,
case MediaPlaylist::MediaPlaylistStreamType::kVideo: case MediaPlaylist::MediaPlaylistStreamType::kVideo:
video_playlists.push_back(playlist); video_playlists.push_back(playlist);
break; break;
case MediaPlaylist::MediaPlaylistStreamType::kVideoIFramesOnly:
iframe_playlists.push_back(playlist);
break;
case MediaPlaylist::MediaPlaylistStreamType::kSubtitle: case MediaPlaylist::MediaPlaylistStreamType::kSubtitle:
subtitle_playlist_groups[GetGroupId(*playlist)].push_back(playlist); subtitle_playlist_groups[GetGroupId(*playlist)].push_back(playlist);
break; break;
@ -313,11 +332,14 @@ void AppendPlaylists(const std::string& default_language,
BuildVariants(audio_playlist_groups, subtitle_playlist_groups); BuildVariants(audio_playlist_groups, subtitle_playlist_groups);
for (const auto& playlist : video_playlists) { for (const auto& playlist : video_playlists) {
for (const auto& variant : variants) { for (const auto& variant : variants) {
BuildVideoTag(*playlist, variant.audio_bitrate, variant.audio_codec, BuildStreamInfTag(*playlist, variant, base_url, content);
variant.audio_group_id, variant.text_group_id, base_url,
content);
} }
} }
for (const auto& playlist : iframe_playlists) {
// I-Frame playlists do not have variant. Just use the default.
BuildStreamInfTag(*playlist, Variant(), base_url, content);
}
} }
} // namespace } // namespace

View File

@ -57,6 +57,16 @@ std::unique_ptr<MockMediaPlaylist> CreateVideoPlaylist(
return playlist; return playlist;
} }
std::unique_ptr<MockMediaPlaylist> CreateIframePlaylist(
const std::string& filename,
const std::string& codec,
uint64_t bitrate) {
auto playlist = CreateVideoPlaylist(filename, codec, bitrate);
playlist->SetStreamTypeForTesting(
MediaPlaylist::MediaPlaylistStreamType::kVideoIFramesOnly);
return playlist;
}
std::unique_ptr<MockMediaPlaylist> CreateAudioPlaylist( std::unique_ptr<MockMediaPlaylist> CreateAudioPlaylist(
const std::string& filename, const std::string& filename,
const std::string& name, const std::string& name,
@ -65,8 +75,6 @@ std::unique_ptr<MockMediaPlaylist> CreateAudioPlaylist(
const std::string& language, const std::string& language,
uint64_t channels, uint64_t channels,
uint64_t bitrate) { uint64_t bitrate) {
// Note that audiocodecs should match for different audio tracks with same
// group ID.
std::unique_ptr<MockMediaPlaylist> playlist( std::unique_ptr<MockMediaPlaylist> playlist(
new MockMediaPlaylist(kVodPlaylist, filename, name, group)); new MockMediaPlaylist(kVodPlaylist, filename, name, group));
@ -85,7 +93,8 @@ std::unique_ptr<MockMediaPlaylist> CreateAudioPlaylist(
return playlist; return playlist;
} }
std::unique_ptr<MockMediaPlaylist> MakeText(const std::string& filename, std::unique_ptr<MockMediaPlaylist> CreateTextPlaylist(
const std::string& filename,
const std::string& name, const std::string& name,
const std::string& group, const std::string& group,
const std::string& language) { const std::string& language) {
@ -140,6 +149,29 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistOneVideo) {
ASSERT_EQ(expected, actual); ASSERT_EQ(expected, actual);
} }
TEST_F(MasterPlaylistTest, WriteMasterPlaylistOneIframePlaylist) {
const uint64_t kBitRate = 435889;
std::unique_ptr<MockMediaPlaylist> mock_playlist =
CreateIframePlaylist("media1.m3u8", "avc1", kBitRate);
const char kBaseUrl[] = "http://myplaylistdomain.com/";
EXPECT_TRUE(master_playlist_.WriteMasterPlaylist(kBaseUrl, test_output_dir_,
{mock_playlist.get()}));
std::string actual;
ASSERT_TRUE(File::ReadFileToString(master_playlist_path_.c_str(), &actual));
const std::string expected =
"#EXTM3U\n"
"## Generated with https://github.com/google/shaka-packager version "
"test\n"
"#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=435889,CODECS=\"avc1\",RESOLUTION="
"800x600,URI=\"http://myplaylistdomain.com/media1.m3u8\"\n";
ASSERT_EQ(expected, actual);
}
TEST_F(MasterPlaylistTest, WriteMasterPlaylistVideoAndAudio) { TEST_F(MasterPlaylistTest, WriteMasterPlaylistVideoAndAudio) {
const uint64_t kVideo1BitRate = 300000; const uint64_t kVideo1BitRate = 300000;
const uint64_t kVideo2BitRate = 700000; const uint64_t kVideo2BitRate = 700000;
@ -295,11 +327,11 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistVideosAndTexts) {
// Text, eng.m3u8. // Text, eng.m3u8.
std::unique_ptr<MockMediaPlaylist> text_eng = std::unique_ptr<MockMediaPlaylist> text_eng =
MakeText("eng.m3u8", "english", "textgroup", "en"); CreateTextPlaylist("eng.m3u8", "english", "textgroup", "en");
// Text, fr.m3u8. // Text, fr.m3u8.
std::unique_ptr<MockMediaPlaylist> text_fr = std::unique_ptr<MockMediaPlaylist> text_fr =
MakeText("fr.m3u8", "french", "textgroup", "fr"); CreateTextPlaylist("fr.m3u8", "french", "textgroup", "fr");
const char kBaseUrl[] = "http://playlists.org/"; const char kBaseUrl[] = "http://playlists.org/";
EXPECT_TRUE(master_playlist_.WriteMasterPlaylist( EXPECT_TRUE(master_playlist_.WriteMasterPlaylist(
@ -335,11 +367,11 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistVideoAndTextGroups) {
// Text, eng.m3u8. // Text, eng.m3u8.
std::unique_ptr<MockMediaPlaylist> text_eng = std::unique_ptr<MockMediaPlaylist> text_eng =
MakeText("eng.m3u8", "english", "en-text-group", "en"); CreateTextPlaylist("eng.m3u8", "english", "en-text-group", "en");
// Text, fr.m3u8. // Text, fr.m3u8.
std::unique_ptr<MockMediaPlaylist> text_fr = std::unique_ptr<MockMediaPlaylist> text_fr =
MakeText("fr.m3u8", "french", "fr-text-group", "fr"); CreateTextPlaylist("fr.m3u8", "french", "fr-text-group", "fr");
const char kBaseUrl[] = "http://playlists.org/"; const char kBaseUrl[] = "http://playlists.org/";
EXPECT_TRUE(master_playlist_.WriteMasterPlaylist( EXPECT_TRUE(master_playlist_.WriteMasterPlaylist(
@ -380,7 +412,7 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistVideoAndAudioAndText) {
// Text, english.m3u8. // Text, english.m3u8.
std::unique_ptr<MockMediaPlaylist> text = std::unique_ptr<MockMediaPlaylist> text =
MakeText("eng.m3u8", "english", "textgroup", "en"); CreateTextPlaylist("eng.m3u8", "english", "textgroup", "en");
const char kBaseUrl[] = "http://playlists.org/"; const char kBaseUrl[] = "http://playlists.org/";
EXPECT_TRUE(master_playlist_.WriteMasterPlaylist( EXPECT_TRUE(master_playlist_.WriteMasterPlaylist(
@ -406,10 +438,11 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistVideoAndAudioAndText) {
ASSERT_EQ(expected, actual); ASSERT_EQ(expected, actual);
} }
TEST_F(MasterPlaylistTest, WriteMasterPlaylistVidesAudiosTextsDifferentGroups) { TEST_F(MasterPlaylistTest, WriteMasterPlaylistMixedPlaylistsDifferentGroups) {
const uint64_t kAudioChannels = 2; const uint64_t kAudioChannels = 2;
const uint64_t kAudioBitRate = 50000; const uint64_t kAudioBitRate = 50000;
const uint64_t kVideoBitRate = 300000; const uint64_t kVideoBitRate = 300000;
const uint64_t kIframeBitRate = 100000;
std::unique_ptr<MockMediaPlaylist> media_playlists[] = { std::unique_ptr<MockMediaPlaylist> media_playlists[] = {
// AUDIO // AUDIO
@ -419,12 +452,16 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistVidesAudiosTextsDifferentGroups) {
"audiocodec", "en", kAudioChannels, kAudioBitRate), "audiocodec", "en", kAudioChannels, kAudioBitRate),
// SUBTITLES // SUBTITLES
MakeText("text-1.m3u8", "text 1", "text-group-1", "en"), CreateTextPlaylist("text-1.m3u8", "text 1", "text-group-1", "en"),
MakeText("text-2.m3u8", "text 2", "text-group-2", "en"), CreateTextPlaylist("text-2.m3u8", "text 2", "text-group-2", "en"),
// VIDEO // VIDEO
CreateVideoPlaylist("video-1.m3u8", "sdvideocodec", kVideoBitRate), CreateVideoPlaylist("video-1.m3u8", "sdvideocodec", kVideoBitRate),
CreateVideoPlaylist("video-2.m3u8", "sdvideocodec", kVideoBitRate), CreateVideoPlaylist("video-2.m3u8", "sdvideocodec", kVideoBitRate),
// I-Frame
CreateIframePlaylist("iframe-1.m3u8", "sdvideocodec", kIframeBitRate),
CreateIframePlaylist("iframe-2.m3u8", "sdvideocodec", kIframeBitRate),
}; };
// Add all the media playlists to the master playlist. // Add all the media playlists to the master playlist.
@ -491,7 +528,13 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistVidesAudiosTextsDifferentGroups) {
"#EXT-X-STREAM-INF:BANDWIDTH=350000,CODECS=\"sdvideocodec,audiocodec\"," "#EXT-X-STREAM-INF:BANDWIDTH=350000,CODECS=\"sdvideocodec,audiocodec\","
"RESOLUTION=800x600,AUDIO=\"audio-group-2\",SUBTITLES=\"text-group-2\"\n" "RESOLUTION=800x600,AUDIO=\"audio-group-2\",SUBTITLES=\"text-group-2\"\n"
"http://playlists.org/video-2.m3u8\n"; "http://playlists.org/video-2.m3u8\n"
"#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=100000,CODECS=\"sdvideocodec\","
"RESOLUTION=800x600,URI=\"http://playlists.org/iframe-1.m3u8\"\n"
"#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=100000,CODECS=\"sdvideocodec\","
"RESOLUTION=800x600,URI=\"http://playlists.org/iframe-2.m3u8\"\n";
ASSERT_EQ(expected, actual); ASSERT_EQ(expected, actual);
} }