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>& iv,
|
||||
const std::vector<ProtectionSystemSpecificInfo>& key_system_infos) {
|
||||
if (!media_started_) {
|
||||
if (!stream_id_) {
|
||||
next_key_id_ = key_id;
|
||||
next_iv_ = iv;
|
||||
next_key_system_infos_ = key_system_infos;
|
||||
|
@ -53,13 +53,13 @@ void HlsNotifyMuxerListener::OnEncryptionInfoReady(
|
|||
}
|
||||
for (const ProtectionSystemSpecificInfo& info : key_system_infos) {
|
||||
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.";
|
||||
}
|
||||
}
|
||||
|
||||
void HlsNotifyMuxerListener::OnEncryptionStart() {
|
||||
if (!media_started_) {
|
||||
if (!stream_id_) {
|
||||
must_notify_encryption_start_ = true;
|
||||
return;
|
||||
}
|
||||
|
@ -71,7 +71,7 @@ void HlsNotifyMuxerListener::OnEncryptionStart() {
|
|||
|
||||
for (const ProtectionSystemSpecificInfo& info : next_key_system_infos_) {
|
||||
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";
|
||||
}
|
||||
next_key_id_.clear();
|
||||
|
@ -84,31 +84,36 @@ void HlsNotifyMuxerListener::OnMediaStart(const MuxerOptions& muxer_options,
|
|||
const StreamInfo& stream_info,
|
||||
uint32_t time_scale,
|
||||
ContainerType container_type) {
|
||||
MediaInfo media_info;
|
||||
std::unique_ptr<MediaInfo> media_info(new MediaInfo);
|
||||
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.";
|
||||
return;
|
||||
}
|
||||
if (protection_scheme_ != FOURCC_NULL) {
|
||||
internal::SetContentProtectionFields(protection_scheme_, next_key_id_,
|
||||
next_key_system_infos_, &media_info);
|
||||
next_key_system_infos_,
|
||||
media_info.get());
|
||||
}
|
||||
|
||||
media_info_ = media_info;
|
||||
if (!media_info_.has_segment_template()) {
|
||||
// The content may be splitted into multiple files, but their MediaInfo
|
||||
// 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;
|
||||
}
|
||||
|
||||
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.";
|
||||
if (!NotifyNewStream())
|
||||
return;
|
||||
}
|
||||
DCHECK(stream_id_);
|
||||
|
||||
media_started_ = true;
|
||||
if (must_notify_encryption_start_) {
|
||||
OnEncryptionStart();
|
||||
}
|
||||
|
@ -118,36 +123,35 @@ void HlsNotifyMuxerListener::OnSampleDurationReady(uint32_t sample_duration) {}
|
|||
|
||||
void HlsNotifyMuxerListener::OnMediaEnd(const MediaRanges& media_ranges,
|
||||
float duration_seconds) {
|
||||
DCHECK(media_info_);
|
||||
// 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
|
||||
// before all Media Playlists are read. Which could cause problems
|
||||
// setting the correct EXT-X-TARGETDURATION.
|
||||
if (media_info_.has_segment_template()) {
|
||||
if (media_info_->has_segment_template()) {
|
||||
return;
|
||||
}
|
||||
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_end(media_ranges.init_range.value().end);
|
||||
}
|
||||
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_end(media_ranges.index_range.value().end);
|
||||
}
|
||||
|
||||
// TODO(rkuroiwa): Make this a method. This is the same as OnMediaStart().
|
||||
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;
|
||||
if (!stream_id_) {
|
||||
if (!NotifyNewStream())
|
||||
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
|
||||
// notification is sent right before the enecrypted (sub)segments.
|
||||
media_started_ = true;
|
||||
if (must_notify_encryption_start_) {
|
||||
OnEncryptionStart();
|
||||
}
|
||||
|
@ -163,7 +167,7 @@ void HlsNotifyMuxerListener::OnMediaEnd(const MediaRanges& media_ranges,
|
|||
if (subsegment_index < num_subsegments) {
|
||||
const Range& range = subsegment_ranges[subsegment_index];
|
||||
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.duration, range.start,
|
||||
range.end + 1 - range.start);
|
||||
|
@ -175,14 +179,14 @@ void HlsNotifyMuxerListener::OnMediaEnd(const MediaRanges& media_ranges,
|
|||
const uint64_t segment_start_offset =
|
||||
subsegment_ranges[subsegment_index].start;
|
||||
hls_notifier_->NotifyKeyFrame(
|
||||
stream_id_, event_info.key_frame.timestamp,
|
||||
stream_id_.value(), event_info.key_frame.timestamp,
|
||||
segment_start_offset +
|
||||
event_info.key_frame.start_offset_in_segment,
|
||||
event_info.key_frame.size);
|
||||
}
|
||||
break;
|
||||
case EventInfoType::kCue:
|
||||
hls_notifier_->NotifyCueEvent(stream_id_,
|
||||
hls_notifier_->NotifyCueEvent(stream_id_.value(),
|
||||
event_info.cue_event_info.timestamp);
|
||||
break;
|
||||
}
|
||||
|
@ -194,13 +198,14 @@ void HlsNotifyMuxerListener::OnMediaEnd(const MediaRanges& media_ranges,
|
|||
<< event_info_.size() << ").";
|
||||
}
|
||||
}
|
||||
event_info_.clear();
|
||||
}
|
||||
|
||||
void HlsNotifyMuxerListener::OnNewSegment(const std::string& file_name,
|
||||
uint64_t start_time,
|
||||
uint64_t duration,
|
||||
uint64_t segment_file_size) {
|
||||
if (!media_info_.has_segment_template()) {
|
||||
if (!media_info_->has_segment_template()) {
|
||||
EventInfo event_info;
|
||||
event_info.type = EventInfoType::kSegment;
|
||||
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.
|
||||
const size_t kStartingByteOffset = 0u;
|
||||
const bool result = hls_notifier_->NotifyNewSegment(
|
||||
stream_id_, file_name, start_time, duration, kStartingByteOffset,
|
||||
segment_file_size);
|
||||
stream_id_.value(), file_name, start_time, duration,
|
||||
kStartingByteOffset, segment_file_size);
|
||||
LOG_IF(WARNING, !result) << "Failed to add new segment.";
|
||||
}
|
||||
}
|
||||
|
@ -220,14 +225,14 @@ void HlsNotifyMuxerListener::OnKeyFrame(uint64_t timestamp,
|
|||
uint64_t size) {
|
||||
if (!iframes_only_)
|
||||
return;
|
||||
if (!media_info_.has_segment_template()) {
|
||||
if (!media_info_->has_segment_template()) {
|
||||
EventInfo event_info;
|
||||
event_info.type = EventInfoType::kKeyFrame;
|
||||
event_info.key_frame = {timestamp, start_byte_offset, size};
|
||||
event_info_.push_back(event_info);
|
||||
} else {
|
||||
const bool result = hls_notifier_->NotifyKeyFrame(stream_id_, timestamp,
|
||||
start_byte_offset, size);
|
||||
const bool result = hls_notifier_->NotifyKeyFrame(
|
||||
stream_id_.value(), timestamp, start_byte_offset, size);
|
||||
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,
|
||||
const std::string& cue_data) {
|
||||
// Not using |cue_data| at this moment.
|
||||
if (!media_info_.has_segment_template()) {
|
||||
if (!media_info_->has_segment_template()) {
|
||||
EventInfo event_info;
|
||||
event_info.type = EventInfoType::kCue;
|
||||
event_info.cue_event_info = {timestamp};
|
||||
event_info_.push_back(event_info);
|
||||
} 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 shaka
|
||||
|
|
|
@ -7,8 +7,10 @@
|
|||
#ifndef PACKAGER_MEDIA_EVENT_HLS_NOTIFY_MUXER_LISTENER_H_
|
||||
#define PACKAGER_MEDIA_EVENT_HLS_NOTIFY_MUXER_LISTENER_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "packager/base/optional.h"
|
||||
#include "packager/media/event/event_info.h"
|
||||
#include "packager/media/event/muxer_listener.h"
|
||||
#include "packager/mpd/base/media_info.pb.h"
|
||||
|
@ -71,14 +73,15 @@ class HlsNotifyMuxerListener : public MuxerListener {
|
|||
HlsNotifyMuxerListener(const HlsNotifyMuxerListener&) = delete;
|
||||
HlsNotifyMuxerListener& operator=(const HlsNotifyMuxerListener&) = delete;
|
||||
|
||||
bool NotifyNewStream();
|
||||
|
||||
const std::string playlist_name_;
|
||||
const bool iframes_only_;
|
||||
const std::string ext_x_media_name_;
|
||||
const std::string ext_x_media_group_id_;
|
||||
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;
|
||||
// Cached encryption info before OnMediaStart() is called.
|
||||
std::vector<uint8_t> next_key_id_;
|
||||
|
@ -88,7 +91,7 @@ class HlsNotifyMuxerListener : public MuxerListener {
|
|||
|
||||
// MediaInfo passed to Notifier::OnNewStream(). Mainly for single segment
|
||||
// playlists.
|
||||
MediaInfo media_info_;
|
||||
std::unique_ptr<MediaInfo> media_info_;
|
||||
// Even information for delayed function calls (NotifyNewSegment and
|
||||
// NotifyCueEvent) after NotifyNewStream is called in OnMediaEnd. Only needed
|
||||
// for on-demand as the functions are called immediately in live mode.
|
||||
|
|
|
@ -18,6 +18,7 @@ namespace media {
|
|||
|
||||
using ::testing::_;
|
||||
using ::testing::Bool;
|
||||
using ::testing::InSequence;
|
||||
using ::testing::Return;
|
||||
using ::testing::StrEq;
|
||||
using ::testing::TestWithParam;
|
||||
|
@ -114,6 +115,23 @@ class HlsNotifyMuxerListenerTest : public ::testing::Test {
|
|||
kDefaultGroupId,
|
||||
&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_;
|
||||
HlsNotifyMuxerListener listener_;
|
||||
};
|
||||
|
@ -289,12 +307,6 @@ TEST_F(HlsNotifyMuxerListenerTest, OnSampleDurationReady) {
|
|||
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) {
|
||||
ON_CALL(mock_notifier_, NotifyNewStream(_, _, _, _, _))
|
||||
.WillByDefault(Return(true));
|
||||
|
@ -332,29 +344,60 @@ TEST_F(HlsNotifyMuxerListenerTest, NoSegmentTemplateOnMediaEnd) {
|
|||
listener_.OnCueEvent(kCueStartTime, "dummy cue data");
|
||||
listener_.OnNewSegment("filename.mp4", kSegmentStartTime, kSegmentDuration,
|
||||
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_,
|
||||
NotifyNewSegment(_, StrEq("filename.mp4"), kSegmentStartTime,
|
||||
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
|
||||
|
@ -374,35 +417,16 @@ TEST_F(HlsNotifyMuxerListenerTest,
|
|||
|
||||
listener_.OnNewSegment("filename.mp4", kSegmentStartTime, kSegmentDuration,
|
||||
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(
|
||||
mock_notifier_,
|
||||
NotifyNewSegment(_, StrEq("filename.mp4"), kSegmentStartTime,
|
||||
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> {
|
||||
|
|
|
@ -20,7 +20,7 @@ namespace shaka {
|
|||
namespace media {
|
||||
|
||||
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->dash_profile() == DashProfile::kOnDemand ||
|
||||
mpd_notifier->dash_profile() == DashProfile::kLive);
|
||||
|
@ -43,12 +43,14 @@ void MpdNotifyMuxerListener::OnEncryptionInfoReady(
|
|||
is_encrypted_ = true;
|
||||
return;
|
||||
}
|
||||
if (!notification_id_)
|
||||
return;
|
||||
DCHECK_EQ(protection_scheme, protection_scheme_);
|
||||
|
||||
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(
|
||||
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.";
|
||||
}
|
||||
}
|
||||
|
@ -75,11 +77,21 @@ void MpdNotifyMuxerListener::OnMediaStart(
|
|||
key_system_info_, media_info.get());
|
||||
}
|
||||
|
||||
// The content may be splitted into multiple files, but their MediaInfo
|
||||
// should be compatible.
|
||||
if (media_info_ &&
|
||||
!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);
|
||||
|
||||
if (mpd_notifier_->dash_profile() == DashProfile::kLive) {
|
||||
// TODO(kqyang): Check return result.
|
||||
mpd_notifier_->NotifyNewContainer(*media_info, ¬ification_id_);
|
||||
} else {
|
||||
media_info_ = std::move(media_info);
|
||||
if (!NotifyNewContainer())
|
||||
return;
|
||||
DCHECK(notification_id_);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -88,7 +100,8 @@ void MpdNotifyMuxerListener::OnMediaStart(
|
|||
void MpdNotifyMuxerListener::OnSampleDurationReady(
|
||||
uint32_t sample_duration) {
|
||||
if (mpd_notifier_->dash_profile() == DashProfile::kLive) {
|
||||
mpd_notifier_->NotifySampleDuration(notification_id_, sample_duration);
|
||||
mpd_notifier_->NotifySampleDuration(notification_id_.value(),
|
||||
sample_duration);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -126,16 +139,21 @@ void MpdNotifyMuxerListener::OnMediaEnd(const MediaRanges& media_ranges,
|
|||
return;
|
||||
}
|
||||
|
||||
uint32_t id;
|
||||
// TODO(kqyang): Check return result.
|
||||
mpd_notifier_->NotifyNewContainer(*media_info_, &id);
|
||||
if (notification_id_) {
|
||||
mpd_notifier_->NotifyMediaInfoUpdate(notification_id_.value(),
|
||||
*media_info_);
|
||||
} else {
|
||||
if (!NotifyNewContainer())
|
||||
return;
|
||||
DCHECK(notification_id_);
|
||||
}
|
||||
// TODO(rkuroiwa): Use media_ranges.subsegment_ranges instead of caching the
|
||||
// subsegments.
|
||||
for (const auto& event_info : event_info_) {
|
||||
switch (event_info.type) {
|
||||
case EventInfoType::kSegment:
|
||||
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.segment_file_size);
|
||||
break;
|
||||
|
@ -143,7 +161,8 @@ void MpdNotifyMuxerListener::OnMediaEnd(const MediaRanges& media_ranges,
|
|||
// NO-OP for DASH.
|
||||
break;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -156,9 +175,8 @@ void MpdNotifyMuxerListener::OnNewSegment(const std::string& file_name,
|
|||
uint64_t duration,
|
||||
uint64_t segment_file_size) {
|
||||
if (mpd_notifier_->dash_profile() == DashProfile::kLive) {
|
||||
// TODO(kqyang): Check return result.
|
||||
mpd_notifier_->NotifyNewSegment(
|
||||
notification_id_, start_time, duration, segment_file_size);
|
||||
mpd_notifier_->NotifyNewSegment(notification_id_.value(), start_time,
|
||||
duration, segment_file_size);
|
||||
if (mpd_notifier_->mpd_type() == MpdType::kDynamic)
|
||||
mpd_notifier_->Flush();
|
||||
} else {
|
||||
|
@ -179,7 +197,7 @@ 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);
|
||||
mpd_notifier_->NotifyCueEvent(notification_id_.value(), timestamp);
|
||||
} else {
|
||||
EventInfo event_info;
|
||||
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 shaka
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "packager/base/optional.h"
|
||||
#include "packager/media/base/muxer_options.h"
|
||||
#include "packager/media/event/event_info.h"
|
||||
#include "packager/media/event/muxer_listener.h"
|
||||
|
@ -60,8 +61,10 @@ class MpdNotifyMuxerListener : public MuxerListener {
|
|||
MpdNotifyMuxerListener(const MpdNotifyMuxerListener&) = delete;
|
||||
MpdNotifyMuxerListener& operator=(const MpdNotifyMuxerListener&) = delete;
|
||||
|
||||
bool NotifyNewContainer();
|
||||
|
||||
MpdNotifier* const mpd_notifier_ = nullptr;
|
||||
uint32_t notification_id_ = 0;
|
||||
base::Optional<uint32_t> notification_id_;
|
||||
std::unique_ptr<MediaInfo> media_info_;
|
||||
|
||||
bool is_encrypted_ = false;
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
|
||||
using ::testing::_;
|
||||
using ::testing::InSequence;
|
||||
using ::testing::Return;
|
||||
|
||||
namespace shaka {
|
||||
|
||||
|
@ -91,6 +92,11 @@ class MpdNotifyMuxerListenerTest : public ::testing::TestWithParam<MpdType> {
|
|||
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, "") {
|
||||
const MediaInfo expected = ConvertToMediaInfo(expected_text_format);
|
||||
*result_listener << arg.ShortDebugString();
|
||||
|
@ -112,7 +118,8 @@ TEST_F(MpdNotifyMuxerListenerTest, VodClearContent) {
|
|||
::testing::Mock::VerifyAndClearExpectations(notifier_.get());
|
||||
|
||||
EXPECT_CALL(*notifier_, NotifyNewContainer(
|
||||
ExpectMediaInfoEq(kExpectedDefaultMediaInfo), _));
|
||||
ExpectMediaInfoEq(kExpectedDefaultMediaInfo), _))
|
||||
.WillOnce(Return(true));
|
||||
EXPECT_CALL(*notifier_, Flush());
|
||||
FireOnMediaEndWithParams(GetDefaultOnMediaEndParams());
|
||||
}
|
||||
|
@ -177,7 +184,8 @@ TEST_F(MpdNotifyMuxerListenerTest, VodEncryptedContent) {
|
|||
::testing::Mock::VerifyAndClearExpectations(notifier_.get());
|
||||
|
||||
EXPECT_CALL(*notifier_,
|
||||
NotifyNewContainer(ExpectMediaInfoEq(kExpectedMediaInfo), _));
|
||||
NotifyNewContainer(ExpectMediaInfoEq(kExpectedMediaInfo), _))
|
||||
.WillOnce(Return(true));
|
||||
EXPECT_CALL(*notifier_, Flush());
|
||||
FireOnMediaEndWithParams(GetDefaultOnMediaEndParams());
|
||||
}
|
||||
|
@ -224,7 +232,8 @@ TEST_F(MpdNotifyMuxerListenerTest, VodOnSampleDurationReady) {
|
|||
::testing::Mock::VerifyAndClearExpectations(notifier_.get());
|
||||
|
||||
EXPECT_CALL(*notifier_,
|
||||
NotifyNewContainer(ExpectMediaInfoEq(kExpectedMediaInfo), _));
|
||||
NotifyNewContainer(ExpectMediaInfoEq(kExpectedMediaInfo), _))
|
||||
.WillOnce(Return(true));
|
||||
EXPECT_CALL(*notifier_, Flush());
|
||||
FireOnMediaEndWithParams(GetDefaultOnMediaEndParams());
|
||||
}
|
||||
|
@ -258,7 +267,8 @@ TEST_F(MpdNotifyMuxerListenerTest, VodOnNewSegment) {
|
|||
|
||||
InSequence s;
|
||||
EXPECT_CALL(*notifier_, NotifyNewContainer(
|
||||
ExpectMediaInfoEq(kExpectedDefaultMediaInfo), _));
|
||||
ExpectMediaInfoEq(kExpectedDefaultMediaInfo), _))
|
||||
.WillOnce(Return(true));
|
||||
EXPECT_CALL(*notifier_,
|
||||
NotifyNewSegment(_, kStartTime1, kDuration1, kSegmentFileSize1));
|
||||
EXPECT_CALL(*notifier_, NotifyCueEvent(_, kStartTime2));
|
||||
|
@ -268,6 +278,69 @@ TEST_F(MpdNotifyMuxerListenerTest, VodOnNewSegment) {
|
|||
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
|
||||
// OnMediaStart() but no more calls.
|
||||
TEST_P(MpdNotifyMuxerListenerTest, LiveNoKeyRotation) {
|
||||
|
@ -314,7 +387,7 @@ TEST_P(MpdNotifyMuxerListenerTest, LiveNoKeyRotation) {
|
|||
EXPECT_CALL(*notifier_, NotifyEncryptionUpdate(_, _, _, _)).Times(0);
|
||||
EXPECT_CALL(*notifier_,
|
||||
NotifyNewContainer(ExpectMediaInfoEq(kExpectedMediaInfo), _))
|
||||
.Times(1);
|
||||
.WillOnce(Return(true));
|
||||
EXPECT_CALL(*notifier_,
|
||||
NotifyNewSegment(_, kStartTime1, kDuration1, kSegmentFileSize1));
|
||||
// Flush should only be called once in OnMediaEnd.
|
||||
|
@ -385,7 +458,7 @@ TEST_P(MpdNotifyMuxerListenerTest, LiveWithKeyRotation) {
|
|||
InSequence s;
|
||||
EXPECT_CALL(*notifier_,
|
||||
NotifyNewContainer(ExpectMediaInfoEq(kExpectedMediaInfo), _))
|
||||
.Times(1);
|
||||
.WillOnce(Return(true));
|
||||
EXPECT_CALL(*notifier_, NotifyEncryptionUpdate(_, _, _, _)).Times(1);
|
||||
EXPECT_CALL(*notifier_,
|
||||
NotifyNewSegment(_, kStartTime1, kDuration1, kSegmentFileSize1));
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#include "packager/media/event/muxer_listener_internal.h"
|
||||
|
||||
#include <google/protobuf/util/message_differencer.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "packager/base/logging.h"
|
||||
|
@ -19,6 +20,8 @@
|
|||
#include "packager/media/codecs/ec3_audio_util.h"
|
||||
#include "packager/mpd/base/media_info.pb.h"
|
||||
|
||||
using ::google::protobuf::util::MessageDifferencer;
|
||||
|
||||
namespace shaka {
|
||||
namespace media {
|
||||
namespace internal {
|
||||
|
@ -176,6 +179,19 @@ bool GenerateMediaInfo(const MuxerOptions& muxer_options,
|
|||
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,
|
||||
float duration_seconds,
|
||||
MediaInfo* media_info) {
|
||||
|
|
|
@ -33,6 +33,11 @@ bool GenerateMediaInfo(const MuxerOptions& muxer_options,
|
|||
MuxerListener::ContainerType container_type,
|
||||
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.
|
||||
/// @return true on success, false otherwise.
|
||||
bool SetVodInformation(const MuxerListener::MediaRanges& media_ranges,
|
||||
|
|
|
@ -37,10 +37,8 @@ class MockMpdNotifier : public MpdNotifier {
|
|||
const std::string& drm_uuid,
|
||||
const std::vector<uint8_t>& new_key_id,
|
||||
const std::vector<uint8_t>& new_pssh));
|
||||
MOCK_METHOD2(
|
||||
AddContentProtectionElement,
|
||||
bool(uint32_t container_id,
|
||||
const ContentProtectionElement& content_protection_element));
|
||||
MOCK_METHOD2(NotifyMediaInfoUpdate,
|
||||
bool(uint32_t container_id, const MediaInfo& media_info));
|
||||
MOCK_METHOD0(Flush, bool());
|
||||
};
|
||||
|
||||
|
|
|
@ -92,6 +92,13 @@ class MpdNotifier {
|
|||
const std::vector<uint8_t>& new_key_id,
|
||||
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
|
||||
/// the MPD to a stream (file, stdout, etc.) when the MPD is updated, this
|
||||
/// forces a flush.
|
||||
|
|
|
@ -286,7 +286,7 @@ xml::scoped_xml_ptr<xmlNode> Representation::GetXml() {
|
|||
|
||||
if (HasVODOnlyFields(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>();
|
||||
}
|
||||
|
||||
|
|
|
@ -145,6 +145,8 @@ class Representation {
|
|||
/// @return ID number for <Representation>.
|
||||
uint32_t id() const { return id_; }
|
||||
|
||||
void set_media_info(const MediaInfo& media_info) { media_info_ = media_info; }
|
||||
|
||||
protected:
|
||||
/// @param media_info is a MediaInfo containing information on the media.
|
||||
/// @a media_info.bandwidth is required for 'static' profile. If @a
|
||||
|
|
|
@ -165,6 +165,22 @@ bool SimpleMpdNotifier::NotifyEncryptionUpdate(
|
|||
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() {
|
||||
base::AutoLock auto_lock(lock_);
|
||||
return WriteMpdToFile(output_path_, mpd_builder_.get());
|
||||
|
|
|
@ -47,6 +47,8 @@ class SimpleMpdNotifier : public MpdNotifier {
|
|||
const std::string& drm_uuid,
|
||||
const std::vector<uint8_t>& new_key_id,
|
||||
const std::vector<uint8_t>& new_pssh) override;
|
||||
bool NotifyMediaInfoUpdate(uint32_t container_id,
|
||||
const MediaInfo& media_info) override;
|
||||
bool Flush() override;
|
||||
/// @}
|
||||
|
||||
|
|
Loading…
Reference in New Issue