Add resolution to HLS manifests

The HLS manifests output by the packager did not have the RESOLUTION
attribute on EXT-X-STREAM-INF tags. This change adds the resolution
tag when resolution values are present.

Close #235

Change-Id: I15ca6de47cb34793cf6a940123d593261627baa4
This commit is contained in:
Aaron Vaage 2017-06-14 12:25:17 -07:00
parent 1aeedd102e
commit 0caab0a4b4
8 changed files with 110 additions and 29 deletions

View File

@ -1,5 +1,5 @@
#EXTM3U
## Generated with https://github.com/google/shaka-packager version <tag>-<hash>-<test>
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio",NAME="stream_0",URI="audio.m3u8"
#EXT-X-STREAM-INF:AUDIO="audio",CODECS="avc1.64001e,mp4a.40.2",BANDWIDTH=1217518
#EXT-X-STREAM-INF:BANDWIDTH=1217518,CODECS="avc1.64001e,mp4a.40.2",RESOLUTION=640x360,AUDIO="audio"
video.m3u8

View File

@ -1,5 +1,5 @@
#EXTM3U
## Generated with https://github.com/google/shaka-packager version <tag>-<hash>-<test>
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio",NAME="stream_0",URI="audio.m3u8"
#EXT-X-STREAM-INF:AUDIO="audio",CODECS="avc1.64001e,mp4a.40.2",BANDWIDTH=1217518
#EXT-X-STREAM-INF:BANDWIDTH=1217518,CODECS="avc1.64001e,mp4a.40.2",RESOLUTION=640x360,AUDIO="audio"
video.m3u8

View File

@ -1,4 +1,4 @@
#EXTM3U
## Generated with https://github.com/google/shaka-packager version <tag>-<hash>-<test>
#EXT-X-STREAM-INF:CODECS="avc1.64001f",BANDWIDTH=1183948
#EXT-X-STREAM-INF:BANDWIDTH=1183948,CODECS="avc1.64001f",RESOLUTION=1024x436
video.m3u8

View File

