diff --git a/packager/media/base/media_handler.h b/packager/media/base/media_handler.h index 18f4bc6482a..7dfd63f10e7 100644 --- a/packager/media/base/media_handler.h +++ b/packager/media/base/media_handler.h @@ -57,6 +57,7 @@ struct SegmentInfo { bool is_encrypted = false; int64_t start_timestamp = -1; int64_t duration = 0; + int64_t segment_index = 0; // This is only available if key rotation is enabled. Note that we may have // a |key_rotation_encryption_config| even if the segment is not encrypted, // which is the case for clear lead. diff --git a/packager/media/base/media_handler_test_base.cc b/packager/media/base/media_handler_test_base.cc index 87e42aa4003..0146843a1e9 100644 --- a/packager/media/base/media_handler_test_base.cc +++ b/packager/media/base/media_handler_test_base.cc @@ -249,6 +249,7 @@ std::unique_ptr MediaHandlerTestBase::GetSegmentInfo( info->start_timestamp = start_timestamp; info->duration = duration; info->is_subsegment = is_subsegment; + info->segment_index = start_timestamp / duration + 1; return info; } diff --git a/packager/media/chunking/chunking_handler.cc b/packager/media/chunking/chunking_handler.cc index a9686071d21..25a18eeed27 100644 --- a/packager/media/chunking/chunking_handler.cc +++ b/packager/media/chunking/chunking_handler.cc @@ -26,6 +26,10 @@ bool IsNewSegmentIndex(int64_t new_index, int64_t current_index) { new_index != current_index - 1; } +bool isGreaterSegmentIndex(int64_t new_index, int64_t current_index) { + return new_index > current_index; +} + } // namespace ChunkingHandler::ChunkingHandler(const ChunkingParams& chunking_params) @@ -60,6 +64,7 @@ Status ChunkingHandler::Process(std::unique_ptr stream_data) { } Status ChunkingHandler::OnFlushRequest(size_t input_stream_index) { + set_segment_index_++; RETURN_IF_ERROR(EndSegmentIfStarted()); return FlushDownstream(kStreamIndex); } @@ -74,6 +79,7 @@ Status ChunkingHandler::OnStreamInfo(std::shared_ptr info) { } Status ChunkingHandler::OnCueEvent(std::shared_ptr event) { + set_segment_index_++; RETURN_IF_ERROR(EndSegmentIfStarted()); const double event_time_in_seconds = event->time_in_seconds; RETURN_IF_ERROR(DispatchCueEvent(kStreamIndex, std::move(event))); @@ -89,7 +95,6 @@ Status ChunkingHandler::OnCueEvent(std::shared_ptr event) { Status ChunkingHandler::OnMediaSample( std::shared_ptr sample) { DCHECK_NE(time_scale_, 0u) << "kStreamInfo should arrive before kMediaSample"; - const int64_t timestamp = sample->pts(); bool started_new_segment = false; @@ -101,6 +106,11 @@ Status ChunkingHandler::OnMediaSample( : (timestamp - cue_offset_) / segment_duration_; if (!segment_start_time_ || IsNewSegmentIndex(segment_index, current_segment_index_)) { + if (!isGreaterSegmentIndex(segment_index, set_segment_index_)) { + set_segment_index_ = current_segment_index_ + 1; + } else { + set_segment_index_ = segment_index; + } current_segment_index_ = segment_index; // Reset subsegment index. current_subsegment_index_ = 0; @@ -151,6 +161,7 @@ Status ChunkingHandler::EndSegmentIfStarted() const { auto segment_info = std::make_shared(); segment_info->start_timestamp = segment_start_time_.value(); segment_info->duration = max_segment_time_ - segment_start_time_.value(); + segment_info->segment_index = set_segment_index_; return DispatchSegmentInfo(kStreamIndex, std::move(segment_info)); } diff --git a/packager/media/chunking/chunking_handler.h b/packager/media/chunking/chunking_handler.h index 8159c8758de..e7dd8333b68 100644 --- a/packager/media/chunking/chunking_handler.h +++ b/packager/media/chunking/chunking_handler.h @@ -72,7 +72,7 @@ class ChunkingHandler : public MediaHandler { // Segment and subsegment duration in stream's time scale. int64_t segment_duration_ = 0; int64_t subsegment_duration_ = 0; - + int64_t set_segment_index_ = 0; // Current segment index, useful to determine where to do chunking. int64_t current_segment_index_ = -1; // Current subsegment index, useful to determine where to do chunking. diff --git a/packager/media/chunking/text_chunker.cc b/packager/media/chunking/text_chunker.cc index ea43f69ec17..60f3bb6fc0b 100644 --- a/packager/media/chunking/text_chunker.cc +++ b/packager/media/chunking/text_chunker.cc @@ -35,6 +35,7 @@ Status TextChunker::OnFlushRequest(size_t input_stream_index) { // Keep outputting segments until all the samples leave the system. Calling // |DispatchSegment| will remove samples over time. while (samples_in_current_segment_.size()) { + segment_index_++; RETURN_IF_ERROR(DispatchSegment(segment_duration_)); } @@ -107,6 +108,10 @@ Status TextChunker::DispatchSegment(int64_t duration) { std::shared_ptr info = std::make_shared(); info->start_timestamp = segment_start_; info->duration = duration; + if (((segment_start_ / segment_duration_) + 1) > segment_index_) { + segment_index_ = (segment_start_ / segment_duration_) + 1; + } + info->segment_index = segment_index_; RETURN_IF_ERROR(DispatchSegmentInfo(kStreamIndex, std::move(info))); // Move onto the next segment. diff --git a/packager/media/chunking/text_chunker.h b/packager/media/chunking/text_chunker.h index 8e9e09accb4..b252d201bff 100644 --- a/packager/media/chunking/text_chunker.h +++ b/packager/media/chunking/text_chunker.h @@ -52,6 +52,7 @@ class TextChunker : public MediaHandler { int64_t segment_start_ = -1; // Set when the first sample comes in. int64_t segment_duration_ = -1; // Set in OnStreamInfo. + int64_t segment_index_ = 0; // All samples that make up the current segment. We must store the samples // until the segment ends because a cue event may end the segment sooner // than we expected. diff --git a/packager/media/event/combined_muxer_listener.cc b/packager/media/event/combined_muxer_listener.cc index 6c28e267056..ab6aaa31763 100644 --- a/packager/media/event/combined_muxer_listener.cc +++ b/packager/media/event/combined_muxer_listener.cc @@ -59,9 +59,11 @@ void CombinedMuxerListener::OnMediaEnd(const MediaRanges& media_ranges, void CombinedMuxerListener::OnNewSegment(const std::string& file_name, int64_t start_time, int64_t duration, - uint64_t segment_file_size) { + uint64_t segment_file_size, + uint64_t segment_index) { for (auto& listener : muxer_listeners_) { - listener->OnNewSegment(file_name, start_time, duration, segment_file_size); + listener->OnNewSegment(file_name, start_time, duration, segment_file_size, + segment_index); } } diff --git a/packager/media/event/combined_muxer_listener.h b/packager/media/event/combined_muxer_listener.h index 2528974ae7d..368871f5aba 100644 --- a/packager/media/event/combined_muxer_listener.h +++ b/packager/media/event/combined_muxer_listener.h @@ -42,7 +42,8 @@ class CombinedMuxerListener : public MuxerListener { void OnNewSegment(const std::string& file_name, int64_t start_time, int64_t duration, - uint64_t segment_file_size) override; + uint64_t segment_file_size, + uint64_t index_segment) override; void OnKeyFrame(int64_t timestamp, uint64_t start_byte_offset, uint64_t size); void OnCueEvent(int64_t timestamp, const std::string& cue_data) override; /// @} diff --git a/packager/media/event/hls_notify_muxer_listener.cc b/packager/media/event/hls_notify_muxer_listener.cc index fdd326c4b6a..7dc75a4152e 100644 --- a/packager/media/event/hls_notify_muxer_listener.cc +++ b/packager/media/event/hls_notify_muxer_listener.cc @@ -228,7 +228,8 @@ void HlsNotifyMuxerListener::OnMediaEnd(const MediaRanges& media_ranges, void HlsNotifyMuxerListener::OnNewSegment(const std::string& file_name, int64_t start_time, int64_t duration, - uint64_t segment_file_size) { + uint64_t segment_file_size, + uint64_t segment_index) { if (!media_info_->has_segment_template()) { EventInfo event_info; event_info.type = EventInfoType::kSegment; diff --git a/packager/media/event/hls_notify_muxer_listener.h b/packager/media/event/hls_notify_muxer_listener.h index ed0cb8a4183..8bd26c32427 100644 --- a/packager/media/event/hls_notify_muxer_listener.h +++ b/packager/media/event/hls_notify_muxer_listener.h @@ -67,7 +67,8 @@ class HlsNotifyMuxerListener : public MuxerListener { void OnNewSegment(const std::string& file_name, int64_t start_time, int64_t duration, - uint64_t segment_file_size) override; + uint64_t segment_file_size, + uint64_t segment_index) override; void OnKeyFrame(int64_t timestamp, uint64_t start_byte_offset, uint64_t size); void OnCueEvent(int64_t timestamp, const std::string& cue_data) override; /// @} diff --git a/packager/media/event/hls_notify_muxer_listener_unittest.cc b/packager/media/event/hls_notify_muxer_listener_unittest.cc index d0923a8f443..622525721c1 100644 --- a/packager/media/event/hls_notify_muxer_listener_unittest.cc +++ b/packager/media/event/hls_notify_muxer_listener_unittest.cc @@ -345,7 +345,7 @@ TEST_F(HlsNotifyMuxerListenerTest, OnNewSegmentAndCueEvent) { kSegmentDuration, _, kSegmentSize)); listener_.OnCueEvent(kCueStartTime, "dummy cue data"); listener_.OnNewSegment("new_segment_name10.ts", kSegmentStartTime, - kSegmentDuration, kSegmentSize); + kSegmentDuration, kSegmentSize, 10); } // Verify that the notifier is called for every segment in OnMediaEnd if @@ -363,7 +363,7 @@ TEST_F(HlsNotifyMuxerListenerTest, NoSegmentTemplateOnMediaEnd) { listener_.OnCueEvent(kCueStartTime, "dummy cue data"); listener_.OnNewSegment("filename.mp4", kSegmentStartTime, kSegmentDuration, - kSegmentSize); + kSegmentSize, 0); EXPECT_CALL(mock_notifier_, NotifyCueEvent(_, kCueStartTime)); EXPECT_CALL( @@ -393,7 +393,7 @@ TEST_F(HlsNotifyMuxerListenerTest, NoSegmentTemplateOnMediaEndTwice) { listener_.OnMediaStart(muxer_options1, *video_stream_info, 90000, MuxerListener::kContainerMpeg2ts); listener_.OnNewSegment("filename1.mp4", kSegmentStartTime, kSegmentDuration, - kSegmentSize); + kSegmentSize, 1); listener_.OnCueEvent(kCueStartTime, "dummy cue data"); EXPECT_CALL(mock_notifier_, NotifyNewStream(_, _, _, _, _)) @@ -410,7 +410,7 @@ TEST_F(HlsNotifyMuxerListenerTest, NoSegmentTemplateOnMediaEndTwice) { listener_.OnMediaStart(muxer_options2, *video_stream_info, 90000, MuxerListener::kContainerMpeg2ts); listener_.OnNewSegment("filename2.mp4", kSegmentStartTime + kSegmentDuration, - kSegmentDuration, kSegmentSize); + kSegmentDuration, kSegmentSize, 2); EXPECT_CALL(mock_notifier_, NotifyNewSegment(_, StrEq("filename2.mp4"), kSegmentStartTime + kSegmentDuration, _, _, _)); @@ -436,7 +436,7 @@ TEST_F(HlsNotifyMuxerListenerTest, MuxerListener::kContainerMpeg2ts); listener_.OnNewSegment("filename.mp4", kSegmentStartTime, kSegmentDuration, - kSegmentSize); + kSegmentSize, 0); EXPECT_CALL( mock_notifier_, NotifyNewSegment(_, StrEq("filename.mp4"), kSegmentStartTime, @@ -498,7 +498,7 @@ TEST_P(HlsNotifyMuxerListenerKeyFrameTest, NoSegmentTemplate) { listener_.OnKeyFrame(kKeyFrameTimestamp, kKeyFrameStartByteOffset, kKeyFrameSize); listener_.OnNewSegment("filename.mp4", kSegmentStartTime, kSegmentDuration, - kSegmentSize); + kSegmentSize, 0); EXPECT_CALL(mock_notifier_, NotifyKeyFrame(_, kKeyFrameTimestamp, diff --git a/packager/media/event/mock_muxer_listener.h b/packager/media/event/mock_muxer_listener.h index 818fb4bcbac..c74075fe7fe 100644 --- a/packager/media/event/mock_muxer_listener.h +++ b/packager/media/event/mock_muxer_listener.h @@ -53,14 +53,14 @@ class MockMuxerListener : public MuxerListener { // Windows 32 bit cannot mock MediaRanges because it has Optionals that use // memory alignment of 8 bytes. The compiler fails if it is mocked. - void OnMediaEnd(const MediaRanges& range, - float duration_seconds) override; + void OnMediaEnd(const MediaRanges& range, float duration_seconds) override; - MOCK_METHOD4(OnNewSegment, + MOCK_METHOD5(OnNewSegment, void(const std::string& segment_name, int64_t start_time, int64_t duration, - uint64_t segment_file_size)); + uint64_t segment_file_size, + uint64_t segment_index)); MOCK_METHOD3(OnKeyFrame, void(int64_t timestamp, diff --git a/packager/media/event/mpd_notify_muxer_listener.cc b/packager/media/event/mpd_notify_muxer_listener.cc index 749b7843cba..1b0b8137d64 100644 --- a/packager/media/event/mpd_notify_muxer_listener.cc +++ b/packager/media/event/mpd_notify_muxer_listener.cc @@ -57,17 +57,13 @@ void MpdNotifyMuxerListener::OnEncryptionInfoReady( void MpdNotifyMuxerListener::OnEncryptionStart() {} -void MpdNotifyMuxerListener::OnMediaStart( - const MuxerOptions& muxer_options, - const StreamInfo& stream_info, - uint32_t time_scale, - ContainerType container_type) { +void MpdNotifyMuxerListener::OnMediaStart(const MuxerOptions& muxer_options, + const StreamInfo& stream_info, + uint32_t time_scale, + ContainerType container_type) { std::unique_ptr media_info(new MediaInfo()); - if (!internal::GenerateMediaInfo(muxer_options, - stream_info, - time_scale, - container_type, - media_info.get())) { + if (!internal::GenerateMediaInfo(muxer_options, stream_info, time_scale, + container_type, media_info.get())) { LOG(ERROR) << "Failed to generate MediaInfo from input."; return; } @@ -79,7 +75,8 @@ void MpdNotifyMuxerListener::OnMediaStart( if (is_encrypted_) { internal::SetContentProtectionFields(protection_scheme_, default_key_id_, key_system_info_, media_info.get()); - media_info->mutable_protected_content()->set_include_mspr_pro(mpd_notifier_->include_mspr_pro()); + media_info->mutable_protected_content()->set_include_mspr_pro( + mpd_notifier_->include_mspr_pro()); } // The content may be splitted into multiple files, but their MediaInfo @@ -102,8 +99,7 @@ void MpdNotifyMuxerListener::OnMediaStart( // Record the sample duration in the media info for VOD so that OnMediaEnd, all // the information is in the media info. -void MpdNotifyMuxerListener::OnSampleDurationReady( - uint32_t sample_duration) { +void MpdNotifyMuxerListener::OnSampleDurationReady(uint32_t sample_duration) { if (mpd_notifier_->dash_profile() == DashProfile::kLive) { mpd_notifier_->NotifySampleDuration(notification_id_.value(), sample_duration); @@ -157,7 +153,10 @@ void MpdNotifyMuxerListener::OnMediaEnd(const MediaRanges& media_ranges, mpd_notifier_->NotifyNewSegment( notification_id_.value(), event_info.segment_info.start_time, event_info.segment_info.duration, - event_info.segment_info.segment_file_size); + event_info.segment_info.segment_file_size, + (event_info.segment_info.start_time / + event_info.segment_info.duration) + + 1); break; case EventInfoType::kKeyFrame: // NO-OP for DASH. @@ -175,10 +174,11 @@ void MpdNotifyMuxerListener::OnMediaEnd(const MediaRanges& media_ranges, void MpdNotifyMuxerListener::OnNewSegment(const std::string& file_name, int64_t start_time, int64_t duration, - uint64_t segment_file_size) { + uint64_t segment_file_size, + uint64_t segment_index) { if (mpd_notifier_->dash_profile() == DashProfile::kLive) { mpd_notifier_->NotifyNewSegment(notification_id_.value(), start_time, - duration, segment_file_size); + duration, segment_file_size, segment_index); if (mpd_notifier_->mpd_type() == MpdType::kDynamic) mpd_notifier_->Flush(); } else { diff --git a/packager/media/event/mpd_notify_muxer_listener.h b/packager/media/event/mpd_notify_muxer_listener.h index ffa84c1f865..112d55e3572 100644 --- a/packager/media/event/mpd_notify_muxer_listener.h +++ b/packager/media/event/mpd_notify_muxer_listener.h @@ -50,7 +50,8 @@ class MpdNotifyMuxerListener : public MuxerListener { void OnNewSegment(const std::string& file_name, int64_t start_time, int64_t duration, - uint64_t segment_file_size) override; + uint64_t segment_file_size, + uint64_t segment_index) override; void OnKeyFrame(int64_t timestamp, uint64_t start_byte_offset, uint64_t size); void OnCueEvent(int64_t timestamp, const std::string& cue_data) override; /// @} diff --git a/packager/media/event/mpd_notify_muxer_listener_unittest.cc b/packager/media/event/mpd_notify_muxer_listener_unittest.cc index 219fac18266..a102ef11d18 100644 --- a/packager/media/event/mpd_notify_muxer_listener_unittest.cc +++ b/packager/media/event/mpd_notify_muxer_listener_unittest.cc @@ -62,15 +62,13 @@ namespace media { class MpdNotifyMuxerListenerTest : public ::testing::TestWithParam { public: - void SetupForVod() { MpdOptions mpd_options; mpd_options.dash_profile = DashProfile::kOnDemand; // On-demand profile should be static. mpd_options.mpd_type = MpdType::kStatic; notifier_.reset(new MockMpdNotifier(mpd_options)); - listener_.reset( - new MpdNotifyMuxerListener(notifier_.get())); + listener_.reset(new MpdNotifyMuxerListener(notifier_.get())); } void SetupForLive() { @@ -256,24 +254,24 @@ TEST_F(MpdNotifyMuxerListenerTest, VodOnNewSegment) { const uint64_t kSegmentFileSize2 = 83743u; EXPECT_CALL(*notifier_, NotifyNewContainer(_, _)).Times(0); - EXPECT_CALL(*notifier_, NotifyNewSegment(_, _, _, _)).Times(0); + EXPECT_CALL(*notifier_, NotifyNewSegment(_, _, _, _, _)).Times(0); listener_->OnMediaStart(muxer_options, *video_stream_info, kDefaultReferenceTimeScale, MuxerListener::kContainerMp4); - listener_->OnNewSegment("", kStartTime1, kDuration1, kSegmentFileSize1); + listener_->OnNewSegment("", kStartTime1, kDuration1, kSegmentFileSize1, 1); listener_->OnCueEvent(kStartTime2, "dummy cue data"); - listener_->OnNewSegment("", kStartTime2, kDuration2, kSegmentFileSize2); + listener_->OnNewSegment("", kStartTime2, kDuration2, kSegmentFileSize2, 1); ::testing::Mock::VerifyAndClearExpectations(notifier_.get()); InSequence s; EXPECT_CALL(*notifier_, NotifyNewContainer( ExpectMediaInfoEq(kExpectedDefaultMediaInfo), _)) .WillOnce(Return(true)); - EXPECT_CALL(*notifier_, - NotifyNewSegment(_, kStartTime1, kDuration1, kSegmentFileSize1)); + EXPECT_CALL(*notifier_, NotifyNewSegment(_, kStartTime1, kDuration1, + kSegmentFileSize1, 1)); EXPECT_CALL(*notifier_, NotifyCueEvent(_, kStartTime2)); - EXPECT_CALL(*notifier_, - NotifyNewSegment(_, kStartTime2, kDuration2, kSegmentFileSize2)); + EXPECT_CALL(*notifier_, NotifyNewSegment(_, kStartTime2, kDuration2, + kSegmentFileSize2, 1)); EXPECT_CALL(*notifier_, Flush()); FireOnMediaEndWithParams(GetDefaultOnMediaEndParams()); } @@ -307,11 +305,11 @@ TEST_F(MpdNotifyMuxerListenerTest, VodMultipleFiles) { // Expectation for first file before OnMediaEnd. EXPECT_CALL(*notifier_, NotifyNewContainer(_, _)).Times(0); - EXPECT_CALL(*notifier_, NotifyNewSegment(_, _, _, _)).Times(0); + EXPECT_CALL(*notifier_, NotifyNewSegment(_, _, _, _, _)).Times(0); listener_->OnMediaStart(muxer_options1, *video_stream_info, kDefaultReferenceTimeScale, MuxerListener::kContainerMp4); - listener_->OnNewSegment("", kStartTime1, kDuration1, kSegmentFileSize1); + listener_->OnNewSegment("", kStartTime1, kDuration1, kSegmentFileSize1, 1); listener_->OnCueEvent(kStartTime2, "dummy cue data"); ::testing::Mock::VerifyAndClearExpectations(notifier_.get()); @@ -320,8 +318,8 @@ TEST_F(MpdNotifyMuxerListenerTest, VodMultipleFiles) { EXPECT_CALL(*notifier_, NotifyNewContainer(EqualsProto(expected_media_info1), _)) .WillOnce(Return(true)); - EXPECT_CALL(*notifier_, - NotifyNewSegment(_, kStartTime1, kDuration1, kSegmentFileSize1)); + EXPECT_CALL(*notifier_, NotifyNewSegment(_, kStartTime1, kDuration1, + kSegmentFileSize1, 1)); EXPECT_CALL(*notifier_, NotifyCueEvent(_, kStartTime2)); EXPECT_CALL(*notifier_, Flush()); FireOnMediaEndWithParams(GetDefaultOnMediaEndParams()); @@ -330,13 +328,13 @@ TEST_F(MpdNotifyMuxerListenerTest, VodMultipleFiles) { listener_->OnMediaStart(muxer_options2, *video_stream_info, kDefaultReferenceTimeScale, MuxerListener::kContainerMp4); - listener_->OnNewSegment("", kStartTime2, kDuration2, kSegmentFileSize2); + listener_->OnNewSegment("", kStartTime2, kDuration2, kSegmentFileSize2, 1); // Expectation for second file OnMediaEnd. EXPECT_CALL(*notifier_, NotifyMediaInfoUpdate(_, EqualsProto(expected_media_info2))); - EXPECT_CALL(*notifier_, - NotifyNewSegment(_, kStartTime2, kDuration2, kSegmentFileSize2)); + EXPECT_CALL(*notifier_, NotifyNewSegment(_, kStartTime2, kDuration2, + kSegmentFileSize2, 1)); EXPECT_CALL(*notifier_, Flush()); FireOnMediaEndWithParams(GetDefaultOnMediaEndParams()); } @@ -389,14 +387,14 @@ TEST_P(MpdNotifyMuxerListenerTest, LiveNoKeyRotation) { EXPECT_CALL(*notifier_, NotifyNewContainer(ExpectMediaInfoEq(kExpectedMediaInfo), _)) .WillOnce(Return(true)); - EXPECT_CALL(*notifier_, - NotifyNewSegment(_, kStartTime1, kDuration1, kSegmentFileSize1)); + EXPECT_CALL(*notifier_, NotifyNewSegment(_, kStartTime1, kDuration1, + kSegmentFileSize1, 0)); // Flush should only be called once in OnMediaEnd. if (GetParam() == MpdType::kDynamic) EXPECT_CALL(*notifier_, Flush()); EXPECT_CALL(*notifier_, NotifyCueEvent(_, kStartTime2)); - EXPECT_CALL(*notifier_, - NotifyNewSegment(_, kStartTime2, kDuration2, kSegmentFileSize2)); + EXPECT_CALL(*notifier_, NotifyNewSegment(_, kStartTime2, kDuration2, + kSegmentFileSize2, 0)); if (GetParam() == MpdType::kDynamic) EXPECT_CALL(*notifier_, Flush()); @@ -407,9 +405,9 @@ TEST_P(MpdNotifyMuxerListenerTest, LiveNoKeyRotation) { listener_->OnMediaStart(muxer_options, *video_stream_info, kDefaultReferenceTimeScale, MuxerListener::kContainerMp4); - listener_->OnNewSegment("", kStartTime1, kDuration1, kSegmentFileSize1); + listener_->OnNewSegment("", kStartTime1, kDuration1, kSegmentFileSize1, 0); listener_->OnCueEvent(kStartTime2, "dummy cue data"); - listener_->OnNewSegment("", kStartTime2, kDuration2, kSegmentFileSize2); + listener_->OnNewSegment("", kStartTime2, kDuration2, kSegmentFileSize2, 0); ::testing::Mock::VerifyAndClearExpectations(notifier_.get()); EXPECT_CALL(*notifier_, Flush()) @@ -462,13 +460,13 @@ TEST_P(MpdNotifyMuxerListenerTest, LiveWithKeyRotation) { NotifyNewContainer(ExpectMediaInfoEq(kExpectedMediaInfo), _)) .WillOnce(Return(true)); EXPECT_CALL(*notifier_, NotifyEncryptionUpdate(_, _, _, _)).Times(1); - EXPECT_CALL(*notifier_, - NotifyNewSegment(_, kStartTime1, kDuration1, kSegmentFileSize1)); + EXPECT_CALL(*notifier_, NotifyNewSegment(_, kStartTime1, kDuration1, + kSegmentFileSize1, 0)); // Flush should only be called once in OnMediaEnd. if (GetParam() == MpdType::kDynamic) EXPECT_CALL(*notifier_, Flush()); - EXPECT_CALL(*notifier_, - NotifyNewSegment(_, kStartTime2, kDuration2, kSegmentFileSize2)); + EXPECT_CALL(*notifier_, NotifyNewSegment(_, kStartTime2, kDuration2, + kSegmentFileSize2, 0)); if (GetParam() == MpdType::kDynamic) EXPECT_CALL(*notifier_, Flush()); @@ -482,8 +480,8 @@ TEST_P(MpdNotifyMuxerListenerTest, LiveWithKeyRotation) { listener_->OnEncryptionInfoReady(kNonInitialEncryptionInfo, FOURCC_cbc1, std::vector(), iv, GetDefaultKeySystemInfo()); - listener_->OnNewSegment("", kStartTime1, kDuration1, kSegmentFileSize1); - listener_->OnNewSegment("", kStartTime2, kDuration2, kSegmentFileSize2); + listener_->OnNewSegment("", kStartTime1, kDuration1, kSegmentFileSize1, 0); + listener_->OnNewSegment("", kStartTime2, kDuration2, kSegmentFileSize2, 0); ::testing::Mock::VerifyAndClearExpectations(notifier_.get()); EXPECT_CALL(*notifier_, Flush()) diff --git a/packager/media/event/multi_codec_muxer_listener_unittest.cc b/packager/media/event/multi_codec_muxer_listener_unittest.cc index acce6036b9b..86f4a420939 100644 --- a/packager/media/event/multi_codec_muxer_listener_unittest.cc +++ b/packager/media/event/multi_codec_muxer_listener_unittest.cc @@ -79,10 +79,10 @@ TEST_F(MultiCodecMuxerListenerTest, OnNewSegmentAfterOnMediaStartSingleCodec) { EXPECT_CALL(*listener_for_first_codec_, OnNewSegment(StrEq("new_segment_name10.ts"), kSegmentStartTime, - kSegmentDuration, kSegmentSize)); + kSegmentDuration, kSegmentSize, 10)); multi_codec_listener_.OnNewSegment("new_segment_name10.ts", kSegmentStartTime, - kSegmentDuration, kSegmentSize); + kSegmentDuration, kSegmentSize, 10); } TEST_F(MultiCodecMuxerListenerTest, OnMediaStartTwoCodecs) { @@ -114,13 +114,13 @@ TEST_F(MultiCodecMuxerListenerTest, OnNewSegmentAfterOnMediaStartTwoCodecs) { EXPECT_CALL(*listener_for_first_codec_, OnNewSegment(StrEq("new_segment_name10.ts"), kSegmentStartTime, - kSegmentDuration, kSegmentSize)); + kSegmentDuration, kSegmentSize, 10)); EXPECT_CALL(*listener_for_second_codec_, OnNewSegment(StrEq("new_segment_name10.ts"), kSegmentStartTime, - kSegmentDuration, kSegmentSize)); + kSegmentDuration, kSegmentSize, 10)); multi_codec_listener_.OnNewSegment("new_segment_name10.ts", kSegmentStartTime, - kSegmentDuration, kSegmentSize); + kSegmentDuration, kSegmentSize, 10); } } // namespace media diff --git a/packager/media/event/muxer_listener.h b/packager/media/event/muxer_listener.h index 65946c124ed..72d83337288 100644 --- a/packager/media/event/muxer_listener.h +++ b/packager/media/event/muxer_listener.h @@ -124,10 +124,12 @@ class MuxerListener { /// @param duration is the duration of the segment, relative to the timescale /// specified by MediaInfo passed to OnMediaStart(). /// @param segment_file_size is the segment size in bytes. + /// @param segment_index is the segment index. virtual void OnNewSegment(const std::string& segment_name, int64_t start_time, int64_t duration, - uint64_t segment_file_size) = 0; + uint64_t segment_file_size, + uint64_t segment_index) = 0; /// Called when there is a new key frame. For Video only. Note that it should /// be called before OnNewSegment is called on the containing segment. diff --git a/packager/media/event/vod_media_info_dump_muxer_listener.cc b/packager/media/event/vod_media_info_dump_muxer_listener.cc index e1f59c4948d..590714bb717 100644 --- a/packager/media/event/vod_media_info_dump_muxer_listener.cc +++ b/packager/media/event/vod_media_info_dump_muxer_listener.cc @@ -50,10 +50,10 @@ void VodMediaInfoDumpMuxerListener::OnMediaStart( DCHECK(muxer_options.segment_template.empty()); media_info_.reset(new MediaInfo()); if (!internal::GenerateMediaInfo(muxer_options, - stream_info, - time_scale, + stream_info, + time_scale, container_type, - media_info_.get())) { + media_info_.get())) { LOG(ERROR) << "Failed to generate MediaInfo from input."; return; } @@ -90,7 +90,8 @@ void VodMediaInfoDumpMuxerListener::OnMediaEnd(const MediaRanges& media_ranges, void VodMediaInfoDumpMuxerListener::OnNewSegment(const std::string& file_name, int64_t start_time, int64_t duration, - uint64_t segment_file_size) { + uint64_t segment_file_size, + uint64_t segment_index) { const double segment_duration_seconds = static_cast(duration) / media_info_->reference_time_scale(); diff --git a/packager/media/event/vod_media_info_dump_muxer_listener.h b/packager/media/event/vod_media_info_dump_muxer_listener.h index 19044d472bc..08cdc7d2998 100644 --- a/packager/media/event/vod_media_info_dump_muxer_listener.h +++ b/packager/media/event/vod_media_info_dump_muxer_listener.h @@ -49,7 +49,8 @@ class VodMediaInfoDumpMuxerListener : public MuxerListener { void OnNewSegment(const std::string& file_name, int64_t start_time, int64_t duration, - uint64_t segment_file_size) override; + uint64_t segment_file_size, + uint64_t segment_index) override; void OnKeyFrame(int64_t timestamp, uint64_t start_byte_offset, uint64_t size); void OnCueEvent(int64_t timestamp, const std::string& cue_data) override; /// @} diff --git a/packager/media/event/vod_media_info_dump_muxer_listener_unittest.cc b/packager/media/event/vod_media_info_dump_muxer_listener_unittest.cc index 9e74b191132..406774cfb8d 100644 --- a/packager/media/event/vod_media_info_dump_muxer_listener_unittest.cc +++ b/packager/media/event/vod_media_info_dump_muxer_listener_unittest.cc @@ -70,17 +70,14 @@ class VodMediaInfoDumpMuxerListenerTest : public ::testing::Test { ASSERT_TRUE(base::CreateTemporaryFile(&temp_file_path_)); DLOG(INFO) << "Created temp file: " << temp_file_path_.value(); - listener_.reset(new VodMediaInfoDumpMuxerListener(temp_file_path_ - .AsUTF8Unsafe())); + listener_.reset( + new VodMediaInfoDumpMuxerListener(temp_file_path_.AsUTF8Unsafe())); } - void TearDown() override { - base::DeleteFile(temp_file_path_, false); - } + void TearDown() override { base::DeleteFile(temp_file_path_, false); } - void FireOnMediaStartWithDefaultMuxerOptions( - const StreamInfo& stream_info, - bool enable_encryption) { + void FireOnMediaStartWithDefaultMuxerOptions(const StreamInfo& stream_info, + bool enable_encryption) { MuxerOptions muxer_options; SetDefaultMuxerOptions(&muxer_options); const uint32_t kReferenceTimeScale = 1000; @@ -100,7 +97,7 @@ class VodMediaInfoDumpMuxerListenerTest : public ::testing::Test { void FireOnNewSegmentWithParams(const OnNewSegmentParameters& params) { listener_->OnNewSegment(params.file_name, params.start_time, - params.duration, params.segment_file_size); + params.duration, params.segment_file_size, 0); } void FireOnMediaEndWithParams(const OnMediaEndParameters& params) { diff --git a/packager/media/formats/mp2t/ts_muxer.cc b/packager/media/formats/mp2t/ts_muxer.cc index bd005afa047..cfa5d5262a0 100644 --- a/packager/media/formats/mp2t/ts_muxer.cc +++ b/packager/media/formats/mp2t/ts_muxer.cc @@ -43,7 +43,8 @@ Status TsMuxer::FinalizeSegment(size_t stream_id, return segment_info.is_subsegment ? Status::OK : segmenter_->FinalizeSegment(segment_info.start_timestamp, - segment_info.duration); + segment_info.duration, + segment_info.segment_index); } void TsMuxer::FireOnMediaStartEvent() { diff --git a/packager/media/formats/mp2t/ts_segmenter.cc b/packager/media/formats/mp2t/ts_segmenter.cc index aaea5ced179..b492e54be57 100644 --- a/packager/media/formats/mp2t/ts_segmenter.cc +++ b/packager/media/formats/mp2t/ts_segmenter.cc @@ -102,7 +102,7 @@ Status TsSegmenter::AddSample(const MediaSample& sample) { if (sample.is_encrypted()) ts_writer_->SignalEncrypted(); - if (!ts_writer_file_opened_ && !sample.is_key_frame()) + if (!ts_writer_buffer_initialized_ && !sample.is_key_frame()) LOG(WARNING) << "A segment will start with a non key frame."; if (!pes_packet_generator_->PushSample(sample)) { @@ -122,19 +122,16 @@ void TsSegmenter::InjectPesPacketGeneratorForTesting( } void TsSegmenter::SetTsWriterFileOpenedForTesting(bool value) { - ts_writer_file_opened_ = value; + ts_writer_buffer_initialized_ = value; } Status TsSegmenter::OpenNewSegmentIfClosed(int64_t next_pts) { - if (ts_writer_file_opened_) + if (ts_writer_buffer_initialized_) return Status::OK; - const std::string segment_name = - GetSegmentName(muxer_options_.segment_template, next_pts, - segment_number_++, muxer_options_.bandwidth); - if (!ts_writer_->NewSegment(segment_name)) + next_pts_ = next_pts; + if (!ts_writer_->NewSegment()) return Status(error::MUXER_FAILURE, "Failed to initilize TsPacketWriter."); - current_segment_path_ = segment_name; - ts_writer_file_opened_ = true; + ts_writer_buffer_initialized_ = true; return Status::OK; } @@ -148,13 +145,13 @@ Status TsSegmenter::WritePesPacketsToFile() { return status; if (listener_ && IsVideoCodec(codec_) && pes_packet->is_key_frame()) { - base::Optional start_pos = ts_writer_->GetFilePosition(); + base::Optional start_pos = ts_writer_->GetPosition(); const int64_t timestamp = pes_packet->pts(); if (!ts_writer_->AddPesPacket(std::move(pes_packet))) return Status(error::MUXER_FAILURE, "Failed to add PES packet."); - base::Optional end_pos = ts_writer_->GetFilePosition(); + base::Optional end_pos = ts_writer_->GetPosition(); if (!start_pos || !end_pos) { return Status(error::MUXER_FAILURE, "Failed to get file position in WritePesPacketsToFile."); @@ -169,30 +166,41 @@ Status TsSegmenter::WritePesPacketsToFile() { } Status TsSegmenter::FinalizeSegment(uint64_t start_timestamp, - uint64_t duration) { + uint64_t duration, + uint64_t segment_index) { if (!pes_packet_generator_->Flush()) { - return Status(error::MUXER_FAILURE, - "Failed to flush PesPacketGenerator."); + return Status(error::MUXER_FAILURE, "Failed to flush PesPacketGenerator."); } Status status = WritePesPacketsToFile(); if (!status.ok()) return status; - // This method may be called from Finalize() so ts_writer_file_opened_ could + // This method may be called from Finalize() so ts_writer_buffer_initialized_ could // be false. - if (ts_writer_file_opened_) { + if (ts_writer_buffer_initialized_) { + std::string segment_name_segment_index = + GetSegmentName(muxer_options_.segment_template, next_pts_, + segment_index - 1, muxer_options_.bandwidth); + + current_segment_path_ = segment_name_segment_index; + if (!ts_writer_->CreateFileAndFlushBuffer(segment_name_segment_index)) { + LOG(ERROR) << "Failed to create new ts segment file."; + } + if (!ts_writer_->FinalizeSegment()) { return Status(error::MUXER_FAILURE, "Failed to finalize TsWriter."); } + if (listener_) { const int64_t file_size = File::GetFileSize(current_segment_path_.c_str()); listener_->OnNewSegment(current_segment_path_, start_timestamp * timescale_scale_ + transport_stream_timestamp_offset_, - duration * timescale_scale_, file_size); + duration * timescale_scale_, file_size, + segment_index); } - ts_writer_file_opened_ = false; + ts_writer_buffer_initialized_ = false; } current_segment_path_.clear(); return Status::OK; diff --git a/packager/media/formats/mp2t/ts_segmenter.h b/packager/media/formats/mp2t/ts_segmenter.h index b140040eb71..dad7e840379 100644 --- a/packager/media/formats/mp2t/ts_segmenter.h +++ b/packager/media/formats/mp2t/ts_segmenter.h @@ -55,10 +55,13 @@ class TsSegmenter { /// stream's time scale. /// @param duration is the segment's duration in the input stream's time /// scale. + /// @param segment_index is the segment index. // TODO(kqyang): Remove the usage of segment start timestamp and duration in // xx_segmenter, which could cause confusions on which is the source of truth // as the segment start timestamp and duration could be tracked locally. - Status FinalizeSegment(uint64_t start_timestamp, uint64_t duration); + Status FinalizeSegment(uint64_t start_timestamp, + uint64_t duration, + uint64_t segment_index); /// Only for testing. void InjectTsWriterForTesting(std::unique_ptr writer); @@ -70,6 +73,8 @@ class TsSegmenter { /// Only for testing. void SetTsWriterFileOpenedForTesting(bool value); + int next_pts_ = -1; + private: Status OpenNewSegmentIfClosed(int64_t next_pts); @@ -89,13 +94,10 @@ class TsSegmenter { // Used for calculating the duration in seconds fo the current segment. double timescale_scale_ = 1.0; - // Used for segment template. - uint64_t segment_number_ = 0; - std::unique_ptr ts_writer_; - // Set to true if TsWriter::NewFile() succeeds, set to false after - // TsWriter::FinalizeFile() succeeds. - bool ts_writer_file_opened_ = false; + // Set to true if ts_writer buffer is initialized, set to false after + // TsWriter::FinalizeSegment() succeeds. + bool ts_writer_buffer_initialized_ = false; std::unique_ptr pes_packet_generator_; // For OnNewSegment(). diff --git a/packager/media/formats/mp2t/ts_segmenter_unittest.cc b/packager/media/formats/mp2t/ts_segmenter_unittest.cc index aa0fd31a9f1..4fbd392e744 100644 --- a/packager/media/formats/mp2t/ts_segmenter_unittest.cc +++ b/packager/media/formats/mp2t/ts_segmenter_unittest.cc @@ -81,7 +81,8 @@ class MockTsWriter : public TsWriter { // Create a bogus pmt writer, which we don't really care. new VideoProgramMapTableWriter(kUnknownCodec))) {} - MOCK_METHOD1(NewSegment, bool(const std::string& file_name)); + MOCK_METHOD1(CreateFileAndFlushBuffer, bool(const std::string& file_name)); + MOCK_METHOD0(NewSegment, bool()); MOCK_METHOD0(SignalEncrypted, void()); MOCK_METHOD0(FinalizeSegment, bool()); @@ -144,7 +145,7 @@ TEST_F(TsSegmenterTest, AddSample) { MediaSample::CopyFrom(kAnyData, arraysize(kAnyData), kIsKeyFrame); Sequence writer_sequence; - EXPECT_CALL(*mock_ts_writer_, NewSegment(StrEq("file1.ts"))) + EXPECT_CALL(*mock_ts_writer_, NewSegment()) .InSequence(writer_sequence) .WillOnce(Return(true)); @@ -159,8 +160,7 @@ TEST_F(TsSegmenterTest, AddSample) { .InSequence(ready_pes_sequence) .WillOnce(Return(0u)); - EXPECT_CALL(*mock_ts_writer_, AddPesPacketMock(_)) - .WillOnce(Return(true)); + EXPECT_CALL(*mock_ts_writer_, AddPesPacketMock(_)).WillOnce(Return(true)); // The pointer is released inside the segmenter. EXPECT_CALL(*mock_pes_packet_generator_, GetNextPesPacketMock()) @@ -190,7 +190,6 @@ TEST_F(TsSegmenterTest, PassedSegmentDuration) { MockMuxerListener mock_listener; TsSegmenter segmenter(options, &mock_listener); - const uint32_t kFirstPts = 1000; EXPECT_CALL(*mock_pes_packet_generator_, Initialize(_)) @@ -209,17 +208,16 @@ TEST_F(TsSegmenterTest, PassedSegmentDuration) { // duration. EXPECT_CALL(mock_listener, OnNewSegment("file1.ts", kFirstPts * kTimeScale / kInputTimescale, - kTimeScale * 11, _)); + kTimeScale * 11, _, _)); Sequence writer_sequence; - EXPECT_CALL(*mock_ts_writer_, NewSegment(StrEq("file1.ts"))) + EXPECT_CALL(*mock_ts_writer_, NewSegment()) .InSequence(writer_sequence) .WillOnce(Return(true)); EXPECT_CALL(*mock_pes_packet_generator_, PushSample(_)) .Times(2) .WillRepeatedly(Return(true)); - Sequence ready_pes_sequence; // First AddSample(). EXPECT_CALL(*mock_pes_packet_generator_, NumberOfReadyPesPackets()) @@ -240,13 +238,13 @@ TEST_F(TsSegmenterTest, PassedSegmentDuration) { .InSequence(ready_pes_sequence) .WillOnce(Return(0u)); - EXPECT_CALL(*mock_pes_packet_generator_, Flush()) - .WillOnce(Return(true)); + EXPECT_CALL(*mock_pes_packet_generator_, Flush()).WillOnce(Return(true)); EXPECT_CALL(*mock_ts_writer_, FinalizeSegment()) .InSequence(writer_sequence) .WillOnce(Return(true)); - EXPECT_CALL(*mock_ts_writer_, NewSegment(StrEq("file2.ts"))) + + EXPECT_CALL(*mock_ts_writer_, NewSegment()) .InSequence(writer_sequence) .WillOnce(Return(true)); @@ -268,7 +266,7 @@ TEST_F(TsSegmenterTest, PassedSegmentDuration) { EXPECT_OK(segmenter.Initialize(*stream_info)); segmenter.InjectTsWriterForTesting(std::move(mock_ts_writer_)); EXPECT_OK(segmenter.AddSample(*sample1)); - EXPECT_OK(segmenter.FinalizeSegment(kFirstPts, sample1->duration())); + EXPECT_OK(segmenter.FinalizeSegment(kFirstPts, sample1->duration(), 1)); EXPECT_OK(segmenter.AddSample(*sample2)); } @@ -326,7 +324,7 @@ TEST_F(TsSegmenterTest, FinalizeSegment) { EXPECT_OK(segmenter.Initialize(*stream_info)); segmenter.InjectTsWriterForTesting(std::move(mock_ts_writer_)); segmenter.SetTsWriterFileOpenedForTesting(true); - EXPECT_OK(segmenter.FinalizeSegment(0, 100 /* arbitrary duration */)); + EXPECT_OK(segmenter.FinalizeSegment(0, 100 /* arbitrary duration */, 1)); } TEST_F(TsSegmenterTest, EncryptedSample) { @@ -343,7 +341,7 @@ TEST_F(TsSegmenterTest, EncryptedSample) { MockMuxerListener mock_listener; TsSegmenter segmenter(options, &mock_listener); - ON_CALL(*mock_ts_writer_, NewSegment(_)).WillByDefault(Return(true)); + ON_CALL(*mock_ts_writer_, NewSegment()).WillByDefault(Return(true)); ON_CALL(*mock_ts_writer_, FinalizeSegment()).WillByDefault(Return(true)); ON_CALL(*mock_ts_writer_, AddPesPacketMock(_)).WillByDefault(Return(true)); ON_CALL(*mock_pes_packet_generator_, Initialize(_)) @@ -406,7 +404,7 @@ TEST_F(TsSegmenterTest, EncryptedSample) { segmenter.InjectTsWriterForTesting(std::move(mock_ts_writer_)); EXPECT_OK(segmenter.AddSample(*sample1)); - EXPECT_OK(segmenter.FinalizeSegment(1, sample1->duration())); + EXPECT_OK(segmenter.FinalizeSegment(1, sample1->duration(), 1)); // Signal encrypted if sample is encrypted. EXPECT_CALL(*mock_ts_writer_raw, SignalEncrypted()); sample2->set_is_encrypted(true); diff --git a/packager/media/formats/mp2t/ts_writer.cc b/packager/media/formats/mp2t/ts_writer.cc index d8dc540178f..a473d0ca5c9 100644 --- a/packager/media/formats/mp2t/ts_writer.cc +++ b/packager/media/formats/mp2t/ts_writer.cc @@ -87,9 +87,9 @@ void WritePtsOrDts(uint8_t leading_bits, writer->AppendInt(fifth_byte); } -bool WritePesToFile(const PesPacket& pes, - ContinuityCounter* continuity_counter, - File* file) { +bool WritePesToBuffer(const PesPacket& pes, + ContinuityCounter* continuity_counter, + BufferWriter* current_buffer) { // The size of the length field. const int kAdaptationFieldLengthSize = 1; // The size of the flags field. @@ -153,7 +153,9 @@ bool WritePesToFile(const PesPacket& pes, !kPayloadUnitStartIndicator, pid, !kHasPcr, 0, continuity_counter, &output_writer); } - return output_writer.WriteToFile(file).ok(); + + current_buffer->AppendBuffer(output_writer); + return true; } } // namespace @@ -163,7 +165,8 @@ TsWriter::TsWriter(std::unique_ptr pmt_writer) TsWriter::~TsWriter() {} -bool TsWriter::NewSegment(const std::string& file_name) { +bool TsWriter::CreateFileAndFlushBuffer(const std::string& file_name) { + if (current_file_) { LOG(ERROR) << "File " << current_file_->file_name() << " still open."; return false; @@ -173,6 +176,12 @@ bool TsWriter::NewSegment(const std::string& file_name) { LOG(ERROR) << "Failed to open file " << file_name; return false; } + return current_buffer_.get()->WriteToFile(current_file_.get()).ok(); +} + +bool TsWriter::NewSegment() { + + current_buffer_ = std::unique_ptr(new BufferWriter()); BufferWriter psi; WritePatToBuffer(kPat, arraysize(kPat), &pat_continuity_counter_, &psi); @@ -185,12 +194,7 @@ bool TsWriter::NewSegment(const std::string& file_name) { return false; } } - - if (!psi.WriteToFile(current_file_.get()).ok()) { - LOG(ERROR) << "Failed to write PSI to file."; - return false; - } - + current_buffer_.get()->AppendBuffer(psi); return true; } @@ -203,10 +207,9 @@ bool TsWriter::FinalizeSegment() { } bool TsWriter::AddPesPacket(std::unique_ptr pes_packet) { - DCHECK(current_file_); - if (!WritePesToFile(*pes_packet, &elementary_stream_continuity_counter_, - current_file_.get())) { - LOG(ERROR) << "Failed to write pes to file."; + if (!WritePesToBuffer(*pes_packet, &elementary_stream_continuity_counter_, + current_buffer_.get())) { + LOG(ERROR) << "Failed to write pes to buffer."; return false; } @@ -214,12 +217,11 @@ bool TsWriter::AddPesPacket(std::unique_ptr pes_packet) { return true; } -base::Optional TsWriter::GetFilePosition() { - if (!current_file_) +base::Optional TsWriter::GetPosition() { + if (!current_buffer_.get()) return base::nullopt; - uint64_t position; - return current_file_->Tell(&position) ? base::make_optional(position) - : base::nullopt; + uint64_t position = current_buffer_.get()->Size(); + return position != 0 ? base::make_optional(position) : base::nullopt; } } // namespace mp2t diff --git a/packager/media/formats/mp2t/ts_writer.h b/packager/media/formats/mp2t/ts_writer.h index 43e4c0d09ab..057c517446a 100644 --- a/packager/media/formats/mp2t/ts_writer.h +++ b/packager/media/formats/mp2t/ts_writer.h @@ -16,6 +16,7 @@ #include "packager/file/file.h" #include "packager/file/file_closer.h" #include "packager/media/formats/mp2t/continuity_counter.h" +#include "packager/media/base/buffer_writer.h" namespace shaka { namespace media { @@ -32,27 +33,31 @@ class TsWriter { virtual ~TsWriter(); /// This will fail if the current segment is not finalized. - /// @param file_name is the output file name. - /// @param encrypted must be true if the new segment is encrypted. /// @return true on success, false otherwise. - virtual bool NewSegment(const std::string& file_name); + virtual bool NewSegment(); /// Signals the writer that the rest of the segments are encrypted. virtual void SignalEncrypted(); - /// Flush all the pending PesPackets that have not been written to file and - /// close the file. + /// Flush all the pending PesPackets that have not been added to the buffer. + /// Create the file, flush the buffer and close the file. /// @return true on success, false otherwise. virtual bool FinalizeSegment(); - /// Add PesPacket to the instance. PesPacket might not get written to file + /// Add PesPacket to the instance. PesPacket might not be added to the buffer /// immediately. /// @param pes_packet gets added to the writer. /// @return true on success, false otherwise. virtual bool AddPesPacket(std::unique_ptr pes_packet); - /// @return current file position on success, nullopt otherwise. - base::Optional GetFilePosition(); + /// @return current buffer position on success, nullopt otherwise. + base::Optional GetPosition(); + + /// Creates a file with name @a file_name and flushes + /// current_buffer_ to it. + /// @param file_name The path to the file to open. + /// @return File creation and buffer flushing succeeded or failed. + virtual bool CreateFileAndFlushBuffer(const std::string& file_name); private: TsWriter(const TsWriter&) = delete; @@ -67,6 +72,7 @@ class TsWriter { std::unique_ptr pmt_writer_; std::unique_ptr current_file_; + std::unique_ptr current_buffer_; }; } // namespace mp2t diff --git a/packager/media/formats/mp2t/ts_writer_unittest.cc b/packager/media/formats/mp2t/ts_writer_unittest.cc index 83ceedd0d42..97be645e3f5 100644 --- a/packager/media/formats/mp2t/ts_writer_unittest.cc +++ b/packager/media/formats/mp2t/ts_writer_unittest.cc @@ -144,7 +144,9 @@ TEST_F(TsWriterTest, ClearH264Psi) { EXPECT_CALL(*mock_pmt_writer, ClearSegmentPmt(_)).WillOnce(WriteOnePmt()); TsWriter ts_writer(std::move(mock_pmt_writer)); - EXPECT_TRUE(ts_writer.NewSegment(test_file_name_)); + EXPECT_TRUE(ts_writer.NewSegment()); + EXPECT_TRUE(ts_writer.CreateFileAndFlushBuffer(test_file_name_)); + ASSERT_TRUE(ts_writer.FinalizeSegment()); std::vector content; @@ -192,7 +194,8 @@ TEST_F(TsWriterTest, ClearAacPmt) { EXPECT_CALL(*mock_pmt_writer, ClearSegmentPmt(_)).WillOnce(WriteOnePmt()); TsWriter ts_writer(std::move(mock_pmt_writer)); - EXPECT_TRUE(ts_writer.NewSegment(test_file_name_)); + EXPECT_TRUE(ts_writer.NewSegment()); + EXPECT_TRUE(ts_writer.CreateFileAndFlushBuffer(test_file_name_)); ASSERT_TRUE(ts_writer.FinalizeSegment()); std::vector content; @@ -213,7 +216,8 @@ TEST_F(TsWriterTest, ClearLeadH264Pmt) { .WillOnce(WriteTwoPmts()); TsWriter ts_writer(std::move(mock_pmt_writer)); - EXPECT_TRUE(ts_writer.NewSegment(test_file_name_)); + EXPECT_TRUE(ts_writer.NewSegment()); + EXPECT_TRUE(ts_writer.CreateFileAndFlushBuffer(test_file_name_)); EXPECT_TRUE(ts_writer.FinalizeSegment()); std::vector content; @@ -233,7 +237,7 @@ TEST_F(TsWriterTest, ClearSegmentPmtFailure) { EXPECT_CALL(*mock_pmt_writer, ClearSegmentPmt(_)).WillOnce(Return(false)); TsWriter ts_writer(std::move(mock_pmt_writer)); - EXPECT_FALSE(ts_writer.NewSegment(test_file_name_)); + EXPECT_FALSE(ts_writer.NewSegment()); } // Check the encrypted segments' PMT (after clear lead). @@ -245,12 +249,14 @@ TEST_F(TsWriterTest, EncryptedSegmentsH264Pmt) { EXPECT_CALL(*mock_pmt_writer, EncryptedSegmentPmt(_)).WillOnce(WriteOnePmt()); TsWriter ts_writer(std::move(mock_pmt_writer)); - EXPECT_TRUE(ts_writer.NewSegment(test_file_name_)); + EXPECT_TRUE(ts_writer.NewSegment()); + EXPECT_TRUE(ts_writer.CreateFileAndFlushBuffer(test_file_name_)); EXPECT_TRUE(ts_writer.FinalizeSegment()); // Overwrite the file but as encrypted segment. ts_writer.SignalEncrypted(); - EXPECT_TRUE(ts_writer.NewSegment(test_file_name_)); + EXPECT_TRUE(ts_writer.NewSegment()); + EXPECT_TRUE(ts_writer.CreateFileAndFlushBuffer(test_file_name_)); EXPECT_TRUE(ts_writer.FinalizeSegment()); std::vector content; @@ -270,11 +276,12 @@ TEST_F(TsWriterTest, EncryptedSegmentPmtFailure) { EXPECT_CALL(*mock_pmt_writer, EncryptedSegmentPmt(_)).WillOnce(Return(false)); TsWriter ts_writer(std::move(mock_pmt_writer)); - EXPECT_TRUE(ts_writer.NewSegment(test_file_name_)); + EXPECT_TRUE(ts_writer.NewSegment()); + EXPECT_TRUE(ts_writer.CreateFileAndFlushBuffer(test_file_name_)); EXPECT_TRUE(ts_writer.FinalizeSegment()); ts_writer.SignalEncrypted(); - EXPECT_FALSE(ts_writer.NewSegment(test_file_name_)); + EXPECT_FALSE(ts_writer.NewSegment()); } // Same as ClearLeadH264Pmt but for AAC. @@ -285,7 +292,8 @@ TEST_F(TsWriterTest, ClearLeadAacPmt) { .WillOnce(WriteTwoPmts()); TsWriter ts_writer(std::move(mock_pmt_writer)); - EXPECT_TRUE(ts_writer.NewSegment(test_file_name_)); + EXPECT_TRUE(ts_writer.NewSegment()); + EXPECT_TRUE(ts_writer.CreateFileAndFlushBuffer(test_file_name_)); ASSERT_TRUE(ts_writer.FinalizeSegment()); std::vector content; @@ -308,12 +316,14 @@ TEST_F(TsWriterTest, EncryptedSegmentsAacPmt) { EXPECT_CALL(*mock_pmt_writer, EncryptedSegmentPmt(_)).WillOnce(WriteOnePmt()); TsWriter ts_writer(std::move(mock_pmt_writer)); - EXPECT_TRUE(ts_writer.NewSegment(test_file_name_)); + EXPECT_TRUE(ts_writer.NewSegment()); + EXPECT_TRUE(ts_writer.CreateFileAndFlushBuffer(test_file_name_)); EXPECT_TRUE(ts_writer.FinalizeSegment()); // Overwrite the file but as encrypted segment. ts_writer.SignalEncrypted(); - EXPECT_TRUE(ts_writer.NewSegment(test_file_name_)); + EXPECT_TRUE(ts_writer.NewSegment()); + EXPECT_TRUE(ts_writer.CreateFileAndFlushBuffer(test_file_name_)); EXPECT_TRUE(ts_writer.FinalizeSegment()); std::vector content; @@ -329,7 +339,7 @@ TEST_F(TsWriterTest, EncryptedSegmentsAacPmt) { TEST_F(TsWriterTest, AddPesPacket) { TsWriter ts_writer(std::unique_ptr( new VideoProgramMapTableWriter(kCodecForTesting))); - EXPECT_TRUE(ts_writer.NewSegment(test_file_name_)); + EXPECT_TRUE(ts_writer.NewSegment()); std::unique_ptr pes(new PesPacket()); pes->set_stream_id(0xE0); @@ -341,6 +351,7 @@ TEST_F(TsWriterTest, AddPesPacket) { pes->mutable_data()->assign(kAnyData, kAnyData + arraysize(kAnyData)); EXPECT_TRUE(ts_writer.AddPesPacket(std::move(pes))); + EXPECT_TRUE(ts_writer.CreateFileAndFlushBuffer(test_file_name_)); ASSERT_TRUE(ts_writer.FinalizeSegment()); std::vector content; @@ -391,7 +402,7 @@ TEST_F(TsWriterTest, AddPesPacket) { TEST_F(TsWriterTest, BigPesPacket) { TsWriter ts_writer(std::unique_ptr( new VideoProgramMapTableWriter(kCodecForTesting))); - EXPECT_TRUE(ts_writer.NewSegment(test_file_name_)); + EXPECT_TRUE(ts_writer.NewSegment()); std::unique_ptr pes(new PesPacket()); pes->set_pts(0); @@ -401,6 +412,7 @@ TEST_F(TsWriterTest, BigPesPacket) { *pes->mutable_data() = big_data; EXPECT_TRUE(ts_writer.AddPesPacket(std::move(pes))); + EXPECT_TRUE(ts_writer.CreateFileAndFlushBuffer(test_file_name_)); ASSERT_TRUE(ts_writer.FinalizeSegment()); std::vector content; @@ -424,7 +436,7 @@ TEST_F(TsWriterTest, BigPesPacket) { TEST_F(TsWriterTest, PesPtsZeroNoDts) { TsWriter ts_writer(std::unique_ptr( new VideoProgramMapTableWriter(kCodecForTesting))); - EXPECT_TRUE(ts_writer.NewSegment(test_file_name_)); + EXPECT_TRUE(ts_writer.NewSegment()); std::unique_ptr pes(new PesPacket()); pes->set_stream_id(0xE0); @@ -435,6 +447,7 @@ TEST_F(TsWriterTest, PesPtsZeroNoDts) { pes->mutable_data()->assign(kAnyData, kAnyData + arraysize(kAnyData)); EXPECT_TRUE(ts_writer.AddPesPacket(std::move(pes))); + EXPECT_TRUE(ts_writer.CreateFileAndFlushBuffer(test_file_name_)); ASSERT_TRUE(ts_writer.FinalizeSegment()); std::vector content; @@ -481,7 +494,7 @@ TEST_F(TsWriterTest, PesPtsZeroNoDts) { TEST_F(TsWriterTest, TsPacketPayload183Bytes) { TsWriter ts_writer(std::unique_ptr( new VideoProgramMapTableWriter(kCodecForTesting))); - EXPECT_TRUE(ts_writer.NewSegment(test_file_name_)); + EXPECT_TRUE(ts_writer.NewSegment()); std::unique_ptr pes(new PesPacket()); pes->set_stream_id(0xE0); @@ -496,6 +509,7 @@ TEST_F(TsWriterTest, TsPacketPayload183Bytes) { *pes->mutable_data() = pes_payload; EXPECT_TRUE(ts_writer.AddPesPacket(std::move(pes))); + EXPECT_TRUE(ts_writer.CreateFileAndFlushBuffer(test_file_name_)); ASSERT_TRUE(ts_writer.FinalizeSegment()); const uint8_t kExpectedOutputPrefix[] = { diff --git a/packager/media/formats/mp4/mp4_muxer.cc b/packager/media/formats/mp4/mp4_muxer.cc index 336f13a0399..41c5eb7d196 100644 --- a/packager/media/formats/mp4/mp4_muxer.cc +++ b/packager/media/formats/mp4/mp4_muxer.cc @@ -34,9 +34,7 @@ namespace { // Sets the range start and end value from offset and size. // |start| and |end| are for byte-range-spec specified in RFC2616. -void SetStartAndEndFromOffsetAndSize(size_t offset, - size_t size, - Range* range) { +void SetStartAndEndFromOffsetAndSize(size_t offset, size_t size, Range* range) { DCHECK(range); range->start = static_cast(offset); // Note that ranges are inclusive. So we need - 1. @@ -194,7 +192,7 @@ Status MP4Muxer::FinalizeSegment(size_t stream_id, DCHECK(segmenter_); VLOG(3) << "Finalizing " << (segment_info.is_subsegment ? "sub" : "") << "segment " << segment_info.start_timestamp << " duration " - << segment_info.duration; + << segment_info.duration << " index " << segment_info.segment_index; return segmenter_->FinalizeSegment(stream_id, segment_info); } @@ -456,7 +454,7 @@ bool MP4Muxer::GenerateAudioTrak(const AudioStreamInfo* audio_info, AudioSampleEntry audio; audio.format = CodecToFourCC(audio_info->codec(), H26xStreamFormat::kUnSpecified); - switch(audio_info->codec()){ + switch (audio_info->codec()) { case kCodecAAC: { audio.esds.es_descriptor.set_esid(track_id); DecoderConfigDescriptor* decoder_config = diff --git a/packager/media/formats/mp4/multi_segment_segmenter.cc b/packager/media/formats/mp4/multi_segment_segmenter.cc index cf12e4c1705..cae3f74187d 100644 --- a/packager/media/formats/mp4/multi_segment_segmenter.cc +++ b/packager/media/formats/mp4/multi_segment_segmenter.cc @@ -28,8 +28,7 @@ MultiSegmentSegmenter::MultiSegmentSegmenter(const MuxerOptions& options, std::unique_ptr ftyp, std::unique_ptr moov) : Segmenter(options, std::move(ftyp), std::move(moov)), - styp_(new SegmentType), - num_segments_(0) { + styp_(new SegmentType) { // Use the same brands for styp as ftyp. styp_->major_brand = Segmenter::ftyp()->major_brand; styp_->compatible_brands = Segmenter::ftyp()->compatible_brands; @@ -67,8 +66,8 @@ Status MultiSegmentSegmenter::DoFinalize() { return Status::OK; } -Status MultiSegmentSegmenter::DoFinalizeSegment() { - return WriteSegment(); +Status MultiSegmentSegmenter::DoFinalizeSegment(uint64_t segment_index) { + return WriteSegment(segment_index); } Status MultiSegmentSegmenter::WriteInitSegment() { @@ -87,7 +86,7 @@ Status MultiSegmentSegmenter::WriteInitSegment() { return buffer->WriteToFile(file.get()); } -Status MultiSegmentSegmenter::WriteSegment() { +Status MultiSegmentSegmenter::WriteSegment(uint64_t segment_index) { DCHECK(sidx()); DCHECK(fragment_buffer()); DCHECK(styp_); @@ -112,7 +111,7 @@ Status MultiSegmentSegmenter::WriteSegment() { } else { file_name = GetSegmentName(options().segment_template, sidx()->earliest_presentation_time, - num_segments_++, options().bandwidth); + segment_index - 1, options().bandwidth); file.reset(File::Open(file_name.c_str(), "w")); if (!file) { return Status(error::FILE_FAILURE, @@ -157,9 +156,9 @@ Status MultiSegmentSegmenter::WriteSegment() { UpdateProgress(segment_duration); if (muxer_listener()) { muxer_listener()->OnSampleDurationReady(sample_duration()); - muxer_listener()->OnNewSegment(file_name, - sidx()->earliest_presentation_time, - segment_duration, segment_size); + muxer_listener()->OnNewSegment( + file_name, sidx()->earliest_presentation_time, segment_duration, + segment_size, segment_index); } return Status::OK; diff --git a/packager/media/formats/mp4/multi_segment_segmenter.h b/packager/media/formats/mp4/multi_segment_segmenter.h index b1c524da216..8ef7258e842 100644 --- a/packager/media/formats/mp4/multi_segment_segmenter.h +++ b/packager/media/formats/mp4/multi_segment_segmenter.h @@ -33,19 +33,17 @@ class MultiSegmentSegmenter : public Segmenter { bool GetIndexRange(size_t* offset, size_t* size) override; std::vector GetSegmentRanges() override; /// @} - private: // Segmenter implementation overrides. Status DoInitialize() override; Status DoFinalize() override; - Status DoFinalizeSegment() override; + Status DoFinalizeSegment(uint64_t segment_index) override; // Write segment to file. Status WriteInitSegment(); - Status WriteSegment(); + Status WriteSegment(uint64_t segment_index); std::unique_ptr styp_; - uint32_t num_segments_; DISALLOW_COPY_AND_ASSIGN(MultiSegmentSegmenter); }; diff --git a/packager/media/formats/mp4/segmenter.cc b/packager/media/formats/mp4/segmenter.cc index cce500dea49..cf9097072c6 100644 --- a/packager/media/formats/mp4/segmenter.cc +++ b/packager/media/formats/mp4/segmenter.cc @@ -225,7 +225,7 @@ Status Segmenter::FinalizeSegment(size_t stream_id, for (std::unique_ptr& fragmenter : fragmenters_) fragmenter->ClearFragmentFinalized(); if (!segment_info.is_subsegment) { - Status status = DoFinalizeSegment(); + Status status = DoFinalizeSegment(segment_info.segment_index); // Reset segment information to initial state. sidx_->references.clear(); key_frame_infos_.clear(); @@ -250,8 +250,10 @@ double Segmenter::GetDuration() const { void Segmenter::UpdateProgress(uint64_t progress) { accumulated_progress_ += progress; - if (!progress_listener_) return; - if (progress_target_ == 0) return; + if (!progress_listener_) + return; + if (progress_target_ == 0) + return; // It might happen that accumulated progress exceeds progress_target due to // computation errors, e.g. rounding error. Cap it so it never reports > 100% // progress. @@ -264,7 +266,8 @@ void Segmenter::UpdateProgress(uint64_t progress) { } void Segmenter::SetComplete() { - if (!progress_listener_) return; + if (!progress_listener_) + return; progress_listener_->OnProgress(1.0); } diff --git a/packager/media/formats/mp4/segmenter.h b/packager/media/formats/mp4/segmenter.h index 724c12ef6fc..35a2bfa101c 100644 --- a/packager/media/formats/mp4/segmenter.h +++ b/packager/media/formats/mp4/segmenter.h @@ -124,7 +124,7 @@ class Segmenter { private: virtual Status DoInitialize() = 0; virtual Status DoFinalize() = 0; - virtual Status DoFinalizeSegment() = 0; + virtual Status DoFinalizeSegment(uint64_t segment_index) = 0; uint32_t GetReferenceStreamId(); diff --git a/packager/media/formats/mp4/single_segment_segmenter.cc b/packager/media/formats/mp4/single_segment_segmenter.cc index 7c54d9235a4..7298af95316 100644 --- a/packager/media/formats/mp4/single_segment_segmenter.cc +++ b/packager/media/formats/mp4/single_segment_segmenter.cc @@ -51,9 +51,8 @@ bool SingleSegmentSegmenter::GetIndexRange(size_t* offset, size_t* size) { std::vector SingleSegmentSegmenter::GetSegmentRanges() { std::vector ranges; - uint64_t next_offset = - ftyp()->ComputeSize() + moov()->ComputeSize() + vod_sidx_->ComputeSize() + - vod_sidx_->first_offset; + uint64_t next_offset = ftyp()->ComputeSize() + moov()->ComputeSize() + + vod_sidx_->ComputeSize() + vod_sidx_->first_offset; for (const SegmentReference& segment_reference : vod_sidx_->references) { Range r; r.start = next_offset; @@ -77,10 +76,9 @@ Status SingleSegmentSegmenter::DoInitialize() { if (!TempFilePath(options().temp_dir, &temp_file_name_)) return Status(error::FILE_FAILURE, "Unable to create temporary file."); temp_file_.reset(File::Open(temp_file_name_.c_str(), "w")); - return temp_file_ - ? Status::OK - : Status(error::FILE_FAILURE, - "Cannot open file to write " + temp_file_name_); + return temp_file_ ? Status::OK + : Status(error::FILE_FAILURE, + "Cannot open file to write " + temp_file_name_); } Status SingleSegmentSegmenter::DoFinalize() { @@ -159,7 +157,7 @@ Status SingleSegmentSegmenter::DoFinalize() { return Status::OK; } -Status SingleSegmentSegmenter::DoFinalizeSegment() { +Status SingleSegmentSegmenter::DoFinalizeSegment(uint64_t segment_index) { DCHECK(sidx()); DCHECK(fragment_buffer()); // sidx() contains pre-generated segment references with one reference per @@ -212,14 +210,15 @@ Status SingleSegmentSegmenter::DoFinalizeSegment() { // Append fragment buffer to temp file. size_t segment_size = fragment_buffer()->Size(); Status status = fragment_buffer()->WriteToFile(temp_file_.get()); - if (!status.ok()) return status; + if (!status.ok()) + return status; UpdateProgress(vod_ref.subsegment_duration); if (muxer_listener()) { muxer_listener()->OnSampleDurationReady(sample_duration()); - muxer_listener()->OnNewSegment(options().output_file_name, - vod_ref.earliest_presentation_time, - vod_ref.subsegment_duration, segment_size); + muxer_listener()->OnNewSegment( + options().output_file_name, vod_ref.earliest_presentation_time, + vod_ref.subsegment_duration, segment_size, segment_index); } return Status::OK; } diff --git a/packager/media/formats/mp4/single_segment_segmenter.h b/packager/media/formats/mp4/single_segment_segmenter.h index 4659f354359..6abe8bd1e42 100644 --- a/packager/media/formats/mp4/single_segment_segmenter.h +++ b/packager/media/formats/mp4/single_segment_segmenter.h @@ -44,7 +44,7 @@ class SingleSegmentSegmenter : public Segmenter { // Segmenter implementation overrides. Status DoInitialize() override; Status DoFinalize() override; - Status DoFinalizeSegment() override; + Status DoFinalizeSegment(uint64_t segment_index) override; std::unique_ptr vod_sidx_; std::string temp_file_name_; diff --git a/packager/media/formats/packed_audio/packed_audio_writer.cc b/packager/media/formats/packed_audio/packed_audio_writer.cc index 29f796c56e0..10229c84209 100644 --- a/packager/media/formats/packed_audio/packed_audio_writer.cc +++ b/packager/media/formats/packed_audio/packed_audio_writer.cc @@ -79,8 +79,7 @@ Status PackedAudioWriter::FinalizeSegment(size_t stream_id, options().segment_template.empty() ? options().output_file_name : GetSegmentName(options().segment_template, segment_timestamp, - segment_number_++, options().bandwidth); - + segment_info.segment_index - 1, options().bandwidth); // Save |segment_size| as it will be cleared after writing. const size_t segment_size = segmenter_->segment_buffer()->Size(); @@ -90,7 +89,8 @@ Status PackedAudioWriter::FinalizeSegment(size_t stream_id, if (muxer_listener()) { muxer_listener()->OnNewSegment( segment_path, segment_timestamp + transport_stream_timestamp_offset_, - segment_info.duration * segmenter_->TimescaleScale(), segment_size); + segment_info.duration * segmenter_->TimescaleScale(), segment_size, + segment_info.segment_index); } return Status::OK; } diff --git a/packager/media/formats/packed_audio/packed_audio_writer_unittest.cc b/packager/media/formats/packed_audio/packed_audio_writer_unittest.cc index 18c68fe5150..2897f642b63 100644 --- a/packager/media/formats/packed_audio/packed_audio_writer_unittest.cc +++ b/packager/media/formats/packed_audio/packed_audio_writer_unittest.cc @@ -42,8 +42,8 @@ const uint32_t kTimescale = 9000; const char kOutputFile[] = "memory://test.aac"; // For multi-segment mode. const char kSegmentTemplate[] = "memory://test_$Number$.aac"; -const char kSegment1Name[] = "memory://test_1.aac"; -const char kSegment2Name[] = "memory://test_2.aac"; +const char kSegment124Name[] = "memory://test_124.aac"; +const char kSegment125Name[] = "memory://test_125.aac"; class MockPackedAudioSegmenter : public PackedAudioSegmenter { public: @@ -124,7 +124,7 @@ TEST_P(PackedAudioWriterTest, SubsegmentIgnored) { auto subsegment_stream_data = StreamData::FromSegmentInfo( kStreamIndex, GetSegmentInfo(kTimestamp, kDuration, kSubsegment)); - EXPECT_CALL(*mock_muxer_listener_ptr_, OnNewSegment(_, _, _, _)).Times(0); + EXPECT_CALL(*mock_muxer_listener_ptr_, OnNewSegment(_, _, _, _, _)).Times(0); EXPECT_CALL(*mock_segmenter_ptr_, FinalizeSegment()).Times(0); ASSERT_OK(Input(kInput)->Dispatch(std::move(subsegment_stream_data))); } @@ -145,9 +145,9 @@ TEST_P(PackedAudioWriterTest, OneSegment) { EXPECT_CALL( *mock_muxer_listener_ptr_, - OnNewSegment(is_single_segment_mode_ ? kOutputFile : kSegment1Name, + OnNewSegment(is_single_segment_mode_ ? kOutputFile : kSegment124Name, kTimestamp * kMockTimescaleScale, - kDuration * kMockTimescaleScale, kSegmentDataSize)); + kDuration * kMockTimescaleScale, kSegmentDataSize, 124)); EXPECT_CALL(*mock_segmenter_ptr_, TimescaleScale()) .WillRepeatedly(Return(kMockTimescaleScale)); @@ -178,7 +178,7 @@ TEST_P(PackedAudioWriterTest, OneSegment) { } ASSERT_OK(Input(kInput)->FlushDownstream(kStreamIndex)); - ASSERT_FILE_STREQ(is_single_segment_mode_ ? kOutputFile : kSegment1Name, + ASSERT_FILE_STREQ(is_single_segment_mode_ ? kOutputFile : kSegment124Name, kMockSegmentData); } @@ -203,15 +203,15 @@ TEST_P(PackedAudioWriterTest, TwoSegments) { EXPECT_CALL( *mock_muxer_listener_ptr_, - OnNewSegment(is_single_segment_mode_ ? kOutputFile : kSegment1Name, + OnNewSegment(is_single_segment_mode_ ? kOutputFile : kSegment124Name, kTimestamp * kMockTimescaleScale, kDuration * kMockTimescaleScale, - sizeof(kMockSegment1Data) - 1)); + sizeof(kMockSegment1Data) - 1, 124)); EXPECT_CALL( *mock_muxer_listener_ptr_, - OnNewSegment(is_single_segment_mode_ ? kOutputFile : kSegment2Name, + OnNewSegment(is_single_segment_mode_ ? kOutputFile : kSegment125Name, (kTimestamp + kDuration) * kMockTimescaleScale, - kDuration * kMockTimescaleScale, kSegment2DataSize)); + kDuration * kMockTimescaleScale, kSegment2DataSize, 125)); EXPECT_CALL(*mock_segmenter_ptr_, TimescaleScale()) .WillRepeatedly(Return(kMockTimescaleScale)); @@ -251,8 +251,8 @@ TEST_P(PackedAudioWriterTest, TwoSegments) { ASSERT_FILE_STREQ(kOutputFile, std::string(kMockSegment1Data) + std::string(kMockSegment2Data)); } else { - ASSERT_FILE_STREQ(kSegment1Name, kMockSegment1Data); - ASSERT_FILE_STREQ(kSegment2Name, kMockSegment2Data); + ASSERT_FILE_STREQ(kSegment124Name, kMockSegment1Data); + ASSERT_FILE_STREQ(kSegment125Name, kMockSegment2Data); } } diff --git a/packager/media/formats/webm/encrypted_segmenter_unittest.cc b/packager/media/formats/webm/encrypted_segmenter_unittest.cc index 3054e193f45..d9ccd0fc3e8 100644 --- a/packager/media/formats/webm/encrypted_segmenter_unittest.cc +++ b/packager/media/formats/webm/encrypted_segmenter_unittest.cc @@ -2,11 +2,12 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "packager/media/formats/webm/two_pass_single_segment_segmenter.h" - #include + #include + #include "packager/media/formats/webm/segmenter_test_base.h" +#include "packager/media/formats/webm/two_pass_single_segment_segmenter.h" namespace shaka { namespace media { @@ -225,7 +226,7 @@ TEST_F(EncryptedSegmenterTest, BasicSupport) { // segment encrypted. for (int i = 0; i < 5; i++) { if (i == 3) { - ASSERT_OK(segmenter_->FinalizeSegment(0, 3 * kDuration, !kSubsegment)); + ASSERT_OK(segmenter_->FinalizeSegment(0, 3 * kDuration, !kSubsegment, 3)); } std::shared_ptr sample = CreateSample(kKeyFrame, kDuration, kNoSideData); @@ -239,8 +240,8 @@ TEST_F(EncryptedSegmenterTest, BasicSupport) { } ASSERT_OK(segmenter_->AddSample(*sample)); } - ASSERT_OK( - segmenter_->FinalizeSegment(3 * kDuration, 2 * kDuration, !kSubsegment)); + ASSERT_OK(segmenter_->FinalizeSegment(3 * kDuration, 2 * kDuration, + !kSubsegment, 2)); ASSERT_OK(segmenter_->Finalize()); ASSERT_FILE_ENDS_WITH(OutputFileName().c_str(), kBasicSupportData); diff --git a/packager/media/formats/webm/mkv_writer.cc b/packager/media/formats/webm/mkv_writer.cc index 0d919d0ac62..0953772c35d 100644 --- a/packager/media/formats/webm/mkv_writer.cc +++ b/packager/media/formats/webm/mkv_writer.cc @@ -13,8 +13,17 @@ MkvWriter::MkvWriter() : position_(0) {} MkvWriter::~MkvWriter() {} +Status MkvWriter::OpenBuffer() { + DCHECK(!file_); + current_buffer_ = std::unique_ptr(new BufferWriter()); + seekable_ = false; + position_ = 0; + return Status::OK; +} + Status MkvWriter::Open(const std::string& name) { DCHECK(!file_); + DCHECK(!current_buffer_); file_.reset(File::Open(name.c_str(), "w")); if (!file_) return Status(error::FILE_FAILURE, "Unable to open file for writing."); @@ -26,6 +35,21 @@ Status MkvWriter::Open(const std::string& name) { return Status::OK; } +bool MkvWriter::CreateFileAndFlushBuffer(const std::string& file_name) { + + if (file_) { + LOG(ERROR) << "File " << file_->file_name() << " is open."; + return false; + } + file_.reset(File::Open(file_name.c_str(), "w")); + if (!file_) { + LOG(ERROR) << "Failed to open file " << file_name; + return false; + } + seekable_ = file_->Seek(0); + return current_buffer_.get()->WriteToFile(file_.get()).ok(); +} + Status MkvWriter::Close() { const std::string file_name = file_->file_name(); if (!file_.release()->Close()) { @@ -38,21 +62,28 @@ Status MkvWriter::Close() { } mkvmuxer::int32 MkvWriter::Write(const void* buf, mkvmuxer::uint32 len) { - DCHECK(file_); - - const char* data = reinterpret_cast(buf); - int64_t total_bytes_written = 0; - while (total_bytes_written < len) { - const int64_t written = - file_->Write(data + total_bytes_written, len - total_bytes_written); - if (written < 0) - return written; - - total_bytes_written += written; + if (seekable_) { + DCHECK(file_); + + const char* data = reinterpret_cast(buf); + int64_t total_bytes_written = 0; + while (total_bytes_written < len) { + const int64_t written = + file_->Write(data + total_bytes_written, len - total_bytes_written); + if (written < 0) + return written; + + total_bytes_written += written; + } + + DCHECK_EQ(total_bytes_written, len); + position_ += len; + } else { + // Write to buffer if file is not present. + const uint8_t* data = reinterpret_cast(buf); + current_buffer_.get()->AppendArray(data, len); + position_ = current_buffer_.get()->Size(); } - - DCHECK_EQ(total_bytes_written, len); - position_ += len; return 0; } @@ -61,6 +92,7 @@ int64_t MkvWriter::WriteFromFile(File* source) { } int64_t MkvWriter::WriteFromFile(File* source, int64_t max_copy) { + DCHECK(seekable_); DCHECK(file_); const int64_t size = File::CopyFile(source, file_.get(), max_copy); diff --git a/packager/media/formats/webm/mkv_writer.h b/packager/media/formats/webm/mkv_writer.h index cfed034a83a..9c2280385db 100644 --- a/packager/media/formats/webm/mkv_writer.h +++ b/packager/media/formats/webm/mkv_writer.h @@ -13,6 +13,7 @@ #include "packager/file/file_closer.h" #include "packager/status.h" #include "packager/third_party/libwebm/src/mkvmuxer.hpp" +#include "packager/media/base/buffer_writer.h" namespace shaka { namespace media { @@ -28,6 +29,11 @@ class MkvWriter : public mkvmuxer::IMkvWriter { /// @param name The path to the file to open. /// @return Whether the operation succeeded. Status Open(const std::string& name); + + /// Initialize a buffer. + /// @return Whether the operation succeeded. + Status OpenBuffer(); + /// Closes the file. MUST call Open before calling any other methods. Status Close(); @@ -58,10 +64,17 @@ class MkvWriter : public mkvmuxer::IMkvWriter { /// @return The number of bytes written; or < 0 on error. int64_t WriteFromFile(File* source, int64_t max_copy); + /// Creates a file with name @a file_name and flushes + /// current_buffer_ to it. + /// @param name The path to the file to open. + /// @return File creation and buffer flushing succeeded or failed. + virtual bool CreateFileAndFlushBuffer(const std::string& file_name); + File* file() { return file_.get(); } - + private: std::unique_ptr file_; + std::unique_ptr current_buffer_; // Keep track of the position and whether we can seek. mkvmuxer::int64 position_; bool seekable_; diff --git a/packager/media/formats/webm/multi_segment_segmenter.cc b/packager/media/formats/webm/multi_segment_segmenter.cc index 4a2f24bae21..fe004365310 100644 --- a/packager/media/formats/webm/multi_segment_segmenter.cc +++ b/packager/media/formats/webm/multi_segment_segmenter.cc @@ -24,15 +24,27 @@ MultiSegmentSegmenter::~MultiSegmentSegmenter() {} Status MultiSegmentSegmenter::FinalizeSegment(uint64_t start_timestamp, uint64_t duration_timestamp, - bool is_subsegment) { + bool is_subsegment, + uint64_t segment_index) { CHECK(cluster()); RETURN_IF_ERROR(Segmenter::FinalizeSegment( - start_timestamp, duration_timestamp, is_subsegment)); + start_timestamp, duration_timestamp, is_subsegment, segment_index)); + if (!is_subsegment) { + std::string segment_name = + GetSegmentName(options().segment_template, start_timestamp, + segment_index - 1, options().bandwidth); + + if (!writer_->CreateFileAndFlushBuffer(segment_name)) { + LOG(ERROR) << "Failed creating file for webm segment."; + } + } + if (!cluster()->Finalize()) return Status(error::FILE_FAILURE, "Error finalizing segment."); if (!is_subsegment) { const std::string segment_name = writer_->file()->file_name(); + // Close the file, which also does flushing, to make sure the file is // written before manifest is updated. RETURN_IF_ERROR(writer_->Close()); @@ -40,7 +52,7 @@ Status MultiSegmentSegmenter::FinalizeSegment(uint64_t start_timestamp, if (muxer_listener()) { const uint64_t size = cluster()->Size(); muxer_listener()->OnNewSegment(segment_name, start_timestamp, - duration_timestamp, size); + duration_timestamp, size, segment_index); } VLOG(1) << "WEBM file '" << writer_->file()->file_name() << "' finalized."; } @@ -77,12 +89,9 @@ Status MultiSegmentSegmenter::DoFinalize() { Status MultiSegmentSegmenter::NewSegment(uint64_t start_timestamp, bool is_subsegment) { if (!is_subsegment) { - // Create a new file for the new segment. - std::string segment_name = - GetSegmentName(options().segment_template, start_timestamp, - num_segment_, options().bandwidth); writer_.reset(new MkvWriter); - Status status = writer_->Open(segment_name); + // Initialize the buffer for a new segment. + Status status = writer_->OpenBuffer(); if (!status.ok()) return status; num_segment_++; diff --git a/packager/media/formats/webm/multi_segment_segmenter.h b/packager/media/formats/webm/multi_segment_segmenter.h index 4d788ca7937..5f86c625f0b 100644 --- a/packager/media/formats/webm/multi_segment_segmenter.h +++ b/packager/media/formats/webm/multi_segment_segmenter.h @@ -30,7 +30,8 @@ class MultiSegmentSegmenter : public Segmenter { /// @{ Status FinalizeSegment(uint64_t start_timestamp, uint64_t duration_timestamp, - bool is_subsegment) override; + bool is_subsegment, + uint64_t segment_index) override; bool GetInitRangeStartAndEnd(uint64_t* start, uint64_t* end) override; bool GetIndexRangeStartAndEnd(uint64_t* start, uint64_t* end) override; std::vector GetSegmentRanges() override; diff --git a/packager/media/formats/webm/multi_segment_segmenter_unittest.cc b/packager/media/formats/webm/multi_segment_segmenter_unittest.cc index 5513f00c51a..42d37921ff3 100644 --- a/packager/media/formats/webm/multi_segment_segmenter_unittest.cc +++ b/packager/media/formats/webm/multi_segment_segmenter_unittest.cc @@ -5,7 +5,9 @@ #include "packager/media/formats/webm/multi_segment_segmenter.h" #include + #include + #include "packager/media/base/muxer_util.h" #include "packager/media/formats/webm/segmenter_test_base.h" @@ -69,6 +71,7 @@ const uint8_t kBasicSupportDataInit[] = { // DisplayHeight: 100 0x54, 0xba, 0x81, 0x64 }; + const uint8_t kBasicSupportDataSegment[] = { // ID: Cluster, Payload Size: 64 0x1f, 0x43, 0xb6, 0x75, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, @@ -126,15 +129,15 @@ TEST_F(MultiSegmentSegmenterTest, BasicSupport) { CreateSample(kKeyFrame, kDuration, kNoSideData); ASSERT_OK(segmenter_->AddSample(*sample)); } - ASSERT_OK(segmenter_->FinalizeSegment(0, 8 * kDuration, !kSubsegment)); + ASSERT_OK(segmenter_->FinalizeSegment(0, 8 * kDuration, !kSubsegment, 8)); ASSERT_OK(segmenter_->Finalize()); // Verify the resulting data. ASSERT_FILE_ENDS_WITH(OutputFileName().c_str(), kBasicSupportDataInit); - ASSERT_FILE_EQ(TemplateFileName(0).c_str(), kBasicSupportDataSegment); + ASSERT_FILE_EQ(TemplateFileName(7).c_str(), kBasicSupportDataSegment); // There is no second segment. - EXPECT_FALSE(File::Open(TemplateFileName(1).c_str(), "r")); + EXPECT_FALSE(File::Open(TemplateFileName(8).c_str(), "r")); } TEST_F(MultiSegmentSegmenterTest, SplitsFilesOnSegment) { @@ -145,27 +148,27 @@ TEST_F(MultiSegmentSegmenterTest, SplitsFilesOnSegment) { // Write the samples to the Segmenter. for (int i = 0; i < 8; i++) { if (i == 5) { - ASSERT_OK(segmenter_->FinalizeSegment(0, 5 * kDuration, !kSubsegment)); + ASSERT_OK(segmenter_->FinalizeSegment(0, 5 * kDuration, !kSubsegment, 5)); } std::shared_ptr sample = CreateSample(kKeyFrame, kDuration, kNoSideData); ASSERT_OK(segmenter_->AddSample(*sample)); } - ASSERT_OK( - segmenter_->FinalizeSegment(5 * kDuration, 8 * kDuration, !kSubsegment)); + ASSERT_OK(segmenter_->FinalizeSegment(5 * kDuration, 8 * kDuration, + !kSubsegment, 8)); ASSERT_OK(segmenter_->Finalize()); // Verify the resulting data. ClusterParser parser; - ASSERT_NO_FATAL_FAILURE(parser.PopulateFromCluster(TemplateFileName(0))); + ASSERT_NO_FATAL_FAILURE(parser.PopulateFromCluster(TemplateFileName(4))); ASSERT_EQ(1u, parser.cluster_count()); EXPECT_EQ(5u, parser.GetFrameCountForCluster(0)); - ASSERT_NO_FATAL_FAILURE(parser.PopulateFromCluster(TemplateFileName(1))); + ASSERT_NO_FATAL_FAILURE(parser.PopulateFromCluster(TemplateFileName(7))); ASSERT_EQ(1u, parser.cluster_count()); EXPECT_EQ(3u, parser.GetFrameCountForCluster(0)); - EXPECT_FALSE(File::Open(TemplateFileName(2).c_str(), "r")); + EXPECT_FALSE(File::Open(TemplateFileName(9).c_str(), "r")); } TEST_F(MultiSegmentSegmenterTest, SplitsClustersOnSubsegment) { @@ -176,23 +179,23 @@ TEST_F(MultiSegmentSegmenterTest, SplitsClustersOnSubsegment) { // Write the samples to the Segmenter. for (int i = 0; i < 8; i++) { if (i == 5) { - ASSERT_OK(segmenter_->FinalizeSegment(0, 5 * kDuration, kSubsegment)); + ASSERT_OK(segmenter_->FinalizeSegment(0, 5 * kDuration, kSubsegment, 5)); } std::shared_ptr sample = CreateSample(kKeyFrame, kDuration, kNoSideData); ASSERT_OK(segmenter_->AddSample(*sample)); } - ASSERT_OK(segmenter_->FinalizeSegment(0, 8 * kDuration, !kSubsegment)); + ASSERT_OK(segmenter_->FinalizeSegment(0, 8 * kDuration, !kSubsegment, 8)); ASSERT_OK(segmenter_->Finalize()); // Verify the resulting data. ClusterParser parser; - ASSERT_NO_FATAL_FAILURE(parser.PopulateFromCluster(TemplateFileName(0))); + ASSERT_NO_FATAL_FAILURE(parser.PopulateFromCluster(TemplateFileName(7))); ASSERT_EQ(2u, parser.cluster_count()); EXPECT_EQ(5u, parser.GetFrameCountForCluster(0)); EXPECT_EQ(3u, parser.GetFrameCountForCluster(1)); - EXPECT_FALSE(File::Open(TemplateFileName(1).c_str(), "r")); + EXPECT_FALSE(File::Open(TemplateFileName(8).c_str(), "r")); } } // namespace media diff --git a/packager/media/formats/webm/segmenter.cc b/packager/media/formats/webm/segmenter.cc index b3ffdcfb188..70c29cf3920 100644 --- a/packager/media/formats/webm/segmenter.cc +++ b/packager/media/formats/webm/segmenter.cc @@ -135,8 +135,8 @@ Status Segmenter::Initialize(const StreamInfo& info, if (info.is_encrypted()) { if (info.encryption_config().per_sample_iv_size != kWebMIvSize) return Status(error::MUXER_FAILURE, "Incorrect size WebM encryption IV."); - status = UpdateTrackForEncryption(info.encryption_config().key_id, - track.get()); + status = + UpdateTrackForEncryption(info.encryption_config().key_id, track.get()); if (!status.ok()) return status; } @@ -196,7 +196,8 @@ Status Segmenter::AddSample(const MediaSample& source_sample) { Status Segmenter::FinalizeSegment(uint64_t start_timestamp, uint64_t duration_timestamp, - bool is_subsegment) { + bool is_subsegment, + uint64_t segment_index) { if (is_subsegment) new_subsegment_ = true; else @@ -211,9 +212,8 @@ float Segmenter::GetDurationInSeconds() const { } uint64_t Segmenter::FromBmffTimestamp(uint64_t bmff_timestamp) { - return NsToWebMTimecode( - BmffTimestampToNs(bmff_timestamp, time_scale_), - segment_info_.timecode_scale()); + return NsToWebMTimecode(BmffTimestampToNs(bmff_timestamp, time_scale_), + segment_info_.timecode_scale()); } uint64_t Segmenter::FromWebMTimecode(uint64_t webm_timecode) { @@ -389,8 +389,7 @@ Status Segmenter::WriteFrame(bool write_duration) { BmffTimestampToNs(prev_sample_->duration(), time_scale_)); } frame.set_is_key(prev_sample_->is_key_frame()); - frame.set_timestamp( - BmffTimestampToNs(prev_sample_->pts(), time_scale_)); + frame.set_timestamp(BmffTimestampToNs(prev_sample_->pts(), time_scale_)); frame.set_track_number(track_id_); if (prev_sample_->side_data_size() > 0) { diff --git a/packager/media/formats/webm/segmenter.h b/packager/media/formats/webm/segmenter.h index f6c7c2184d1..0284c6d6970 100644 --- a/packager/media/formats/webm/segmenter.h +++ b/packager/media/formats/webm/segmenter.h @@ -57,7 +57,8 @@ class Segmenter { /// Finalize the (sub)segment. virtual Status FinalizeSegment(uint64_t start_timestamp, uint64_t duration_timestamp, - bool is_subsegment) = 0; + bool is_subsegment, + uint64_t segment_index) = 0; /// @return true if there is an initialization range, while setting @a start /// and @a end; or false if initialization range does not apply. diff --git a/packager/media/formats/webm/single_segment_segmenter.cc b/packager/media/formats/webm/single_segment_segmenter.cc index 29a6be75722..3cb9c269e44 100644 --- a/packager/media/formats/webm/single_segment_segmenter.cc +++ b/packager/media/formats/webm/single_segment_segmenter.cc @@ -21,9 +21,10 @@ SingleSegmentSegmenter::~SingleSegmentSegmenter() {} Status SingleSegmentSegmenter::FinalizeSegment(uint64_t start_timestamp, uint64_t duration_timestamp, - bool is_subsegment) { - Status status = Segmenter::FinalizeSegment(start_timestamp, - duration_timestamp, is_subsegment); + bool is_subsegment, + uint64_t segment_index) { + Status status = Segmenter::FinalizeSegment( + start_timestamp, duration_timestamp, is_subsegment, segment_index); if (!status.ok()) return status; // No-op for subsegment in single segment mode. @@ -35,7 +36,7 @@ Status SingleSegmentSegmenter::FinalizeSegment(uint64_t start_timestamp, if (muxer_listener()) { const uint64_t size = cluster()->Size(); muxer_listener()->OnNewSegment(options().output_file_name, start_timestamp, - duration_timestamp, size); + duration_timestamp, size, segment_index); } return Status::OK; } diff --git a/packager/media/formats/webm/single_segment_segmenter.h b/packager/media/formats/webm/single_segment_segmenter.h index 0115fcd0afe..201ca1e1743 100644 --- a/packager/media/formats/webm/single_segment_segmenter.h +++ b/packager/media/formats/webm/single_segment_segmenter.h @@ -7,10 +7,10 @@ #ifndef PACKAGER_MEDIA_FORMATS_WEBM_SINGLE_SEGMENT_SEGMENTER_H_ #define PACKAGER_MEDIA_FORMATS_WEBM_SINGLE_SEGMENT_SEGMENTER_H_ -#include "packager/media/formats/webm/segmenter.h" - #include + #include "packager/media/formats/webm/mkv_writer.h" +#include "packager/media/formats/webm/segmenter.h" #include "packager/status.h" namespace shaka { @@ -32,7 +32,8 @@ class SingleSegmentSegmenter : public Segmenter { /// @{ Status FinalizeSegment(uint64_t start_timestamp, uint64_t duration_timestamp, - bool is_subsegment) override; + bool is_subsegment, + uint64_t segment_index) override; bool GetInitRangeStartAndEnd(uint64_t* start, uint64_t* end) override; bool GetIndexRangeStartAndEnd(uint64_t* start, uint64_t* end) override; std::vector GetSegmentRanges() override; diff --git a/packager/media/formats/webm/single_segment_segmenter_unittest.cc b/packager/media/formats/webm/single_segment_segmenter_unittest.cc index 175931183b4..50260b8a18f 100644 --- a/packager/media/formats/webm/single_segment_segmenter_unittest.cc +++ b/packager/media/formats/webm/single_segment_segmenter_unittest.cc @@ -2,11 +2,12 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "packager/media/formats/webm/two_pass_single_segment_segmenter.h" - #include + #include + #include "packager/media/formats/webm/segmenter_test_base.h" +#include "packager/media/formats/webm/two_pass_single_segment_segmenter.h" namespace shaka { namespace media { @@ -163,7 +164,7 @@ TEST_F(SingleSegmentSegmenterTest, BasicSupport) { CreateSample(kKeyFrame, kDuration, side_data_flag); ASSERT_OK(segmenter_->AddSample(*sample)); } - ASSERT_OK(segmenter_->FinalizeSegment(0, 5 * kDuration, !kSubsegment)); + ASSERT_OK(segmenter_->FinalizeSegment(0, 5 * kDuration, !kSubsegment, 5)); ASSERT_OK(segmenter_->Finalize()); ASSERT_FILE_ENDS_WITH(OutputFileName().c_str(), kBasicSupportData); @@ -176,14 +177,14 @@ TEST_F(SingleSegmentSegmenterTest, SplitsClustersOnSegment) { // Write the samples to the Segmenter. for (int i = 0; i < 8; i++) { if (i == 5) { - ASSERT_OK(segmenter_->FinalizeSegment(0, 5 * kDuration, !kSubsegment)); + ASSERT_OK(segmenter_->FinalizeSegment(0, 5 * kDuration, !kSubsegment, 5)); } std::shared_ptr sample = CreateSample(kKeyFrame, kDuration, kNoSideData); ASSERT_OK(segmenter_->AddSample(*sample)); } - ASSERT_OK( - segmenter_->FinalizeSegment(5 * kDuration, 8 * kDuration, !kSubsegment)); + ASSERT_OK(segmenter_->FinalizeSegment(5 * kDuration, 8 * kDuration, + !kSubsegment, 8)); ASSERT_OK(segmenter_->Finalize()); // Verify the resulting data. @@ -201,13 +202,13 @@ TEST_F(SingleSegmentSegmenterTest, IgnoresSubsegment) { // Write the samples to the Segmenter. for (int i = 0; i < 8; i++) { if (i == 5) { - ASSERT_OK(segmenter_->FinalizeSegment(0, 5 * kDuration, kSubsegment)); + ASSERT_OK(segmenter_->FinalizeSegment(0, 5 * kDuration, kSubsegment, 5)); } std::shared_ptr sample = CreateSample(kKeyFrame, kDuration, kNoSideData); ASSERT_OK(segmenter_->AddSample(*sample)); } - ASSERT_OK(segmenter_->FinalizeSegment(0, 8 * kDuration, !kSubsegment)); + ASSERT_OK(segmenter_->FinalizeSegment(0, 8 * kDuration, !kSubsegment, 8)); ASSERT_OK(segmenter_->Finalize()); // Verify the resulting data. @@ -234,7 +235,7 @@ TEST_F(SingleSegmentSegmenterTest, LargeTimestamp) { ASSERT_OK(segmenter_->AddSample(*sample)); } ASSERT_OK(segmenter_->FinalizeSegment(kLargeTimestamp, 5 * kDuration, - !kSubsegment)); + !kSubsegment, 5)); ASSERT_OK(segmenter_->Finalize()); // Verify the resulting data. @@ -270,7 +271,7 @@ TEST_F(SingleSegmentSegmenterTest, ReallyLargeTimestamp) { ASSERT_OK(segmenter_->AddSample(*sample)); } ASSERT_OK(segmenter_->FinalizeSegment(kReallyLargeTimestamp, 5 * kDuration, - !kSubsegment)); + !kSubsegment, 5)); ASSERT_OK(segmenter_->Finalize()); // Verify the resulting data. diff --git a/packager/media/formats/webm/webm_muxer.cc b/packager/media/formats/webm/webm_muxer.cc index dcf02be7a7a..786640e5780 100644 --- a/packager/media/formats/webm/webm_muxer.cc +++ b/packager/media/formats/webm/webm_muxer.cc @@ -78,9 +78,9 @@ Status WebMMuxer::FinalizeSegment(size_t stream_id, return Status(error::UNIMPLEMENTED, "Key rotation is not implemented for WebM"); } - return segmenter_->FinalizeSegment(segment_info.start_timestamp, - segment_info.duration, - segment_info.is_subsegment); + return segmenter_->FinalizeSegment( + segment_info.start_timestamp, segment_info.duration, + segment_info.is_subsegment, segment_info.segment_index); } void WebMMuxer::FireOnMediaStartEvent() { diff --git a/packager/media/formats/webvtt/webvtt_text_output_handler.cc b/packager/media/formats/webvtt/webvtt_text_output_handler.cc index 3043209114e..e338bdbccc2 100644 --- a/packager/media/formats/webvtt/webvtt_text_output_handler.cc +++ b/packager/media/formats/webvtt/webvtt_text_output_handler.cc @@ -85,13 +85,13 @@ Status WebVttTextOutputHandler::OnSegmentInfo(const SegmentInfo& info) { total_duration_ms_ += info.duration; const std::string& segment_template = muxer_options_.segment_template; - const uint32_t index = segment_index_++; + const uint32_t index = info.segment_index; const uint64_t start = info.start_timestamp; const uint64_t duration = info.duration; const uint32_t bandwidth = muxer_options_.bandwidth; const std::string filename = - GetSegmentName(segment_template, start, index, bandwidth); + GetSegmentName(segment_template, start, index - 1, bandwidth); // Write everything to the file before telling the manifest so that the // file will exist on disk. @@ -110,7 +110,8 @@ Status WebVttTextOutputHandler::OnSegmentInfo(const SegmentInfo& info) { // Update the manifest with our new file. const uint64_t size = File::GetFileSize(filename.c_str()); - muxer_listener_->OnNewSegment(filename, start, duration, size); + muxer_listener_->OnNewSegment(filename, start, duration, size, + info.segment_index); return Status::OK; } diff --git a/packager/media/formats/webvtt/webvtt_text_output_handler_unittest.cc b/packager/media/formats/webvtt/webvtt_text_output_handler_unittest.cc index cde1afa78dc..3212f5061a6 100644 --- a/packager/media/formats/webvtt/webvtt_text_output_handler_unittest.cc +++ b/packager/media/formats/webvtt/webvtt_text_output_handler_unittest.cc @@ -4,6 +4,8 @@ // license that can be found in the LICENSE file or at // https://developers.google.com/open-source/licenses/bsd +#include "packager/media/formats/webvtt/webvtt_text_output_handler.h" + #include #include @@ -12,7 +14,6 @@ #include "packager/media/base/text_stream_info.h" #include "packager/media/event/combined_muxer_listener.h" #include "packager/media/event/mock_muxer_listener.h" -#include "packager/media/formats/webvtt/webvtt_text_output_handler.h" #include "packager/status_test_util.h" namespace shaka { @@ -60,7 +61,7 @@ class WebVttSegmentedOutputTest : public MediaHandlerTestBase { }; TEST_F(WebVttSegmentedOutputTest, WithNoSegmentAndWithNoSamples) { - EXPECT_CALL(*muxer_listener_, OnNewSegment(_, _, _, _)).Times(0); + EXPECT_CALL(*muxer_listener_, OnNewSegment(_, _, _, _, _)).Times(0); { // No segments should have be created as there were no samples. @@ -94,7 +95,7 @@ TEST_F(WebVttSegmentedOutputTest, WithOneSegmentAndWithOneSample) { EXPECT_CALL(*muxer_listener_, OnMediaStart(_, _, _, _)); EXPECT_CALL(*muxer_listener_, OnNewSegment(kSegmentedFileOutput1, kSegmentStart, - kSegmentDuration, _)); + kSegmentDuration, _, 1)); const float kMediaDuration = 1 * kSegmentDuration / kMillisecondsPerSecond; EXPECT_CALL(*muxer_listener_, @@ -141,10 +142,10 @@ TEST_F(WebVttSegmentedOutputTest, WithTwoSegmentAndWithOneSample) { EXPECT_CALL(*muxer_listener_, OnMediaStart(_, _, _, _)); EXPECT_CALL(*muxer_listener_, OnNewSegment(kSegmentedFileOutput1, kSegment1Start, - kSegmentDuration, _)); + kSegmentDuration, _, _)); EXPECT_CALL(*muxer_listener_, OnNewSegment(kSegmentedFileOutput2, kSegment2Start, - kSegmentDuration, _)); + kSegmentDuration, _, _)); const float kMediaDuration = 2 * kSegmentDuration / kMillisecondsPerSecond; EXPECT_CALL(*muxer_listener_, @@ -201,10 +202,10 @@ TEST_F(WebVttSegmentedOutputTest, WithAnEmptySegment) { EXPECT_CALL(*muxer_listener_, OnMediaStart(_, _, _, _)); EXPECT_CALL(*muxer_listener_, OnNewSegment(kSegmentedFileOutput1, kSegment1Start, - kSegmentDuration, _)); + kSegmentDuration, _, _)); EXPECT_CALL(*muxer_listener_, OnNewSegment(kSegmentedFileOutput2, kSegment2Start, - kSegmentDuration, _)); + kSegmentDuration, _, _)); const float kMediaDuration = 2 * kSegmentDuration / kMillisecondsPerSecond; EXPECT_CALL(*muxer_listener_, diff --git a/packager/mpd/base/adaptation_set_unittest.cc b/packager/mpd/base/adaptation_set_unittest.cc index 0d6e37d3b89..1b602b98582 100644 --- a/packager/mpd/base/adaptation_set_unittest.cc +++ b/packager/mpd/base/adaptation_set_unittest.cc @@ -682,30 +682,33 @@ TEST_F(OnDemandAdaptationSetTest, SubsegmentAlignment) { const uint64_t kStartTime = 0u; const uint64_t kDuration = 10u; const uint64_t kAnySize = 19834u; + const uint64_t kSegmentIndex = 1; auto adaptation_set = CreateAdaptationSet(kNoLanguage); Representation* representation_480p = adaptation_set->AddRepresentation(ConvertToMediaInfo(k480pMediaInfo)); // Add a subsegment immediately before adding the 360p Representation. // This should still work for VOD. - representation_480p->AddNewSegment(kStartTime, kDuration, kAnySize); + representation_480p->AddNewSegment(kStartTime, kDuration, kAnySize, + kSegmentIndex); Representation* representation_360p = adaptation_set->AddRepresentation(ConvertToMediaInfo(k360pMediaInfo)); - representation_360p->AddNewSegment(kStartTime, kDuration, kAnySize); + representation_360p->AddNewSegment(kStartTime, kDuration, kAnySize, + kSegmentIndex); xml::scoped_xml_ptr aligned(adaptation_set->GetXml()); EXPECT_THAT(aligned.get(), AttributeEqual("subsegmentAlignment", "true")); // Unknown because 480p has an extra subsegments. - representation_480p->AddNewSegment(11, 20, kAnySize); + representation_480p->AddNewSegment(11, 20, kAnySize, (11 / 20) + 1); xml::scoped_xml_ptr alignment_unknown(adaptation_set->GetXml()); EXPECT_THAT(alignment_unknown.get(), Not(AttributeSet("subsegmentAlignment"))); // Add segments that make them not aligned. - representation_360p->AddNewSegment(10, 1, kAnySize); - representation_360p->AddNewSegment(11, 19, kAnySize); + representation_360p->AddNewSegment(10, 1, kAnySize, (10 / 1) + 1); + representation_360p->AddNewSegment(11, 19, kAnySize, (11 / 19) + 1); xml::scoped_xml_ptr unaligned(adaptation_set->GetXml()); EXPECT_THAT(unaligned.get(), Not(AttributeSet("subsegmentAlignment"))); @@ -747,8 +750,8 @@ TEST_F(OnDemandAdaptationSetTest, ForceSetsubsegmentAlignment) { static_assert(kStartTime1 != kStartTime2, "StartTimesShouldBeDifferent"); const uint64_t kDuration = 10u; const uint64_t kAnySize = 19834u; - representation_480p->AddNewSegment(kStartTime1, kDuration, kAnySize); - representation_360p->AddNewSegment(kStartTime2, kDuration, kAnySize); + representation_480p->AddNewSegment(kStartTime1, kDuration, kAnySize, 1); + representation_360p->AddNewSegment(kStartTime2, kDuration, kAnySize, 1); xml::scoped_xml_ptr unaligned(adaptation_set->GetXml()); EXPECT_THAT(unaligned.get(), Not(AttributeSet("subsegmentAlignment"))); @@ -798,15 +801,15 @@ TEST_F(LiveAdaptationSetTest, SegmentAlignmentDynamicMpd) { Representation* representation_360p = adaptation_set->AddRepresentation(ConvertToMediaInfo(k360pMediaInfo)); - representation_480p->AddNewSegment(kStartTime, kDuration, kAnySize); - representation_360p->AddNewSegment(kStartTime, kDuration, kAnySize); + representation_480p->AddNewSegment(kStartTime, kDuration, kAnySize, 1); + representation_360p->AddNewSegment(kStartTime, kDuration, kAnySize, 1); xml::scoped_xml_ptr aligned(adaptation_set->GetXml()); EXPECT_THAT(aligned.get(), AttributeEqual("segmentAlignment", "true")); // Add segments that make them not aligned. - representation_480p->AddNewSegment(11, 20, kAnySize); - representation_360p->AddNewSegment(10, 1, kAnySize); - representation_360p->AddNewSegment(11, 19, kAnySize); + representation_480p->AddNewSegment(11, 20, kAnySize, (11 / 20) + 1); + representation_360p->AddNewSegment(10, 1, kAnySize, (10 / 1) + 1); + representation_360p->AddNewSegment(11, 19, kAnySize, (11 / 19) + 1); xml::scoped_xml_ptr unaligned(adaptation_set->GetXml()); EXPECT_THAT(unaligned.get(), Not(AttributeSet("segmentAlignment"))); @@ -851,16 +854,16 @@ TEST_F(LiveAdaptationSetTest, SegmentAlignmentStaticMpd) { // Representation. Representation* representation_480p = adaptation_set->AddRepresentation(ConvertToMediaInfo(k480pMediaInfo)); - representation_480p->AddNewSegment(kStartTime, kDuration, kAnySize); + representation_480p->AddNewSegment(kStartTime, kDuration, kAnySize, 1); Representation* representation_360p = adaptation_set->AddRepresentation(ConvertToMediaInfo(k360pMediaInfo)); - representation_360p->AddNewSegment(kStartTime, kDuration, kAnySize); + representation_360p->AddNewSegment(kStartTime, kDuration, kAnySize, 1); representation_480p->AddNewSegment(kStartTime + kDuration, kDuration, - kAnySize); + kAnySize, 2); representation_360p->AddNewSegment(kStartTime + kDuration, kDuration, - kAnySize); + kAnySize, 2); xml::scoped_xml_ptr aligned(adaptation_set->GetXml()); EXPECT_THAT(aligned.get(), AttributeEqual("segmentAlignment", "true")); diff --git a/packager/mpd/base/mock_mpd_builder.h b/packager/mpd/base/mock_mpd_builder.h index 6d46396f306..7c4b70cdeab 100644 --- a/packager/mpd/base/mock_mpd_builder.h +++ b/packager/mpd/base/mock_mpd_builder.h @@ -75,8 +75,11 @@ class MockRepresentation : public Representation { void(const ContentProtectionElement& element)); MOCK_METHOD2(UpdateContentProtectionPssh, void(const std::string& drm_uuid, const std::string& pssh)); - MOCK_METHOD3(AddNewSegment, - void(int64_t start_time, int64_t duration, uint64_t size)); + MOCK_METHOD4(AddNewSegment, + void(int64_t start_time, + int64_t duration, + uint64_t size, + uint64_t segment_index)); MOCK_METHOD1(SetSampleDuration, void(uint32_t sample_duration)); MOCK_CONST_METHOD0(GetMediaInfo, const MediaInfo&()); }; diff --git a/packager/mpd/base/mock_mpd_notifier.h b/packager/mpd/base/mock_mpd_notifier.h index 0726f520925..7d2eacd8254 100644 --- a/packager/mpd/base/mock_mpd_notifier.h +++ b/packager/mpd/base/mock_mpd_notifier.h @@ -7,12 +7,11 @@ #ifndef MPD_BASE_MOCK_MPD_NOTIFIER_H_ #define MPD_BASE_MOCK_MPD_NOTIFIER_H_ -#include "packager/mpd/base/mpd_notifier.h" - #include #include "packager/mpd/base/content_protection_element.h" #include "packager/mpd/base/media_info.pb.h" +#include "packager/mpd/base/mpd_notifier.h" namespace shaka { @@ -26,11 +25,12 @@ class MockMpdNotifier : public MpdNotifier { bool(const MediaInfo& media_info, uint32_t* container_id)); MOCK_METHOD2(NotifySampleDuration, bool(uint32_t container_id, uint32_t sample_duration)); - MOCK_METHOD4(NotifyNewSegment, + MOCK_METHOD5(NotifyNewSegment, bool(uint32_t container_id, uint64_t start_time, uint64_t duration, - uint64_t size)); + uint64_t size, + uint64_t segment_index)); MOCK_METHOD2(NotifyCueEvent, bool(uint32_t container_id, uint64_t timestamp)); MOCK_METHOD4(NotifyEncryptionUpdate, bool(uint32_t container_id, diff --git a/packager/mpd/base/mpd_builder_unittest.cc b/packager/mpd/base/mpd_builder_unittest.cc index acd7ed1c8e1..69a3d170749 100644 --- a/packager/mpd/base/mpd_builder_unittest.cc +++ b/packager/mpd/base/mpd_builder_unittest.cc @@ -4,11 +4,12 @@ // license that can be found in the LICENSE file or at // https://developers.google.com/open-source/licenses/bsd +#include "packager/mpd/base/mpd_builder.h" + #include #include #include "packager/mpd/base/adaptation_set.h" -#include "packager/mpd/base/mpd_builder.h" #include "packager/mpd/base/period.h" #include "packager/mpd/base/representation.h" #include "packager/mpd/test/mpd_builder_test_helper.h" @@ -71,7 +72,8 @@ class MpdBuilderTest : public ::testing::Test { adaptation_set->AddRepresentation(media_info); representation->AddNewSegment( segment_start_time_seconds * media_info.reference_time_scale(), - segment_duration_seconds * media_info.reference_time_scale(), kBytes); + segment_duration_seconds * media_info.reference_time_scale(), kBytes, + (segment_start_time_seconds / segment_duration_seconds) + 1); } protected: @@ -117,14 +119,14 @@ class LiveMpdBuilderTest : public MpdBuilderTest { // Injects a clock that always returns 2016 Jan 11 15:10:24 in UTC. void InjectTestClock() { - base::Time::Exploded test_time = { 2016, // year. - 1, // month - 1, // day_of_week = Monday. - 11, // day_of_month - 15, // hour. - 10, // minute. - 24, // second. - 0 }; // millisecond. + base::Time::Exploded test_time = {2016, // year. + 1, // month + 1, // day_of_week = Monday. + 11, // day_of_month + 15, // hour. + 10, // minute. + 24, // second. + 0}; // millisecond. ASSERT_TRUE(test_time.HasValidValues()); mpd_.InjectClockForTesting(std::unique_ptr( new TestClock(base::Time::FromUTCExploded(test_time)))); diff --git a/packager/mpd/base/mpd_notifier.h b/packager/mpd/base/mpd_notifier.h index 6576b7c625d..ad954056034 100644 --- a/packager/mpd/base/mpd_notifier.h +++ b/packager/mpd/base/mpd_notifier.h @@ -65,11 +65,13 @@ class MpdNotifier { /// @param duration is the duration of the new segment, in units of the /// stream's time scale. /// @param size is the new segment size in bytes. + /// @param segment_index is the segment index. /// @return true on success, false otherwise. virtual bool NotifyNewSegment(uint32_t container_id, uint64_t start_time, uint64_t duration, - uint64_t size) = 0; + uint64_t size, + uint64_t segment_index) = 0; /// Notifies MpdBuilder that there is a new CueEvent. /// @param container_id Container ID obtained from calling @@ -105,7 +107,9 @@ class MpdNotifier { virtual bool Flush() = 0; /// @return include_mspr_pro option flag - bool include_mspr_pro() const { return mpd_options_.mpd_params.include_mspr_pro; } + bool include_mspr_pro() const { + return mpd_options_.mpd_params.include_mspr_pro; + } /// @return The dash profile for this object. DashProfile dash_profile() const { return mpd_options_.dash_profile; } diff --git a/packager/mpd/base/representation.cc b/packager/mpd/base/representation.cc index b1b46c8c2e0..04442193cea 100644 --- a/packager/mpd/base/representation.cc +++ b/packager/mpd/base/representation.cc @@ -170,7 +170,8 @@ void Representation::UpdateContentProtectionPssh(const std::string& drm_uuid, void Representation::AddNewSegment(int64_t start_time, int64_t duration, - uint64_t size) { + uint64_t size, + uint64_t segment_index) { if (start_time == 0 && duration == 0) { LOG(WARNING) << "Got segment with start_time and duration == 0. Ignoring."; return; @@ -186,7 +187,7 @@ void Representation::AddNewSegment(int64_t start_time, if (state_change_listener_) state_change_listener_->OnNewSegmentForRepresentation(start_time, duration); - AddSegmentInfo(start_time, duration); + AddSegmentInfo(start_time, duration, segment_index); current_buffer_depth_ += segment_infos_.back().duration; bandwidth_estimator_.AddBlock( @@ -328,10 +329,20 @@ bool Representation::HasRequiredMediaInfoFields() const { return true; } -void Representation::AddSegmentInfo(int64_t start_time, int64_t duration) { +void Representation::AddSegmentInfo(int64_t start_time, + int64_t duration, + uint64_t segment_index) { const uint64_t kNoRepeat = 0; const int64_t adjusted_duration = AdjustDuration(duration); + if (segment_infos_.empty()) { + if (mpd_options_.mpd_params.target_segment_duration > 0 && + mpd_options_.mpd_params.allow_approximate_segment_timeline) { + start_number_ = segment_index; + stream_just_started_ = true; + } + } + if (!segment_infos_.empty()) { // Contiguous segment. const SegmentInfo& previous = segment_infos_.back(); @@ -348,6 +359,19 @@ void Representation::AddSegmentInfo(int64_t start_time, int64_t duration) { if (ApproximiatelyEqual(segment_end_time_for_same_duration, actual_segment_end_time)) { ++segment_infos_.back().repeat; + if (mpd_options_.mpd_params.allow_approximate_segment_timeline && + mpd_options_.mpd_params.target_segment_duration > 0 && + stream_just_started_ && segment_infos_.size() == 2) { + const SegmentInfo& first_segment = segment_infos_.front(); + const SegmentInfo& last_segment = segment_infos_.back(); + if (first_segment.repeat == 0 && + first_segment.duration < last_segment.duration && + last_segment.repeat > 0) { + segment_infos_.pop_front(); + start_number_ = last_segment.start_time / last_segment.duration + 1; + } + stream_just_started_ = false; + } } else { segment_infos_.push_back( {previous_segment_end_time, diff --git a/packager/mpd/base/representation.h b/packager/mpd/base/representation.h index 813d43edc5f..f0621ea4f68 100644 --- a/packager/mpd/base/representation.h +++ b/packager/mpd/base/representation.h @@ -9,16 +9,16 @@ #ifndef PACKAGER_MPD_BASE_REPRESENTATION_H_ #define PACKAGER_MPD_BASE_REPRESENTATION_H_ -#include "packager/mpd/base/bandwidth_estimator.h" -#include "packager/mpd/base/media_info.pb.h" -#include "packager/mpd/base/segment_info.h" -#include "packager/mpd/base/xml/scoped_xml_ptr.h" - #include #include #include +#include "packager/mpd/base/bandwidth_estimator.h" +#include "packager/mpd/base/media_info.pb.h" +#include "packager/mpd/base/segment_info.h" +#include "packager/mpd/base/xml/scoped_xml_ptr.h" + namespace shaka { struct ContentProtectionElement; @@ -100,10 +100,12 @@ class Representation { /// stream's time scale. /// @param duration is the duration of the segment, in units of the stream's /// time scale. + /// @param segment_index is the current segment index. /// @param size of the segment in bytes. virtual void AddNewSegment(int64_t start_time, int64_t duration, - uint64_t size); + uint64_t size, + uint64_t segment_index); /// Set the sample duration of this Representation. /// Sample duration is not available right away especially for live. This @@ -182,7 +184,9 @@ class Representation { // Add a SegmentInfo. This function may insert an adjusted SegmentInfo if // |allow_approximate_segment_timeline_| is set. - void AddSegmentInfo(int64_t start_time, int64_t duration); + void AddSegmentInfo(int64_t start_time, + int64_t duration, + uint64_t segment_index); // Check if two timestamps are approximately equal if // |allow_approximate_segment_timeline_| is set; Otherwise check whether the @@ -231,6 +235,8 @@ class Representation { // Starts from 1. uint32_t start_number_ = 1; + bool stream_just_started_ = false; + // If this is not null, then Representation is responsible for calling the // right methods at right timings. std::unique_ptr state_change_listener_; diff --git a/packager/mpd/base/representation_unittest.cc b/packager/mpd/base/representation_unittest.cc index c9a9f97684b..6a73062e522 100644 --- a/packager/mpd/base/representation_unittest.cc +++ b/packager/mpd/base/representation_unittest.cc @@ -280,7 +280,8 @@ TEST_F(RepresentationTest, kAnyRepresentationId, std::move(listener)); EXPECT_TRUE(representation->Init()); - representation->AddNewSegment(kStartTime, kDuration, 10 /* any size */); + representation->AddNewSegment(kStartTime, kDuration, 10 /* any size */, + (kStartTime / kDuration) + 1); } // Make sure @@ -468,7 +469,8 @@ class SegmentTemplateTest : public RepresentationTest { } for (int i = 0; i < repeat + 1; ++i) { - representation_->AddNewSegment(start_time, duration, size); + representation_->AddNewSegment(start_time, duration, size, + (start_time / duration) + 1); start_time += duration; bandwidth_estimator_.AddBlock( size, static_cast(duration) / kDefaultTimeScale); @@ -799,12 +801,16 @@ TEST_P(ApproximateSegmentTimelineTest, if (allow_approximate_segment_timeline_) { expected_s_elements = base::StringPrintf( kSElementTemplateWithoutR, kStartTime, kScaledTargetSegmentDuration); + EXPECT_THAT(representation_->GetXml().get(), + XmlNodeEqual(SegmentTimelineTestBase::ExpectedXml( + expected_s_elements, 1372))); } else { expected_s_elements = base::StringPrintf(kSElementTemplateWithoutR, kStartTime, kDurationSmaller); + EXPECT_THAT(representation_->GetXml().get(), + XmlNodeEqual(SegmentTimelineTestBase::ExpectedXml( + expected_s_elements, 1))); } - EXPECT_THAT(representation_->GetXml().get(), - XmlNodeEqual(ExpectedXml(expected_s_elements))); } TEST_P(ApproximateSegmentTimelineTest, SegmentsWithSimilarDurations) { diff --git a/packager/mpd/base/simple_mpd_notifier.cc b/packager/mpd/base/simple_mpd_notifier.cc index 9f93a4a642f..34d9c7f5895 100644 --- a/packager/mpd/base/simple_mpd_notifier.cc +++ b/packager/mpd/base/simple_mpd_notifier.cc @@ -86,14 +86,15 @@ bool SimpleMpdNotifier::NotifySampleDuration(uint32_t container_id, bool SimpleMpdNotifier::NotifyNewSegment(uint32_t container_id, uint64_t start_time, uint64_t duration, - uint64_t size) { + uint64_t size, + uint64_t segment_index) { 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; } - it->second->AddNewSegment(start_time, duration, size); + it->second->AddNewSegment(start_time, duration, size, segment_index); return true; } diff --git a/packager/mpd/base/simple_mpd_notifier.h b/packager/mpd/base/simple_mpd_notifier.h index 749da46a1cf..b90e105d63e 100644 --- a/packager/mpd/base/simple_mpd_notifier.h +++ b/packager/mpd/base/simple_mpd_notifier.h @@ -41,7 +41,8 @@ class SimpleMpdNotifier : public MpdNotifier { bool NotifyNewSegment(uint32_t container_id, uint64_t start_time, uint64_t duration, - uint64_t size) override; + uint64_t size, + uint64_t segment_index) override; bool NotifyCueEvent(uint32_t container_id, uint64_t timestamp) override; bool NotifyEncryptionUpdate(uint32_t container_id, const std::string& drm_uuid, diff --git a/packager/mpd/base/simple_mpd_notifier_unittest.cc b/packager/mpd/base/simple_mpd_notifier_unittest.cc index f9d34928676..5032ee89d40 100644 --- a/packager/mpd/base/simple_mpd_notifier_unittest.cc +++ b/packager/mpd/base/simple_mpd_notifier_unittest.cc @@ -4,6 +4,8 @@ // license that can be found in the LICENSE file or at // https://developers.google.com/open-source/licenses/bsd +#include "packager/mpd/base/simple_mpd_notifier.h" + #include #include #include @@ -13,7 +15,6 @@ #include "packager/mpd/base/mock_mpd_builder.h" #include "packager/mpd/base/mpd_builder.h" #include "packager/mpd/base/mpd_options.h" -#include "packager/mpd/base/simple_mpd_notifier.h" #include "packager/mpd/test/mpd_builder_test_helper.h" namespace shaka { @@ -189,10 +190,10 @@ TEST_F(SimpleMpdNotifierTest, NotifyNewSegment) { const uint32_t kSegmentDuration = 100u; const uint64_t kSegmentSize = 123456u; EXPECT_CALL(*mock_representation, - AddNewSegment(kStartTime, kSegmentDuration, kSegmentSize)); + AddNewSegment(kStartTime, kSegmentDuration, kSegmentSize, 1)); EXPECT_TRUE(notifier.NotifyNewSegment(kRepresentationId, kStartTime, - kSegmentDuration, kSegmentSize)); + kSegmentDuration, kSegmentSize, 1)); } TEST_F(SimpleMpdNotifierTest, NotifyCueEvent) { diff --git a/packager/mpd/base/xml/xml_node.cc b/packager/mpd/base/xml/xml_node.cc index a231b3683fc..7bb314eec74 100644 --- a/packager/mpd/base/xml/xml_node.cc +++ b/packager/mpd/base/xml/xml_node.cc @@ -56,7 +56,7 @@ bool IsTimelineConstantDuration(const std::list& segment_infos, return false; const SegmentInfo& first_segment = segment_infos.front(); - if (first_segment.start_time != first_segment.duration * (start_number - 1)) + if (first_segment.start_time / first_segment.duration != (start_number - 1)) return false; if (segment_infos.size() == 1) @@ -179,9 +179,8 @@ void XmlNode::SetStringAttribute(const char* attribute_name, void XmlNode::SetIntegerAttribute(const char* attribute_name, uint64_t number) { DCHECK(node_); DCHECK(attribute_name); - xmlSetProp(node_.get(), - BAD_CAST attribute_name, - BAD_CAST (base::Uint64ToString(number).c_str())); + xmlSetProp(node_.get(), BAD_CAST attribute_name, + BAD_CAST(base::Uint64ToString(number).c_str())); } void XmlNode::SetFloatingPointAttribute(const char* attribute_name, @@ -437,12 +436,12 @@ bool RepresentationXmlNode::AddLiveOnlyInfo( segment_infos.front().duration); if (FLAGS_dash_add_last_segment_number_when_needed) { uint32_t last_segment_number = start_number - 1; - for (const auto& segment_info_element : segment_infos) + for (const auto& segment_info_element : segment_infos) last_segment_number += segment_info_element.repeat + 1; - + AddSupplementalProperty( - "http://dashif.org/guidelines/last-segment-number", - std::to_string(last_segment_number)); + "http://dashif.org/guidelines/last-segment-number", + std::to_string(last_segment_number)); } } else { XmlNode segment_timeline("SegmentTimeline"); diff --git a/packager/mpd/base/xml/xml_node_unittest.cc b/packager/mpd/base/xml/xml_node_unittest.cc index 1840765c703..870db0cbbab 100644 --- a/packager/mpd/base/xml/xml_node_unittest.cc +++ b/packager/mpd/base/xml/xml_node_unittest.cc @@ -4,6 +4,8 @@ // license that can be found in the LICENSE file or at // https://developers.google.com/open-source/licenses/bsd +#include "packager/mpd/base/xml/xml_node.h" + #include #include #include @@ -14,7 +16,6 @@ #include "packager/base/logging.h" #include "packager/base/strings/string_util.h" #include "packager/mpd/base/segment_info.h" -#include "packager/mpd/base/xml/xml_node.h" #include "packager/mpd/test/xml_compare.h" DECLARE_bool(segment_template_constant_duration); @@ -57,7 +58,6 @@ TEST(XmlNodeTest, MetaTestXmlElementsEqual) { " \n" ""; - // This is same as kXml1 but the attributes are reordered. Note that the // children are not reordered. static const char kXml1AttributeReorder[] = @@ -134,9 +134,8 @@ TEST(XmlNodeTest, MetaTestXmlElementsEqual) { // But if it is run on for the first XML, it will return "content1", but // for second XML will return "c". TEST(XmlNodeTest, MetaTestXmlEqualDifferentContent) { - ASSERT_FALSE(XmlEqual( - "content1content2", - "content1content2")); + ASSERT_FALSE(XmlEqual("content1content2", + "content1content2")); } TEST(XmlNodeTest, ExtractReferencedNamespaces) { @@ -449,31 +448,31 @@ TEST_F(LiveSegmentTimelineTest, TwoSegmentInfoWithGap) { "")); } -TEST_F(LiveSegmentTimelineTest, LastSegmentNumberSupplementalProperty) { - const uint32_t kStartNumber = 1; - const uint64_t kStartTime = 0; - const uint64_t kDuration = 100; - const uint64_t kRepeat = 9; - - std::list segment_infos = { - {kStartTime, kDuration, kRepeat}, - }; - RepresentationXmlNode representation; - FLAGS_dash_add_last_segment_number_when_needed = true; - - ASSERT_TRUE( +TEST_F(LiveSegmentTimelineTest, LastSegmentNumberSupplementalProperty) { + const uint32_t kStartNumber = 1; + const uint64_t kStartTime = 0; + const uint64_t kDuration = 100; + const uint64_t kRepeat = 9; + + std::list segment_infos = { + {kStartTime, kDuration, kRepeat}, + }; + RepresentationXmlNode representation; + FLAGS_dash_add_last_segment_number_when_needed = true; + + ASSERT_TRUE( representation.AddLiveOnlyInfo(media_info_, segment_infos, kStartNumber)); - - EXPECT_THAT( - representation.GetRawPtr(), - XmlNodeEqual("" - "" - " " - "")); - FLAGS_dash_add_last_segment_number_when_needed = false; -} + + EXPECT_THAT( + representation.GetRawPtr(), + XmlNodeEqual("" + "" + " " + "")); + FLAGS_dash_add_last_segment_number_when_needed = false; +} } // namespace xml } // namespace shaka