From b3194866a6a7ba80aace681e6cd8d874f2e3d518 Mon Sep 17 00:00:00 2001 From: KongQun Yang Date: Fri, 8 Dec 2017 17:53:17 -0800 Subject: [PATCH] [HLS] Add CHANNELS attribute to #EXT-X-MEDIA As specified in HLS spec (https://tools.ietf.org/html/rfc8216): All audio EXT-X-MEDIA tags SHOULD have a CHANNELS attribute. If a Master Playlist contains two Renditions encoded with the same codec but a different number of channels, then the CHANNELS attribute is REQUIRED; otherwise, it is OPTIONAL. Fixes #299 Change-Id: Ic2308c39b170178b11cb0d94c3a8083c8b5f3353 --- .../bear-640x360-av-master-golden.m3u8 | 2 +- ...ear-640x360-av-mp4-master-cenc-golden.m3u8 | 2 +- .../bear-640x360-av-mp4-master-golden.m3u8 | 2 +- packager/hls/base/master_playlist.cc | 36 ++++++++++------- packager/hls/base/master_playlist_unittest.cc | 40 +++++++++++-------- packager/hls/base/media_playlist.cc | 4 ++ packager/hls/base/media_playlist.h | 3 ++ packager/hls/base/media_playlist_unittest.cc | 16 ++++++++ packager/hls/base/mock_media_playlist.h | 1 + 9 files changed, 72 insertions(+), 34 deletions(-) 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 19f6cf54c1..53b169982e 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-MEDIA:TYPE=AUDIO,URI="audio.m3u8",GROUP-ID="audio",NAME="stream_0",CHANNELS="2" #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-mp4-master-cenc-golden.m3u8 b/packager/app/test/testdata/bear-640x360-av-mp4-master-cenc-golden.m3u8 index b895318cfe..87952b537c 100644 --- a/packager/app/test/testdata/bear-640x360-av-mp4-master-cenc-golden.m3u8 +++ b/packager/app/test/testdata/bear-640x360-av-mp4-master-cenc-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-MEDIA:TYPE=AUDIO,URI="audio.m3u8",GROUP-ID="audio",NAME="stream_0",CHANNELS="2" #EXT-X-STREAM-INF:BANDWIDTH=1111147,CODECS="avc1.64001e,mp4a.40.2",RESOLUTION=640x360,AUDIO="audio" video.m3u8 diff --git a/packager/app/test/testdata/bear-640x360-av-mp4-master-golden.m3u8 b/packager/app/test/testdata/bear-640x360-av-mp4-master-golden.m3u8 index 543dc9a79e..7e1a2c6e00 100644 --- a/packager/app/test/testdata/bear-640x360-av-mp4-master-golden.m3u8 +++ b/packager/app/test/testdata/bear-640x360-av-mp4-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/audio.m3u8" +#EXT-X-MEDIA:TYPE=AUDIO,URI="audio/audio.m3u8",GROUP-ID="audio",NAME="stream_0",CHANNELS="2" #EXT-X-STREAM-INF:BANDWIDTH=1105129,CODECS="avc1.64001e,mp4a.40.2",RESOLUTION=640x360,AUDIO="audio" video/video.m3u8 diff --git a/packager/hls/base/master_playlist.cc b/packager/hls/base/master_playlist.cc index b0bf8cff1b..8751b51917 100644 --- a/packager/hls/base/master_playlist.cc +++ b/packager/hls/base/master_playlist.cc @@ -20,6 +20,26 @@ namespace hls { namespace { +void AppendMediaTag(const std::string& base_url, + const std::string& group_id, + const MediaPlaylist* audio_playlist, + std::string* out) { + DCHECK(audio_playlist); + DCHECK(out); + + out->append("#EXT-X-MEDIA:TYPE=AUDIO"); + base::StringAppendF(out, ",URI=\"%s\"", + (base_url + audio_playlist->file_name()).c_str()); + base::StringAppendF(out, ",GROUP-ID=\"%s\"", group_id.c_str()); + std::string language = audio_playlist->GetLanguage(); + if (!language.empty()) + base::StringAppendF(out, ",LANGUAGE=\"%s\"", language.c_str()); + base::StringAppendF(out, ",NAME=\"%s\"", audio_playlist->name().c_str()); + base::StringAppendF(out, ",CHANNELS=\"%d\"", + audio_playlist->GetNumChannels()); + out->append("\n"); +} + void AppendStreamInfoTag(uint64_t bitrate, const std::string& codecs, uint32_t width, @@ -81,21 +101,7 @@ bool MasterPlaylist::WriteMasterPlaylist(const std::string& base_url, uint64_t max_audio_bitrate = 0; for (const MediaPlaylist* audio_playlist : audio_playlists) { - base::StringAppendF( - &audio_output, - "#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"%s\",NAME=\"%s\",", - group_id.c_str(), audio_playlist->name().c_str()); - std::string language = audio_playlist->GetLanguage(); - if (!language.empty()) { - base::StringAppendF( - &audio_output, - "LANGUAGE=\"%s\",", - language.c_str()); - } - base::StringAppendF( - &audio_output, - "URI=\"%s\"\n", - (base_url + audio_playlist->file_name()).c_str()); + AppendMediaTag(base_url, group_id, audio_playlist, &audio_output); const uint64_t audio_bitrate = audio_playlist->Bitrate(); if (audio_bitrate > max_audio_bitrate) max_audio_bitrate = audio_bitrate; diff --git a/packager/hls/base/master_playlist_unittest.cc b/packager/hls/base/master_playlist_unittest.cc index 6f1d02cb92..2b9ba5ffa6 100644 --- a/packager/hls/base/master_playlist_unittest.cc +++ b/packager/hls/base/master_playlist_unittest.cc @@ -128,6 +128,7 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistVideoAndAudio) { MockMediaPlaylist english_playlist(kVodPlaylist, "eng.m3u8", "english", "audiogroup"); EXPECT_CALL(english_playlist, GetLanguage()).WillRepeatedly(Return("en")); + EXPECT_CALL(english_playlist, GetNumChannels()).WillRepeatedly(Return(2)); english_playlist.SetStreamTypeForTesting( MediaPlaylist::MediaPlaylistStreamType::kPlayListAudio); english_playlist.SetCodecForTesting(audio_codec); @@ -142,6 +143,7 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistVideoAndAudio) { MockMediaPlaylist spanish_playlist(kVodPlaylist, "spa.m3u8", "espanol", "audiogroup"); EXPECT_CALL(spanish_playlist, GetLanguage()).WillRepeatedly(Return("es")); + EXPECT_CALL(spanish_playlist, GetNumChannels()).WillRepeatedly(Return(5)); spanish_playlist.SetStreamTypeForTesting( MediaPlaylist::MediaPlaylistStreamType::kPlayListAudio); spanish_playlist.SetCodecForTesting(audio_codec); @@ -162,15 +164,17 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistVideoAndAudio) { "#EXTM3U\n" "## Generated with https://github.com/google/shaka-packager version " "test\n" - "#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"audiogroup\",NAME=\"english\"," - "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:BANDWIDTH=360000,CODECS=\"sdvideocodec,audiocodec\"" - ",RESOLUTION=800x600,AUDIO=\"audiogroup\"\n" + "#EXT-X-MEDIA:TYPE=AUDIO,URI=\"http://playlists.org/eng.m3u8\"," + "GROUP-ID=\"audiogroup\",LANGUAGE=\"en\",NAME=\"english\"," + "CHANNELS=\"2\"\n" + "#EXT-X-MEDIA:TYPE=AUDIO,URI=\"http://playlists.org/spa.m3u8\"," + "GROUP-ID=\"audiogroup\",LANGUAGE=\"es\",NAME=\"espanol\"," + "CHANNELS=\"5\"\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:BANDWIDTH=760000,CODECS=\"hdvideocodec,audiocodec\"" - ",RESOLUTION=800x600,AUDIO=\"audiogroup\"\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); @@ -198,6 +202,7 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistMultipleAudioGroups) { MockMediaPlaylist eng_lo_playlist(kVodPlaylist, "eng_lo.m3u8", "english_lo", "audio_lo"); EXPECT_CALL(eng_lo_playlist, GetLanguage()).WillRepeatedly(Return("en")); + EXPECT_CALL(eng_lo_playlist, GetNumChannels()).WillRepeatedly(Return(1)); eng_lo_playlist.SetStreamTypeForTesting( MediaPlaylist::MediaPlaylistStreamType::kPlayListAudio); eng_lo_playlist.SetCodecForTesting(audio_codec_lo); @@ -212,6 +217,7 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistMultipleAudioGroups) { MockMediaPlaylist eng_hi_playlist(kVodPlaylist, "eng_hi.m3u8", "english_hi", "audio_hi"); EXPECT_CALL(eng_hi_playlist, GetLanguage()).WillRepeatedly(Return("en")); + EXPECT_CALL(eng_hi_playlist, GetNumChannels()).WillRepeatedly(Return(8)); eng_hi_playlist.SetStreamTypeForTesting( MediaPlaylist::MediaPlaylistStreamType::kPlayListAudio); eng_hi_playlist.SetCodecForTesting(audio_codec_hi); @@ -232,15 +238,17 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistMultipleAudioGroups) { "#EXTM3U\n" "## Generated with https://github.com/google/shaka-packager version " "test\n" - "#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"audio_hi\",NAME=\"english_hi\"," - "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:BANDWIDTH=400000,CODECS=\"videocodec,audiocodec_hi\"" - ",RESOLUTION=800x600,AUDIO=\"audio_hi\"\n" + "#EXT-X-MEDIA:TYPE=AUDIO,URI=\"http://anydomain.com/eng_hi.m3u8\"," + "GROUP-ID=\"audio_hi\",LANGUAGE=\"en\",NAME=\"english_hi\"," + "CHANNELS=\"8\"\n" + "#EXT-X-MEDIA:TYPE=AUDIO,URI=\"http://anydomain.com/eng_lo.m3u8\"," + "GROUP-ID=\"audio_lo\",LANGUAGE=\"en\",NAME=\"english_lo\"," + "CHANNELS=\"1\"\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:BANDWIDTH=350000,CODECS=\"videocodec,audiocodec_lo\"" - ",RESOLUTION=800x600,AUDIO=\"audio_lo\"\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); diff --git a/packager/hls/base/media_playlist.cc b/packager/hls/base/media_playlist.cc index d246fcd849..13f0c2c361 100644 --- a/packager/hls/base/media_playlist.cc +++ b/packager/hls/base/media_playlist.cc @@ -434,6 +434,10 @@ std::string MediaPlaylist::GetLanguage() const { return LanguageToShortestForm(lang); } +int MediaPlaylist::GetNumChannels() const { + return media_info_.audio_info().num_channels(); +} + bool MediaPlaylist::GetDisplayResolution(uint32_t* width, uint32_t* height) const { DCHECK(width); diff --git a/packager/hls/base/media_playlist.h b/packager/hls/base/media_playlist.h index ed4168b806..ac882206c6 100644 --- a/packager/hls/base/media_playlist.h +++ b/packager/hls/base/media_playlist.h @@ -155,6 +155,9 @@ class MediaPlaylist { /// form. May be an empty string for video. virtual std::string GetLanguage() const; + /// @return number of channels for audio. 0 is returned for video. + virtual int GetNumChannels() const; + /// @return true if |width| and |height| have been set with a valid /// resolution values. virtual bool GetDisplayResolution(uint32_t* width, uint32_t* height) const; diff --git a/packager/hls/base/media_playlist_unittest.cc b/packager/hls/base/media_playlist_unittest.cc index 43fb5dd97d..67216920b6 100644 --- a/packager/hls/base/media_playlist_unittest.cc +++ b/packager/hls/base/media_playlist_unittest.cc @@ -432,6 +432,22 @@ TEST_F(MediaPlaylistMultiSegmentTest, GetLanguage) { EXPECT_EQ("apa", media_playlist_.GetLanguage()); // no short form exists } +TEST_F(MediaPlaylistMultiSegmentTest, GetNumChannels) { + MediaInfo media_info; + media_info.set_reference_time_scale(kTimeScale); + + // Returns 0 by default if not audio. + EXPECT_EQ(0, media_playlist_.GetNumChannels()); + + media_info.mutable_audio_info()->set_num_channels(2); + ASSERT_TRUE(media_playlist_.SetMediaInfo(media_info)); + EXPECT_EQ(2, media_playlist_.GetNumChannels()); + + media_info.mutable_audio_info()->set_num_channels(8); + ASSERT_TRUE(media_playlist_.SetMediaInfo(media_info)); + EXPECT_EQ(8, media_playlist_.GetNumChannels()); +} + TEST_F(MediaPlaylistMultiSegmentTest, InitSegment) { valid_video_media_info_.set_reference_time_scale(90000); valid_video_media_info_.set_init_segment_name("init_segment.mp4"); diff --git a/packager/hls/base/mock_media_playlist.h b/packager/hls/base/mock_media_playlist.h index a8f141c57e..c0401bcc54 100644 --- a/packager/hls/base/mock_media_playlist.h +++ b/packager/hls/base/mock_media_playlist.h @@ -43,6 +43,7 @@ class MockMediaPlaylist : public MediaPlaylist { MOCK_CONST_METHOD0(GetLongestSegmentDuration, double()); MOCK_METHOD1(SetTargetDuration, void(uint32_t target_duration)); MOCK_CONST_METHOD0(GetLanguage, std::string()); + MOCK_CONST_METHOD0(GetNumChannels, int()); MOCK_CONST_METHOD2(GetDisplayResolution, bool(uint32_t* width, uint32_t* height)); };