Support EXT-X-I-FRAME-STREAM-INF in master playlist
Issue: #287 Change-Id: I07fdfa095fe1797b3aa091d48798a2b5fbb4dfbe
This commit is contained in:
parent
c991490e82
commit
1750357024
|
@ -145,43 +145,59 @@ 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
|
||||||
// grouped with other playlists.
|
// grouped with other playlists.
|
||||||
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue