Propage CueEvent to MpdNotifier/HlsNotifier

Change-Id: I9828af11a28300d20cc8742251bbe1b4ebfdbad1
This commit is contained in:
KongQun Yang 2018-01-02 16:10:33 -08:00
parent b0edec8c40
commit db74d6756e
25 changed files with 283 additions and 45 deletions

View File

@ -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

View File

@ -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<uint8_t>& key_id,

View File

@ -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<uint8_t>& key_id,

View File

@ -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<MediaSample> MediaHandlerTestBase::GetMediaSample(
std::shared_ptr<MediaSample> sample =
MediaSample::CopyFrom(data, data_length, nullptr, 0, is_keyframe);
sample->set_dts(timestamp);
sample->set_pts(timestamp);
sample->set_duration(duration);
return sample;

View File

@ -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 <gmock/gmock.h>
#include <gtest/gtest.h>
@ -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<int>(crypt_byte_block) << ","
<< static_cast<int>(skip_byte_block) << ","
<< static_cast<int>(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<int>(arg.crypt_byte_block) << ","
<< static_cast<int>(arg.skip_byte_block) << ","
<< static_cast<int>(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_

View File

@ -72,6 +72,12 @@ Status Muxer::Process(std::unique_ptr<StreamData> 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<int>(stream_data->stream_data_type) << " ignored.";

View File

@ -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<CueEvent> 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_.
// For simplicity, don't change |current_segment_index_|.
current_subsegment_index_ = 0;
new_segment = true;
}
cue_event = std::make_shared<CueEvent>();
// 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<CueEvent> cue_event) {
Status status;
for (size_t i = 0; i < segment_info_.size() && status.ok(); ++i) {
std::shared_ptr<CueEvent> 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

View File

@ -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<CueEvent> cue_event);
const ChunkingParams chunking_params_;

View File

@ -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<Scte35Event>();
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<double>(kVideoStartTimestamp + kDuration1)),
IsMediaSample(kStreamIndex0, kVideoStartTimestamp + kDuration1 * 1,
kDuration1, !kEncrypted),
IsMediaSample(kStreamIndex0, kVideoStartTimestamp + kDuration1 * 2,
kDuration1, !kEncrypted)));
}
} // namespace media
} // namespace shaka

View File

@ -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

View File

@ -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<std::unique_ptr<MuxerListener>> muxer_listeners_;

View File

@ -155,16 +155,19 @@ void HlsNotifyMuxerListener::OnMediaEnd(const MediaRanges& media_ranges,
const std::vector<Range>& 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

View File

@ -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<SegmentInfo> segment_infos_;
std::vector<SubsegmentInfo> subsegments_;
// Whether the next subsegment contains AdCue break.
bool next_subsegment_contains_cue_break_ = false;
DISALLOW_COPY_AND_ASSIGN(HlsNotifyMuxerListener);
};

View File

@ -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));

View File

@ -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

View File

@ -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<SubsegmentInfo>::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

View File

@ -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<MediaInfo> 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<uint8_t> default_key_id_;
std::vector<ProtectionSystemSpecificInfo> 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<SubsegmentInfo> subsegments_;
// Whether the next subsegment contains AdCue break.
bool next_subsegment_contains_cue_break_ = false;
DISALLOW_COPY_AND_ASSIGN(MpdNotifyMuxerListener);
};

View File

@ -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());

View File

@ -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() {};
};

View File

@ -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,

View File

@ -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.

View File

@ -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,

View File

@ -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

View File

@ -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,

View File

@ -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<uint8_t>& new_key_id,