@ -24,6 +24,30 @@
namespace shaka {
namespace hls {
namespace {
void AppendStreamInfoTag(uint64_t bitrate,
const std::string& codecs,
uint32_t width,
uint32_t height,
const std::string* audio_group_id,
const std::string& base_url,
const std::string& file_name,
std::string* out) {
DCHECK(out);
base::StringAppendF(out, "#EXT-X-STREAM-INF:");
base::StringAppendF(out, "BANDWIDTH=%" PRIu64, bitrate);
base::StringAppendF(out, ",CODECS=\"%s\"", codecs.c_str());
base::StringAppendF(out, ",RESOLUTION=%" PRIu32 "x%" PRIu32, width, height);
if (audio_group_id) {
base::StringAppendF(out, ",AUDIO=\"%s\"", audio_group_id->c_str());
}
base::StringAppendF(out, "\n%s%s\n", base_url.c_str(), file_name.c_str());
}
} // namespace
MasterPlaylist::MasterPlaylist(const std::string& file_name)
: file_name_(file_name) {}
MasterPlaylist::~MasterPlaylist() {}
@ -144,13 +168,19 @@ bool MasterPlaylist::WriteMasterPlaylist(const std::string& base_url,
// Assume all codecs are the same for same group ID.
const std::string& audio_codec = audio_playlists.front()->codec();
base::StringAppendF(
&video_output,
"#EXT-X-STREAM-INF:AUDIO=\"%s\",CODECS=\"%s\",BANDWIDTH=%" PRIu64 "\n"
"%s\n",
group_id.c_str(), (video_codec + "," + audio_codec).c_str(),
video_bitrate + max_audio_bitrate,
(base_url + video_playlist->file_name()).c_str());
uint32_t video_width;
uint32_t video_height;
CHECK(video_playlist->GetResolution(&video_width, &video_height));
AppendStreamInfoTag(video_bitrate + max_audio_bitrate,
video_codec + "," + audio_codec,
video_width,
video_height,
&group_id,
base_url,
video_playlist->file_name(),
&video_output);
}
}
@ -158,11 +188,19 @@ bool MasterPlaylist::WriteMasterPlaylist(const std::string& base_url,
for (const MediaPlaylist* video_playlist : video_playlists) {
const std::string& video_codec = video_playlist->codec();
const uint64_t video_bitrate = video_playlist->Bitrate();
base::StringAppendF(&video_output,
"#EXT-X-STREAM-INF:CODECS=\"%s\",BANDWIDTH=%" PRIu64
"\n%s\n",
video_codec.c_str(), video_bitrate,
(base_url + video_playlist->file_name()).c_str());
uint32_t video_width;
uint32_t video_height;
CHECK(video_playlist->GetResolution(&video_width, &video_height));
AppendStreamInfoTag(video_bitrate,
video_codec,
video_width,
video_height,
nullptr,
base_url,
video_playlist->file_name(),
&video_output);
}
}

View File

@ -19,13 +19,17 @@ namespace shaka {
namespace hls {
using ::testing::AtLeast;
using ::testing::NotNull;
using ::testing::Return;
using ::testing::ReturnRef;
using ::testing::SetArgPointee;
using ::testing::_;
using base::FilePath;
namespace {
const char kDefaultMasterPlaylistName[] = "playlist.m3u8";
const uint32_t kWidth = 800;
const uint32_t kHeight = 600;
const MediaPlaylist::MediaPlaylistType kVodPlaylist =
MediaPlaylist::MediaPlaylistType::kVod;
} // namespace
@ -75,6 +79,10 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistOneVideo) {
MediaPlaylist::MediaPlaylistStreamType::kPlayListVideo);
mock_playlist.SetCodecForTesting(codec);
EXPECT_CALL(mock_playlist, Bitrate()).WillOnce(Return(435889));
EXPECT_CALL(mock_playlist, GetResolution(NotNull(), NotNull()))
.WillOnce(DoAll(SetArgPointee<0>(kWidth),
SetArgPointee<1>(kHeight),
Return(true)));
master_playlist_.AddMediaPlaylist(&mock_playlist);
const char kBaseUrl[] = "http://myplaylistdomain.com/";
@ -93,7 +101,7 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistOneVideo) {
"#EXTM3U\n"
"## Generated with https://github.com/google/shaka-packager version "
"test\n"
"#EXT-X-STREAM-INF:CODECS=\"avc1\",BANDWIDTH=435889\n"
"#EXT-X-STREAM-INF:BANDWIDTH=435889,CODECS=\"avc1\",RESOLUTION=800x600\n"
"http://myplaylistdomain.com/media1.m3u8\n";
ASSERT_EQ(expected, actual);
@ -110,6 +118,10 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistVideoAndAudio) {
EXPECT_CALL(sd_video_playlist, Bitrate())
.Times(AtLeast(1))
.WillRepeatedly(Return(300000));
EXPECT_CALL(sd_video_playlist, GetResolution(NotNull(), NotNull()))
.WillRepeatedly(DoAll(SetArgPointee<0>(kWidth),
SetArgPointee<1>(kHeight),
Return(true)));
master_playlist_.AddMediaPlaylist(&sd_video_playlist);
// Second video, hd.m3u8.
@ -122,6 +134,10 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistVideoAndAudio) {
EXPECT_CALL(hd_video_playlist, Bitrate())
.Times(AtLeast(1))
.WillRepeatedly(Return(700000));
EXPECT_CALL(hd_video_playlist, GetResolution(NotNull(), NotNull()))
.WillRepeatedly(DoAll(SetArgPointee<0>(kWidth),
SetArgPointee<1>(kHeight),
Return(true)));
master_playlist_.AddMediaPlaylist(&hd_video_playlist);
// First audio, english.m3u8.
@ -137,6 +153,8 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistVideoAndAudio) {
EXPECT_CALL(english_playlist, Bitrate())
.Times(AtLeast(1))
.WillRepeatedly(Return(50000));
EXPECT_CALL(english_playlist, GetResolution(NotNull(), NotNull()))
.Times(0);
master_playlist_.AddMediaPlaylist(&english_playlist);
// Second audio, spanish.m3u8.
@ -149,6 +167,8 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistVideoAndAudio) {
EXPECT_CALL(spanish_playlist, Bitrate())
.Times(AtLeast(1))
.WillRepeatedly(Return(60000));
EXPECT_CALL(spanish_playlist, GetResolution(NotNull(), NotNull()))
.Times(0);
master_playlist_.AddMediaPlaylist(&spanish_playlist);
const char kBaseUrl[] = "http://playlists.org/";
@ -171,13 +191,11 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistVideoAndAudio) {
"LANGUAGE=\"en\",URI=\"http://playlists.org/eng.m3u8\"\n"
"#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"audiogroup\",NAME=\"espanol\","
"LANGUAGE=\"es\",URI=\"http://playlists.org/spa.m3u8\"\n"
"#EXT-X-STREAM-INF:AUDIO=\"audiogroup\","
"CODECS=\"sdvideocodec,audiocodec\","
"BANDWIDTH=360000\n"
"#EXT-X-STREAM-INF:BANDWIDTH=360000,CODECS=\"sdvideocodec,audiocodec\""
",RESOLUTION=800x600,AUDIO=\"audiogroup\"\n"
"http://playlists.org/sd.m3u8\n"
"#EXT-X-STREAM-INF:AUDIO=\"audiogroup\","
"CODECS=\"hdvideocodec,audiocodec\","
"BANDWIDTH=760000\n"
"#EXT-X-STREAM-INF:BANDWIDTH=760000,CODECS=\"hdvideocodec,audiocodec\""
",RESOLUTION=800x600,AUDIO=\"audiogroup\"\n"
"http://playlists.org/hd.m3u8\n";
ASSERT_EQ(expected, actual);
@ -194,6 +212,10 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistMultipleAudioGroups) {
EXPECT_CALL(video_playlist, Bitrate())
.Times(AtLeast(1))
.WillRepeatedly(Return(300000));
EXPECT_CALL(video_playlist, GetResolution(NotNull(), NotNull()))
.WillRepeatedly(DoAll(SetArgPointee<0>(kWidth),
SetArgPointee<1>(kHeight),
Return(true)));
master_playlist_.AddMediaPlaylist(&video_playlist);
// First audio, eng_lo.m3u8.
@ -207,6 +229,8 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistMultipleAudioGroups) {
EXPECT_CALL(eng_lo_playlist, Bitrate())
.Times(AtLeast(1))
.WillRepeatedly(Return(50000));
EXPECT_CALL(eng_lo_playlist, GetResolution(NotNull(), NotNull()))
.Times(0);
master_playlist_.AddMediaPlaylist(&eng_lo_playlist);
std::string audio_codec_hi = "audiocodec_hi";
@ -219,6 +243,8 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistMultipleAudioGroups) {
EXPECT_CALL(eng_hi_playlist, Bitrate())
.Times(AtLeast(1))
.WillRepeatedly(Return(100000));
EXPECT_CALL(eng_hi_playlist, GetResolution(NotNull(), NotNull()))
.Times(0);
master_playlist_.AddMediaPlaylist(&eng_hi_playlist);
const char kBaseUrl[] = "http://anydomain.com/";
@ -240,13 +266,11 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistMultipleAudioGroups) {
"LANGUAGE=\"en\",URI=\"http://anydomain.com/eng_hi.m3u8\"\n"
"#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"audio_lo\",NAME=\"english_lo\","
"LANGUAGE=\"en\",URI=\"http://anydomain.com/eng_lo.m3u8\"\n"
"#EXT-X-STREAM-INF:AUDIO=\"audio_hi\","
"CODECS=\"videocodec,audiocodec_hi\","
"BANDWIDTH=400000\n"
"#EXT-X-STREAM-INF:BANDWIDTH=400000,CODECS=\"videocodec,audiocodec_hi\""
",RESOLUTION=800x600,AUDIO=\"audio_hi\"\n"
"http://anydomain.com/video.m3u8\n"
"#EXT-X-STREAM-INF:AUDIO=\"audio_lo\","
"CODECS=\"videocodec,audiocodec_lo\","
"BANDWIDTH=350000\n"
"#EXT-X-STREAM-INF:BANDWIDTH=350000,CODECS=\"videocodec,audiocodec_lo\""
",RESOLUTION=800x600,AUDIO=\"audio_lo\"\n"
"http://anydomain.com/video.m3u8\n";
ASSERT_EQ(expected, actual);
@ -269,6 +293,10 @@ TEST_F(MasterPlaylistTest, WriteAllPlaylists) {
MediaPlaylist::MediaPlaylistStreamType::kPlayListVideo);
mock_playlist.SetCodecForTesting(codec);
ON_CALL(mock_playlist, Bitrate()).WillByDefault(Return(435889));
ON_CALL(mock_playlist, GetResolution(NotNull(), NotNull())).WillByDefault(
DoAll(SetArgPointee<0>(kWidth),
SetArgPointee<1>(kHeight),
Return(true)));
EXPECT_CALL(mock_playlist, GetLongestSegmentDuration()).WillOnce(Return(10));
EXPECT_CALL(mock_playlist, SetTargetDuration(10)).WillOnce(Return(true));
@ -288,6 +316,5 @@ TEST_F(MasterPlaylistTest, WriteAllPlaylists) {
ASSERT_TRUE(base::PathExists(master_playlist_path))
<< "Cannot find master playlist at " << master_playlist_path.value();
}
} // namespace hls
} // namespace shaka

