From 924d6d4693e7a1bfb65a0de4f2e1067c2749709c Mon Sep 17 00:00:00 2001 From: Joey Parrish Date: Fri, 10 Mar 2017 15:18:42 -0800 Subject: [PATCH] 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 --- packager/app/stream_descriptor.cc | 2 +- packager/hls/base/master_playlist.cc | 14 ++++++++++++-- packager/hls/base/master_playlist_unittest.cc | 12 ++++++++---- packager/hls/base/media_playlist.cc | 16 ++++++++++++++++ packager/hls/base/media_playlist.h | 4 ++++ packager/hls/base/media_playlist_unittest.cc | 18 ++++++++++++++++++ packager/hls/base/mock_media_playlist.h | 1 + packager/{mpd => media}/base/language_utils.cc | 7 ++++++- packager/{mpd => media}/base/language_utils.h | 13 ++++++++----- packager/media/base/media_base.gyp | 2 ++ packager/mpd/base/mpd_builder.cc | 2 +- packager/mpd/mpd.gyp | 2 -- 12 files changed, 77 insertions(+), 16 deletions(-) rename packager/{mpd => media}/base/language_utils.cc (97%) rename packager/{mpd => media}/base/language_utils.h (70%) diff --git a/packager/app/stream_descriptor.cc b/packager/app/stream_descriptor.cc index 21954aa534..d19b3daedb 100644 --- a/packager/app/stream_descriptor.cc +++ b/packager/app/stream_descriptor.cc @@ -11,7 +11,7 @@ #include "packager/base/strings/string_number_conversions.h" #include "packager/base/strings/string_split.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 media { diff --git a/packager/hls/base/master_playlist.cc b/packager/hls/base/master_playlist.cc index 67182480e0..007473c654 100644 --- a/packager/hls/base/master_playlist.cc +++ b/packager/hls/base/master_playlist.cc @@ -121,8 +121,18 @@ bool MasterPlaylist::WriteMasterPlaylist(const std::string& base_url, for (const MediaPlaylist* audio_playlist : audio_playlists) { base::StringAppendF( &audio_output, - "#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"%s\",NAME=\"%s\",URI=\"%s\"\n", - group_id.c_str(), audio_playlist->name().c_str(), + "#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(); if (audio_bitrate > max_audio_bitrate) diff --git a/packager/hls/base/master_playlist_unittest.cc b/packager/hls/base/master_playlist_unittest.cc index 7daa4d5282..b130ca16c2 100644 --- a/packager/hls/base/master_playlist_unittest.cc +++ b/packager/hls/base/master_playlist_unittest.cc @@ -130,6 +130,7 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistVideoAndAudio) { std::string audio_codec = "audiocodec"; MockMediaPlaylist english_playlist(kVodPlaylist, "eng.m3u8", "english", "audiogroup"); + EXPECT_CALL(english_playlist, GetLanguage()).WillRepeatedly(Return("en")); english_playlist.SetStreamTypeForTesting( MediaPlaylist::MediaPlaylistStreamType::kPlayListAudio); english_playlist.SetCodecForTesting(audio_codec); @@ -141,6 +142,7 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistVideoAndAudio) { // Second audio, spanish.m3u8. MockMediaPlaylist spanish_playlist(kVodPlaylist, "spa.m3u8", "espanol", "audiogroup"); + EXPECT_CALL(spanish_playlist, GetLanguage()).WillRepeatedly(Return("es")); spanish_playlist.SetStreamTypeForTesting( MediaPlaylist::MediaPlaylistStreamType::kPlayListAudio); spanish_playlist.SetCodecForTesting(audio_codec); @@ -166,9 +168,9 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistVideoAndAudio) { "## Generated with https://github.com/google/shaka-packager version " "test\n" "#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\"," - "URI=\"http://playlists.org/spa.m3u8\"\n" + "LANGUAGE=\"es\",URI=\"http://playlists.org/spa.m3u8\"\n" "#EXT-X-STREAM-INF:AUDIO=\"audiogroup\"," "CODECS=\"sdvideocodec,audiocodec\"," "BANDWIDTH=360000\n" @@ -198,6 +200,7 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistMultipleAudioGroups) { std::string audio_codec_lo = "audiocodec_lo"; MockMediaPlaylist eng_lo_playlist(kVodPlaylist, "eng_lo.m3u8", "english_lo", "audio_lo"); + EXPECT_CALL(eng_lo_playlist, GetLanguage()).WillRepeatedly(Return("en")); eng_lo_playlist.SetStreamTypeForTesting( MediaPlaylist::MediaPlaylistStreamType::kPlayListAudio); eng_lo_playlist.SetCodecForTesting(audio_codec_lo); @@ -209,6 +212,7 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistMultipleAudioGroups) { std::string audio_codec_hi = "audiocodec_hi"; MockMediaPlaylist eng_hi_playlist(kVodPlaylist, "eng_hi.m3u8", "english_hi", "audio_hi"); + EXPECT_CALL(eng_hi_playlist, GetLanguage()).WillRepeatedly(Return("en")); eng_hi_playlist.SetStreamTypeForTesting( MediaPlaylist::MediaPlaylistStreamType::kPlayListAudio); eng_hi_playlist.SetCodecForTesting(audio_codec_hi); @@ -233,9 +237,9 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistMultipleAudioGroups) { "## Generated with https://github.com/google/shaka-packager version " "test\n" "#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\"," - "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\"," "CODECS=\"videocodec,audiocodec_hi\"," "BANDWIDTH=400000\n" diff --git a/packager/hls/base/media_playlist.cc b/packager/hls/base/media_playlist.cc index 03f25eed63..640f474b6f 100644 --- a/packager/hls/base/media_playlist.cc +++ b/packager/hls/base/media_playlist.cc @@ -12,6 +12,7 @@ #include "packager/base/logging.h" #include "packager/base/strings/stringprintf.h" +#include "packager/media/base/language_utils.h" #include "packager/media/file/file.h" #include "packager/version/version.h" @@ -344,5 +345,20 @@ bool MediaPlaylist::SetTargetDuration(uint32_t target_duration) { 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 shaka diff --git a/packager/hls/base/media_playlist.h b/packager/hls/base/media_playlist.h index 583b3eebbb..59e6eafbdf 100644 --- a/packager/hls/base/media_playlist.h +++ b/packager/hls/base/media_playlist.h @@ -149,6 +149,10 @@ class MediaPlaylist { /// @return true if set, false otherwise. 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: // Mainly for MasterPlaylist to use these values. const std::string file_name_; diff --git a/packager/hls/base/media_playlist_unittest.cc b/packager/hls/base/media_playlist_unittest.cc index 4e884d0c5f..ab7b321d17 100644 --- a/packager/hls/base/media_playlist_unittest.cc +++ b/packager/hls/base/media_playlist_unittest.cc @@ -352,5 +352,23 @@ TEST_F(MediaPlaylistTest, RemoveOldestSegment) { 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 shaka diff --git a/packager/hls/base/mock_media_playlist.h b/packager/hls/base/mock_media_playlist.h index b0e8abc4ca..01b51eddde 100644 --- a/packager/hls/base/mock_media_playlist.h +++ b/packager/hls/base/mock_media_playlist.h @@ -40,6 +40,7 @@ class MockMediaPlaylist : public MediaPlaylist { MOCK_CONST_METHOD0(Bitrate, uint64_t()); MOCK_CONST_METHOD0(GetLongestSegmentDuration, double()); MOCK_METHOD1(SetTargetDuration, bool(uint32_t target_duration)); + MOCK_CONST_METHOD0(GetLanguage, std::string()); }; } // namespace hls diff --git a/packager/mpd/base/language_utils.cc b/packager/media/base/language_utils.cc similarity index 97% rename from packager/mpd/base/language_utils.cc rename to packager/media/base/language_utils.cc index cc2d938d66..5860d3b5df 100644 --- a/packager/mpd/base/language_utils.cc +++ b/packager/media/base/language_utils.cc @@ -4,7 +4,7 @@ // license that can be found in the LICENSE file or at // 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" @@ -87,6 +87,11 @@ void SplitLanguageTag(const std::string& tag, namespace shaka { 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 subtag; SplitLanguageTag(language, &main_language, &subtag); diff --git a/packager/mpd/base/language_utils.h b/packager/media/base/language_utils.h similarity index 70% rename from packager/mpd/base/language_utils.h rename to packager/media/base/language_utils.h index 0a1a5e4054..fc01d984f1 100644 --- a/packager/mpd/base/language_utils.h +++ b/packager/media/base/language_utils.h @@ -6,16 +6,19 @@ // // Funtions used by MpdBuilder class to generate an MPD file. -#ifndef MPD_BASE_LANGUAGE_UTILS_H_ -#define MPD_BASE_LANGUAGE_UTILS_H_ +#ifndef MEDIA_BASE_LANGUAGE_UTILS_H_ +#define MEDIA_BASE_LANGUAGE_UTILS_H_ #include namespace shaka { +class MediaInfo; + /// 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 -/// ISO-639-1 language tag. Regions and variants are not supported. +/// indicated in the MPD and HLS specs. Assumes the input is a valid ISO-639-2 +/// 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); /// 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 -#endif // MPD_BASE_LANGUAGE_UTILS_H_ +#endif // MEDIA_BASE_LANGUAGE_UTILS_H_ diff --git a/packager/media/base/media_base.gyp b/packager/media/base/media_base.gyp index 5ecc2fb0b7..8a3083567e 100644 --- a/packager/media/base/media_base.gyp +++ b/packager/media/base/media_base.gyp @@ -52,6 +52,8 @@ 'key_fetcher.h', 'key_source.cc', 'key_source.h', + 'language_utils.cc', + 'language_utils.h', 'limits.h', 'macros.h', 'media_handler.cc', diff --git a/packager/mpd/base/mpd_builder.cc b/packager/mpd/base/mpd_builder.cc index 1220317478..234db73323 100644 --- a/packager/mpd/base/mpd_builder.cc +++ b/packager/mpd/base/mpd_builder.cc @@ -24,9 +24,9 @@ #include "packager/base/synchronization/lock.h" #include "packager/base/time/default_clock.h" #include "packager/base/time/time.h" +#include "packager/media/base/language_utils.h" #include "packager/media/file/file.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/xml/xml_node.h" #include "packager/version/version.h" diff --git a/packager/mpd/mpd.gyp b/packager/mpd/mpd.gyp index 53776b57f6..67499b2d2d 100644 --- a/packager/mpd/mpd.gyp +++ b/packager/mpd/mpd.gyp @@ -39,8 +39,6 @@ 'base/content_protection_element.h', 'base/dash_iop_mpd_notifier.cc', 'base/dash_iop_mpd_notifier.h', - 'base/language_utils.cc', - 'base/language_utils.h', 'base/mpd_builder.cc', 'base/mpd_builder.h', 'base/mpd_notifier_util.cc',