From db74d6756e6fa3091813cde96c7906cd29d05431 Mon Sep 17 00:00:00 2001 From: KongQun Yang Date: Tue, 2 Jan 2018 16:10:33 -0800 Subject: [PATCH] Propage CueEvent to MpdNotifier/HlsNotifier Change-Id: I9828af11a28300d20cc8742251bbe1b4ebfdbad1 --- packager/hls/base/hls_notifier.h | 5 ++ packager/hls/base/simple_hls_notifier.cc | 6 ++ packager/hls/base/simple_hls_notifier.h | 1 + .../media/base/media_handler_test_base.cc | 25 ++++++ packager/media/base/media_handler_test_base.h | 88 ++++++++++++++----- packager/media/base/muxer.cc | 6 ++ packager/media/chunking/chunking_handler.cc | 31 +++++-- packager/media/chunking/chunking_handler.h | 1 + .../chunking/chunking_handler_unittest.cc | 38 ++++++++ .../media/event/combined_muxer_listener.cc | 7 ++ .../media/event/combined_muxer_listener.h | 1 + .../media/event/hls_notify_muxer_listener.cc | 28 ++++-- .../media/event/hls_notify_muxer_listener.h | 14 ++- .../hls_notify_muxer_listener_unittest.cc | 7 +- packager/media/event/mock_muxer_listener.h | 3 + .../media/event/mpd_notify_muxer_listener.cc | 25 ++++-- .../media/event/mpd_notify_muxer_listener.h | 12 ++- .../mpd_notify_muxer_listener_unittest.cc | 4 + packager/media/event/muxer_listener.h | 5 ++ .../vod_media_info_dump_muxer_listener.cc | 5 ++ .../vod_media_info_dump_muxer_listener.h | 1 + packager/mpd/base/mock_mpd_notifier.h | 1 + packager/mpd/base/mpd_notifier.h | 7 ++ packager/mpd/base/simple_mpd_notifier.cc | 6 ++ packager/mpd/base/simple_mpd_notifier.h | 1 + 25 files changed, 283 insertions(+), 45 deletions(-) diff --git a/packager/hls/base/hls_notifier.h b/packager/hls/base/hls_notifier.h index 073e8963c9..c385231059 100644 --- a/packager/hls/base/hls_notifier.h +++ b/packager/hls/base/hls_notifier.h @@ -57,6 +57,11 @@ class HlsNotifier { uint64_t start_byte_offset, uint64_t size) = 0; + /// @param stream_id is the value set by NotifyNewStream(). + /// @param timestamp is the timestamp of the CueEvent. + /// @return true on success, false otherwise. + virtual bool NotifyCueEvent(uint32_t stream_id, uint64_t timestamp) = 0; + /// @param stream_id is the value set by NotifyNewStream(). /// @param key_id is the key ID for the stream. /// @param system_id is the DRM system ID in e.g. PSSH boxes. For example this diff --git a/packager/hls/base/simple_hls_notifier.cc b/packager/hls/base/simple_hls_notifier.cc index e816dcba4c..c59903a612 100644 --- a/packager/hls/base/simple_hls_notifier.cc +++ b/packager/hls/base/simple_hls_notifier.cc @@ -367,6 +367,12 @@ bool SimpleHlsNotifier::NotifyNewSegment(uint32_t stream_id, return true; } +bool SimpleHlsNotifier::NotifyCueEvent(uint32_t container_id, + uint64_t timestamp) { + NOTIMPLEMENTED(); + return false; +} + bool SimpleHlsNotifier::NotifyEncryptionUpdate( uint32_t stream_id, const std::vector& key_id, diff --git a/packager/hls/base/simple_hls_notifier.h b/packager/hls/base/simple_hls_notifier.h index 5a57ba421c..ffe75e3185 100644 --- a/packager/hls/base/simple_hls_notifier.h +++ b/packager/hls/base/simple_hls_notifier.h @@ -73,6 +73,7 @@ class SimpleHlsNotifier : public HlsNotifier { uint64_t duration, uint64_t start_byte_offset, uint64_t size) override; + bool NotifyCueEvent(uint32_t container_id, uint64_t timestamp) override; bool NotifyEncryptionUpdate( uint32_t stream_id, const std::vector& key_id, diff --git a/packager/media/base/media_handler_test_base.cc b/packager/media/base/media_handler_test_base.cc index 2a6ce3df65..6791bc5294 100644 --- a/packager/media/base/media_handler_test_base.cc +++ b/packager/media/base/media_handler_test_base.cc @@ -60,6 +60,30 @@ const uint8_t kData[]{ namespace shaka { namespace media { +std::string StreamDataTypeToString(StreamDataType stream_data_type) { + switch (stream_data_type) { + case StreamDataType::kStreamInfo: + return "stream info"; + case StreamDataType::kMediaSample: + return "media sample"; + case StreamDataType::kTextSample: + return "text sample"; + case StreamDataType::kSegmentInfo: + return "segment info"; + case StreamDataType::kScte35Event: + return "scte35 event"; + case StreamDataType::kCueEvent: + return "cue event"; + case StreamDataType::kUnknown: + return "unknown"; + } + return "unknown"; +} + +std::string BoolToString(bool value) { + return value ? "true" : "false"; +} + bool FakeInputMediaHandler::ValidateOutputStreamIndex(size_t index) const { return true; } @@ -170,6 +194,7 @@ std::shared_ptr MediaHandlerTestBase::GetMediaSample( std::shared_ptr sample = MediaSample::CopyFrom(data, data_length, nullptr, 0, is_keyframe); sample->set_dts(timestamp); + sample->set_pts(timestamp); sample->set_duration(duration); return sample; diff --git a/packager/media/base/media_handler_test_base.h b/packager/media/base/media_handler_test_base.h index eab512ef1a..4b4d56a95b 100644 --- a/packager/media/base/media_handler_test_base.h +++ b/packager/media/base/media_handler_test_base.h @@ -4,6 +4,9 @@ // license that can be found in the LICENSE file or at // https://developers.google.com/open-source/licenses/bsd +#ifndef PACKAGER_MEDIA_BASE_MEDIA_HANDLER_TEST_BASE_H_ +#define PACKAGER_MEDIA_BASE_MEDIA_HANDLER_TEST_BASE_H_ + #include #include @@ -13,16 +16,25 @@ namespace shaka { namespace media { +std::string StreamDataTypeToString(StreamDataType stream_data_type); +std::string BoolToString(bool value); + MATCHER_P(IsStreamInfo, stream_index, "") { return arg->stream_index == stream_index && arg->stream_data_type == StreamDataType::kStreamInfo; } MATCHER_P3(IsStreamInfo, stream_index, time_scale, encrypted, "") { - *result_listener << "which is (" << stream_index << "," << time_scale << "," - << (encrypted ? "encrypted" : "not encrypted") << ")"; + if (arg->stream_data_type != StreamDataType::kStreamInfo) { + *result_listener << "which is " + << StreamDataTypeToString(arg->stream_data_type); + return false; + } + + *result_listener << "which is (" << arg->stream_index << "," + << arg->stream_info->time_scale() << "," + << BoolToString(arg->stream_info->is_encrypted()) << ")"; return arg->stream_index == stream_index && - arg->stream_data_type == StreamDataType::kStreamInfo && arg->stream_info->time_scale() == time_scale && arg->stream_info->is_encrypted() == encrypted; } @@ -34,12 +46,18 @@ MATCHER_P5(IsSegmentInfo, subsegment, encrypted, "") { - *result_listener << "which is (" << stream_index << "," << start_timestamp - << "," << duration << "," - << (subsegment ? "subsegment" : "not subsegment") << "," - << (encrypted ? "encrypted" : "not encrypted") << ")"; + if (arg->stream_data_type != StreamDataType::kSegmentInfo) { + *result_listener << "which is " + << StreamDataTypeToString(arg->stream_data_type); + return false; + } + + *result_listener << "which is (" << arg->stream_index << "," + << arg->segment_info->start_timestamp << "," + << arg->segment_info->duration << "," + << BoolToString(arg->segment_info->is_subsegment) << "," + << BoolToString(arg->segment_info->is_encrypted) << ")"; return arg->stream_index == stream_index && - arg->stream_data_type == StreamDataType::kSegmentInfo && arg->segment_info->start_timestamp == start_timestamp && arg->segment_info->duration == duration && arg->segment_info->is_subsegment == subsegment && @@ -54,12 +72,14 @@ MATCHER_P6(MatchEncryptionConfig, constant_iv, key_id, "") { - *result_listener << "which is (" << FourCCToString(protection_scheme) << "," - << static_cast(crypt_byte_block) << "," - << static_cast(skip_byte_block) << "," - << static_cast(per_sample_iv_size) << "," - << base::HexEncode(constant_iv.data(), constant_iv.size()) - << "," << base::HexEncode(key_id.data(), key_id.size()) + *result_listener << "which is (" << FourCCToString(arg.protection_scheme) + << "," << static_cast(arg.crypt_byte_block) << "," + << static_cast(arg.skip_byte_block) << "," + << static_cast(arg.per_sample_iv_size) << "," + << base::HexEncode(arg.constant_iv.data(), + arg.constant_iv.size()) + << "," + << base::HexEncode(arg.key_id.data(), arg.key_id.size()) << ")"; return arg.protection_scheme == protection_scheme && arg.crypt_byte_block == crypt_byte_block && @@ -69,25 +89,51 @@ MATCHER_P6(MatchEncryptionConfig, } MATCHER_P4(IsMediaSample, stream_index, timestamp, duration, encrypted, "") { - *result_listener << "which is (" << stream_index << "," << timestamp << "," - << duration << "," - << (encrypted ? "encrypted" : "not encrypted") << ")"; + if (arg->stream_data_type != StreamDataType::kMediaSample) { + *result_listener << "which is " + << StreamDataTypeToString(arg->stream_data_type); + return false; + } + *result_listener << "which is (" << stream_index << "," + << arg->media_sample->dts() << "," + << arg->media_sample->duration() << "," + << BoolToString(arg->media_sample->is_encrypted()) << ")"; return arg->stream_index == stream_index && - arg->stream_data_type == StreamDataType::kMediaSample && arg->media_sample->dts() == timestamp && arg->media_sample->duration() == duration && arg->media_sample->is_encrypted() == encrypted; } MATCHER_P5(IsTextSample, id, start_time, end_time, settings, payload, "") { - return arg->stream_data_type == StreamDataType::kTextSample && - arg->text_sample->id() == id && + if (arg->stream_data_type != StreamDataType::kTextSample) { + *result_listener << "which is " + << StreamDataTypeToString(arg->stream_data_type); + return false; + } + *result_listener << "which is (" << arg->text_sample->id() << "," + << arg->text_sample->start_time() << "," + << arg->text_sample->EndTime() << "," + << arg->text_sample->settings() << "," + << arg->text_sample->payload() << ")"; + return arg->text_sample->id() == id && arg->text_sample->start_time() == start_time && arg->text_sample->EndTime() == end_time && arg->text_sample->settings() == settings && arg->text_sample->payload() == payload; } +MATCHER_P2(IsCueEvent, stream_index, timestamp, "") { + if (arg->stream_data_type != StreamDataType::kCueEvent) { + *result_listener << "which is " + << StreamDataTypeToString(arg->stream_data_type); + return false; + } + *result_listener << "which is (" << arg->stream_index << "," + << arg->cue_event->timestamp << ")"; + return arg->stream_index == stream_index && + arg->cue_event->timestamp == timestamp; +} + class FakeInputMediaHandler : public MediaHandler { public: using MediaHandler::Dispatch; @@ -234,3 +280,5 @@ class MediaHandlerGraphTestBase : public MediaHandlerTestBase { } // namespace media } // namespace shaka + +#endif // PACKAGER_MEDIA_BASE_MEDIA_HANDLER_TEST_BASE_H_ diff --git a/packager/media/base/muxer.cc b/packager/media/base/muxer.cc index f0ece9cd91..ef31861473 100644 --- a/packager/media/base/muxer.cc +++ b/packager/media/base/muxer.cc @@ -72,6 +72,12 @@ Status Muxer::Process(std::unique_ptr stream_data) { case StreamDataType::kMediaSample: return AddSample(stream_data->stream_index, *stream_data->media_sample); + case StreamDataType::kCueEvent: + if (muxer_listener_) { + muxer_listener_->OnCueEvent(stream_data->cue_event->timestamp, + stream_data->cue_event->cue_data); + } + break; default: VLOG(3) << "Stream data type " << static_cast(stream_data->stream_data_type) << " ignored."; diff --git a/packager/media/chunking/chunking_handler.cc b/packager/media/chunking/chunking_handler.cc index cdb26a3412..31e07d4647 100644 --- a/packager/media/chunking/chunking_handler.cc +++ b/packager/media/chunking/chunking_handler.cc @@ -149,6 +149,7 @@ Status ChunkingHandler::ProcessMediaSample(const MediaSample* sample) { // Check if we need to terminate the current (sub)segment. bool new_segment = false; bool new_subsegment = false; + std::shared_ptr cue_event; if (is_key_frame || !chunking_params_.segment_sap_aligned) { const int64_t segment_index = timestamp / segment_duration_; if (segment_index != current_segment_index_) { @@ -161,11 +162,16 @@ Status ChunkingHandler::ProcessMediaSample(const MediaSample* sample) { // events that may be very close to each other. while (!scte35_events_.empty() && (scte35_events_.top()->scte35_event->start_time <= timestamp)) { - if (!new_segment) { - // Reset subsegment index but don't change current_segment_index_. - current_subsegment_index_ = 0; - new_segment = true; - } + // For simplicity, don't change |current_segment_index_|. + current_subsegment_index_ = 0; + new_segment = true; + + cue_event = std::make_shared(); + // Use PTS instead of DTS for cue event timestamp. + cue_event->timestamp = sample->pts(); + cue_event->cue_data = scte35_events_.top()->scte35_event->cue_data; + VLOG(1) << "Chunked at " << timestamp << " for Ad Cue."; + scte35_events_.pop(); } } @@ -190,6 +196,9 @@ Status ChunkingHandler::ProcessMediaSample(const MediaSample* sample) { if (new_segment) { status.Update(DispatchSegmentInfoForAllStreams()); segment_info_[main_stream_index_]->start_timestamp = timestamp; + + if (cue_event) + status.Update(DispatchCueEventForAllStreams(std::move(cue_event))); } if (subsegment_duration_ > 0 && (new_segment || new_subsegment)) { status.Update(DispatchSubsegmentInfoForAllStreams()); @@ -273,5 +282,17 @@ Status ChunkingHandler::DispatchSubsegmentInfoForAllStreams() { return status; } +Status ChunkingHandler::DispatchCueEventForAllStreams( + std::shared_ptr cue_event) { + Status status; + for (size_t i = 0; i < segment_info_.size() && status.ok(); ++i) { + std::shared_ptr new_cue_event(new CueEvent(*cue_event)); + new_cue_event->timestamp = cue_event->timestamp * time_scales_[i] / + time_scales_[main_stream_index_]; + status.Update(DispatchCueEvent(i, std::move(new_cue_event))); + } + return status; +} + } // namespace media } // namespace shaka diff --git a/packager/media/chunking/chunking_handler.h b/packager/media/chunking/chunking_handler.h index 1d6fad937e..4940c6f1fb 100644 --- a/packager/media/chunking/chunking_handler.h +++ b/packager/media/chunking/chunking_handler.h @@ -71,6 +71,7 @@ class ChunkingHandler : public MediaHandler { // The (sub)segments are aligned and dispatched together. Status DispatchSegmentInfoForAllStreams(); Status DispatchSubsegmentInfoForAllStreams(); + Status DispatchCueEventForAllStreams(std::shared_ptr cue_event); const ChunkingParams chunking_params_; diff --git a/packager/media/chunking/chunking_handler_unittest.cc b/packager/media/chunking/chunking_handler_unittest.cc index bae09b199d..4d940c859c 100644 --- a/packager/media/chunking/chunking_handler_unittest.cc +++ b/packager/media/chunking/chunking_handler_unittest.cc @@ -286,5 +286,43 @@ TEST_F(ChunkingHandlerTest, AudioAndVideo) { EXPECT_THAT(GetOutputStreamDataVector(), IsEmpty()); } +TEST_F(ChunkingHandlerTest, Scte35Event) { + ChunkingParams chunking_params; + chunking_params.segment_duration_in_seconds = 1; + chunking_params.subsegment_duration_in_seconds = 0.5; + SetUpChunkingHandler(1, chunking_params); + + ASSERT_OK(Process(StreamData::FromStreamInfo( + kStreamIndex0, GetVideoStreamInfo(kTimeScale1)))); + + const int64_t kVideoStartTimestamp = 12345; + + auto scte35_event = std::make_shared(); + scte35_event->start_time = kVideoStartTimestamp + kDuration1; + ASSERT_OK(Process(StreamData::FromScte35Event(kStreamIndex0, scte35_event))); + + for (int i = 0; i < 3; ++i) { + const bool is_key_frame = true; + ASSERT_OK(Process(StreamData::FromMediaSample( + kStreamIndex0, GetMediaSample(kVideoStartTimestamp + i * kDuration1, + kDuration1, is_key_frame)))); + } + EXPECT_THAT( + GetOutputStreamDataVector(), + ElementsAre( + IsStreamInfo(kStreamIndex0, kTimeScale1, !kEncrypted), + IsMediaSample(kStreamIndex0, kVideoStartTimestamp, kDuration1, + !kEncrypted), + // A new segment is created due to the existance of Cue. + IsSegmentInfo(kStreamIndex0, kVideoStartTimestamp, kDuration1, + !kIsSubsegment, !kEncrypted), + IsCueEvent(kStreamIndex0, + static_cast(kVideoStartTimestamp + kDuration1)), + IsMediaSample(kStreamIndex0, kVideoStartTimestamp + kDuration1 * 1, + kDuration1, !kEncrypted), + IsMediaSample(kStreamIndex0, kVideoStartTimestamp + kDuration1 * 2, + kDuration1, !kEncrypted))); +} + } // namespace media } // namespace shaka diff --git a/packager/media/event/combined_muxer_listener.cc b/packager/media/event/combined_muxer_listener.cc index f1b2ea5953..45ba0e1481 100644 --- a/packager/media/event/combined_muxer_listener.cc +++ b/packager/media/event/combined_muxer_listener.cc @@ -65,5 +65,12 @@ void CombinedMuxerListener::OnNewSegment(const std::string& file_name, } } +void CombinedMuxerListener::OnCueEvent(uint64_t timestamp, + const std::string& cue_data) { + for (auto& listener : muxer_listeners_) { + listener->OnCueEvent(timestamp, cue_data); + } +} + } // namespace media } // namespace shaka diff --git a/packager/media/event/combined_muxer_listener.h b/packager/media/event/combined_muxer_listener.h index e48d98912d..62687eb779 100644 --- a/packager/media/event/combined_muxer_listener.h +++ b/packager/media/event/combined_muxer_listener.h @@ -39,6 +39,7 @@ class CombinedMuxerListener : public MuxerListener { uint64_t start_time, uint64_t duration, uint64_t segment_file_size) override; + void OnCueEvent(uint64_t timestamp, const std::string& cue_data) override; private: std::list> muxer_listeners_; diff --git a/packager/media/event/hls_notify_muxer_listener.cc b/packager/media/event/hls_notify_muxer_listener.cc index b26b52db55..18f372ccd1 100644 --- a/packager/media/event/hls_notify_muxer_listener.cc +++ b/packager/media/event/hls_notify_muxer_listener.cc @@ -155,16 +155,19 @@ void HlsNotifyMuxerListener::OnMediaEnd(const MediaRanges& media_ranges, const std::vector& subsegment_ranges = media_ranges.subsegment_ranges; size_t num_subsegments = subsegment_ranges.size(); - if (segment_infos_.size() != num_subsegments) { + if (subsegments_.size() != num_subsegments) { LOG(WARNING) << "Number of subsegment ranges (" << num_subsegments << ") does not match the number of subsegments notified to " "OnNewSegment() (" - << segment_infos_.size() << ")."; - num_subsegments = std::min(segment_infos_.size(), num_subsegments); + << subsegments_.size() << ")."; + num_subsegments = std::min(subsegments_.size(), num_subsegments); } for (size_t i = 0; i < num_subsegments; ++i) { const Range& range = subsegment_ranges[i]; - const SegmentInfo& subsegment_info = segment_infos_[i]; + const SubsegmentInfo& subsegment_info = subsegments_[i]; + if (subsegment_info.cue_break) { + hls_notifier_->NotifyCueEvent(stream_id_, subsegment_info.start_time); + } hls_notifier_->NotifyNewSegment( stream_id_, media_info_.media_file_name(), subsegment_info.start_time, subsegment_info.duration, range.start, range.end + 1 - range.start); @@ -177,10 +180,10 @@ void HlsNotifyMuxerListener::OnNewSegment(const std::string& file_name, uint64_t duration, uint64_t segment_file_size) { if (!media_info_.has_segment_template()) { - SegmentInfo info; - info.duration = duration; - info.start_time = start_time; - segment_infos_.push_back(info); + SubsegmentInfo subsegment = {start_time, duration, segment_file_size, + next_subsegment_contains_cue_break_}; + subsegments_.push_back(subsegment); + next_subsegment_contains_cue_break_ = false; return; } // For multisegment, it always starts from the beginning of the file. @@ -191,5 +194,14 @@ void HlsNotifyMuxerListener::OnNewSegment(const std::string& file_name, LOG_IF(WARNING, !result) << "Failed to add new segment."; } +void HlsNotifyMuxerListener::OnCueEvent(uint64_t timestamp, + const std::string& cue_data) { + if (!media_info_.has_segment_template()) { + next_subsegment_contains_cue_break_ = true; + return; + } + hls_notifier_->NotifyCueEvent(stream_id_, timestamp); +} + } // 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 e39e5e5d4a..08cc00c89e 100644 --- a/packager/media/event/hls_notify_muxer_listener.h +++ b/packager/media/event/hls_notify_muxer_listener.h @@ -12,7 +12,6 @@ #include "packager/base/macros.h" #include "packager/media/event/muxer_listener.h" #include "packager/mpd/base/media_info.pb.h" -#include "packager/mpd/base/segment_info.h" namespace shaka { @@ -59,9 +58,18 @@ class HlsNotifyMuxerListener : public MuxerListener { uint64_t start_time, uint64_t duration, uint64_t segment_file_size) override; + void OnCueEvent(uint64_t timestamp, const std::string& cue_data) override; /// @} private: + // This stores data passed into OnNewSegment() for VOD. + struct SubsegmentInfo { + uint64_t start_time; + uint64_t duration; + uint64_t segment_file_size; + bool cue_break; + }; + const std::string playlist_name_; const std::string ext_x_media_name_; const std::string ext_x_media_group_id_; @@ -79,7 +87,9 @@ class HlsNotifyMuxerListener : public MuxerListener { // MediaInfo passed to Notifier::OnNewStream(). Mainly for single segment // playlists. MediaInfo media_info_; - std::vector segment_infos_; + std::vector subsegments_; + // Whether the next subsegment contains AdCue break. + bool next_subsegment_contains_cue_break_ = false; DISALLOW_COPY_AND_ASSIGN(HlsNotifyMuxerListener); }; diff --git a/packager/media/event/hls_notify_muxer_listener_unittest.cc b/packager/media/event/hls_notify_muxer_listener_unittest.cc index c880b0b424..0ff1c7ba3e 100644 --- a/packager/media/event/hls_notify_muxer_listener_unittest.cc +++ b/packager/media/event/hls_notify_muxer_listener_unittest.cc @@ -40,6 +40,7 @@ class MockHlsNotifier : public hls::HlsNotifier { uint64_t duration, uint64_t start_byte_offset, uint64_t size)); + MOCK_METHOD2(NotifyCueEvent, bool(uint32_t stream_id, uint64_t timestamp)); MOCK_METHOD5( NotifyEncryptionUpdate, bool(uint32_t stream_id, @@ -322,7 +323,7 @@ TEST_F(HlsNotifyMuxerListenerTest, OnMediaEnd) { listener_.OnMediaEnd(MuxerListener::MediaRanges(), 0); } -TEST_F(HlsNotifyMuxerListenerTest, OnNewSegment) { +TEST_F(HlsNotifyMuxerListenerTest, OnNewSegmentAndCueEvent) { ON_CALL(mock_notifier_, NotifyNewStream(_, _, _, _, _)) .WillByDefault(Return(true)); VideoStreamInfoParameters video_params = GetDefaultVideoStreamInfoParams(); @@ -336,9 +337,11 @@ TEST_F(HlsNotifyMuxerListenerTest, OnNewSegment) { const uint64_t kStartTime = 19283; const uint64_t kDuration = 98028; const uint64_t kFileSize = 756739; + EXPECT_CALL(mock_notifier_, NotifyCueEvent(_, kStartTime)); EXPECT_CALL(mock_notifier_, NotifyNewSegment(_, StrEq("new_segment_name10.ts"), kStartTime, kDuration, _, kFileSize)); + listener_.OnCueEvent(kStartTime, "dummy cue data"); listener_.OnNewSegment("new_segment_name10.ts", kStartTime, kDuration, kFileSize); } @@ -361,6 +364,7 @@ TEST_F(HlsNotifyMuxerListenerTest, NoSegmentTemplateOnMediaEnd) { const uint64_t kDuration = 98028; const uint64_t kFileSize = 756739; + listener_.OnCueEvent(kStartTime, "dummy cue data"); listener_.OnNewSegment("filename.mp4", kStartTime, kDuration, kFileSize); MuxerListener::MediaRanges ranges; @@ -380,6 +384,7 @@ TEST_F(HlsNotifyMuxerListenerTest, NoSegmentTemplateOnMediaEnd) { ranges.index_range = index_range; ranges.subsegment_ranges = segment_ranges; + EXPECT_CALL(mock_notifier_, NotifyCueEvent(_, kStartTime)); EXPECT_CALL(mock_notifier_, NotifyNewSegment(_, StrEq("filename.mp4"), kStartTime, kDuration, kSegmentStartOffset, kFileSize)); diff --git a/packager/media/event/mock_muxer_listener.h b/packager/media/event/mock_muxer_listener.h index 622ffc466a..348a7fdbc0 100644 --- a/packager/media/event/mock_muxer_listener.h +++ b/packager/media/event/mock_muxer_listener.h @@ -61,6 +61,9 @@ class MockMuxerListener : public MuxerListener { uint64_t start_time, uint64_t duration, uint64_t segment_file_size)); + + MOCK_METHOD2(OnCueEvent, + void(uint64_t timestamp, const std::string& cue_data)); }; } // namespace media diff --git a/packager/media/event/mpd_notify_muxer_listener.cc b/packager/media/event/mpd_notify_muxer_listener.cc index 78cf1a2560..5cafd85066 100644 --- a/packager/media/event/mpd_notify_muxer_listener.cc +++ b/packager/media/event/mpd_notify_muxer_listener.cc @@ -132,10 +132,13 @@ void MpdNotifyMuxerListener::OnMediaEnd(const MediaRanges& media_ranges, mpd_notifier_->NotifyNewContainer(*media_info_, &id); // TODO(rkuroiwa): Use media_ranges.subsegment_ranges instead of caching the // subsegments. - for (std::list::const_iterator it = subsegments_.begin(); - it != subsegments_.end(); ++it) { - mpd_notifier_->NotifyNewSegment(id, it->start_time, it->duration, - it->segment_file_size); + for (const SubsegmentInfo& subsegment : subsegments_) { + if (subsegment.cue_break) { + mpd_notifier_->NotifyCueEvent(id, subsegment.start_time); + } + mpd_notifier_->NotifyNewSegment(id, subsegment.start_time, + subsegment.duration, + subsegment.segment_file_size); } subsegments_.clear(); mpd_notifier_->Flush(); @@ -152,10 +155,22 @@ void MpdNotifyMuxerListener::OnNewSegment(const std::string& file_name, if (mpd_notifier_->mpd_type() == MpdType::kDynamic) mpd_notifier_->Flush(); } else { - SubsegmentInfo subsegment = {start_time, duration, segment_file_size}; + SubsegmentInfo subsegment = {start_time, duration, segment_file_size, + next_subsegment_contains_cue_break_}; + next_subsegment_contains_cue_break_ = false; subsegments_.push_back(subsegment); } } +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); + } else { + next_subsegment_contains_cue_break_ = 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 cb37e0f555..c5b50341c7 100644 --- a/packager/media/event/mpd_notify_muxer_listener.h +++ b/packager/media/event/mpd_notify_muxer_listener.h @@ -51,6 +51,7 @@ class MpdNotifyMuxerListener : public MuxerListener { uint64_t start_time, uint64_t duration, uint64_t segment_file_size) override; + void OnCueEvent(uint64_t timestamp, const std::string& cue_data) override; /// @} private: @@ -59,15 +60,16 @@ class MpdNotifyMuxerListener : public MuxerListener { uint64_t start_time; uint64_t duration; uint64_t segment_file_size; + bool cue_break; }; - MpdNotifier* const mpd_notifier_; - uint32_t notification_id_; + MpdNotifier* const mpd_notifier_ = nullptr; + uint32_t notification_id_ = 0; std::unique_ptr media_info_; - bool is_encrypted_; + bool is_encrypted_ = false; // Storage for values passed to OnEncryptionInfoReady(). - FourCC protection_scheme_; + FourCC protection_scheme_ = FOURCC_NULL; std::vector default_key_id_; std::vector key_system_info_; @@ -76,6 +78,8 @@ class MpdNotifyMuxerListener : public MuxerListener { // (in OnMediaEnd). This is not used for live because NotifyNewSegment() is // called immediately in OnNewSegment(). std::list subsegments_; + // Whether the next subsegment contains AdCue break. + bool next_subsegment_contains_cue_break_ = false; DISALLOW_COPY_AND_ASSIGN(MpdNotifyMuxerListener); }; diff --git a/packager/media/event/mpd_notify_muxer_listener_unittest.cc b/packager/media/event/mpd_notify_muxer_listener_unittest.cc index 7cb1459691..ce8790f71e 100644 --- a/packager/media/event/mpd_notify_muxer_listener_unittest.cc +++ b/packager/media/event/mpd_notify_muxer_listener_unittest.cc @@ -251,6 +251,7 @@ TEST_F(MpdNotifyMuxerListenerTest, VodOnNewSegment) { kDefaultReferenceTimeScale, MuxerListener::kContainerMp4); listener_->OnNewSegment("", kStartTime1, kDuration1, kSegmentFileSize1); + listener_->OnCueEvent(kStartTime2, "dummy cue data"); listener_->OnNewSegment("", kStartTime2, kDuration2, kSegmentFileSize2); ::testing::Mock::VerifyAndClearExpectations(notifier_.get()); @@ -259,6 +260,7 @@ TEST_F(MpdNotifyMuxerListenerTest, VodOnNewSegment) { ExpectMediaInfoEq(kExpectedDefaultMediaInfo), _)); EXPECT_CALL(*notifier_, NotifyNewSegment(_, kStartTime1, kDuration1, kSegmentFileSize1)); + EXPECT_CALL(*notifier_, NotifyCueEvent(_, kStartTime2)); EXPECT_CALL(*notifier_, NotifyNewSegment(_, kStartTime2, kDuration2, kSegmentFileSize2)); EXPECT_CALL(*notifier_, Flush()); @@ -317,6 +319,7 @@ TEST_P(MpdNotifyMuxerListenerTest, LiveNoKeyRotation) { // Flush should only be called once in OnMediaEnd. if (GetParam() == MpdType::kDynamic) EXPECT_CALL(*notifier_, Flush()); + EXPECT_CALL(*notifier_, NotifyCueEvent(_, kStartTime2)); EXPECT_CALL(*notifier_, NotifyNewSegment(_, kStartTime2, kDuration2, kSegmentFileSize2)); if (GetParam() == MpdType::kDynamic) @@ -330,6 +333,7 @@ TEST_P(MpdNotifyMuxerListenerTest, LiveNoKeyRotation) { kDefaultReferenceTimeScale, MuxerListener::kContainerMp4); listener_->OnNewSegment("", kStartTime1, kDuration1, kSegmentFileSize1); + listener_->OnCueEvent(kStartTime2, "dummy cue data"); listener_->OnNewSegment("", kStartTime2, kDuration2, kSegmentFileSize2); ::testing::Mock::VerifyAndClearExpectations(notifier_.get()); diff --git a/packager/media/event/muxer_listener.h b/packager/media/event/muxer_listener.h index f486fc43b6..1b489d819e 100644 --- a/packager/media/event/muxer_listener.h +++ b/packager/media/event/muxer_listener.h @@ -127,6 +127,11 @@ class MuxerListener { uint64_t duration, uint64_t segment_file_size) = 0; + /// Called when there is a new Ad Cue, which should align with (sub)segments. + /// @param timestamp indicate the cue timestamp. + /// @param cue_data is the data of the cue. + virtual void OnCueEvent(uint64_t timestamp, const std::string& cue_data) = 0; + protected: MuxerListener() {}; }; diff --git a/packager/media/event/vod_media_info_dump_muxer_listener.cc b/packager/media/event/vod_media_info_dump_muxer_listener.cc index b09b373c05..fa9ca77a63 100644 --- a/packager/media/event/vod_media_info_dump_muxer_listener.cc +++ b/packager/media/event/vod_media_info_dump_muxer_listener.cc @@ -88,6 +88,11 @@ void VodMediaInfoDumpMuxerListener::OnNewSegment(const std::string& file_name, uint64_t duration, uint64_t segment_file_size) {} +void VodMediaInfoDumpMuxerListener::OnCueEvent(uint64_t timestamp, + const std::string& cue_data) { + NOTIMPLEMENTED(); +} + // static bool VodMediaInfoDumpMuxerListener::WriteMediaInfoToFile( const MediaInfo& media_info, diff --git a/packager/media/event/vod_media_info_dump_muxer_listener.h b/packager/media/event/vod_media_info_dump_muxer_listener.h index 9ddcfe0823..db0369d3b1 100644 --- a/packager/media/event/vod_media_info_dump_muxer_listener.h +++ b/packager/media/event/vod_media_info_dump_muxer_listener.h @@ -50,6 +50,7 @@ class VodMediaInfoDumpMuxerListener : public MuxerListener { uint64_t start_time, uint64_t duration, uint64_t segment_file_size) override; + void OnCueEvent(uint64_t timestamp, const std::string& cue_data) override; /// @} /// Write @a media_info to @a output_file_path in human readable format. diff --git a/packager/mpd/base/mock_mpd_notifier.h b/packager/mpd/base/mock_mpd_notifier.h index d4a78859dc..f40e4aa25b 100644 --- a/packager/mpd/base/mock_mpd_notifier.h +++ b/packager/mpd/base/mock_mpd_notifier.h @@ -31,6 +31,7 @@ class MockMpdNotifier : public MpdNotifier { uint64_t start_time, uint64_t duration, uint64_t size)); + MOCK_METHOD2(NotifyCueEvent, bool(uint32_t container_id, uint64_t timestamp)); MOCK_METHOD4(NotifyEncryptionUpdate, bool(uint32_t container_id, const std::string& drm_uuid, diff --git a/packager/mpd/base/mpd_notifier.h b/packager/mpd/base/mpd_notifier.h index 84569651df..41e550ab8b 100644 --- a/packager/mpd/base/mpd_notifier.h +++ b/packager/mpd/base/mpd_notifier.h @@ -71,6 +71,13 @@ class MpdNotifier { uint64_t duration, uint64_t size) = 0; + /// Notifies MpdBuilder that there is a new CueEvent. + /// @param container_id Container ID obtained from calling + /// NotifyNewContainer(). + /// @param timestamp is the timestamp of the CueEvent. + /// @return true on success, false otherwise. + virtual bool NotifyCueEvent(uint32_t container_id, uint64_t timestamp) = 0; + /// Notifiers MpdBuilder that there is a new PSSH for the container. /// This may be called whenever the key has to change, e.g. key rotation. /// @param container_id Container ID obtained from calling diff --git a/packager/mpd/base/simple_mpd_notifier.cc b/packager/mpd/base/simple_mpd_notifier.cc index 5a52f4c735..e9946f6d4d 100644 --- a/packager/mpd/base/simple_mpd_notifier.cc +++ b/packager/mpd/base/simple_mpd_notifier.cc @@ -96,6 +96,12 @@ bool SimpleMpdNotifier::NotifyNewSegment(uint32_t container_id, return true; } +bool SimpleMpdNotifier::NotifyCueEvent(uint32_t container_id, + uint64_t timestamp) { + NOTIMPLEMENTED(); + return false; +} + bool SimpleMpdNotifier::NotifyEncryptionUpdate( uint32_t container_id, const std::string& drm_uuid, diff --git a/packager/mpd/base/simple_mpd_notifier.h b/packager/mpd/base/simple_mpd_notifier.h index b7a7c05970..0863ebd580 100644 --- a/packager/mpd/base/simple_mpd_notifier.h +++ b/packager/mpd/base/simple_mpd_notifier.h @@ -43,6 +43,7 @@ class SimpleMpdNotifier : public MpdNotifier { uint64_t start_time, uint64_t duration, uint64_t size) override; + bool NotifyCueEvent(uint32_t container_id, uint64_t timestamp) override; bool NotifyEncryptionUpdate(uint32_t container_id, const std::string& drm_uuid, const std::vector& new_key_id,