Add HLS audio language support

Before this, HLS output did not contain language information.
Now, media playlists are properly tagged with a language in the
master playlist.

b/36134267

Change-Id: I172e946dbedd096a44cb2f917b007cc004756228
This commit is contained in:
Joey Parrish 2017-03-10 15:18:42 -08:00
parent c6cbd73465
commit 924d6d4693
12 changed files with 77 additions and 16 deletions

View File

@ -11,7 +11,7 @@
#include "packager/base/strings/string_number_conversions.h" #include "packager/base/strings/string_number_conversions.h"
#include "packager/base/strings/string_split.h" #include "packager/base/strings/string_split.h"
#include "packager/media/base/container_names.h" #include "packager/media/base/container_names.h"
#include "packager/mpd/base/language_utils.h" #include "packager/media/base/language_utils.h"
namespace shaka { namespace shaka {
namespace media { namespace media {

View File

@ -121,8 +121,18 @@ bool MasterPlaylist::WriteMasterPlaylist(const std::string& base_url,
for (const MediaPlaylist* audio_playlist : audio_playlists) { for (const MediaPlaylist* audio_playlist : audio_playlists) {
base::StringAppendF( base::StringAppendF(
&audio_output, &audio_output,
"#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"%s\",NAME=\"%s\",URI=\"%s\"\n", "#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"%s\",NAME=\"%s\",",
group_id.c_str(), audio_playlist->name().c_str(), 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()); (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)

View File

@ -130,6 +130,7 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistVideoAndAudio) {
std::string audio_codec = "audiocodec"; std::string audio_codec = "audiocodec";
MockMediaPlaylist english_playlist(kVodPlaylist, "eng.m3u8", "english", MockMediaPlaylist english_playlist(kVodPlaylist, "eng.m3u8", "english",
"audiogroup"); "audiogroup");
EXPECT_CALL(english_playlist, GetLanguage()).WillRepeatedly(Return("en"));
english_playlist.SetStreamTypeForTesting( english_playlist.SetStreamTypeForTesting(
MediaPlaylist::MediaPlaylistStreamType::kPlayListAudio); MediaPlaylist::MediaPlaylistStreamType::kPlayListAudio);
english_playlist.SetCodecForTesting(audio_codec); english_playlist.SetCodecForTesting(audio_codec);
@ -141,6 +142,7 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistVideoAndAudio) {
// Second audio, spanish.m3u8. // Second audio, spanish.m3u8.
MockMediaPlaylist spanish_playlist(kVodPlaylist, "spa.m3u8", "espanol", MockMediaPlaylist spanish_playlist(kVodPlaylist, "spa.m3u8", "espanol",
"audiogroup"); "audiogroup");
EXPECT_CALL(spanish_playlist, GetLanguage()).WillRepeatedly(Return("es"));
spanish_playlist.SetStreamTypeForTesting( spanish_playlist.SetStreamTypeForTesting(
MediaPlaylist::MediaPlaylistStreamType::kPlayListAudio); MediaPlaylist::MediaPlaylistStreamType::kPlayListAudio);
spanish_playlist.SetCodecForTesting(audio_codec); spanish_playlist.SetCodecForTesting(audio_codec);
@ -166,9 +168,9 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistVideoAndAudio) {
"## 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,GROUP-ID=\"audiogroup\",NAME=\"english\","
"URI=\"http://playlists.org/eng.m3u8\"\n" "LANGUAGE=\"en\",URI=\"http://playlists.org/eng.m3u8\"\n"
"#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"audiogroup\",NAME=\"espanol\"," "#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"audiogroup\",NAME=\"espanol\","
"URI=\"http://playlists.org/spa.m3u8\"\n" "LANGUAGE=\"es\",URI=\"http://playlists.org/spa.m3u8\"\n"
"#EXT-X-STREAM-INF:AUDIO=\"audiogroup\"," "#EXT-X-STREAM-INF:AUDIO=\"audiogroup\","
"CODECS=\"sdvideocodec,audiocodec\"," "CODECS=\"sdvideocodec,audiocodec\","
"BANDWIDTH=360000\n" "BANDWIDTH=360000\n"
@ -198,6 +200,7 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistMultipleAudioGroups) {
std::string audio_codec_lo = "audiocodec_lo"; std::string audio_codec_lo = "audiocodec_lo";
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"));
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);
@ -209,6 +212,7 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistMultipleAudioGroups) {
std::string audio_codec_hi = "audiocodec_hi"; std::string audio_codec_hi = "audiocodec_hi";
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"));
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);
@ -233,9 +237,9 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistMultipleAudioGroups) {
"## 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,GROUP-ID=\"audio_hi\",NAME=\"english_hi\","
"URI=\"http://anydomain.com/eng_hi.m3u8\"\n" "LANGUAGE=\"en\",URI=\"http://anydomain.com/eng_hi.m3u8\"\n"
"#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"audio_lo\",NAME=\"english_lo\"," "#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"audio_lo\",NAME=\"english_lo\","
"URI=\"http://anydomain.com/eng_lo.m3u8\"\n" "LANGUAGE=\"en\",URI=\"http://anydomain.com/eng_lo.m3u8\"\n"
"#EXT-X-STREAM-INF:AUDIO=\"audio_hi\"," "#EXT-X-STREAM-INF:AUDIO=\"audio_hi\","
"CODECS=\"videocodec,audiocodec_hi\"," "CODECS=\"videocodec,audiocodec_hi\","
"BANDWIDTH=400000\n" "BANDWIDTH=400000\n"

View File

@ -12,6 +12,7 @@
#include "packager/base/logging.h" #include "packager/base/logging.h"
#include "packager/base/strings/stringprintf.h" #include "packager/base/strings/stringprintf.h"
#include "packager/media/base/language_utils.h"
#include "packager/media/file/file.h" #include "packager/media/file/file.h"
#include "packager/version/version.h" #include "packager/version/version.h"
@ -344,5 +345,20 @@ bool MediaPlaylist::SetTargetDuration(uint32_t target_duration) {
return true; return true;
} }
// Duplicated from MpdUtils because:
// 1. MpdUtils header depends on libxml header, which is not in the deps here
// 2. GetLanguage depends on MediaInfo from packager/mpd/
// 3. Moving GetLanguage to LanguageUtils would create a a media => mpd dep.
// TODO: fix this dependency situation and factor this out to a common location
std::string MediaPlaylist::GetLanguage() const {
std::string lang;
if (media_info_.has_audio_info()) {
lang = media_info_.audio_info().language();
} else if (media_info_.has_text_info()) {
lang = media_info_.text_info().language();
}
return LanguageToShortestForm(lang);
}
} // namespace hls } // namespace hls
} // namespace shaka } // namespace shaka

View File

@ -149,6 +149,10 @@ class MediaPlaylist {
/// @return true if set, false otherwise. /// @return true if set, false otherwise.
virtual bool SetTargetDuration(uint32_t target_duration); virtual bool SetTargetDuration(uint32_t target_duration);
/// @return the language of the media, as an ISO language tag in its shortest
/// form. May be an empty string for video.
virtual std::string GetLanguage() const;
private: private:
// Mainly for MasterPlaylist to use these values. // Mainly for MasterPlaylist to use these values.
const std::string file_name_; const std::string file_name_;

View File

@ -352,5 +352,23 @@ TEST_F(MediaPlaylistTest, RemoveOldestSegment) {
EXPECT_TRUE(media_playlist_.WriteToFile(&file)); EXPECT_TRUE(media_playlist_.WriteToFile(&file));
} }
TEST_F(MediaPlaylistTest, GetLanguage) {
MediaInfo media_info;
media_info.set_reference_time_scale(90000);
// Check conversions from long to short form.
media_info.mutable_audio_info()->set_language("eng");
ASSERT_TRUE(media_playlist_.SetMediaInfo(media_info));
EXPECT_EQ("en", media_playlist_.GetLanguage()); // short form
media_info.mutable_audio_info()->set_language("eng-US");
ASSERT_TRUE(media_playlist_.SetMediaInfo(media_info));
EXPECT_EQ("en-US", media_playlist_.GetLanguage()); // region preserved
media_info.mutable_audio_info()->set_language("apa");
ASSERT_TRUE(media_playlist_.SetMediaInfo(media_info));
EXPECT_EQ("apa", media_playlist_.GetLanguage()); // no short form exists
}
} // namespace hls } // namespace hls
} // namespace shaka } // namespace shaka

View File

@ -40,6 +40,7 @@ class MockMediaPlaylist : public MediaPlaylist {
MOCK_CONST_METHOD0(Bitrate, uint64_t()); MOCK_CONST_METHOD0(Bitrate, uint64_t());
MOCK_CONST_METHOD0(GetLongestSegmentDuration, double()); MOCK_CONST_METHOD0(GetLongestSegmentDuration, double());
MOCK_METHOD1(SetTargetDuration, bool(uint32_t target_duration)); MOCK_METHOD1(SetTargetDuration, bool(uint32_t target_duration));
MOCK_CONST_METHOD0(GetLanguage, std::string());
}; };
} // namespace hls } // namespace hls

