Propage CueEvent to MpdNotifier/HlsNotifier
Change-Id: I9828af11a28300d20cc8742251bbe1b4ebfdbad1
This commit is contained in:
parent
b0edec8c40
commit
db74d6756e
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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_
|
||||
|
|
|
@ -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.";
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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_;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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_;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
|
|
@ -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());
|
||||
|
||||
|
|
|
@ -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() {};
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue