From 0caab0a4b48933ccd685e82d90a6a4cc506b2f24 Mon Sep 17 00:00:00 2001 From: Aaron Vaage Date: Wed, 14 Jun 2017 12:25:17 -0700 Subject: [PATCH] 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 --- .../bear-640x360-av-enc-master-golden.m3u8 | 2 +- .../bear-640x360-av-master-golden.m3u8 | 2 +- .../sintel-1024x436-v-enc-master-golden.m3u8 | 2 +- packager/hls/base/master_playlist.cc | 62 +++++++++++++++---- packager/hls/base/master_playlist_unittest.cc | 55 +++++++++++----- packager/hls/base/media_playlist.cc | 11 ++++ packager/hls/base/media_playlist.h | 4 ++ packager/hls/base/mock_media_playlist.h | 1 + 8 files changed, 110 insertions(+), 29 deletions(-) diff --git a/packager/app/test/testdata/bear-640x360-av-enc-master-golden.m3u8 b/packager/app/test/testdata/bear-640x360-av-enc-master-golden.m3u8 index f3be2834f1..19f6cf54c1 100644 --- a/packager/app/test/testdata/bear-640x360-av-enc-master-golden.m3u8 +++ b/packager/app/test/testdata/bear-640x360-av-enc-master-golden.m3u8 @@ -1,5 +1,5 @@ #EXTM3U ## Generated with https://github.com/google/shaka-packager version -- #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 diff --git a/packager/app/test/testdata/bear-640x360-av-master-golden.m3u8 b/packager/app/test/testdata/bear-640x360-av-master-golden.m3u8 index f3be2834f1..19f6cf54c1 100644 --- a/packager/app/test/testdata/bear-640x360-av-master-golden.m3u8 +++ b/packager/app/test/testdata/bear-640x360-av-master-golden.m3u8 @@ -1,5 +1,5 @@ #EXTM3U ## Generated with https://github.com/google/shaka-packager version -- #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 diff --git a/packager/app/test/testdata/sintel-1024x436-v-enc-master-golden.m3u8 b/packager/app/test/testdata/sintel-1024x436-v-enc-master-golden.m3u8 index 94e6cd0d57..57a59039f3 100644 --- a/packager/app/test/testdata/sintel-1024x436-v-enc-master-golden.m3u8 +++ b/packager/app/test/testdata/sintel-1024x436-v-enc-master-golden.m3u8 @@ -1,4 +1,4 @@ #EXTM3U ## Generated with https://github.com/google/shaka-packager version -- -#EXT-X-STREAM-INF:CODECS="avc1.64001f",BANDWIDTH=1183948 +#EXT-X-STREAM-INF:BANDWIDTH=1183948,CODECS="avc1.64001f",RESOLUTION=1024x436 video.m3u8 diff --git a/packager/hls/base/master_playlist.cc b/packager/hls/base/master_playlist.cc index 007473c654..91cf462a47 100644 --- a/packager/hls/base/master_playlist.cc +++ b/packager/hls/base/master_playlist.cc @@ -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); } } diff --git a/packager/hls/base/master_playlist_unittest.cc b/packager/hls/base/master_playlist_unittest.cc index b130ca16c2..48a912f380 100644 --- a/packager/hls/base/master_playlist_unittest.cc +++ b/packager/hls/base/master_playlist_unittest.cc @@ -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 diff --git a/packager/hls/base/media_playlist.cc b/packager/hls/base/media_playlist.cc index 9f2453c4fa..723d91b3d1 100644 --- a/packager/hls/base/media_playlist.cc +++ b/packager/hls/base/media_playlist.cc @@ -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 diff --git a/packager/hls/base/media_playlist.h b/packager/hls/base/media_playlist.h index f1916b768b..069866f956 100644 --- a/packager/hls/base/media_playlist.h +++ b/packager/hls/base/media_playlist.h @@ -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_; diff --git a/packager/hls/base/mock_media_playlist.h b/packager/hls/base/mock_media_playlist.h index 105126d9b4..268fd8dfc2 100644 --- a/packager/hls/base/mock_media_playlist.h +++ b/packager/hls/base/mock_media_playlist.h @@ -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