[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:
parent
d089a36ca9
commit
b3194866a6
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -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));
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue