[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
This commit is contained in:
KongQun Yang 2017-12-08 17:53:17 -08:00
parent d089a36ca9
commit b3194866a6
9 changed files with 72 additions and 34 deletions

View File

@ -1,5 +1,5 @@
#EXTM3U #EXTM3U
## Generated with https://github.com/google/shaka-packager version <tag>-<hash>-<test> ## 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-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" #EXT-X-STREAM-INF:BANDWIDTH=1217518,CODECS="avc1.64001e,mp4a.40.2",RESOLUTION=640x360,AUDIO="audio"
video.m3u8 video.m3u8

View File

@ -1,5 +1,5 @@
#EXTM3U #EXTM3U
## Generated with https://github.com/google/shaka-packager version <tag>-<hash>-<test> ## 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-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" #EXT-X-STREAM-INF:BANDWIDTH=1111147,CODECS="avc1.64001e,mp4a.40.2",RESOLUTION=640x360,AUDIO="audio"
video.m3u8 video.m3u8

View File

@ -1,5 +1,5 @@
#EXTM3U #EXTM3U
## Generated with https://github.com/google/shaka-packager version <tag>-<hash>-<test> ## 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/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" #EXT-X-STREAM-INF:BANDWIDTH=1105129,CODECS="avc1.64001e,mp4a.40.2",RESOLUTION=640x360,AUDIO="audio"
video/video.m3u8 video/video.m3u8

View File

@ -20,6 +20,26 @@ namespace hls {
namespace { 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, void AppendStreamInfoTag(uint64_t bitrate,
const std::string& codecs, const std::string& codecs,
uint32_t width, uint32_t width,
@ -81,21 +101,7 @@ bool MasterPlaylist::WriteMasterPlaylist(const std::string& base_url,
uint64_t max_audio_bitrate = 0; uint64_t max_audio_bitrate = 0;
for (const MediaPlaylist* audio_playlist : audio_playlists) { for (const MediaPlaylist* audio_playlist : audio_playlists) {
base::StringAppendF( AppendMediaTag(base_url, group_id, audio_playlist, &audio_output);
&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());
const uint64_t audio_bitrate = audio_playlist->Bitrate(); const uint64_t audio_bitrate = audio_playlist->Bitrate();
if (audio_bitrate > max_audio_bitrate) if (audio_bitrate > max_audio_bitrate)
max_audio_bitrate = audio_bitrate; max_audio_bitrate = audio_bitrate;

View File

@ -128,6 +128,7 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistVideoAndAudio) {
MockMediaPlaylist english_playlist(kVodPlaylist, "eng.m3u8", "english", MockMediaPlaylist english_playlist(kVodPlaylist, "eng.m3u8", "english",
"audiogroup"); "audiogroup");
EXPECT_CALL(english_playlist, GetLanguage()).WillRepeatedly(Return("en")); EXPECT_CALL(english_playlist, GetLanguage()).WillRepeatedly(Return("en"));
EXPECT_CALL(english_playlist, GetNumChannels()).WillRepeatedly(Return(2));
english_playlist.SetStreamTypeForTesting( english_playlist.SetStreamTypeForTesting(
MediaPlaylist::MediaPlaylistStreamType::kPlayListAudio); MediaPlaylist::MediaPlaylistStreamType::kPlayListAudio);
english_playlist.SetCodecForTesting(audio_codec); english_playlist.SetCodecForTesting(audio_codec);
@ -142,6 +143,7 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistVideoAndAudio) {
MockMediaPlaylist spanish_playlist(kVodPlaylist, "spa.m3u8", "espanol", MockMediaPlaylist spanish_playlist(kVodPlaylist, "spa.m3u8", "espanol",
"audiogroup"); "audiogroup");
EXPECT_CALL(spanish_playlist, GetLanguage()).WillRepeatedly(Return("es")); EXPECT_CALL(spanish_playlist, GetLanguage()).WillRepeatedly(Return("es"));
EXPECT_CALL(spanish_playlist, GetNumChannels()).WillRepeatedly(Return(5));
spanish_playlist.SetStreamTypeForTesting( spanish_playlist.SetStreamTypeForTesting(
MediaPlaylist::MediaPlaylistStreamType::kPlayListAudio); MediaPlaylist::MediaPlaylistStreamType::kPlayListAudio);
spanish_playlist.SetCodecForTesting(audio_codec); spanish_playlist.SetCodecForTesting(audio_codec);
@ -162,15 +164,17 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistVideoAndAudio) {
"#EXTM3U\n" "#EXTM3U\n"
"## Generated with https://github.com/google/shaka-packager version " "## Generated with https://github.com/google/shaka-packager version "
"test\n" "test\n"
"#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"audiogroup\",NAME=\"english\"," "#EXT-X-MEDIA:TYPE=AUDIO,URI=\"http://playlists.org/eng.m3u8\","
"LANGUAGE=\"en\",URI=\"http://playlists.org/eng.m3u8\"\n" "GROUP-ID=\"audiogroup\",LANGUAGE=\"en\",NAME=\"english\","
"#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"audiogroup\",NAME=\"espanol\"," "CHANNELS=\"2\"\n"
"LANGUAGE=\"es\",URI=\"http://playlists.org/spa.m3u8\"\n" "#EXT-X-MEDIA:TYPE=AUDIO,URI=\"http://playlists.org/spa.m3u8\","
"#EXT-X-STREAM-INF:BANDWIDTH=360000,CODECS=\"sdvideocodec,audiocodec\"" "GROUP-ID=\"audiogroup\",LANGUAGE=\"es\",NAME=\"espanol\","
",RESOLUTION=800x600,AUDIO=\"audiogroup\"\n" "CHANNELS=\"5\"\n"
"#EXT-X-STREAM-INF:BANDWIDTH=360000,CODECS=\"sdvideocodec,audiocodec\","
"RESOLUTION=800x600,AUDIO=\"audiogroup\"\n"
"http://playlists.org/sd.m3u8\n" "http://playlists.org/sd.m3u8\n"
"#EXT-X-STREAM-INF:BANDWIDTH=760000,CODECS=\"hdvideocodec,audiocodec\"" "#EXT-X-STREAM-INF:BANDWIDTH=760000,CODECS=\"hdvideocodec,audiocodec\","
",RESOLUTION=800x600,AUDIO=\"audiogroup\"\n" "RESOLUTION=800x600,AUDIO=\"audiogroup\"\n"
"http://playlists.org/hd.m3u8\n"; "http://playlists.org/hd.m3u8\n";
ASSERT_EQ(expected, actual); ASSERT_EQ(expected, actual);
@ -198,6 +202,7 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistMultipleAudioGroups) {
MockMediaPlaylist eng_lo_playlist(kVodPlaylist, "eng_lo.m3u8", "english_lo", MockMediaPlaylist eng_lo_playlist(kVodPlaylist, "eng_lo.m3u8", "english_lo",
"audio_lo"); "audio_lo");
EXPECT_CALL(eng_lo_playlist, GetLanguage()).WillRepeatedly(Return("en")); EXPECT_CALL(eng_lo_playlist, GetLanguage()).WillRepeatedly(Return("en"));
EXPECT_CALL(eng_lo_playlist, GetNumChannels()).WillRepeatedly(Return(1));
eng_lo_playlist.SetStreamTypeForTesting( eng_lo_playlist.SetStreamTypeForTesting(
MediaPlaylist::MediaPlaylistStreamType::kPlayListAudio); MediaPlaylist::MediaPlaylistStreamType::kPlayListAudio);
eng_lo_playlist.SetCodecForTesting(audio_codec_lo); 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", MockMediaPlaylist eng_hi_playlist(kVodPlaylist, "eng_hi.m3u8", "english_hi",
"audio_hi"); "audio_hi");
EXPECT_CALL(eng_hi_playlist, GetLanguage()).WillRepeatedly(Return("en")); EXPECT_CALL(eng_hi_playlist, GetLanguage()).WillRepeatedly(Return("en"));
EXPECT_CALL(eng_hi_playlist, GetNumChannels()).WillRepeatedly(Return(8));
eng_hi_playlist.SetStreamTypeForTesting( eng_hi_playlist.SetStreamTypeForTesting(
MediaPlaylist::MediaPlaylistStreamType::kPlayListAudio); MediaPlaylist::MediaPlaylistStreamType::kPlayListAudio);
eng_hi_playlist.SetCodecForTesting(audio_codec_hi); eng_hi_playlist.SetCodecForTesting(audio_codec_hi);
@ -232,15 +238,17 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistMultipleAudioGroups) {
"#EXTM3U\n" "#EXTM3U\n"
"## Generated with https://github.com/google/shaka-packager version " "## Generated with https://github.com/google/shaka-packager version "
"test\n" "test\n"
"#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"audio_hi\",NAME=\"english_hi\"," "#EXT-X-MEDIA:TYPE=AUDIO,URI=\"http://anydomain.com/eng_hi.m3u8\","
"LANGUAGE=\"en\",URI=\"http://anydomain.com/eng_hi.m3u8\"\n" "GROUP-ID=\"audio_hi\",LANGUAGE=\"en\",NAME=\"english_hi\","
"#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"audio_lo\",NAME=\"english_lo\"," "CHANNELS=\"8\"\n"
"LANGUAGE=\"en\",URI=\"http://anydomain.com/eng_lo.m3u8\"\n" "#EXT-X-MEDIA:TYPE=AUDIO,URI=\"http://anydomain.com/eng_lo.m3u8\","
"#EXT-X-STREAM-INF:BANDWIDTH=400000,CODECS=\"videocodec,audiocodec_hi\"" "GROUP-ID=\"audio_lo\",LANGUAGE=\"en\",NAME=\"english_lo\","
",RESOLUTION=800x600,AUDIO=\"audio_hi\"\n" "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" "http://anydomain.com/video.m3u8\n"
"#EXT-X-STREAM-INF:BANDWIDTH=350000,CODECS=\"videocodec,audiocodec_lo\"" "#EXT-X-STREAM-INF:BANDWIDTH=350000,CODECS=\"videocodec,audiocodec_lo\","
",RESOLUTION=800x600,AUDIO=\"audio_lo\"\n" "RESOLUTION=800x600,AUDIO=\"audio_lo\"\n"
"http://anydomain.com/video.m3u8\n"; "http://anydomain.com/video.m3u8\n";
ASSERT_EQ(expected, actual); ASSERT_EQ(expected, actual);

View File

@ -434,6 +434,10 @@ std::string MediaPlaylist::GetLanguage() const {
return LanguageToShortestForm(lang); return LanguageToShortestForm(lang);
} }
int MediaPlaylist::GetNumChannels() const {
return media_info_.audio_info().num_channels();
}
bool MediaPlaylist::GetDisplayResolution(uint32_t* width, bool MediaPlaylist::GetDisplayResolution(uint32_t* width,
uint32_t* height) const { uint32_t* height) const {
DCHECK(width); DCHECK(width);

View File

@ -155,6 +155,9 @@ class MediaPlaylist {
/// form. May be an empty string for video. /// form. May be an empty string for video.
virtual std::string GetLanguage() const; 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 /// @return true if |width| and |height| have been set with a valid
/// resolution values. /// resolution values.
virtual bool GetDisplayResolution(uint32_t* width, uint32_t* height) const; virtual bool GetDisplayResolution(uint32_t* width, uint32_t* height) const;

View File

@ -432,6 +432,22 @@ TEST_F(MediaPlaylistMultiSegmentTest, GetLanguage) {
EXPECT_EQ("apa", media_playlist_.GetLanguage()); // no short form exists 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) { TEST_F(MediaPlaylistMultiSegmentTest, InitSegment) {
valid_video_media_info_.set_reference_time_scale(90000); valid_video_media_info_.set_reference_time_scale(90000);
valid_video_media_info_.set_init_segment_name("init_segment.mp4"); valid_video_media_info_.set_init_segment_name("init_segment.mp4");

View File

@ -43,6 +43,7 @@ class MockMediaPlaylist : public MediaPlaylist {
MOCK_CONST_METHOD0(GetLongestSegmentDuration, double()); MOCK_CONST_METHOD0(GetLongestSegmentDuration, double());
MOCK_METHOD1(SetTargetDuration, void(uint32_t target_duration)); MOCK_METHOD1(SetTargetDuration, void(uint32_t target_duration));
MOCK_CONST_METHOD0(GetLanguage, std::string()); MOCK_CONST_METHOD0(GetLanguage, std::string());
MOCK_CONST_METHOD0(GetNumChannels, int());
MOCK_CONST_METHOD2(GetDisplayResolution, MOCK_CONST_METHOD2(GetDisplayResolution,
bool(uint32_t* width, uint32_t* height)); bool(uint32_t* width, uint32_t* height));
}; };