From 192ef2a0ac7b5dbf6de9c32343e88a56168ee985 Mon Sep 17 00:00:00 2001 From: KongQun Yang Date: Mon, 21 May 2018 17:39:21 -0700 Subject: [PATCH] Update MuxerListeners to support multiple OnMediaStart/End This is needed to support one file per representation per period where there will be multiple content files generated. Issue: #384 Change-Id: Ib7af750edf864d99075b8da4f3640217a5a94302 --- .../media/event/hls_notify_muxer_listener.cc | 96 ++++++++------ .../media/event/hls_notify_muxer_listener.h | 9 +- .../hls_notify_muxer_listener_unittest.cc | 120 +++++++++++------- .../media/event/mpd_notify_muxer_listener.cc | 62 ++++++--- .../media/event/mpd_notify_muxer_listener.h | 5 +- .../mpd_notify_muxer_listener_unittest.cc | 85 ++++++++++++- .../media/event/muxer_listener_internal.cc | 16 +++ .../media/event/muxer_listener_internal.h | 5 + packager/mpd/base/mock_mpd_notifier.h | 6 +- packager/mpd/base/mpd_notifier.h | 7 + packager/mpd/base/representation.cc | 2 +- packager/mpd/base/representation.h | 2 + packager/mpd/base/simple_mpd_notifier.cc | 16 +++ packager/mpd/base/simple_mpd_notifier.h | 2 + 14 files changed, 315 insertions(+), 118 deletions(-) diff --git a/packager/media/event/hls_notify_muxer_listener.cc b/packager/media/event/hls_notify_muxer_listener.cc index 285b14bed0..124c3ac0d7 100644 --- a/packager/media/event/hls_notify_muxer_listener.cc +++ b/packager/media/event/hls_notify_muxer_listener.cc @@ -44,7 +44,7 @@ void HlsNotifyMuxerListener::OnEncryptionInfoReady( const std::vector& key_id, const std::vector& iv, const std::vector& key_system_infos) { - if (!media_started_) { + if (!stream_id_) { next_key_id_ = key_id; next_iv_ = iv; next_key_system_infos_ = key_system_infos; @@ -53,13 +53,13 @@ void HlsNotifyMuxerListener::OnEncryptionInfoReady( } for (const ProtectionSystemSpecificInfo& info : key_system_infos) { const bool result = hls_notifier_->NotifyEncryptionUpdate( - stream_id_, key_id, info.system_id, iv, info.psshs); + stream_id_.value(), key_id, info.system_id, iv, info.psshs); LOG_IF(WARNING, !result) << "Failed to add encryption info."; } } void HlsNotifyMuxerListener::OnEncryptionStart() { - if (!media_started_) { + if (!stream_id_) { must_notify_encryption_start_ = true; return; } @@ -71,7 +71,7 @@ void HlsNotifyMuxerListener::OnEncryptionStart() { for (const ProtectionSystemSpecificInfo& info : next_key_system_infos_) { const bool result = hls_notifier_->NotifyEncryptionUpdate( - stream_id_, next_key_id_, info.system_id, next_iv_, info.psshs); + stream_id_.value(), next_key_id_, info.system_id, next_iv_, info.psshs); LOG_IF(WARNING, !result) << "Failed to add encryption info"; } next_key_id_.clear(); @@ -84,31 +84,36 @@ void HlsNotifyMuxerListener::OnMediaStart(const MuxerOptions& muxer_options, const StreamInfo& stream_info, uint32_t time_scale, ContainerType container_type) { - MediaInfo media_info; + std::unique_ptr media_info(new MediaInfo); if (!internal::GenerateMediaInfo(muxer_options, stream_info, time_scale, - container_type, &media_info)) { + container_type, media_info.get())) { LOG(ERROR) << "Failed to generate MediaInfo from input."; return; } if (protection_scheme_ != FOURCC_NULL) { internal::SetContentProtectionFields(protection_scheme_, next_key_id_, - next_key_system_infos_, &media_info); + next_key_system_infos_, + media_info.get()); } - media_info_ = media_info; - if (!media_info_.has_segment_template()) { + // The content may be splitted into multiple files, but their MediaInfo + // should be compatible. + if (media_info_ && + !internal::IsMediaInfoCompatible(*media_info, *media_info_)) { + LOG(WARNING) << "Incompatible MediaInfo " << media_info->ShortDebugString() + << " vs " << media_info_->ShortDebugString() + << ". The result manifest may not be playable."; + } + media_info_ = std::move(media_info); + + if (!media_info_->has_segment_template()) { return; } - const bool result = hls_notifier_->NotifyNewStream( - media_info, playlist_name_, ext_x_media_name_, ext_x_media_group_id_, - &stream_id_); - if (!result) { - LOG(WARNING) << "Failed to notify new stream."; + if (!NotifyNewStream()) return; - } + DCHECK(stream_id_); - media_started_ = true; if (must_notify_encryption_start_) { OnEncryptionStart(); } @@ -118,36 +123,35 @@ void HlsNotifyMuxerListener::OnSampleDurationReady(uint32_t sample_duration) {} void HlsNotifyMuxerListener::OnMediaEnd(const MediaRanges& media_ranges, float duration_seconds) { + DCHECK(media_info_); // TODO(kqyang): Should we just Flush here to avoid calling Flush explicitly? // Don't flush the notifier here. Flushing here would write all the playlists // before all Media Playlists are read. Which could cause problems // setting the correct EXT-X-TARGETDURATION. - if (media_info_.has_segment_template()) { + if (media_info_->has_segment_template()) { return; } if (media_ranges.init_range) { - shaka::Range* init_range = media_info_.mutable_init_range(); + shaka::Range* init_range = media_info_->mutable_init_range(); init_range->set_begin(media_ranges.init_range.value().start); init_range->set_end(media_ranges.init_range.value().end); } if (media_ranges.index_range) { - shaka::Range* index_range = media_info_.mutable_index_range(); + shaka::Range* index_range = media_info_->mutable_index_range(); index_range->set_begin(media_ranges.index_range.value().start); index_range->set_end(media_ranges.index_range.value().end); } - // TODO(rkuroiwa): Make this a method. This is the same as OnMediaStart(). - const bool result = hls_notifier_->NotifyNewStream( - media_info_, playlist_name_, ext_x_media_name_, ext_x_media_group_id_, - &stream_id_); - if (!result) { - LOG(WARNING) << "Failed to notify new stream for VOD."; - return; + if (!stream_id_) { + if (!NotifyNewStream()) + return; + DCHECK(stream_id_); + } else { + // HLS is not interested in MediaInfo update. } // TODO(rkuroiwa); Keep track of which (sub)segments are encrypted so that the // notification is sent right before the enecrypted (sub)segments. - media_started_ = true; if (must_notify_encryption_start_) { OnEncryptionStart(); } @@ -163,7 +167,7 @@ void HlsNotifyMuxerListener::OnMediaEnd(const MediaRanges& media_ranges, if (subsegment_index < num_subsegments) { const Range& range = subsegment_ranges[subsegment_index]; hls_notifier_->NotifyNewSegment( - stream_id_, media_info_.media_file_name(), + stream_id_.value(), media_info_->media_file_name(), event_info.segment_info.start_time, event_info.segment_info.duration, range.start, range.end + 1 - range.start); @@ -175,14 +179,14 @@ void HlsNotifyMuxerListener::OnMediaEnd(const MediaRanges& media_ranges, const uint64_t segment_start_offset = subsegment_ranges[subsegment_index].start; hls_notifier_->NotifyKeyFrame( - stream_id_, event_info.key_frame.timestamp, + stream_id_.value(), event_info.key_frame.timestamp, segment_start_offset + event_info.key_frame.start_offset_in_segment, event_info.key_frame.size); } break; case EventInfoType::kCue: - hls_notifier_->NotifyCueEvent(stream_id_, + hls_notifier_->NotifyCueEvent(stream_id_.value(), event_info.cue_event_info.timestamp); break; } @@ -194,13 +198,14 @@ void HlsNotifyMuxerListener::OnMediaEnd(const MediaRanges& media_ranges, << event_info_.size() << ")."; } } + event_info_.clear(); } void HlsNotifyMuxerListener::OnNewSegment(const std::string& file_name, uint64_t start_time, uint64_t duration, uint64_t segment_file_size) { - if (!media_info_.has_segment_template()) { + if (!media_info_->has_segment_template()) { EventInfo event_info; event_info.type = EventInfoType::kSegment; event_info.segment_info = {start_time, duration, segment_file_size}; @@ -209,8 +214,8 @@ void HlsNotifyMuxerListener::OnNewSegment(const std::string& file_name, // For multisegment, it always starts from the beginning of the file. const size_t kStartingByteOffset = 0u; const bool result = hls_notifier_->NotifyNewSegment( - stream_id_, file_name, start_time, duration, kStartingByteOffset, - segment_file_size); + stream_id_.value(), file_name, start_time, duration, + kStartingByteOffset, segment_file_size); LOG_IF(WARNING, !result) << "Failed to add new segment."; } } @@ -220,14 +225,14 @@ void HlsNotifyMuxerListener::OnKeyFrame(uint64_t timestamp, uint64_t size) { if (!iframes_only_) return; - if (!media_info_.has_segment_template()) { + if (!media_info_->has_segment_template()) { EventInfo event_info; event_info.type = EventInfoType::kKeyFrame; event_info.key_frame = {timestamp, start_byte_offset, size}; event_info_.push_back(event_info); } else { - const bool result = hls_notifier_->NotifyKeyFrame(stream_id_, timestamp, - start_byte_offset, size); + const bool result = hls_notifier_->NotifyKeyFrame( + stream_id_.value(), timestamp, start_byte_offset, size); LOG_IF(WARNING, !result) << "Failed to add new segment."; } } @@ -235,15 +240,30 @@ void HlsNotifyMuxerListener::OnKeyFrame(uint64_t timestamp, void HlsNotifyMuxerListener::OnCueEvent(uint64_t timestamp, const std::string& cue_data) { // Not using |cue_data| at this moment. - if (!media_info_.has_segment_template()) { + if (!media_info_->has_segment_template()) { EventInfo event_info; event_info.type = EventInfoType::kCue; event_info.cue_event_info = {timestamp}; event_info_.push_back(event_info); } else { - hls_notifier_->NotifyCueEvent(stream_id_, timestamp); + hls_notifier_->NotifyCueEvent(stream_id_.value(), timestamp); } } +bool HlsNotifyMuxerListener::NotifyNewStream() { + DCHECK(media_info_); + + 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); + if (!result) { + LOG(WARNING) << "Failed to notify new stream for VOD."; + return false; + } + stream_id_ = stream_id; + return true; +} + } // namespace media } // namespace shaka diff --git a/packager/media/event/hls_notify_muxer_listener.h b/packager/media/event/hls_notify_muxer_listener.h index a6f924d876..268b4f051f 100644 --- a/packager/media/event/hls_notify_muxer_listener.h +++ b/packager/media/event/hls_notify_muxer_listener.h @@ -7,8 +7,10 @@ #ifndef PACKAGER_MEDIA_EVENT_HLS_NOTIFY_MUXER_LISTENER_H_ #define PACKAGER_MEDIA_EVENT_HLS_NOTIFY_MUXER_LISTENER_H_ +#include #include +#include "packager/base/optional.h" #include "packager/media/event/event_info.h" #include "packager/media/event/muxer_listener.h" #include "packager/mpd/base/media_info.pb.h" @@ -71,14 +73,15 @@ class HlsNotifyMuxerListener : public MuxerListener { HlsNotifyMuxerListener(const HlsNotifyMuxerListener&) = delete; HlsNotifyMuxerListener& operator=(const HlsNotifyMuxerListener&) = delete; + bool NotifyNewStream(); + const std::string playlist_name_; const bool iframes_only_; const std::string ext_x_media_name_; const std::string ext_x_media_group_id_; hls::HlsNotifier* const hls_notifier_; - uint32_t stream_id_ = 0; + base::Optional stream_id_; - bool media_started_ = false; bool must_notify_encryption_start_ = false; // Cached encryption info before OnMediaStart() is called. std::vector next_key_id_; @@ -88,7 +91,7 @@ class HlsNotifyMuxerListener : public MuxerListener { // MediaInfo passed to Notifier::OnNewStream(). Mainly for single segment // playlists. - MediaInfo media_info_; + std::unique_ptr media_info_; // Even information for delayed function calls (NotifyNewSegment and // NotifyCueEvent) after NotifyNewStream is called in OnMediaEnd. Only needed // for on-demand as the functions are called immediately in live mode. diff --git a/packager/media/event/hls_notify_muxer_listener_unittest.cc b/packager/media/event/hls_notify_muxer_listener_unittest.cc index e3231ed8ec..6cabc88794 100644 --- a/packager/media/event/hls_notify_muxer_listener_unittest.cc +++ b/packager/media/event/hls_notify_muxer_listener_unittest.cc @@ -18,6 +18,7 @@ namespace media { using ::testing::_; using ::testing::Bool; +using ::testing::InSequence; using ::testing::Return; using ::testing::StrEq; using ::testing::TestWithParam; @@ -114,6 +115,23 @@ class HlsNotifyMuxerListenerTest : public ::testing::Test { kDefaultGroupId, &mock_notifier_) {} + MuxerListener::MediaRanges GetMediaRanges( + const std::vector& segment_ranges) { + MuxerListener::MediaRanges ranges; + // We don't care about init range and index range values. + Range init_range; + init_range.start = 0; + init_range.end = 100; + Range index_range; + index_range.start = 101; + index_range.end = 200; + + ranges.init_range = init_range; + ranges.index_range = index_range; + ranges.subsegment_ranges = segment_ranges; + return ranges; + } + MockHlsNotifier mock_notifier_; HlsNotifyMuxerListener listener_; }; @@ -289,12 +307,6 @@ TEST_F(HlsNotifyMuxerListenerTest, OnSampleDurationReady) { listener_.OnSampleDurationReady(2340); } -// Make sure it doesn't crash. -TEST_F(HlsNotifyMuxerListenerTest, OnMediaEnd) { - // None of these values matter, they are not used. - listener_.OnMediaEnd(MuxerListener::MediaRanges(), 0); -} - TEST_F(HlsNotifyMuxerListenerTest, OnNewSegmentAndCueEvent) { ON_CALL(mock_notifier_, NotifyNewStream(_, _, _, _, _)) .WillByDefault(Return(true)); @@ -332,29 +344,60 @@ TEST_F(HlsNotifyMuxerListenerTest, NoSegmentTemplateOnMediaEnd) { listener_.OnCueEvent(kCueStartTime, "dummy cue data"); listener_.OnNewSegment("filename.mp4", kSegmentStartTime, kSegmentDuration, kSegmentSize); - MuxerListener::MediaRanges ranges; - Range init_range; - init_range.start = 0; - init_range.end = 100; - Range index_range; - index_range.start = 101; - index_range.end = 200; - // Only one segment range for this test. - std::vector segment_ranges; - Range segment_range; - segment_range.start = kSegmentStartOffset; - segment_range.end = kSegmentStartOffset + kSegmentSize - 1; - segment_ranges.push_back(segment_range); - ranges.init_range = init_range; - ranges.index_range = index_range; - ranges.subsegment_ranges = segment_ranges; EXPECT_CALL(mock_notifier_, NotifyCueEvent(_, kCueStartTime)); EXPECT_CALL( mock_notifier_, NotifyNewSegment(_, StrEq("filename.mp4"), kSegmentStartTime, kSegmentDuration, kSegmentStartOffset, kSegmentSize)); - listener_.OnMediaEnd(ranges, 200000); + listener_.OnMediaEnd( + GetMediaRanges( + {{kSegmentStartOffset, kSegmentStartOffset + kSegmentSize - 1}}), + 200000); +} + +// Verify the event handling with multiple files, i.e. multiple OnMediaStart and +// OnMediaEnd calls. +TEST_F(HlsNotifyMuxerListenerTest, NoSegmentTemplateOnMediaEndTwice) { + VideoStreamInfoParameters video_params = GetDefaultVideoStreamInfoParams(); + std::shared_ptr video_stream_info = + CreateVideoStreamInfo(video_params); + MuxerOptions muxer_options1; + muxer_options1.output_file_name = "filename1.mp4"; + MuxerOptions muxer_options2 = muxer_options1; + muxer_options2.output_file_name = "filename2.mp4"; + + InSequence in_sequence; + + // Event flow for first file. + listener_.OnMediaStart(muxer_options1, *video_stream_info, 90000, + MuxerListener::kContainerMpeg2ts); + listener_.OnNewSegment("filename1.mp4", kSegmentStartTime, kSegmentDuration, + kSegmentSize); + listener_.OnCueEvent(kCueStartTime, "dummy cue data"); + + EXPECT_CALL(mock_notifier_, NotifyNewStream(_, _, _, _, _)) + .WillOnce(Return(true)); + EXPECT_CALL(mock_notifier_, NotifyNewSegment(_, StrEq("filename1.mp4"), + kSegmentStartTime, _, _, _)); + EXPECT_CALL(mock_notifier_, NotifyCueEvent(_, kCueStartTime)); + listener_.OnMediaEnd( + GetMediaRanges( + {{kSegmentStartOffset, kSegmentStartOffset + kSegmentSize - 1}}), + 200000); + + // Event flow for second file. + listener_.OnMediaStart(muxer_options2, *video_stream_info, 90000, + MuxerListener::kContainerMpeg2ts); + listener_.OnNewSegment("filename2.mp4", kSegmentStartTime + kSegmentDuration, + kSegmentDuration, kSegmentSize); + EXPECT_CALL(mock_notifier_, + NotifyNewSegment(_, StrEq("filename2.mp4"), + kSegmentStartTime + kSegmentDuration, _, _, _)); + listener_.OnMediaEnd( + GetMediaRanges( + {{kSegmentStartOffset, kSegmentStartOffset + kSegmentSize - 1}}), + 200000); } // Verify that when there is a mismatch in the number of calls to @@ -374,35 +417,16 @@ TEST_F(HlsNotifyMuxerListenerTest, listener_.OnNewSegment("filename.mp4", kSegmentStartTime, kSegmentDuration, kSegmentSize); - MuxerListener::MediaRanges ranges; - Range init_range; - init_range.start = 0; - init_range.end = 100; - Range index_range; - index_range.start = 101; - index_range.end = 200; - // Only one segment range for this test. - std::vector segment_ranges; - - Range segment_range1; - segment_range1.start = kSegmentStartOffset; - segment_range1.end = kSegmentStartOffset + kSegmentSize - 1; - segment_ranges.push_back(segment_range1); - - Range segment_range2; - segment_range2.start = segment_range1.end + 1; - segment_range2.end = segment_range2.start + 109823; - segment_ranges.push_back(segment_range2); - - ranges.init_range = init_range; - ranges.index_range = index_range; - ranges.subsegment_ranges = segment_ranges; - EXPECT_CALL( mock_notifier_, NotifyNewSegment(_, StrEq("filename.mp4"), kSegmentStartTime, kSegmentDuration, kSegmentStartOffset, kSegmentSize)); - listener_.OnMediaEnd(ranges, 200000); + listener_.OnMediaEnd( + GetMediaRanges( + {{kSegmentStartOffset, kSegmentStartOffset + kSegmentSize - 1}, + {kSegmentStartOffset + kSegmentSize, + kSegmentStartOffset + kSegmentSize * 2 - 1}}), + 200000); } class HlsNotifyMuxerListenerKeyFrameTest : public TestWithParam { diff --git a/packager/media/event/mpd_notify_muxer_listener.cc b/packager/media/event/mpd_notify_muxer_listener.cc index 9f6aed0cb1..e76cf15c74 100644 --- a/packager/media/event/mpd_notify_muxer_listener.cc +++ b/packager/media/event/mpd_notify_muxer_listener.cc @@ -20,7 +20,7 @@ namespace shaka { namespace media { MpdNotifyMuxerListener::MpdNotifyMuxerListener(MpdNotifier* mpd_notifier) - : mpd_notifier_(mpd_notifier), notification_id_(0), is_encrypted_(false) { + : mpd_notifier_(mpd_notifier), is_encrypted_(false) { DCHECK(mpd_notifier); DCHECK(mpd_notifier->dash_profile() == DashProfile::kOnDemand || mpd_notifier->dash_profile() == DashProfile::kLive); @@ -43,12 +43,14 @@ void MpdNotifyMuxerListener::OnEncryptionInfoReady( is_encrypted_ = true; return; } + if (!notification_id_) + return; DCHECK_EQ(protection_scheme, protection_scheme_); for (const ProtectionSystemSpecificInfo& info : key_system_info) { - std::string drm_uuid = internal::CreateUUIDString(info.system_id); + const std::string drm_uuid = internal::CreateUUIDString(info.system_id); bool updated = mpd_notifier_->NotifyEncryptionUpdate( - notification_id_, drm_uuid, key_id, info.psshs); + notification_id_.value(), drm_uuid, key_id, info.psshs); LOG_IF(WARNING, !updated) << "Failed to update encryption info."; } } @@ -75,11 +77,21 @@ void MpdNotifyMuxerListener::OnMediaStart( key_system_info_, media_info.get()); } + // The content may be splitted into multiple files, but their MediaInfo + // should be compatible. + if (media_info_ && + !internal::IsMediaInfoCompatible(*media_info, *media_info_)) { + LOG(WARNING) << "Incompatible MediaInfo \n" + << media_info->ShortDebugString() << "\n vs \n" + << media_info_->ShortDebugString() + << "\nThe result manifest may not be playable."; + } + media_info_ = std::move(media_info); + if (mpd_notifier_->dash_profile() == DashProfile::kLive) { - // TODO(kqyang): Check return result. - mpd_notifier_->NotifyNewContainer(*media_info, ¬ification_id_); - } else { - media_info_ = std::move(media_info); + if (!NotifyNewContainer()) + return; + DCHECK(notification_id_); } } @@ -88,7 +100,8 @@ void MpdNotifyMuxerListener::OnMediaStart( void MpdNotifyMuxerListener::OnSampleDurationReady( uint32_t sample_duration) { if (mpd_notifier_->dash_profile() == DashProfile::kLive) { - mpd_notifier_->NotifySampleDuration(notification_id_, sample_duration); + mpd_notifier_->NotifySampleDuration(notification_id_.value(), + sample_duration); return; } @@ -126,16 +139,21 @@ void MpdNotifyMuxerListener::OnMediaEnd(const MediaRanges& media_ranges, return; } - uint32_t id; - // TODO(kqyang): Check return result. - mpd_notifier_->NotifyNewContainer(*media_info_, &id); + if (notification_id_) { + mpd_notifier_->NotifyMediaInfoUpdate(notification_id_.value(), + *media_info_); + } else { + if (!NotifyNewContainer()) + return; + DCHECK(notification_id_); + } // TODO(rkuroiwa): Use media_ranges.subsegment_ranges instead of caching the // subsegments. for (const auto& event_info : event_info_) { switch (event_info.type) { case EventInfoType::kSegment: mpd_notifier_->NotifyNewSegment( - id, event_info.segment_info.start_time, + notification_id_.value(), event_info.segment_info.start_time, event_info.segment_info.duration, event_info.segment_info.segment_file_size); break; @@ -143,7 +161,8 @@ void MpdNotifyMuxerListener::OnMediaEnd(const MediaRanges& media_ranges, // NO-OP for DASH. break; case EventInfoType::kCue: - mpd_notifier_->NotifyCueEvent(id, event_info.cue_event_info.timestamp); + mpd_notifier_->NotifyCueEvent(notification_id_.value(), + event_info.cue_event_info.timestamp); break; } } @@ -156,9 +175,8 @@ void MpdNotifyMuxerListener::OnNewSegment(const std::string& file_name, uint64_t duration, uint64_t segment_file_size) { if (mpd_notifier_->dash_profile() == DashProfile::kLive) { - // TODO(kqyang): Check return result. - mpd_notifier_->NotifyNewSegment( - notification_id_, start_time, duration, segment_file_size); + mpd_notifier_->NotifyNewSegment(notification_id_.value(), start_time, + duration, segment_file_size); if (mpd_notifier_->mpd_type() == MpdType::kDynamic) mpd_notifier_->Flush(); } else { @@ -179,7 +197,7 @@ void MpdNotifyMuxerListener::OnCueEvent(uint64_t timestamp, const std::string& cue_data) { // Not using |cue_data| at this moment. if (mpd_notifier_->dash_profile() == DashProfile::kLive) { - mpd_notifier_->NotifyCueEvent(notification_id_, timestamp); + mpd_notifier_->NotifyCueEvent(notification_id_.value(), timestamp); } else { EventInfo event_info; event_info.type = EventInfoType::kCue; @@ -188,5 +206,15 @@ void MpdNotifyMuxerListener::OnCueEvent(uint64_t timestamp, } } +bool MpdNotifyMuxerListener::NotifyNewContainer() { + uint32_t notification_id; + if (!mpd_notifier_->NotifyNewContainer(*media_info_, ¬ification_id)) { + LOG(ERROR) << "Failed to notify MpdNotifier."; + return false; + } + notification_id_ = notification_id; + return true; +} + } // namespace media } // namespace shaka diff --git a/packager/media/event/mpd_notify_muxer_listener.h b/packager/media/event/mpd_notify_muxer_listener.h index 3b186d7fb6..dc035691f5 100644 --- a/packager/media/event/mpd_notify_muxer_listener.h +++ b/packager/media/event/mpd_notify_muxer_listener.h @@ -12,6 +12,7 @@ #include #include +#include "packager/base/optional.h" #include "packager/media/base/muxer_options.h" #include "packager/media/event/event_info.h" #include "packager/media/event/muxer_listener.h" @@ -60,8 +61,10 @@ class MpdNotifyMuxerListener : public MuxerListener { MpdNotifyMuxerListener(const MpdNotifyMuxerListener&) = delete; MpdNotifyMuxerListener& operator=(const MpdNotifyMuxerListener&) = delete; + bool NotifyNewContainer(); + MpdNotifier* const mpd_notifier_ = nullptr; - uint32_t notification_id_ = 0; + base::Optional notification_id_; std::unique_ptr media_info_; bool is_encrypted_ = false; diff --git a/packager/media/event/mpd_notify_muxer_listener_unittest.cc b/packager/media/event/mpd_notify_muxer_listener_unittest.cc index 5c1f0ee02f..52540ac036 100644 --- a/packager/media/event/mpd_notify_muxer_listener_unittest.cc +++ b/packager/media/event/mpd_notify_muxer_listener_unittest.cc @@ -23,6 +23,7 @@ using ::testing::_; using ::testing::InSequence; +using ::testing::Return; namespace shaka { @@ -91,6 +92,11 @@ class MpdNotifyMuxerListenerTest : public ::testing::TestWithParam { std::unique_ptr notifier_; }; +MATCHER_P(EqualsProto, message, "") { + *result_listener << arg.ShortDebugString(); + return ::google::protobuf::util::MessageDifferencer::Equals(arg, message); +} + MATCHER_P(ExpectMediaInfoEq, expected_text_format, "") { const MediaInfo expected = ConvertToMediaInfo(expected_text_format); *result_listener << arg.ShortDebugString(); @@ -112,7 +118,8 @@ TEST_F(MpdNotifyMuxerListenerTest, VodClearContent) { ::testing::Mock::VerifyAndClearExpectations(notifier_.get()); EXPECT_CALL(*notifier_, NotifyNewContainer( - ExpectMediaInfoEq(kExpectedDefaultMediaInfo), _)); + ExpectMediaInfoEq(kExpectedDefaultMediaInfo), _)) + .WillOnce(Return(true)); EXPECT_CALL(*notifier_, Flush()); FireOnMediaEndWithParams(GetDefaultOnMediaEndParams()); } @@ -177,7 +184,8 @@ TEST_F(MpdNotifyMuxerListenerTest, VodEncryptedContent) { ::testing::Mock::VerifyAndClearExpectations(notifier_.get()); EXPECT_CALL(*notifier_, - NotifyNewContainer(ExpectMediaInfoEq(kExpectedMediaInfo), _)); + NotifyNewContainer(ExpectMediaInfoEq(kExpectedMediaInfo), _)) + .WillOnce(Return(true)); EXPECT_CALL(*notifier_, Flush()); FireOnMediaEndWithParams(GetDefaultOnMediaEndParams()); } @@ -224,7 +232,8 @@ TEST_F(MpdNotifyMuxerListenerTest, VodOnSampleDurationReady) { ::testing::Mock::VerifyAndClearExpectations(notifier_.get()); EXPECT_CALL(*notifier_, - NotifyNewContainer(ExpectMediaInfoEq(kExpectedMediaInfo), _)); + NotifyNewContainer(ExpectMediaInfoEq(kExpectedMediaInfo), _)) + .WillOnce(Return(true)); EXPECT_CALL(*notifier_, Flush()); FireOnMediaEndWithParams(GetDefaultOnMediaEndParams()); } @@ -258,7 +267,8 @@ TEST_F(MpdNotifyMuxerListenerTest, VodOnNewSegment) { InSequence s; EXPECT_CALL(*notifier_, NotifyNewContainer( - ExpectMediaInfoEq(kExpectedDefaultMediaInfo), _)); + ExpectMediaInfoEq(kExpectedDefaultMediaInfo), _)) + .WillOnce(Return(true)); EXPECT_CALL(*notifier_, NotifyNewSegment(_, kStartTime1, kDuration1, kSegmentFileSize1)); EXPECT_CALL(*notifier_, NotifyCueEvent(_, kStartTime2)); @@ -268,6 +278,69 @@ TEST_F(MpdNotifyMuxerListenerTest, VodOnNewSegment) { FireOnMediaEndWithParams(GetDefaultOnMediaEndParams()); } +// Verify the event handling with multiple files, i.e. multiple OnMediaStart and +// OnMediaEnd calls. +TEST_F(MpdNotifyMuxerListenerTest, VodMultipleFiles) { + SetupForVod(); + MuxerOptions muxer_options1; + SetDefaultMuxerOptions(&muxer_options1); + muxer_options1.output_file_name = "test_output1.mp4"; + MuxerOptions muxer_options2 = muxer_options1; + muxer_options2.output_file_name = "test_output2.mp4"; + + MediaInfo expected_media_info1 = + ConvertToMediaInfo(kExpectedDefaultMediaInfo); + expected_media_info1.set_media_file_name("test_output1.mp4"); + MediaInfo expected_media_info2 = expected_media_info1; + expected_media_info2.set_media_file_name("test_output2.mp4"); + + VideoStreamInfoParameters video_params = GetDefaultVideoStreamInfoParams(); + std::shared_ptr video_stream_info = + CreateVideoStreamInfo(video_params); + + const uint64_t kStartTime1 = 0u; + const uint64_t kDuration1 = 1000u; + const uint64_t kSegmentFileSize1 = 29812u; + const uint64_t kStartTime2 = 1001u; + const uint64_t kDuration2 = 3787u; + const uint64_t kSegmentFileSize2 = 83743u; + + // Expectation for first file before OnMediaEnd. + EXPECT_CALL(*notifier_, NotifyNewContainer(_, _)).Times(0); + EXPECT_CALL(*notifier_, NotifyNewSegment(_, _, _, _)).Times(0); + listener_->OnMediaStart(muxer_options1, *video_stream_info, + kDefaultReferenceTimeScale, + MuxerListener::kContainerMp4); + listener_->OnNewSegment("", kStartTime1, kDuration1, kSegmentFileSize1); + listener_->OnCueEvent(kStartTime2, "dummy cue data"); + ::testing::Mock::VerifyAndClearExpectations(notifier_.get()); + + // Expectation for first file OnMediaEnd. + InSequence s; + EXPECT_CALL(*notifier_, + NotifyNewContainer(EqualsProto(expected_media_info1), _)) + .WillOnce(Return(true)); + EXPECT_CALL(*notifier_, + NotifyNewSegment(_, kStartTime1, kDuration1, kSegmentFileSize1)); + EXPECT_CALL(*notifier_, NotifyCueEvent(_, kStartTime2)); + EXPECT_CALL(*notifier_, Flush()); + FireOnMediaEndWithParams(GetDefaultOnMediaEndParams()); + + // Expectation for second file before OnMediaEnd. + listener_->OnMediaStart(muxer_options2, *video_stream_info, + kDefaultReferenceTimeScale, + MuxerListener::kContainerMp4); + listener_->OnNewSegment("", kStartTime2, kDuration2, kSegmentFileSize2); + + // Expectation for second file OnMediaEnd. + EXPECT_CALL(*notifier_, + NotifyMediaInfoUpdate(_, EqualsProto(expected_media_info2))); + EXPECT_CALL(*notifier_, + NotifyNewSegment(_, kStartTime2, kDuration2, kSegmentFileSize2)); + EXPECT_CALL(*notifier_, Flush()); + FireOnMediaEndWithParams(GetDefaultOnMediaEndParams()); +} + // Live without key rotation. Note that OnEncryptionInfoReady() is called before // OnMediaStart() but no more calls. TEST_P(MpdNotifyMuxerListenerTest, LiveNoKeyRotation) { @@ -314,7 +387,7 @@ TEST_P(MpdNotifyMuxerListenerTest, LiveNoKeyRotation) { EXPECT_CALL(*notifier_, NotifyEncryptionUpdate(_, _, _, _)).Times(0); EXPECT_CALL(*notifier_, NotifyNewContainer(ExpectMediaInfoEq(kExpectedMediaInfo), _)) - .Times(1); + .WillOnce(Return(true)); EXPECT_CALL(*notifier_, NotifyNewSegment(_, kStartTime1, kDuration1, kSegmentFileSize1)); // Flush should only be called once in OnMediaEnd. @@ -385,7 +458,7 @@ TEST_P(MpdNotifyMuxerListenerTest, LiveWithKeyRotation) { InSequence s; EXPECT_CALL(*notifier_, NotifyNewContainer(ExpectMediaInfoEq(kExpectedMediaInfo), _)) - .Times(1); + .WillOnce(Return(true)); EXPECT_CALL(*notifier_, NotifyEncryptionUpdate(_, _, _, _)).Times(1); EXPECT_CALL(*notifier_, NotifyNewSegment(_, kStartTime1, kDuration1, kSegmentFileSize1)); diff --git a/packager/media/event/muxer_listener_internal.cc b/packager/media/event/muxer_listener_internal.cc index e8980a78a6..d824886aaf 100644 --- a/packager/media/event/muxer_listener_internal.cc +++ b/packager/media/event/muxer_listener_internal.cc @@ -6,6 +6,7 @@ #include "packager/media/event/muxer_listener_internal.h" +#include #include #include "packager/base/logging.h" @@ -19,6 +20,8 @@ #include "packager/media/codecs/ec3_audio_util.h" #include "packager/mpd/base/media_info.pb.h" +using ::google::protobuf::util::MessageDifferencer; + namespace shaka { namespace media { namespace internal { @@ -176,6 +179,19 @@ bool GenerateMediaInfo(const MuxerOptions& muxer_options, return true; } +bool IsMediaInfoCompatible(const MediaInfo& media_info1, + const MediaInfo& media_info2) { + return media_info1.reference_time_scale() == + media_info2.reference_time_scale() && + media_info1.container_type() == media_info2.container_type() && + MessageDifferencer::Equals(media_info1.video_info(), + media_info2.video_info()) && + MessageDifferencer::Equals(media_info1.audio_info(), + media_info2.audio_info()) && + MessageDifferencer::Equals(media_info1.text_info(), + media_info2.text_info()); +} + bool SetVodInformation(const MuxerListener::MediaRanges& media_ranges, float duration_seconds, MediaInfo* media_info) { diff --git a/packager/media/event/muxer_listener_internal.h b/packager/media/event/muxer_listener_internal.h index d08dcce2a1..a803928b3e 100644 --- a/packager/media/event/muxer_listener_internal.h +++ b/packager/media/event/muxer_listener_internal.h @@ -33,6 +33,11 @@ bool GenerateMediaInfo(const MuxerOptions& muxer_options, MuxerListener::ContainerType container_type, MediaInfo* media_info); +/// @return True if @a media_info1 and @a media_info2 are compatible. MediaInfos +/// are considered to be compatible if codec and container are the same. +bool IsMediaInfoCompatible(const MediaInfo& media_info1, + const MediaInfo& media_info2); + /// @param[in,out] media_info points to the MediaInfo object to be filled. /// @return true on success, false otherwise. bool SetVodInformation(const MuxerListener::MediaRanges& media_ranges, diff --git a/packager/mpd/base/mock_mpd_notifier.h b/packager/mpd/base/mock_mpd_notifier.h index f40e4aa25b..0726f52092 100644 --- a/packager/mpd/base/mock_mpd_notifier.h +++ b/packager/mpd/base/mock_mpd_notifier.h @@ -37,10 +37,8 @@ class MockMpdNotifier : public MpdNotifier { const std::string& drm_uuid, const std::vector& new_key_id, const std::vector& new_pssh)); - MOCK_METHOD2( - AddContentProtectionElement, - bool(uint32_t container_id, - const ContentProtectionElement& content_protection_element)); + MOCK_METHOD2(NotifyMediaInfoUpdate, + bool(uint32_t container_id, const MediaInfo& media_info)); MOCK_METHOD0(Flush, bool()); }; diff --git a/packager/mpd/base/mpd_notifier.h b/packager/mpd/base/mpd_notifier.h index 41e550ab8b..e9a0cb36d9 100644 --- a/packager/mpd/base/mpd_notifier.h +++ b/packager/mpd/base/mpd_notifier.h @@ -92,6 +92,13 @@ class MpdNotifier { const std::vector& new_key_id, const std::vector& new_pssh) = 0; + /// @param container_id Container ID obtained from calling + /// NotifyNewContainer(). + /// @param media_info is the new MediaInfo. Note that codec related + /// information cannot be updated. + virtual bool NotifyMediaInfoUpdate(uint32_t container_id, + const MediaInfo& media_info) = 0; + /// Call this method to force a flush. Implementations might not write out /// the MPD to a stream (file, stdout, etc.) when the MPD is updated, this /// forces a flush. diff --git a/packager/mpd/base/representation.cc b/packager/mpd/base/representation.cc index 6227b0a8bc..44092d30bd 100644 --- a/packager/mpd/base/representation.cc +++ b/packager/mpd/base/representation.cc @@ -286,7 +286,7 @@ xml::scoped_xml_ptr Representation::GetXml() { if (HasVODOnlyFields(media_info_) && !representation.AddVODOnlyInfo(media_info_)) { - LOG(ERROR) << "Failed to add VOD segment info."; + LOG(ERROR) << "Failed to add VOD info."; return xml::scoped_xml_ptr(); } diff --git a/packager/mpd/base/representation.h b/packager/mpd/base/representation.h index fef1ca3aa4..012fff6127 100644 --- a/packager/mpd/base/representation.h +++ b/packager/mpd/base/representation.h @@ -145,6 +145,8 @@ class Representation { /// @return ID number for . uint32_t id() const { return id_; } + void set_media_info(const MediaInfo& media_info) { media_info_ = media_info; } + protected: /// @param media_info is a MediaInfo containing information on the media. /// @a media_info.bandwidth is required for 'static' profile. If @a diff --git a/packager/mpd/base/simple_mpd_notifier.cc b/packager/mpd/base/simple_mpd_notifier.cc index b03d3d38d1..9f93a4a642 100644 --- a/packager/mpd/base/simple_mpd_notifier.cc +++ b/packager/mpd/base/simple_mpd_notifier.cc @@ -165,6 +165,22 @@ bool SimpleMpdNotifier::NotifyEncryptionUpdate( return true; } +bool SimpleMpdNotifier::NotifyMediaInfoUpdate(uint32_t container_id, + const MediaInfo& media_info) { + base::AutoLock auto_lock(lock_); + auto it = representation_map_.find(container_id); + if (it == representation_map_.end()) { + LOG(ERROR) << "Unexpected container_id: " << container_id; + return false; + } + + MediaInfo adjusted_media_info(media_info); + MpdBuilder::MakePathsRelativeToMpd(output_path_, &adjusted_media_info); + + it->second->set_media_info(adjusted_media_info); + return true; +} + bool SimpleMpdNotifier::Flush() { base::AutoLock auto_lock(lock_); return WriteMpdToFile(output_path_, mpd_builder_.get()); diff --git a/packager/mpd/base/simple_mpd_notifier.h b/packager/mpd/base/simple_mpd_notifier.h index 0c10cfb377..749da46a1c 100644 --- a/packager/mpd/base/simple_mpd_notifier.h +++ b/packager/mpd/base/simple_mpd_notifier.h @@ -47,6 +47,8 @@ class SimpleMpdNotifier : public MpdNotifier { const std::string& drm_uuid, const std::vector& new_key_id, const std::vector& new_pssh) override; + bool NotifyMediaInfoUpdate(uint32_t container_id, + const MediaInfo& media_info) override; bool Flush() override; /// @}