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:
KongQun Yang 2018-05-21 17:39:21 -07:00
parent cf3fc61fbe
commit 192ef2a0ac
14 changed files with 315 additions and 118 deletions

View File

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

View File

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

View File

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

View File

@ -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, &notification_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_, &notification_id)) {
LOG(ERROR) << "Failed to notify MpdNotifier.";
return false;
}
notification_id_ = notification_id;
return true;
}
} // namespace media } // namespace media
} // namespace shaka } // namespace shaka

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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;
/// @} /// @}