View File

@ -381,5 +381,16 @@ std::string MediaPlaylist::GetLanguage() const {
return LanguageToShortestForm(lang);
}
bool MediaPlaylist::GetResolution(uint32_t* width, uint32_t* height) const {
DCHECK(width);
DCHECK(height);
if (media_info_.has_video_info()) {
*width = media_info_.video_info().width();
*height = media_info_.video_info().height();
return true;
}
return false;
}
} // namespace hls
} // namespace shaka

View File

@ -156,6 +156,10 @@ class MediaPlaylist {
/// form. May be an empty string for video.
virtual std::string GetLanguage() const;
/// @return true if |width| and |height| have been set with a valid
/// resolution values.
virtual bool GetResolution(uint32_t* width, uint32_t* height) const;
private:
// Mainly for MasterPlaylist to use these values.
const std::string file_name_;

View File

@ -42,6 +42,7 @@ class MockMediaPlaylist : public MediaPlaylist {
MOCK_CONST_METHOD0(GetLongestSegmentDuration, double());
MOCK_METHOD1(SetTargetDuration, bool(uint32_t target_duration));
MOCK_CONST_METHOD0(GetLanguage, std::string());
MOCK_CONST_METHOD2(GetResolution, bool(uint32_t* width, uint32_t* height));
};
} // namespace hls