View File

@ -4,7 +4,7 @@
// license that can be found in the LICENSE file or at // license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd // https://developers.google.com/open-source/licenses/bsd
#include "packager/mpd/base/language_utils.h" #include "packager/media/base/language_utils.h"
#include "packager/base/logging.h" #include "packager/base/logging.h"
@ -87,6 +87,11 @@ void SplitLanguageTag(const std::string& tag,
namespace shaka { namespace shaka {
std::string LanguageToShortestForm(const std::string& language) { std::string LanguageToShortestForm(const std::string& language) {
// Do not try to mangle blank strings.
if (language.size() == 0) {
return language;
}
std::string main_language; std::string main_language;
std::string subtag; std::string subtag;
SplitLanguageTag(language, &main_language, &subtag); SplitLanguageTag(language, &main_language, &subtag);

View File

@ -6,16 +6,19 @@
// //
// Funtions used by MpdBuilder class to generate an MPD file. // Funtions used by MpdBuilder class to generate an MPD file.
#ifndef MPD_BASE_LANGUAGE_UTILS_H_ #ifndef MEDIA_BASE_LANGUAGE_UTILS_H_
#define MPD_BASE_LANGUAGE_UTILS_H_ #define MEDIA_BASE_LANGUAGE_UTILS_H_
#include <string> #include <string>
namespace shaka { namespace shaka {
class MediaInfo;
/// Convert a language tag to its shortest form, as required by RFC 5646 /// Convert a language tag to its shortest form, as required by RFC 5646
/// indicated in the MPD spec. Assumes the input is a valid ISO-639-2 or /// indicated in the MPD and HLS specs. Assumes the input is a valid ISO-639-2
/// ISO-639-1 language tag. Regions and variants are not supported. /// or ISO-639-1 language tag, or an empty string. Regions and variants are
/// preserved in the conversion.
std::string LanguageToShortestForm(const std::string& language); std::string LanguageToShortestForm(const std::string& language);
/// Convert a language tag to a 3-letter ISO-639-2 code, as required by the ISO /// Convert a language tag to a 3-letter ISO-639-2 code, as required by the ISO
@ -25,4 +28,4 @@ std::string LanguageToISO_639_2(const std::string& language);
} // namespace shaka } // namespace shaka
#endif // MPD_BASE_LANGUAGE_UTILS_H_ #endif // MEDIA_BASE_LANGUAGE_UTILS_H_

View File

@ -52,6 +52,8 @@
'key_fetcher.h', 'key_fetcher.h',
'key_source.cc', 'key_source.cc',
'key_source.h', 'key_source.h',
'language_utils.cc',
'language_utils.h',
'limits.h', 'limits.h',
'macros.h', 'macros.h',
'media_handler.cc', 'media_handler.cc',

View File

@ -24,9 +24,9 @@
#include "packager/base/synchronization/lock.h" #include "packager/base/synchronization/lock.h"
#include "packager/base/time/default_clock.h" #include "packager/base/time/default_clock.h"
#include "packager/base/time/time.h" #include "packager/base/time/time.h"
#include "packager/media/base/language_utils.h"
#include "packager/media/file/file.h" #include "packager/media/file/file.h"
#include "packager/mpd/base/content_protection_element.h" #include "packager/mpd/base/content_protection_element.h"
#include "packager/mpd/base/language_utils.h"
#include "packager/mpd/base/mpd_utils.h" #include "packager/mpd/base/mpd_utils.h"
#include "packager/mpd/base/xml/xml_node.h" #include "packager/mpd/base/xml/xml_node.h"
#include "packager/version/version.h" #include "packager/version/version.h"

View File

@ -39,8 +39,6 @@
'base/content_protection_element.h', 'base/content_protection_element.h',
'base/dash_iop_mpd_notifier.cc', 'base/dash_iop_mpd_notifier.cc',
'base/dash_iop_mpd_notifier.h', 'base/dash_iop_mpd_notifier.h',
'base/language_utils.cc',
'base/language_utils.h',
'base/mpd_builder.cc', 'base/mpd_builder.cc',
'base/mpd_builder.h', 'base/mpd_builder.h',
'base/mpd_notifier_util.cc', 'base/mpd_notifier_util.cc',