From 17503570243ff0e2d3bb24fddea8852848996d34 Mon Sep 17 00:00:00 2001 From: KongQun Yang Date: Fri, 2 Feb 2018 14:45:52 -0800 Subject: [PATCH] Support EXT-X-I-FRAME-STREAM-INF in master playlist Issue: #287 Change-Id: I07fdfa095fe1797b3aa091d48798a2b5fbb4dfbe --- packager/hls/base/master_playlist.cc | 62 +++++++++++----- packager/hls/base/master_playlist_unittest.cc | 73 +++++++++++++++---- 2 files changed, 100 insertions(+), 35 deletions(-) diff --git a/packager/hls/base/master_playlist.cc b/packager/hls/base/master_playlist.cc index a068dd7d75..afc0707a52 100644 --- a/packager/hls/base/master_playlist.cc +++ b/packager/hls/base/master_playlist.cc @@ -145,42 +145,58 @@ std::list BuildVariants( return merged; } -void BuildVideoTag(const MediaPlaylist& playlist, - uint64_t max_audio_bitrate, - const std::string& audio_codec, - const std::string* audio_group_id, - const std::string* text_group_id, - const std::string& base_url, - std::string* out) { +void BuildStreamInfTag(const MediaPlaylist& playlist, + const Variant& variant, + const std::string& base_url, + std::string* 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 height; CHECK(playlist.GetDisplayResolution(&width, &height)); std::string codecs = playlist.codec(); - if (!audio_codec.empty()) { - base::StringAppendF(&codecs, ",%s", audio_codec.c_str()); + if (!variant.audio_codec.empty()) { + 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(playlist.stream_type()); + break; + } + Tag tag(tag_name, out); tag.AddNumber("BANDWIDTH", bitrate); tag.AddQuotedString("CODECS", codecs); tag.AddNumberPair("RESOLUTION", width, 'x', height); - if (audio_group_id) { - tag.AddQuotedString("AUDIO", *audio_group_id); + if (variant.audio_group_id) { + tag.AddQuotedString("AUDIO", *variant.audio_group_id); } - if (text_group_id) { - tag.AddQuotedString("SUBTITLES", *text_group_id); + if (variant.text_group_id) { + tag.AddQuotedString("SUBTITLES", *variant.text_group_id); } - base::StringAppendF(out, "\n%s%s\n", base_url.c_str(), - playlist.file_name().c_str()); + 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(), + playlist.file_name().c_str()); + } } // 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: video_playlists.push_back(playlist); break; + case MediaPlaylist::MediaPlaylistStreamType::kVideoIFramesOnly: + iframe_playlists.push_back(playlist); + break; case MediaPlaylist::MediaPlaylistStreamType::kSubtitle: subtitle_playlist_groups[GetGroupId(*playlist)].push_back(playlist); break; @@ -313,11 +332,14 @@ void AppendPlaylists(const std::string& default_language, BuildVariants(audio_playlist_groups, subtitle_playlist_groups); for (const auto& playlist : video_playlists) { for (const auto& variant : variants) { - BuildVideoTag(*playlist, variant.audio_bitrate, variant.audio_codec, - variant.audio_group_id, variant.text_group_id, base_url, - content); + BuildStreamInfTag(*playlist, variant, 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 diff --git a/packager/hls/base/master_playlist_unittest.cc b/packager/hls/base/master_playlist_unittest.cc index 7d357f3df6..8dae49c2f9 100644 --- a/packager/hls/base/master_playlist_unittest.cc +++ b/packager/hls/base/master_playlist_unittest.cc @@ -57,6 +57,16 @@ std::unique_ptr CreateVideoPlaylist( return playlist; } +std::unique_ptr 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 CreateAudioPlaylist( const std::string& filename, const std::string& name, @@ -65,8 +75,6 @@ std::unique_ptr CreateAudioPlaylist( const std::string& language, uint64_t channels, uint64_t bitrate) { - // Note that audiocodecs should match for different audio tracks with same - // group ID. std::unique_ptr playlist( new MockMediaPlaylist(kVodPlaylist, filename, name, group)); @@ -85,10 +93,11 @@ std::unique_ptr CreateAudioPlaylist( return playlist; } -std::unique_ptr MakeText(const std::string& filename, - const std::string& name, - const std::string& group, - const std::string& language) { +std::unique_ptr CreateTextPlaylist( + const std::string& filename, + const std::string& name, + const std::string& group, + const std::string& language) { std::unique_ptr playlist( new MockMediaPlaylist(kVodPlaylist, filename, name, group)); @@ -140,6 +149,29 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistOneVideo) { ASSERT_EQ(expected, actual); } +TEST_F(MasterPlaylistTest, WriteMasterPlaylistOneIframePlaylist) { + const uint64_t kBitRate = 435889; + + std::unique_ptr 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) { const uint64_t kVideo1BitRate = 300000; const uint64_t kVideo2BitRate = 700000; @@ -295,11 +327,11 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistVideosAndTexts) { // Text, eng.m3u8. std::unique_ptr text_eng = - MakeText("eng.m3u8", "english", "textgroup", "en"); + CreateTextPlaylist("eng.m3u8", "english", "textgroup", "en"); // Text, fr.m3u8. std::unique_ptr text_fr = - MakeText("fr.m3u8", "french", "textgroup", "fr"); + CreateTextPlaylist("fr.m3u8", "french", "textgroup", "fr"); const char kBaseUrl[] = "http://playlists.org/"; EXPECT_TRUE(master_playlist_.WriteMasterPlaylist( @@ -335,11 +367,11 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistVideoAndTextGroups) { // Text, eng.m3u8. std::unique_ptr text_eng = - MakeText("eng.m3u8", "english", "en-text-group", "en"); + CreateTextPlaylist("eng.m3u8", "english", "en-text-group", "en"); // Text, fr.m3u8. std::unique_ptr text_fr = - MakeText("fr.m3u8", "french", "fr-text-group", "fr"); + CreateTextPlaylist("fr.m3u8", "french", "fr-text-group", "fr"); const char kBaseUrl[] = "http://playlists.org/"; EXPECT_TRUE(master_playlist_.WriteMasterPlaylist( @@ -380,7 +412,7 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistVideoAndAudioAndText) { // Text, english.m3u8. std::unique_ptr text = - MakeText("eng.m3u8", "english", "textgroup", "en"); + CreateTextPlaylist("eng.m3u8", "english", "textgroup", "en"); const char kBaseUrl[] = "http://playlists.org/"; EXPECT_TRUE(master_playlist_.WriteMasterPlaylist( @@ -406,10 +438,11 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistVideoAndAudioAndText) { ASSERT_EQ(expected, actual); } -TEST_F(MasterPlaylistTest, WriteMasterPlaylistVidesAudiosTextsDifferentGroups) { +TEST_F(MasterPlaylistTest, WriteMasterPlaylistMixedPlaylistsDifferentGroups) { const uint64_t kAudioChannels = 2; const uint64_t kAudioBitRate = 50000; const uint64_t kVideoBitRate = 300000; + const uint64_t kIframeBitRate = 100000; std::unique_ptr media_playlists[] = { // AUDIO @@ -419,12 +452,16 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistVidesAudiosTextsDifferentGroups) { "audiocodec", "en", kAudioChannels, kAudioBitRate), // SUBTITLES - MakeText("text-1.m3u8", "text 1", "text-group-1", "en"), - MakeText("text-2.m3u8", "text 2", "text-group-2", "en"), + CreateTextPlaylist("text-1.m3u8", "text 1", "text-group-1", "en"), + CreateTextPlaylist("text-2.m3u8", "text 2", "text-group-2", "en"), // VIDEO CreateVideoPlaylist("video-1.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. @@ -491,7 +528,13 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistVidesAudiosTextsDifferentGroups) { "#EXT-X-STREAM-INF:BANDWIDTH=350000,CODECS=\"sdvideocodec,audiocodec\"," "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); }