From 2eff0a5c12d94b975d8b8f95853a72ba90e8731c Mon Sep 17 00:00:00 2001 From: Jingyi Wei Date: Fri, 26 Nov 2021 12:01:08 -0500 Subject: [PATCH] Implement stream ordering in HLS playlist based on input order. Fixed #560 with suggested implementation: https://github.com/google/shaka-packager/issues/560#issuecomment-611336440 --- packager/app/packager_main.cc | 1 + packager/hls/base/hls_notifier.h | 1 + packager/hls/base/master_playlist.cc | 42 +++++++---- packager/hls/base/master_playlist_unittest.cc | 6 +- packager/hls/base/media_playlist.cc | 12 ++-- packager/hls/base/media_playlist.h | 5 +- packager/hls/base/media_playlist_unittest.cc | 4 +- packager/hls/base/mock_media_playlist.cc | 5 +- packager/hls/base/mock_media_playlist.h | 3 +- packager/hls/base/simple_hls_notifier.cc | 8 ++- packager/hls/base/simple_hls_notifier.h | 4 +- .../hls/base/simple_hls_notifier_unittest.cc | 72 ++++++++++--------- .../media/event/hls_notify_muxer_listener.cc | 4 +- .../media/event/hls_notify_muxer_listener.h | 2 + .../hls_notify_muxer_listener_unittest.cc | 31 ++++---- .../media/event/muxer_listener_factory.cc | 9 ++- packager/media/event/muxer_listener_factory.h | 2 + packager/packager.cc | 1 + packager/packager.h | 2 + 19 files changed, 132 insertions(+), 82 deletions(-) diff --git a/packager/app/packager_main.cc b/packager/app/packager_main.cc index e256c423bfc..e2ea9a2a768 100644 --- a/packager/app/packager_main.cc +++ b/packager/app/packager_main.cc @@ -540,6 +540,7 @@ int PackagerMain(int argc, char** argv) { ParseStreamDescriptor(argv[i]); if (!stream_descriptor) return kArgumentValidationFailed; + stream_descriptor->output_order = i; stream_descriptors.push_back(stream_descriptor.value()); } Packager packager; diff --git a/packager/hls/base/hls_notifier.h b/packager/hls/base/hls_notifier.h index 3b1504fa766..e7e3706747b 100644 --- a/packager/hls/base/hls_notifier.h +++ b/packager/hls/base/hls_notifier.h @@ -39,6 +39,7 @@ class HlsNotifier { const std::string& playlist_name, const std::string& stream_name, const std::string& group_id, + int output_order, uint32_t* stream_id) = 0; /// Change the sample duration of stream with @a stream_id. diff --git a/packager/hls/base/master_playlist.cc b/packager/hls/base/master_playlist.cc index c62f24f2d05..0e6a70604d7 100644 --- a/packager/hls/base/master_playlist.cc +++ b/packager/hls/base/master_playlist.cc @@ -52,7 +52,8 @@ struct Variant { uint64_t avg_audio_bitrate = 0; }; -uint64_t GetMaximumMaxBitrate(const std::list playlists) { +uint64_t GetMaximumMaxBitrate( + const std::vector playlists) { uint64_t max = 0; for (const auto& playlist : playlists) { max = std::max(max, playlist->MaxBitrate()); @@ -60,7 +61,8 @@ uint64_t GetMaximumMaxBitrate(const std::list playlists) { return max; } -uint64_t GetMaximumAvgBitrate(const std::list playlists) { +uint64_t GetMaximumAvgBitrate( + const std::vector playlists) { uint64_t max = 0; for (const auto& playlist : playlists) { max = std::max(max, playlist->AvgBitrate()); @@ -69,7 +71,7 @@ uint64_t GetMaximumAvgBitrate(const std::list playlists) { } std::set GetGroupCodecString( - const std::list& group) { + const std::vector& group) { std::set codecs; for (const MediaPlaylist* playlist : group) { @@ -97,7 +99,7 @@ std::set GetGroupCodecString( } std::list AudioGroupsToVariants( - const std::map>& groups) { + const std::map>& groups) { std::list variants; for (const auto& group : groups) { @@ -139,7 +141,7 @@ const char* GetGroupId(const MediaPlaylist& playlist) { } std::list SubtitleGroupsToVariants( - const std::map>& groups) { + const std::map>& groups) { std::list variants; for (const auto& group : groups) { @@ -160,8 +162,9 @@ std::list SubtitleGroupsToVariants( } std::list BuildVariants( - const std::map>& audio_groups, - const std::map>& + const std::map>& + audio_groups, + const std::map>& subtitle_groups) { std::list audio_variants = AudioGroupsToVariants(audio_groups); std::list subtitle_variants = @@ -356,7 +359,7 @@ void BuildMediaTag(const MediaPlaylist& playlist, } void BuildMediaTags( - const std::map>& groups, + const std::map>& groups, const std::string& default_language, const std::string& base_url, std::string* out) { @@ -408,11 +411,12 @@ void AppendPlaylists(const std::string& default_audio_language, const std::string& base_url, const std::list& playlists, std::string* content) { - std::map> audio_playlist_groups; - std::map> + std::map> + audio_playlist_groups; + std::map> subtitle_playlist_groups; - std::list video_playlists; - std::list iframe_playlists; + std::vector video_playlists; + std::vector iframe_playlists; for (const MediaPlaylist* playlist : playlists) { switch (playlist->stream_type()) { case MediaPlaylist::MediaPlaylistStreamType::kAudio: @@ -433,6 +437,20 @@ void AppendPlaylists(const std::string& default_audio_language, } } + // Sort the streams by input order in the command line. + auto comparator = [](const MediaPlaylist* a, const MediaPlaylist* b) { + return a->output_order() < b->output_order(); + }; + for (auto& it : audio_playlist_groups) { + std::stable_sort(it.second.begin(), it.second.end(), comparator); + } + for (auto& it : subtitle_playlist_groups) { + std::stable_sort(it.second.begin(), it.second.end(), comparator); + } + std::stable_sort(video_playlists.begin(), video_playlists.end(), comparator); + std::stable_sort(iframe_playlists.begin(), iframe_playlists.end(), + comparator); + if (!audio_playlist_groups.empty()) { content->append("\n"); BuildMediaTags(audio_playlist_groups, default_audio_language, base_url, diff --git a/packager/hls/base/master_playlist_unittest.cc b/packager/hls/base/master_playlist_unittest.cc index 450583341c1..d9ed4d2a537 100644 --- a/packager/hls/base/master_playlist_unittest.cc +++ b/packager/hls/base/master_playlist_unittest.cc @@ -47,7 +47,7 @@ std::unique_ptr CreateVideoPlaylist( const char kNoGroup[] = ""; std::unique_ptr playlist( - new MockMediaPlaylist(filename, kNoName, kNoGroup)); + new MockMediaPlaylist(filename, kNoName, kNoGroup, 0)); playlist->SetStreamTypeForTesting( MediaPlaylist::MediaPlaylistStreamType::kVideo); @@ -91,7 +91,7 @@ std::unique_ptr CreateAudioPlaylist( bool ac4_ims_flag, bool ac4_cbi_flag) { std::unique_ptr playlist( - new MockMediaPlaylist(filename, name, group)); + new MockMediaPlaylist(filename, name, group, 0)); EXPECT_CALL(*playlist, GetNumChannels()).WillRepeatedly(Return(channels)); EXPECT_CALL(*playlist, GetEC3JocComplexity()) @@ -123,7 +123,7 @@ std::unique_ptr CreateTextPlaylist( const std::string& codec, const std::string& language) { std::unique_ptr playlist( - new MockMediaPlaylist(filename, name, group)); + new MockMediaPlaylist(filename, name, group, 0)); playlist->SetStreamTypeForTesting( MediaPlaylist::MediaPlaylistStreamType::kSubtitle); diff --git a/packager/hls/base/media_playlist.cc b/packager/hls/base/media_playlist.cc index 4016cdbcc75..131925653e2 100644 --- a/packager/hls/base/media_playlist.cc +++ b/packager/hls/base/media_playlist.cc @@ -339,16 +339,18 @@ HlsEntry::~HlsEntry() {} MediaPlaylist::MediaPlaylist(const HlsParams& hls_params, const std::string& file_name, const std::string& name, - const std::string& group_id) + const std::string& group_id, + int output_order) : hls_params_(hls_params), file_name_(file_name), name_(name), group_id_(group_id), + output_order_(output_order), media_sequence_number_(hls_params_.media_sequence_number) { - // When there's a forced media_sequence_number, start with discontinuity - if (media_sequence_number_ > 0) - entries_.emplace_back(new DiscontinuityEntry()); - } + // When there's a forced media_sequence_number, start with discontinuity + if (media_sequence_number_ > 0) + entries_.emplace_back(new DiscontinuityEntry()); +} MediaPlaylist::~MediaPlaylist() {} diff --git a/packager/hls/base/media_playlist.h b/packager/hls/base/media_playlist.h index 50f45f9f7e1..2aa82e964df 100644 --- a/packager/hls/base/media_playlist.h +++ b/packager/hls/base/media_playlist.h @@ -71,12 +71,14 @@ class MediaPlaylist { MediaPlaylist(const HlsParams& hls_params, const std::string& file_name, const std::string& name, - const std::string& group_id); + const std::string& group_id, + int output_order); virtual ~MediaPlaylist(); const std::string& file_name() const { return file_name_; } const std::string& name() const { return name_; } const std::string& group_id() const { return group_id_; } + int output_order() const { return output_order_; } MediaPlaylistStreamType stream_type() const { return stream_type_; } const std::string& codec() const { return codec_; } @@ -253,6 +255,7 @@ class MediaPlaylist { const std::string file_name_; const std::string name_; const std::string group_id_; + int output_order_; MediaInfo media_info_; MediaPlaylistStreamType stream_type_ = MediaPlaylistStreamType::kUnknown; // Whether to use byte range for SegmentInfoEntry. diff --git a/packager/hls/base/media_playlist_unittest.cc b/packager/hls/base/media_playlist_unittest.cc index 7abcd3045ea..4537ff3dd8d 100644 --- a/packager/hls/base/media_playlist_unittest.cc +++ b/packager/hls/base/media_playlist_unittest.cc @@ -50,8 +50,8 @@ class MediaPlaylistTest : public ::testing::Test { default_group_id_("default_group_id") { hls_params_.playlist_type = type; hls_params_.time_shift_buffer_depth = kTimeShiftBufferDepth; - media_playlist_.reset(new MediaPlaylist(hls_params_, default_file_name_, - default_name_, default_group_id_)); + media_playlist_.reset(new MediaPlaylist( + hls_params_, default_file_name_, default_name_, default_group_id_, 0)); } void SetUp() override { diff --git a/packager/hls/base/mock_media_playlist.cc b/packager/hls/base/mock_media_playlist.cc index 32c6e769274..e7f63486d16 100644 --- a/packager/hls/base/mock_media_playlist.cc +++ b/packager/hls/base/mock_media_playlist.cc @@ -13,8 +13,9 @@ namespace hls { MockMediaPlaylist::MockMediaPlaylist(const std::string& file_name, const std::string& name, - const std::string& group_id) - : MediaPlaylist(HlsParams(), file_name, name, group_id) {} + const std::string& group_id, + int output_order) + : MediaPlaylist(HlsParams(), file_name, name, group_id, output_order) {} MockMediaPlaylist::~MockMediaPlaylist() {} } // namespace hls diff --git a/packager/hls/base/mock_media_playlist.h b/packager/hls/base/mock_media_playlist.h index a971dd8b74e..899f9587125 100644 --- a/packager/hls/base/mock_media_playlist.h +++ b/packager/hls/base/mock_media_playlist.h @@ -20,7 +20,8 @@ class MockMediaPlaylist : public MediaPlaylist { // matter because the return value can be mocked. MockMediaPlaylist(const std::string& file_name, const std::string& name, - const std::string& group_id); + const std::string& group_id, + int output_order); ~MockMediaPlaylist() override; MOCK_METHOD1(SetMediaInfo, bool(const MediaInfo& media_info)); diff --git a/packager/hls/base/simple_hls_notifier.cc b/packager/hls/base/simple_hls_notifier.cc index 300ec0178a7..03f0dc2eeda 100644 --- a/packager/hls/base/simple_hls_notifier.cc +++ b/packager/hls/base/simple_hls_notifier.cc @@ -272,9 +272,10 @@ std::unique_ptr MediaPlaylistFactory::Create( const HlsParams& hls_params, const std::string& file_name, const std::string& name, - const std::string& group_id) { + const std::string& group_id, + int output_order) { return std::unique_ptr( - new MediaPlaylist(hls_params, file_name, name, group_id)); + new MediaPlaylist(hls_params, file_name, name, group_id, output_order)); } SimpleHlsNotifier::SimpleHlsNotifier(const HlsParams& hls_params) @@ -304,6 +305,7 @@ bool SimpleHlsNotifier::NotifyNewStream(const MediaInfo& media_info, const std::string& playlist_name, const std::string& name, const std::string& group_id, + int output_order, uint32_t* stream_id) { DCHECK(stream_id); @@ -312,7 +314,7 @@ bool SimpleHlsNotifier::NotifyNewStream(const MediaInfo& media_info, std::unique_ptr media_playlist = media_playlist_factory_->Create(hls_params(), relative_playlist_path, - name, group_id); + name, group_id, output_order); MediaInfo adjusted_media_info = MakeMediaInfoPathsRelativeToPlaylist( media_info, hls_params().base_url, master_playlist_dir_, media_playlist->file_name()); diff --git a/packager/hls/base/simple_hls_notifier.h b/packager/hls/base/simple_hls_notifier.h index 314ea9c7656..795f66ce423 100644 --- a/packager/hls/base/simple_hls_notifier.h +++ b/packager/hls/base/simple_hls_notifier.h @@ -31,7 +31,8 @@ class MediaPlaylistFactory { virtual std::unique_ptr Create(const HlsParams& hls_params, const std::string& file_name, const std::string& name, - const std::string& group_id); + const std::string& group_id, + int output_order); }; /// This is thread safe. @@ -48,6 +49,7 @@ class SimpleHlsNotifier : public HlsNotifier { const std::string& playlist_name, const std::string& stream_name, const std::string& group_id, + int output_order, uint32_t* stream_id) override; bool NotifySampleDuration(uint32_t stream_id, int32_t sample_duration) override; diff --git a/packager/hls/base/simple_hls_notifier_unittest.cc b/packager/hls/base/simple_hls_notifier_unittest.cc index 896d41a06bf..c1fcd9becbe 100644 --- a/packager/hls/base/simple_hls_notifier_unittest.cc +++ b/packager/hls/base/simple_hls_notifier_unittest.cc @@ -60,18 +60,20 @@ class MockMasterPlaylist : public MasterPlaylist { class MockMediaPlaylistFactory : public MediaPlaylistFactory { public: - MOCK_METHOD4(CreateMock, + MOCK_METHOD5(CreateMock, MediaPlaylist*(const HlsParams& hls_params, const std::string& file_name, const std::string& name, - const std::string& group_id)); + const std::string& group_id, + int output_order)); std::unique_ptr Create(const HlsParams& hls_params, const std::string& file_name, const std::string& name, - const std::string& group_id) override { + const std::string& group_id, + int output_order) override { return std::unique_ptr( - CreateMock(hls_params, file_name, name, group_id)); + CreateMock(hls_params, file_name, name, group_id, output_order)); } }; @@ -133,7 +135,7 @@ class SimpleHlsNotifierTest : public ::testing::Test { new MockMediaPlaylistFactory()); EXPECT_CALL(*mock_media_playlist, SetMediaInfo(_)).WillOnce(Return(true)); - EXPECT_CALL(*factory, CreateMock(_, _, _, _)) + EXPECT_CALL(*factory, CreateMock(_, _, _, _, _)) .WillOnce(Return(mock_media_playlist)); InjectMasterPlaylist(std::move(mock_master_playlist), notifier); @@ -141,7 +143,7 @@ class SimpleHlsNotifierTest : public ::testing::Test { EXPECT_TRUE(notifier->Init()); uint32_t stream_id; EXPECT_TRUE(notifier->NotifyNewStream(media_info, "playlist.m3u8", "name", - "groupid", &stream_id)); + "groupid", 0, &stream_id)); return stream_id; } @@ -176,11 +178,11 @@ TEST_F(SimpleHlsNotifierTest, NotifyNewStream) { // Pointer released by SimpleHlsNotifier. MockMediaPlaylist* mock_media_playlist = - new MockMediaPlaylist("playlist.m3u8", "", ""); + new MockMediaPlaylist("playlist.m3u8", "", "", 0); EXPECT_CALL(*mock_media_playlist, SetMediaInfo(_)).WillOnce(Return(true)); EXPECT_CALL(*factory, CreateMock(_, StrEq("video_playlist.m3u8"), - StrEq("name"), StrEq("groupid"))) + StrEq("name"), StrEq("groupid"), 0)) .WillOnce(Return(mock_media_playlist)); SimpleHlsNotifier notifier(hls_params_); @@ -191,7 +193,7 @@ TEST_F(SimpleHlsNotifierTest, NotifyNewStream) { MediaInfo media_info; uint32_t stream_id; EXPECT_TRUE(notifier.NotifyNewStream(media_info, "video_playlist.m3u8", - "name", "groupid", &stream_id)); + "name", "groupid", 0, &stream_id)); EXPECT_EQ(1u, NumRegisteredMediaPlaylists(notifier)); } @@ -203,10 +205,10 @@ TEST_F(SimpleHlsNotifierTest, NotifyNewSegment) { // Pointer released by SimpleHlsNotifier. MockMediaPlaylist* mock_media_playlist = - new MockMediaPlaylist("playlist.m3u8", "", ""); + new MockMediaPlaylist("playlist.m3u8", "", "", 0); EXPECT_CALL(*mock_media_playlist, SetMediaInfo(_)).WillOnce(Return(true)); - EXPECT_CALL(*factory, CreateMock(_, _, _, _)) + EXPECT_CALL(*factory, CreateMock(_, _, _, _, _)) .WillOnce(Return(mock_media_playlist)); const int64_t kStartTime = 1328; @@ -230,7 +232,7 @@ TEST_F(SimpleHlsNotifierTest, NotifyNewSegment) { MediaInfo media_info; uint32_t stream_id; EXPECT_TRUE(notifier.NotifyNewStream(media_info, "playlist.m3u8", "name", - "groupid", &stream_id)); + "groupid", 0, &stream_id)); EXPECT_TRUE(notifier.NotifyNewSegment(stream_id, segment_name, kStartTime, kDuration, 203, kSize)); @@ -256,7 +258,7 @@ TEST_F(SimpleHlsNotifierTest, NotifyNewSegment) { TEST_F(SimpleHlsNotifierTest, NotifyKeyFrame) { // Pointer released by SimpleHlsNotifier. MockMediaPlaylist* mock_media_playlist = - new MockMediaPlaylist("playlist.m3u8", "", ""); + new MockMediaPlaylist("playlist.m3u8", "", "", 0); SimpleHlsNotifier notifier(hls_params_); const uint32_t stream_id = SetupStream(kCencProtectionScheme, mock_media_playlist, ¬ifier); @@ -279,7 +281,7 @@ TEST_F(SimpleHlsNotifierTest, NotifyNewSegmentWithoutStreamsRegistered) { TEST_F(SimpleHlsNotifierTest, NotifyEncryptionUpdateIdentityKey) { // Pointer released by SimpleHlsNotifier. MockMediaPlaylist* mock_media_playlist = - new MockMediaPlaylist("playlist.m3u8", "", ""); + new MockMediaPlaylist("playlist.m3u8", "", "", 0); SimpleHlsNotifier notifier(hls_params_); const uint32_t stream_id = SetupStream(kSampleAesProtectionScheme, mock_media_playlist, ¬ifier); @@ -306,7 +308,7 @@ TEST_F(SimpleHlsNotifierTest, NotifyEncryptionUpdateIdentityKey) { TEST_F(SimpleHlsNotifierTest, EncryptionScheme) { // Pointer released by SimpleHlsNotifier. MockMediaPlaylist* mock_media_playlist = - new MockMediaPlaylist("playlist.m3u8", "", ""); + new MockMediaPlaylist("playlist.m3u8", "", "", 0); hls_params_.key_uri = kIdentityKeyUri; SimpleHlsNotifier notifier(hls_params_); const uint32_t stream_id = @@ -330,7 +332,7 @@ TEST_F(SimpleHlsNotifierTest, EncryptionScheme) { TEST_F(SimpleHlsNotifierTest, NotifyEncryptionUpdateFairPlay) { // Pointer released by SimpleHlsNotifier. MockMediaPlaylist* mock_media_playlist = - new MockMediaPlaylist("playlist.m3u8", "", ""); + new MockMediaPlaylist("playlist.m3u8", "", "", 0); hls_params_.playlist_type = kLivePlaylist; hls_params_.key_uri = kFairPlayKeyUri; SimpleHlsNotifier notifier(hls_params_); @@ -363,7 +365,7 @@ TEST_F(SimpleHlsNotifierTest, NotifyEncryptionUpdateWithoutStreamsRegistered) { TEST_F(SimpleHlsNotifierTest, NotifyCueEvent) { // Pointer released by SimpleHlsNotifier. MockMediaPlaylist* mock_media_playlist = - new MockMediaPlaylist("playlist.m3u8", "", ""); + new MockMediaPlaylist("playlist.m3u8", "", "", 0); SimpleHlsNotifier notifier(hls_params_); const uint32_t stream_id = SetupStream(kCencProtectionScheme, mock_media_playlist, ¬ifier); @@ -424,8 +426,8 @@ TEST_P(SimpleHlsNotifierRebaseUrlTest, Test) { new MockMediaPlaylistFactory()); // Pointer released by SimpleHlsNotifier. - MockMediaPlaylist* mock_media_playlist = - new MockMediaPlaylist(test_data_.expected_relative_playlist_path, "", ""); + MockMediaPlaylist* mock_media_playlist = new MockMediaPlaylist( + test_data_.expected_relative_playlist_path, "", "", 0); EXPECT_CALL( *mock_media_playlist, @@ -439,7 +441,7 @@ TEST_P(SimpleHlsNotifierRebaseUrlTest, Test) { } EXPECT_CALL(*factory, CreateMock(_, StrEq(test_data_.expected_relative_playlist_path), - StrEq("name"), StrEq("groupid"))) + StrEq("name"), StrEq("groupid"), 0)) .WillOnce(Return(mock_media_playlist)); InjectMasterPlaylist(std::move(mock_master_playlist), &test_notifier); @@ -451,7 +453,7 @@ TEST_P(SimpleHlsNotifierRebaseUrlTest, Test) { media_info.set_init_segment_name(test_data_.init_segment_path); uint32_t stream_id; EXPECT_TRUE(test_notifier.NotifyNewStream( - media_info, test_data_.playlist_path, "name", "groupid", &stream_id)); + media_info, test_data_.playlist_path, "name", "groupid", 0, &stream_id)); if (!test_data_.segment_path.empty()) { EXPECT_TRUE(test_notifier.NotifyNewSegment( stream_id, test_data_.segment_path, kAnyStartTime, kAnyDuration, 0, @@ -532,10 +534,10 @@ TEST_P(LiveOrEventSimpleHlsNotifierTest, NotifyNewSegment) { // Pointer released by SimpleHlsNotifier. MockMediaPlaylist* mock_media_playlist = - new MockMediaPlaylist("playlist.m3u8", "", ""); + new MockMediaPlaylist("playlist.m3u8", "", "", 0); EXPECT_CALL(*mock_media_playlist, SetMediaInfo(_)).WillOnce(Return(true)); - EXPECT_CALL(*factory, CreateMock(_, _, _, _)) + EXPECT_CALL(*factory, CreateMock(_, _, _, _, _)) .WillOnce(Return(mock_media_playlist)); const int64_t kStartTime = 1328; @@ -571,7 +573,7 @@ TEST_P(LiveOrEventSimpleHlsNotifierTest, NotifyNewSegment) { MediaInfo media_info; uint32_t stream_id; EXPECT_TRUE(notifier.NotifyNewStream(media_info, "playlist.m3u8", "name", - "groupid", &stream_id)); + "groupid", 0, &stream_id)); EXPECT_TRUE(notifier.NotifyNewSegment(stream_id, segment_name, kStartTime, kDuration, 0, kSize)); @@ -591,14 +593,14 @@ TEST_P(LiveOrEventSimpleHlsNotifierTest, NotifyNewSegmentsWithMultipleStreams) { // Pointer released by SimpleHlsNotifier. MockMediaPlaylist* mock_media_playlist1 = - new MockMediaPlaylist("playlist1.m3u8", "", ""); + new MockMediaPlaylist("playlist1.m3u8", "", "", 0); MockMediaPlaylist* mock_media_playlist2 = - new MockMediaPlaylist("playlist2.m3u8", "", ""); + new MockMediaPlaylist("playlist2.m3u8", "", "", 1); - EXPECT_CALL(*factory, CreateMock(_, StrEq("playlist1.m3u8"), _, _)) + EXPECT_CALL(*factory, CreateMock(_, StrEq("playlist1.m3u8"), _, _, 0)) .WillOnce(Return(mock_media_playlist1)); EXPECT_CALL(*mock_media_playlist1, SetMediaInfo(_)).WillOnce(Return(true)); - EXPECT_CALL(*factory, CreateMock(_, StrEq("playlist2.m3u8"), _, _)) + EXPECT_CALL(*factory, CreateMock(_, StrEq("playlist2.m3u8"), _, _, 1)) .WillOnce(Return(mock_media_playlist2)); EXPECT_CALL(*mock_media_playlist2, SetMediaInfo(_)).WillOnce(Return(true)); @@ -612,10 +614,10 @@ TEST_P(LiveOrEventSimpleHlsNotifierTest, NotifyNewSegmentsWithMultipleStreams) { MediaInfo media_info; uint32_t stream_id1; EXPECT_TRUE(notifier.NotifyNewStream(media_info, "playlist1.m3u8", "name", - "groupid", &stream_id1)); + "groupid", 0, &stream_id1)); uint32_t stream_id2; EXPECT_TRUE(notifier.NotifyNewStream(media_info, "playlist2.m3u8", "name", - "groupid", &stream_id2)); + "groupid", 1, &stream_id2)); EXPECT_CALL(*mock_media_playlist1, AddSegment(_, _, _, _, _)).Times(1); const double kLongestSegmentDuration = 11.3; @@ -684,7 +686,7 @@ class WidevineSimpleHlsNotifierTest : public SimpleHlsNotifierTest, TEST_P(WidevineSimpleHlsNotifierTest, NotifyEncryptionUpdate) { // Pointer released by SimpleHlsNotifier. MockMediaPlaylist* mock_media_playlist = - new MockMediaPlaylist("playlist.m3u8", "", ""); + new MockMediaPlaylist("playlist.m3u8", "", "", 0); SimpleHlsNotifier notifier(hls_params_); const uint32_t stream_id = SetupStream(kSampleAesProtectionScheme, mock_media_playlist, ¬ifier); @@ -744,7 +746,7 @@ TEST_P(WidevineSimpleHlsNotifierTest, NotifyEncryptionUpdate) { TEST_P(WidevineSimpleHlsNotifierTest, NotifyEncryptionUpdateNoKeyidsInPssh) { // Pointer released by SimpleHlsNotifier. MockMediaPlaylist* mock_media_playlist = - new MockMediaPlaylist("playlist.m3u8", "", ""); + new MockMediaPlaylist("playlist.m3u8", "", "", 0); SimpleHlsNotifier notifier(hls_params_); const uint32_t stream_id = SetupStream(kSampleAesProtectionScheme, mock_media_playlist, ¬ifier); @@ -803,7 +805,7 @@ TEST_P(WidevineSimpleHlsNotifierTest, NotifyEncryptionUpdateNoKeyidsInPssh) { TEST_P(WidevineSimpleHlsNotifierTest, MultipleKeyIdsNoContentIdInPssh) { // Pointer released by SimpleHlsNotifier. MockMediaPlaylist* mock_media_playlist = - new MockMediaPlaylist("playlist.m3u8", "", ""); + new MockMediaPlaylist("playlist.m3u8", "", "", 0); SimpleHlsNotifier notifier(hls_params_); uint32_t stream_id = SetupStream(kSampleAesProtectionScheme, mock_media_playlist, ¬ifier); @@ -879,7 +881,7 @@ TEST_P(WidevineSimpleHlsNotifierTest, MultipleKeyIdsNoContentIdInPssh) { TEST_P(WidevineSimpleHlsNotifierTest, CencEncryptionScheme) { // Pointer released by SimpleHlsNotifier. MockMediaPlaylist* mock_media_playlist = - new MockMediaPlaylist("playlist.m3u8", "", ""); + new MockMediaPlaylist("playlist.m3u8", "", "", 0); SimpleHlsNotifier notifier(hls_params_); const uint32_t stream_id = SetupStream(kCencProtectionScheme, mock_media_playlist, ¬ifier); @@ -920,7 +922,7 @@ TEST_P(WidevineSimpleHlsNotifierTest, CencEncryptionScheme) { TEST_P(WidevineSimpleHlsNotifierTest, NotifyEncryptionUpdateEmptyIv) { // Pointer released by SimpleHlsNotifier. MockMediaPlaylist* mock_media_playlist = - new MockMediaPlaylist("playlist.m3u8", "", ""); + new MockMediaPlaylist("playlist.m3u8", "", "", 0); SimpleHlsNotifier notifier(hls_params_); const uint32_t stream_id = SetupStream(kSampleAesProtectionScheme, mock_media_playlist, ¬ifier); diff --git a/packager/media/event/hls_notify_muxer_listener.cc b/packager/media/event/hls_notify_muxer_listener.cc index c208afe1513..a5fb2011118 100644 --- a/packager/media/event/hls_notify_muxer_listener.cc +++ b/packager/media/event/hls_notify_muxer_listener.cc @@ -22,12 +22,14 @@ HlsNotifyMuxerListener::HlsNotifyMuxerListener( const std::string& ext_x_media_name, const std::string& ext_x_media_group_id, const std::vector& characteristics, + int output_order, hls::HlsNotifier* hls_notifier) : playlist_name_(playlist_name), iframes_only_(iframes_only), ext_x_media_name_(ext_x_media_name), ext_x_media_group_id_(ext_x_media_group_id), characteristics_(characteristics), + output_order_(output_order), hls_notifier_(hls_notifier) { DCHECK(hls_notifier); } @@ -280,7 +282,7 @@ bool HlsNotifyMuxerListener::NotifyNewStream() { uint32_t stream_id; const bool result = hls_notifier_->NotifyNewStream( *media_info_, playlist_name_, ext_x_media_name_, ext_x_media_group_id_, - &stream_id); + output_order_, &stream_id); if (!result) { LOG(WARNING) << "Failed to notify new stream for VOD."; return false; diff --git a/packager/media/event/hls_notify_muxer_listener.h b/packager/media/event/hls_notify_muxer_listener.h index 69bcc5533b5..0f4fe53e1d5 100644 --- a/packager/media/event/hls_notify_muxer_listener.h +++ b/packager/media/event/hls_notify_muxer_listener.h @@ -45,6 +45,7 @@ class HlsNotifyMuxerListener : public MuxerListener { const std::string& ext_x_media_name, const std::string& ext_x_media_group_id, const std::vector& characteristics, + int output_order, hls::HlsNotifier* hls_notifier); ~HlsNotifyMuxerListener() override; @@ -83,6 +84,7 @@ class HlsNotifyMuxerListener : public MuxerListener { const std::string ext_x_media_name_; const std::string ext_x_media_group_id_; const std::vector characteristics_; + int output_order_; hls::HlsNotifier* const hls_notifier_; base::Optional stream_id_; diff --git a/packager/media/event/hls_notify_muxer_listener_unittest.cc b/packager/media/event/hls_notify_muxer_listener_unittest.cc index d5e2869a482..a2743ca3d94 100644 --- a/packager/media/event/hls_notify_muxer_listener_unittest.cc +++ b/packager/media/event/hls_notify_muxer_listener_unittest.cc @@ -32,11 +32,12 @@ class MockHlsNotifier : public hls::HlsNotifier { MockHlsNotifier() : HlsNotifier(HlsParams()) {} MOCK_METHOD0(Init, bool()); - MOCK_METHOD5(NotifyNewStream, + MOCK_METHOD6(NotifyNewStream, bool(const MediaInfo& media_info, const std::string& playlist_name, const std::string& name, const std::string& group_id, + int output_order, uint32_t* stream_id)); MOCK_METHOD2(NotifySampleDuration, bool(uint32_t stream_id, int32_t sample_duration)); @@ -120,6 +121,7 @@ class HlsNotifyMuxerListenerTest : public ::testing::Test { kDefaultName, kDefaultGroupId, std::vector{kCharactersticA, kCharactersticB}, + 0, &mock_notifier_) {} MuxerListener::MediaRanges GetMediaRanges( @@ -164,7 +166,7 @@ TEST_F(HlsNotifyMuxerListenerTest, OnMediaStart) { NotifyNewStream(Property(&MediaInfo::hls_characteristics, ElementsAre(kCharactersticA, kCharactersticB)), StrEq(kDefaultPlaylistName), StrEq("DEFAULTNAME"), - StrEq("DEFAULTGROUPID"), _)) + StrEq("DEFAULTGROUPID"), 0, _)) .WillOnce(Return(true)); MuxerOptions muxer_options; @@ -187,7 +189,7 @@ TEST_F(HlsNotifyMuxerListenerTest, OnEncryptionStart) { iv, {{system_id, pssh}}); ::testing::Mock::VerifyAndClearExpectations(&mock_notifier_); - ON_CALL(mock_notifier_, NotifyNewStream(_, _, _, _, _)) + ON_CALL(mock_notifier_, NotifyNewStream(_, _, _, _, _, _)) .WillByDefault(Return(true)); VideoStreamInfoParameters video_params = GetDefaultVideoStreamInfoParams(); std::shared_ptr video_stream_info = @@ -221,7 +223,7 @@ TEST_F(HlsNotifyMuxerListenerTest, OnEncryptionStartBeforeMediaStart) { iv, {{system_id, pssh}}); ::testing::Mock::VerifyAndClearExpectations(&mock_notifier_); - ON_CALL(mock_notifier_, NotifyNewStream(_, _, _, _, _)) + ON_CALL(mock_notifier_, NotifyNewStream(_, _, _, _, _, _)) .WillByDefault(Return(true)); VideoStreamInfoParameters video_params = GetDefaultVideoStreamInfoParams(); std::shared_ptr video_stream_info = @@ -251,7 +253,7 @@ TEST_F(HlsNotifyMuxerListenerTest, NoEncryptionUpdateIfNotifyNewStreamFails) { iv, GetDefaultKeySystemInfo()); ::testing::Mock::VerifyAndClearExpectations(&mock_notifier_); - EXPECT_CALL(mock_notifier_, NotifyNewStream(_, _, _, _, _)) + EXPECT_CALL(mock_notifier_, NotifyNewStream(_, _, _, _, _, _)) .WillOnce(Return(false)); VideoStreamInfoParameters video_params = GetDefaultVideoStreamInfoParams(); std::shared_ptr video_stream_info = @@ -267,7 +269,7 @@ TEST_F(HlsNotifyMuxerListenerTest, NoEncryptionUpdateIfNotifyNewStreamFails) { // Verify that after OnMediaStart(), OnEncryptionInfoReady() calls // NotifyEncryptionUpdate(). TEST_F(HlsNotifyMuxerListenerTest, OnEncryptionInfoReady) { - ON_CALL(mock_notifier_, NotifyNewStream(_, _, _, _, _)) + ON_CALL(mock_notifier_, NotifyNewStream(_, _, _, _, _, _)) .WillByDefault(Return(true)); VideoStreamInfoParameters video_params = GetDefaultVideoStreamInfoParams(); std::shared_ptr video_stream_info = @@ -301,7 +303,7 @@ TEST_F(HlsNotifyMuxerListenerTest, OnEncryptionInfoReadyWithProtectionScheme) { ::testing::Mock::VerifyAndClearExpectations(&mock_notifier_); ON_CALL(mock_notifier_, - NotifyNewStream(HasEncryptionScheme("cenc"), _, _, _, _)) + NotifyNewStream(HasEncryptionScheme("cenc"), _, _, _, _, _)) .WillByDefault(Return(true)); VideoStreamInfoParameters video_params = GetDefaultVideoStreamInfoParams(); std::shared_ptr video_stream_info = @@ -313,7 +315,7 @@ TEST_F(HlsNotifyMuxerListenerTest, OnEncryptionInfoReadyWithProtectionScheme) { } TEST_F(HlsNotifyMuxerListenerTest, OnSampleDurationReady) { - ON_CALL(mock_notifier_, NotifyNewStream(_, _, _, _, _)) + ON_CALL(mock_notifier_, NotifyNewStream(_, _, _, _, _, _)) .WillByDefault(Return(true)); VideoStreamInfoParameters video_params = GetDefaultVideoStreamInfoParams(); std::shared_ptr video_stream_info = @@ -328,7 +330,7 @@ TEST_F(HlsNotifyMuxerListenerTest, OnSampleDurationReady) { } TEST_F(HlsNotifyMuxerListenerTest, OnNewSegmentAndCueEvent) { - ON_CALL(mock_notifier_, NotifyNewStream(_, _, _, _, _)) + ON_CALL(mock_notifier_, NotifyNewStream(_, _, _, _, _, _)) .WillByDefault(Return(true)); VideoStreamInfoParameters video_params = GetDefaultVideoStreamInfoParams(); std::shared_ptr video_stream_info = @@ -351,7 +353,7 @@ TEST_F(HlsNotifyMuxerListenerTest, OnNewSegmentAndCueEvent) { // Verify that the notifier is called for every segment in OnMediaEnd if // segment_template is not set. TEST_F(HlsNotifyMuxerListenerTest, NoSegmentTemplateOnMediaEnd) { - ON_CALL(mock_notifier_, NotifyNewStream(_, _, _, _, _)) + ON_CALL(mock_notifier_, NotifyNewStream(_, _, _, _, _, _)) .WillByDefault(Return(true)); VideoStreamInfoParameters video_params = GetDefaultVideoStreamInfoParams(); std::shared_ptr video_stream_info = @@ -396,7 +398,7 @@ TEST_F(HlsNotifyMuxerListenerTest, NoSegmentTemplateOnMediaEndTwice) { kSegmentSize); listener_.OnCueEvent(kCueStartTime, "dummy cue data"); - EXPECT_CALL(mock_notifier_, NotifyNewStream(_, _, _, _, _)) + EXPECT_CALL(mock_notifier_, NotifyNewStream(_, _, _, _, _, _)) .WillOnce(Return(true)); EXPECT_CALL(mock_notifier_, NotifyNewSegment(_, StrEq("filename1.mp4"), kSegmentStartTime, _, _, _)); @@ -425,7 +427,7 @@ TEST_F(HlsNotifyMuxerListenerTest, NoSegmentTemplateOnMediaEndTwice) { // two. TEST_F(HlsNotifyMuxerListenerTest, NoSegmentTemplateOnMediaEndSubsegmentSizeMismatch) { - ON_CALL(mock_notifier_, NotifyNewStream(_, _, _, _, _)) + ON_CALL(mock_notifier_, NotifyNewStream(_, _, _, _, _, _)) .WillByDefault(Return(true)); VideoStreamInfoParameters video_params = GetDefaultVideoStreamInfoParams(); std::shared_ptr video_stream_info = @@ -457,6 +459,7 @@ class HlsNotifyMuxerListenerKeyFrameTest : public TestWithParam { kDefaultName, kDefaultGroupId, std::vector(), // no characteristics. + 0, &mock_notifier_) {} MockHlsNotifier mock_notifier_; @@ -464,7 +467,7 @@ class HlsNotifyMuxerListenerKeyFrameTest : public TestWithParam { }; TEST_P(HlsNotifyMuxerListenerKeyFrameTest, WithSegmentTemplate) { - ON_CALL(mock_notifier_, NotifyNewStream(_, _, _, _, _)) + ON_CALL(mock_notifier_, NotifyNewStream(_, _, _, _, _, _)) .WillByDefault(Return(true)); VideoStreamInfoParameters video_params = GetDefaultVideoStreamInfoParams(); std::shared_ptr video_stream_info = @@ -485,7 +488,7 @@ TEST_P(HlsNotifyMuxerListenerKeyFrameTest, WithSegmentTemplate) { // Verify that the notifier is called for every key frame in OnMediaEnd if // segment_template is not set. TEST_P(HlsNotifyMuxerListenerKeyFrameTest, NoSegmentTemplate) { - ON_CALL(mock_notifier_, NotifyNewStream(_, _, _, _, _)) + ON_CALL(mock_notifier_, NotifyNewStream(_, _, _, _, _, _)) .WillByDefault(Return(true)); VideoStreamInfoParameters video_params = GetDefaultVideoStreamInfoParams(); std::shared_ptr video_stream_info = diff --git a/packager/media/event/muxer_listener_factory.cc b/packager/media/event/muxer_listener_factory.cc index 287a936f967..47e8aff97f8 100644 --- a/packager/media/event/muxer_listener_factory.cc +++ b/packager/media/event/muxer_listener_factory.cc @@ -59,6 +59,10 @@ std::list> CreateHlsListenersInternal( const std::string& iframe_playlist_name = stream.hls_iframe_playlist_name; const std::vector& characteristics = stream.hls_characteristics; + if (stream.output_order > 0) { + stream_index = stream.output_order; + } + if (name.empty()) { name = base::StringPrintf("stream_%d", stream_index); } @@ -70,11 +74,12 @@ std::list> CreateHlsListenersInternal( const bool kIFramesOnly = true; std::list> listeners; listeners.emplace_back(new HlsNotifyMuxerListener( - playlist_name, !kIFramesOnly, name, group_id, characteristics, notifier)); + playlist_name, !kIFramesOnly, name, group_id, characteristics, + stream.output_order, notifier)); if (!iframe_playlist_name.empty()) { listeners.emplace_back(new HlsNotifyMuxerListener( iframe_playlist_name, kIFramesOnly, name, group_id, - std::vector(), notifier)); + std::vector(), stream.output_order, notifier)); } return listeners; } diff --git a/packager/media/event/muxer_listener_factory.h b/packager/media/event/muxer_listener_factory.h index fd7e384fdcf..d0d9f460b19 100644 --- a/packager/media/event/muxer_listener_factory.h +++ b/packager/media/event/muxer_listener_factory.h @@ -38,6 +38,8 @@ class MuxerListenerFactory { // The stream's output destination. Will only be used if the factory is // told to output media info. std::string media_info_output; + // The stream's order in HLS manifest (TODO: and DASH mpd). + int output_order; // HLS specific values needed to write to HLS manifests. Will only be used // if an HlsNotifier is given to the factory. diff --git a/packager/packager.cc b/packager/packager.cc index 25a3d14f078..599d85b8fec 100644 --- a/packager/packager.cc +++ b/packager/packager.cc @@ -73,6 +73,7 @@ MuxerListenerFactory::StreamData ToMuxerListenerData( const StreamDescriptor& stream) { MuxerListenerFactory::StreamData data; data.media_info_output = stream.output; + data.output_order = stream.output_order; data.hls_group_id = stream.hls_group_id; data.hls_name = stream.hls_name; diff --git a/packager/packager.h b/packager/packager.h index 38494853ef7..55790cb611c 100644 --- a/packager/packager.h +++ b/packager/packager.h @@ -113,6 +113,8 @@ struct StreamDescriptor { /// formats, there are multiple "channels" in a single stream. This allows /// selecting only one channel. int32_t cc_index = -1; + /// Optional value for the order of the streams in the output playlist files. + int output_order = 0; /// Required for audio when outputting HLS. It defines the name of the output /// stream, which is not necessarily the same as output. This is used as the