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