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 start_byte_offset,
uint64_t size) = 0; 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 stream_id is the value set by NotifyNewStream().
/// @param key_id is the key ID for the stream. /// @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 /// @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; return true;
} }
bool SimpleHlsNotifier::NotifyCueEvent(uint32_t container_id,
uint64_t timestamp) {
NOTIMPLEMENTED();
return false;
}
bool SimpleHlsNotifier::NotifyEncryptionUpdate( bool SimpleHlsNotifier::NotifyEncryptionUpdate(
uint32_t stream_id, uint32_t stream_id,
const std::vector<uint8_t>& key_id, const std::vector<uint8_t>& key_id,

View File

@ -73,6 +73,7 @@ class SimpleHlsNotifier : public HlsNotifier {
uint64_t duration, uint64_t duration,
uint64_t start_byte_offset, uint64_t start_byte_offset,
uint64_t size) override; uint64_t size) override;
bool NotifyCueEvent(uint32_t container_id, uint64_t timestamp) override;
bool NotifyEncryptionUpdate( bool NotifyEncryptionUpdate(
uint32_t stream_id, uint32_t stream_id,
const std::vector<uint8_t>& key_id, const std::vector<uint8_t>& key_id,

View File

@ -60,6 +60,30 @@ const uint8_t kData[]{
namespace shaka { namespace shaka {
namespace media { 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 { bool FakeInputMediaHandler::ValidateOutputStreamIndex(size_t index) const {
return true; return true;
} }
@ -170,6 +194,7 @@ std::shared_ptr<MediaSample> MediaHandlerTestBase::GetMediaSample(
std::shared_ptr<MediaSample> sample = std::shared_ptr<MediaSample> sample =
MediaSample::CopyFrom(data, data_length, nullptr, 0, is_keyframe); MediaSample::CopyFrom(data, data_length, nullptr, 0, is_keyframe);
sample->set_dts(timestamp); sample->set_dts(timestamp);
sample->set_pts(timestamp);
sample->set_duration(duration); sample->set_duration(duration);
return sample; return sample;

View File

@ -4,6 +4,9 @@
// license that can be found in the LICENSE file or at // license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd // 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 <gmock/gmock.h>
#include <gtest/gtest.h> #include <gtest/gtest.h>
@ -13,16 +16,25 @@
namespace shaka { namespace shaka {
namespace media { namespace media {
std::string StreamDataTypeToString(StreamDataType stream_data_type);
std::string BoolToString(bool value);
MATCHER_P(IsStreamInfo, stream_index, "") { MATCHER_P(IsStreamInfo, stream_index, "") {
return arg->stream_index == stream_index && return arg->stream_index == stream_index &&
arg->stream_data_type == StreamDataType::kStreamInfo; arg->stream_data_type == StreamDataType::kStreamInfo;
} }
MATCHER_P3(IsStreamInfo, stream_index, time_scale, encrypted, "") { MATCHER_P3(IsStreamInfo, stream_index, time_scale, encrypted, "") {
*result_listener << "which is (" << stream_index << "," << time_scale << "," if (arg->stream_data_type != StreamDataType::kStreamInfo) {
<< (encrypted ? "encrypted" : "not encrypted") << ")"; *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 && return arg->stream_index == stream_index &&
arg->stream_data_type == StreamDataType::kStreamInfo &&
arg->stream_info->time_scale() == time_scale && arg->stream_info->time_scale() == time_scale &&
arg->stream_info->is_encrypted() == encrypted; arg->stream_info->is_encrypted() == encrypted;
} }
@ -34,12 +46,18 @@ MATCHER_P5(IsSegmentInfo,
subsegment, subsegment,
encrypted, encrypted,
"") { "") {
*result_listener << "which is (" << stream_index << "," << start_timestamp if (arg->stream_data_type != StreamDataType::kSegmentInfo) {
<< "," << duration << "," *result_listener << "which is "
<< (subsegment ? "subsegment" : "not subsegment") << "," << StreamDataTypeToString(arg->stream_data_type);
<< (encrypted ? "encrypted" : "not encrypted") << ")"; 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 && return arg->stream_index == stream_index &&
arg->stream_data_type == StreamDataType::kSegmentInfo &&
arg->segment_info->start_timestamp == start_timestamp && arg->segment_info->start_timestamp == start_timestamp &&
arg->segment_info->duration == duration && arg->segment_info->duration == duration &&
arg->segment_info->is_subsegment == subsegment && arg->segment_info->is_subsegment == subsegment &&
@ -54,12 +72,14 @@ MATCHER_P6(MatchEncryptionConfig,
constant_iv, constant_iv,
key_id, key_id,
"") { "") {
*result_listener << "which is (" << FourCCToString(protection_scheme) << "," *result_listener << "which is (" << FourCCToString(arg.protection_scheme)
<< static_cast<int>(crypt_byte_block) << "," << "," << static_cast<int>(arg.crypt_byte_block) << ","
<< static_cast<int>(skip_byte_block) << "," << static_cast<int>(arg.skip_byte_block) << ","
<< static_cast<int>(per_sample_iv_size) << "," << static_cast<int>(arg.per_sample_iv_size) << ","
<< base::HexEncode(constant_iv.data(), constant_iv.size()) << base::HexEncode(arg.constant_iv.data(),
<< "," << base::HexEncode(key_id.data(), key_id.size()) arg.constant_iv.size())
<< ","
<< base::HexEncode(arg.key_id.data(), arg.key_id.size())
<< ")"; << ")";
return arg.protection_scheme == protection_scheme && return arg.protection_scheme == protection_scheme &&
arg.crypt_byte_block == crypt_byte_block && arg.crypt_byte_block == crypt_byte_block &&
@ -69,25 +89,51 @@ MATCHER_P6(MatchEncryptionConfig,
} }
MATCHER_P4(IsMediaSample, stream_index, timestamp, duration, encrypted, "") { MATCHER_P4(IsMediaSample, stream_index, timestamp, duration, encrypted, "") {
*result_listener << "which is (" << stream_index << "," << timestamp << "," if (arg->stream_data_type != StreamDataType::kMediaSample) {
<< duration << "," *result_listener << "which is "
<< (encrypted ? "encrypted" : "not encrypted") << ")"; << 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 && return arg->stream_index == stream_index &&
arg->stream_data_type == StreamDataType::kMediaSample &&
arg->media_sample->dts() == timestamp && arg->media_sample->dts() == timestamp &&
arg->media_sample->duration() == duration && arg->media_sample->duration() == duration &&
arg->media_sample->is_encrypted() == encrypted; arg->media_sample->is_encrypted() == encrypted;
} }
MATCHER_P5(IsTextSample, id, start_time, end_time, settings, payload, "") { MATCHER_P5(IsTextSample, id, start_time, end_time, settings, payload, "") {
return arg->stream_data_type == StreamDataType::kTextSample && if (arg->stream_data_type != StreamDataType::kTextSample) {
arg->text_sample->id() == id && *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->start_time() == start_time &&
arg->text_sample->EndTime() == end_time && arg->text_sample->EndTime() == end_time &&
arg->text_sample->settings() == settings && arg->text_sample->settings() == settings &&
arg->text_sample->payload() == payload; 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 { class FakeInputMediaHandler : public MediaHandler {
public: public:
using MediaHandler::Dispatch; using MediaHandler::Dispatch;
@ -234,3 +280,5 @@ class MediaHandlerGraphTestBase : public MediaHandlerTestBase {
} // namespace media } // namespace media
} // namespace shaka } // 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: case StreamDataType::kMediaSample:
return AddSample(stream_data->stream_index, return AddSample(stream_data->stream_index,
*stream_data->media_sample); *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: default:
VLOG(3) << "Stream data type " VLOG(3) << "Stream data type "
<< static_cast<int>(stream_data->stream_data_type) << " ignored."; << 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. // Check if we need to terminate the current (sub)segment.
bool new_segment = false; bool new_segment = false;
bool new_subsegment = false; bool new_subsegment = false;
std::shared_ptr<CueEvent> cue_event;
if (is_key_frame || !chunking_params_.segment_sap_aligned) { if (is_key_frame || !chunking_params_.segment_sap_aligned) {
const int64_t segment_index = timestamp / segment_duration_; const int64_t segment_index = timestamp / segment_duration_;
if (segment_index != current_segment_index_) { 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. // events that may be very close to each other.
while (!scte35_events_.empty() && while (!scte35_events_.empty() &&
(scte35_events_.top()->scte35_event->start_time <= timestamp)) { (scte35_events_.top()->scte35_event->start_time <= timestamp)) {
if (!new_segment) { // For simplicity, don't change |current_segment_index_|.
// Reset subsegment index but don't change current_segment_index_. current_subsegment_index_ = 0;
current_subsegment_index_ = 0; new_segment = true;
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(); scte35_events_.pop();
} }
} }
@ -190,6 +196,9 @@ Status ChunkingHandler::ProcessMediaSample(const MediaSample* sample) {
if (new_segment) { if (new_segment) {
status.Update(DispatchSegmentInfoForAllStreams()); status.Update(DispatchSegmentInfoForAllStreams());
segment_info_[main_stream_index_]->start_timestamp = timestamp; 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)) { if (subsegment_duration_ > 0 && (new_segment || new_subsegment)) {
status.Update(DispatchSubsegmentInfoForAllStreams()); status.Update(DispatchSubsegmentInfoForAllStreams());
@ -273,5 +282,17 @@ Status ChunkingHandler::DispatchSubsegmentInfoForAllStreams() {
return status; 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 media
} // namespace shaka } // namespace shaka

View File

@ -71,6 +71,7 @@ class ChunkingHandler : public MediaHandler {
// The (sub)segments are aligned and dispatched together. // The (sub)segments are aligned and dispatched together.
Status DispatchSegmentInfoForAllStreams(); Status DispatchSegmentInfoForAllStreams();
Status DispatchSubsegmentInfoForAllStreams(); Status DispatchSubsegmentInfoForAllStreams();
Status DispatchCueEventForAllStreams(std::shared_ptr<CueEvent> cue_event);
const ChunkingParams chunking_params_; const ChunkingParams chunking_params_;

View File

@ -286,5 +286,43 @@ TEST_F(ChunkingHandlerTest, AudioAndVideo) {
EXPECT_THAT(GetOutputStreamDataVector(), IsEmpty()); 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 media
} // namespace shaka } // 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 media
} // namespace shaka } // namespace shaka

View File

@ -39,6 +39,7 @@ class CombinedMuxerListener : public MuxerListener {
uint64_t start_time, uint64_t start_time,
uint64_t duration, uint64_t duration,
uint64_t segment_file_size) override; uint64_t segment_file_size) override;
void OnCueEvent(uint64_t timestamp, const std::string& cue_data) override;
private: private:
std::list<std::unique_ptr<MuxerListener>> muxer_listeners_; 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 = const std::vector<Range>& subsegment_ranges =
media_ranges.subsegment_ranges; media_ranges.subsegment_ranges;
size_t num_subsegments = subsegment_ranges.size(); 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 LOG(WARNING) << "Number of subsegment ranges (" << num_subsegments
<< ") does not match the number of subsegments notified to " << ") does not match the number of subsegments notified to "
"OnNewSegment() (" "OnNewSegment() ("
<< segment_infos_.size() << ")."; << subsegments_.size() << ").";
num_subsegments = std::min(segment_infos_.size(), num_subsegments); num_subsegments = std::min(subsegments_.size(), num_subsegments);
} }
for (size_t i = 0; i < num_subsegments; ++i) { for (size_t i = 0; i < num_subsegments; ++i) {
const Range& range = subsegment_ranges[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( hls_notifier_->NotifyNewSegment(
stream_id_, media_info_.media_file_name(), subsegment_info.start_time, stream_id_, media_info_.media_file_name(), subsegment_info.start_time,
subsegment_info.duration, range.start, range.end + 1 - range.start); 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 duration,
uint64_t segment_file_size) { uint64_t segment_file_size) {
if (!media_info_.has_segment_template()) { if (!media_info_.has_segment_template()) {
SegmentInfo info; SubsegmentInfo subsegment = {start_time, duration, segment_file_size,
info.duration = duration; next_subsegment_contains_cue_break_};
info.start_time = start_time; subsegments_.push_back(subsegment);
segment_infos_.push_back(info); next_subsegment_contains_cue_break_ = false;
return; return;
} }
// For multisegment, it always starts from the beginning of the file. // 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."; 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 media
} // namespace shaka } // namespace shaka

View File

@ -12,7 +12,6 @@
#include "packager/base/macros.h" #include "packager/base/macros.h"
#include "packager/media/event/muxer_listener.h" #include "packager/media/event/muxer_listener.h"
#include "packager/mpd/base/media_info.pb.h" #include "packager/mpd/base/media_info.pb.h"
#include "packager/mpd/base/segment_info.h"
namespace shaka { namespace shaka {
@ -59,9 +58,18 @@ class HlsNotifyMuxerListener : public MuxerListener {
uint64_t start_time, uint64_t start_time,
uint64_t duration, uint64_t duration,
uint64_t segment_file_size) override; uint64_t segment_file_size) override;
void OnCueEvent(uint64_t timestamp, const std::string& cue_data) override;
/// @} /// @}
private: 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 playlist_name_;
const std::string ext_x_media_name_; const std::string ext_x_media_name_;
const std::string ext_x_media_group_id_; 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 // MediaInfo passed to Notifier::OnNewStream(). Mainly for single segment
// playlists. // playlists.
MediaInfo media_info_; 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); DISALLOW_COPY_AND_ASSIGN(HlsNotifyMuxerListener);
}; };

View File

@ -40,6 +40,7 @@ class MockHlsNotifier : public hls::HlsNotifier {
uint64_t duration, uint64_t duration,
uint64_t start_byte_offset, uint64_t start_byte_offset,
uint64_t size)); uint64_t size));
MOCK_METHOD2(NotifyCueEvent, bool(uint32_t stream_id, uint64_t timestamp));
MOCK_METHOD5( MOCK_METHOD5(
NotifyEncryptionUpdate, NotifyEncryptionUpdate,
bool(uint32_t stream_id, bool(uint32_t stream_id,
@ -322,7 +323,7 @@ TEST_F(HlsNotifyMuxerListenerTest, OnMediaEnd) {
listener_.OnMediaEnd(MuxerListener::MediaRanges(), 0); listener_.OnMediaEnd(MuxerListener::MediaRanges(), 0);
} }
TEST_F(HlsNotifyMuxerListenerTest, OnNewSegment) { TEST_F(HlsNotifyMuxerListenerTest, OnNewSegmentAndCueEvent) {
ON_CALL(mock_notifier_, NotifyNewStream(_, _, _, _, _)) ON_CALL(mock_notifier_, NotifyNewStream(_, _, _, _, _))
.WillByDefault(Return(true)); .WillByDefault(Return(true));
VideoStreamInfoParameters video_params = GetDefaultVideoStreamInfoParams(); VideoStreamInfoParameters video_params = GetDefaultVideoStreamInfoParams();
@ -336,9 +337,11 @@ TEST_F(HlsNotifyMuxerListenerTest, OnNewSegment) {
const uint64_t kStartTime = 19283; const uint64_t kStartTime = 19283;
const uint64_t kDuration = 98028; const uint64_t kDuration = 98028;
const uint64_t kFileSize = 756739; const uint64_t kFileSize = 756739;
EXPECT_CALL(mock_notifier_, NotifyCueEvent(_, kStartTime));
EXPECT_CALL(mock_notifier_, EXPECT_CALL(mock_notifier_,
NotifyNewSegment(_, StrEq("new_segment_name10.ts"), kStartTime, NotifyNewSegment(_, StrEq("new_segment_name10.ts"), kStartTime,
kDuration, _, kFileSize)); kDuration, _, kFileSize));
listener_.OnCueEvent(kStartTime, "dummy cue data");
listener_.OnNewSegment("new_segment_name10.ts", kStartTime, kDuration, listener_.OnNewSegment("new_segment_name10.ts", kStartTime, kDuration,
kFileSize); kFileSize);
} }
@ -361,6 +364,7 @@ TEST_F(HlsNotifyMuxerListenerTest, NoSegmentTemplateOnMediaEnd) {
const uint64_t kDuration = 98028; const uint64_t kDuration = 98028;
const uint64_t kFileSize = 756739; const uint64_t kFileSize = 756739;
listener_.OnCueEvent(kStartTime, "dummy cue data");
listener_.OnNewSegment("filename.mp4", kStartTime, kDuration, listener_.OnNewSegment("filename.mp4", kStartTime, kDuration,
kFileSize); kFileSize);
MuxerListener::MediaRanges ranges; MuxerListener::MediaRanges ranges;
@ -380,6 +384,7 @@ TEST_F(HlsNotifyMuxerListenerTest, NoSegmentTemplateOnMediaEnd) {
ranges.index_range = index_range; ranges.index_range = index_range;
ranges.subsegment_ranges = segment_ranges; ranges.subsegment_ranges = segment_ranges;
EXPECT_CALL(mock_notifier_, NotifyCueEvent(_, kStartTime));
EXPECT_CALL(mock_notifier_, EXPECT_CALL(mock_notifier_,
NotifyNewSegment(_, StrEq("filename.mp4"), kStartTime, NotifyNewSegment(_, StrEq("filename.mp4"), kStartTime,
kDuration, kSegmentStartOffset, kFileSize)); kDuration, kSegmentStartOffset, kFileSize));

View File

@ -61,6 +61,9 @@ class MockMuxerListener : public MuxerListener {
uint64_t start_time, uint64_t start_time,
uint64_t duration, uint64_t duration,
uint64_t segment_file_size)); uint64_t segment_file_size));
MOCK_METHOD2(OnCueEvent,
void(uint64_t timestamp, const std::string& cue_data));
}; };
} // namespace media } // namespace media

View File

@ -132,10 +132,13 @@ void MpdNotifyMuxerListener::OnMediaEnd(const MediaRanges& media_ranges,
mpd_notifier_->NotifyNewContainer(*media_info_, &id); mpd_notifier_->NotifyNewContainer(*media_info_, &id);
// TODO(rkuroiwa): Use media_ranges.subsegment_ranges instead of caching the // TODO(rkuroiwa): Use media_ranges.subsegment_ranges instead of caching the
// subsegments. // subsegments.
for (std::list<SubsegmentInfo>::const_iterator it = subsegments_.begin(); for (const SubsegmentInfo& subsegment : subsegments_) {
it != subsegments_.end(); ++it) { if (subsegment.cue_break) {
mpd_notifier_->NotifyNewSegment(id, it->start_time, it->duration, mpd_notifier_->NotifyCueEvent(id, subsegment.start_time);
it->segment_file_size); }
mpd_notifier_->NotifyNewSegment(id, subsegment.start_time,
subsegment.duration,
subsegment.segment_file_size);
} }
subsegments_.clear(); subsegments_.clear();
mpd_notifier_->Flush(); mpd_notifier_->Flush();
@ -152,10 +155,22 @@ void MpdNotifyMuxerListener::OnNewSegment(const std::string& file_name,
if (mpd_notifier_->mpd_type() == MpdType::kDynamic) if (mpd_notifier_->mpd_type() == MpdType::kDynamic)
mpd_notifier_->Flush(); mpd_notifier_->Flush();
} else { } 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); 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 media
} // namespace shaka } // namespace shaka

View File

@ -51,6 +51,7 @@ class MpdNotifyMuxerListener : public MuxerListener {
uint64_t start_time, uint64_t start_time,
uint64_t duration, uint64_t duration,
uint64_t segment_file_size) override; uint64_t segment_file_size) override;
void OnCueEvent(uint64_t timestamp, const std::string& cue_data) override;
/// @} /// @}
private: private:
@ -59,15 +60,16 @@ class MpdNotifyMuxerListener : public MuxerListener {
uint64_t start_time; uint64_t start_time;
uint64_t duration; uint64_t duration;
uint64_t segment_file_size; uint64_t segment_file_size;
bool cue_break;
}; };
MpdNotifier* const mpd_notifier_; MpdNotifier* const mpd_notifier_ = nullptr;
uint32_t notification_id_; uint32_t notification_id_ = 0;
std::unique_ptr<MediaInfo> media_info_; std::unique_ptr<MediaInfo> media_info_;
bool is_encrypted_; bool is_encrypted_ = false;
// Storage for values passed to OnEncryptionInfoReady(). // Storage for values passed to OnEncryptionInfoReady().
FourCC protection_scheme_; FourCC protection_scheme_ = FOURCC_NULL;
std::vector<uint8_t> default_key_id_; std::vector<uint8_t> default_key_id_;
std::vector<ProtectionSystemSpecificInfo> key_system_info_; 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 // (in OnMediaEnd). This is not used for live because NotifyNewSegment() is
// called immediately in OnNewSegment(). // called immediately in OnNewSegment().
std::list<SubsegmentInfo> subsegments_; std::list<SubsegmentInfo> subsegments_;
// Whether the next subsegment contains AdCue break.
bool next_subsegment_contains_cue_break_ = false;
DISALLOW_COPY_AND_ASSIGN(MpdNotifyMuxerListener); DISALLOW_COPY_AND_ASSIGN(MpdNotifyMuxerListener);
}; };

View File

@ -251,6 +251,7 @@ TEST_F(MpdNotifyMuxerListenerTest, VodOnNewSegment) {
kDefaultReferenceTimeScale, kDefaultReferenceTimeScale,
MuxerListener::kContainerMp4); MuxerListener::kContainerMp4);
listener_->OnNewSegment("", kStartTime1, kDuration1, kSegmentFileSize1); listener_->OnNewSegment("", kStartTime1, kDuration1, kSegmentFileSize1);
listener_->OnCueEvent(kStartTime2, "dummy cue data");
listener_->OnNewSegment("", kStartTime2, kDuration2, kSegmentFileSize2); listener_->OnNewSegment("", kStartTime2, kDuration2, kSegmentFileSize2);
::testing::Mock::VerifyAndClearExpectations(notifier_.get()); ::testing::Mock::VerifyAndClearExpectations(notifier_.get());
@ -259,6 +260,7 @@ TEST_F(MpdNotifyMuxerListenerTest, VodOnNewSegment) {
ExpectMediaInfoEq(kExpectedDefaultMediaInfo), _)); ExpectMediaInfoEq(kExpectedDefaultMediaInfo), _));
EXPECT_CALL(*notifier_, EXPECT_CALL(*notifier_,
NotifyNewSegment(_, kStartTime1, kDuration1, kSegmentFileSize1)); NotifyNewSegment(_, kStartTime1, kDuration1, kSegmentFileSize1));
EXPECT_CALL(*notifier_, NotifyCueEvent(_, kStartTime2));
EXPECT_CALL(*notifier_, EXPECT_CALL(*notifier_,
NotifyNewSegment(_, kStartTime2, kDuration2, kSegmentFileSize2)); NotifyNewSegment(_, kStartTime2, kDuration2, kSegmentFileSize2));
EXPECT_CALL(*notifier_, Flush()); EXPECT_CALL(*notifier_, Flush());
@ -317,6 +319,7 @@ TEST_P(MpdNotifyMuxerListenerTest, LiveNoKeyRotation) {
// Flush should only be called once in OnMediaEnd. // Flush should only be called once in OnMediaEnd.
if (GetParam() == MpdType::kDynamic) if (GetParam() == MpdType::kDynamic)
EXPECT_CALL(*notifier_, Flush()); EXPECT_CALL(*notifier_, Flush());
EXPECT_CALL(*notifier_, NotifyCueEvent(_, kStartTime2));
EXPECT_CALL(*notifier_, EXPECT_CALL(*notifier_,
NotifyNewSegment(_, kStartTime2, kDuration2, kSegmentFileSize2)); NotifyNewSegment(_, kStartTime2, kDuration2, kSegmentFileSize2));
if (GetParam() == MpdType::kDynamic) if (GetParam() == MpdType::kDynamic)
@ -330,6 +333,7 @@ TEST_P(MpdNotifyMuxerListenerTest, LiveNoKeyRotation) {
kDefaultReferenceTimeScale, kDefaultReferenceTimeScale,
MuxerListener::kContainerMp4); MuxerListener::kContainerMp4);
listener_->OnNewSegment("", kStartTime1, kDuration1, kSegmentFileSize1); listener_->OnNewSegment("", kStartTime1, kDuration1, kSegmentFileSize1);
listener_->OnCueEvent(kStartTime2, "dummy cue data");
listener_->OnNewSegment("", kStartTime2, kDuration2, kSegmentFileSize2); listener_->OnNewSegment("", kStartTime2, kDuration2, kSegmentFileSize2);
::testing::Mock::VerifyAndClearExpectations(notifier_.get()); ::testing::Mock::VerifyAndClearExpectations(notifier_.get());

View File

@ -127,6 +127,11 @@ class MuxerListener {
uint64_t duration, uint64_t duration,
uint64_t segment_file_size) = 0; 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: protected:
MuxerListener() {}; MuxerListener() {};
}; };

View File

@ -88,6 +88,11 @@ void VodMediaInfoDumpMuxerListener::OnNewSegment(const std::string& file_name,
uint64_t duration, uint64_t duration,
uint64_t segment_file_size) {} uint64_t segment_file_size) {}
void VodMediaInfoDumpMuxerListener::OnCueEvent(uint64_t timestamp,
const std::string& cue_data) {
NOTIMPLEMENTED();
}
// static // static
bool VodMediaInfoDumpMuxerListener::WriteMediaInfoToFile( bool VodMediaInfoDumpMuxerListener::WriteMediaInfoToFile(
const MediaInfo& media_info, const MediaInfo& media_info,

View File

@ -50,6 +50,7 @@ class VodMediaInfoDumpMuxerListener : public MuxerListener {
uint64_t start_time, uint64_t start_time,
uint64_t duration, uint64_t duration,
uint64_t segment_file_size) override; 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. /// 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 start_time,
uint64_t duration, uint64_t duration,
uint64_t size)); uint64_t size));
MOCK_METHOD2(NotifyCueEvent, bool(uint32_t container_id, uint64_t timestamp));
MOCK_METHOD4(NotifyEncryptionUpdate, MOCK_METHOD4(NotifyEncryptionUpdate,
bool(uint32_t container_id, bool(uint32_t container_id,
const std::string& drm_uuid, const std::string& drm_uuid,

View File

@ -71,6 +71,13 @@ class MpdNotifier {
uint64_t duration, uint64_t duration,
uint64_t size) = 0; 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. /// 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. /// This may be called whenever the key has to change, e.g. key rotation.
/// @param container_id Container ID obtained from calling /// @param container_id Container ID obtained from calling

View File

@ -96,6 +96,12 @@ bool SimpleMpdNotifier::NotifyNewSegment(uint32_t container_id,
return true; return true;
} }
bool SimpleMpdNotifier::NotifyCueEvent(uint32_t container_id,
uint64_t timestamp) {
NOTIMPLEMENTED();
return false;
}
bool SimpleMpdNotifier::NotifyEncryptionUpdate( bool SimpleMpdNotifier::NotifyEncryptionUpdate(
uint32_t container_id, uint32_t container_id,
const std::string& drm_uuid, const std::string& drm_uuid,

View File

@ -43,6 +43,7 @@ class SimpleMpdNotifier : public MpdNotifier {
uint64_t start_time, uint64_t start_time,
uint64_t duration, uint64_t duration,
uint64_t size) override; uint64_t size) override;
bool NotifyCueEvent(uint32_t container_id, uint64_t timestamp) override;
bool NotifyEncryptionUpdate(uint32_t container_id, bool NotifyEncryptionUpdate(uint32_t container_id,
const std::string& drm_uuid, const std::string& drm_uuid,
const std::vector<uint8_t>& new_key_id, const std::vector<uint8_t>& new_key_id,