diff --git a/packager/media/event/combined_muxer_listener.cc b/packager/media/event/combined_muxer_listener.cc index 7e1bc445a4..a52bde4702 100644 --- a/packager/media/event/combined_muxer_listener.cc +++ b/packager/media/event/combined_muxer_listener.cc @@ -77,6 +77,13 @@ void CombinedMuxerListener::OnNewSegment(const std::string& file_name, } } +void CombinedMuxerListener::OnCompletedSegment(int64_t duration, + uint64_t segment_file_size) { + for (auto& listener : muxer_listeners_) { + listener->OnCompletedSegment(duration, segment_file_size); + } +} + void CombinedMuxerListener::OnKeyFrame(int64_t timestamp, uint64_t start_byte_offset, uint64_t size) { diff --git a/packager/media/event/combined_muxer_listener.h b/packager/media/event/combined_muxer_listener.h index ef66772bfd..3d6fb91adb 100644 --- a/packager/media/event/combined_muxer_listener.h +++ b/packager/media/event/combined_muxer_listener.h @@ -45,6 +45,8 @@ class CombinedMuxerListener : public MuxerListener { int64_t start_time, int64_t duration, uint64_t segment_file_size) override; + void OnCompletedSegment(int64_t duration, + uint64_t segment_file_size) 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.cc b/packager/media/event/mpd_notify_muxer_listener.cc index d16d7b1966..4e96a5df0b 100644 --- a/packager/media/event/mpd_notify_muxer_listener.cc +++ b/packager/media/event/mpd_notify_muxer_listener.cc @@ -204,6 +204,12 @@ void MpdNotifyMuxerListener::OnNewSegment(const std::string& file_name, } } +void MpdNotifyMuxerListener::OnCompletedSegment(int64_t duration, + uint64_t segment_file_size) { + mpd_notifier_->NotifyCompletedSegment(notification_id_.value(), duration, + segment_file_size); +} + void MpdNotifyMuxerListener::OnKeyFrame(int64_t timestamp, uint64_t start_byte_offset, uint64_t size) { diff --git a/packager/media/event/mpd_notify_muxer_listener.h b/packager/media/event/mpd_notify_muxer_listener.h index 271e7f3e25..8b62c4402d 100644 --- a/packager/media/event/mpd_notify_muxer_listener.h +++ b/packager/media/event/mpd_notify_muxer_listener.h @@ -53,6 +53,8 @@ class MpdNotifyMuxerListener : public MuxerListener { int64_t start_time, int64_t duration, uint64_t segment_file_size) override; + void OnCompletedSegment(int64_t duration, + uint64_t segment_file_size) 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/muxer_listener.h b/packager/media/event/muxer_listener.h index 1c1ee75a67..f5a9f4d317 100644 --- a/packager/media/event/muxer_listener.h +++ b/packager/media/event/muxer_listener.h @@ -120,9 +120,11 @@ class MuxerListener { float duration_seconds) = 0; /// Called when a segment has been muxed and the file has been written. - /// Note: For some implementations, this is used to signal new subsegments. - /// For example, for generating video on demand (VOD) MPD manifest, this is - /// called to signal subsegments. + /// Note: For some implementations, this is used to signal new subsegments + /// or chunks. For example, for generating video on demand (VOD) MPD manifest, + /// this is called to signal subsegments. In the low latency case, this + /// indicates the start of a new segment and will contain info about the + /// segment's first chunk. /// @param segment_name is the name of the new segment. Note that some /// implementations may not require this, e.g. if this is a subsegment. /// @param start_time is the start time of the segment, relative to the @@ -135,6 +137,15 @@ class MuxerListener { int64_t duration, uint64_t segment_file_size) = 0; + /// Called when a segment has been muxed and the entire file has been written. + /// For Low Latency only. Note that it should be called after OnNewSegment. + /// When the low latency segment is initally added to the manifest, the size + /// and duration are not known, because the segment is still being processed. + /// This will update the segment's duration and size after the segment is + /// fully written and these values are known. + virtual void OnCompletedSegment(int64_t duration, + uint64_t segment_file_size) {} + /// 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. /// @param timestamp is in terms of the timescale of the media. diff --git a/packager/media/formats/mp4/low_latency_segment_segmenter.cc b/packager/media/formats/mp4/low_latency_segment_segmenter.cc index 36574a633b..fa8a8a4aeb 100644 --- a/packager/media/formats/mp4/low_latency_segment_segmenter.cc +++ b/packager/media/formats/mp4/low_latency_segment_segmenter.cc @@ -128,8 +128,8 @@ Status LowLatencySegmentSegmenter::WriteInitialChunk() { styp_->Write(buffer.get()); const size_t segment_header_size = buffer->Size(); - const size_t segment_size = segment_header_size + fragment_buffer()->Size(); - DCHECK_NE(segment_size, 0u); + segment_size_ = segment_header_size + fragment_buffer()->Size(); + DCHECK_NE(segment_size_, 0u); RETURN_IF_ERROR(buffer->WriteToFile(segment_file_.get())); if (muxer_listener()) { @@ -160,7 +160,7 @@ Status LowLatencySegmentSegmenter::WriteInitialChunk() { // Following chunks will be appended to the open segment file. muxer_listener()->OnNewSegment(file_name_, sidx()->earliest_presentation_time, - segment_duration, segment_size); + segment_duration, segment_size_); is_initial_chunk_in_seg_ = false; } @@ -179,6 +179,9 @@ Status LowLatencySegmentSegmenter::WriteChunk() { } Status LowLatencySegmentSegmenter::FinalizeSegment() { + if (muxer_listener()) { + muxer_listener()->OnCompletedSegment(GetSegmentDuration(), segment_size_); + } // Close the file now that the final chunk has been written if (!segment_file_.release()->Close()) { return Status( @@ -190,6 +193,7 @@ Status LowLatencySegmentSegmenter::FinalizeSegment() { // Current segment is complete. Reset state in preparation for the next // segment. is_initial_chunk_in_seg_ = true; + segment_size_ = 0u; num_segments_++; return Status::OK; diff --git a/packager/media/formats/mp4/low_latency_segment_segmenter.h b/packager/media/formats/mp4/low_latency_segment_segmenter.h index a3dc3cd1b7..9aa397c02a 100644 --- a/packager/media/formats/mp4/low_latency_segment_segmenter.h +++ b/packager/media/formats/mp4/low_latency_segment_segmenter.h @@ -61,6 +61,7 @@ class LowLatencySegmentSegmenter : public Segmenter { bool ll_dash_mpd_values_initialized_ = false; std::unique_ptr segment_file_; std::string file_name_; + size_t segment_size_ = 0u; DISALLOW_COPY_AND_ASSIGN(LowLatencySegmentSegmenter); }; diff --git a/packager/mpd/base/mock_mpd_notifier.h b/packager/mpd/base/mock_mpd_notifier.h index 60a4effdaa..1c3d096df3 100644 --- a/packager/mpd/base/mock_mpd_notifier.h +++ b/packager/mpd/base/mock_mpd_notifier.h @@ -31,6 +31,8 @@ class MockMpdNotifier : public MpdNotifier { int64_t start_time, int64_t duration, uint64_t size)); + MOCK_METHOD3(NotifyCompletedSegment, + bool(uint32_t container_id, int64_t duration, uint64_t size)); MOCK_METHOD1(NotifyAvailabilityTimeOffset, bool(uint32_t container_id)); MOCK_METHOD1(NotifySegmentDuration, bool(uint32_t container_id)); MOCK_METHOD2(NotifyCueEvent, bool(uint32_t container_id, int64_t timestamp)); diff --git a/packager/mpd/base/mpd_notifier.h b/packager/mpd/base/mpd_notifier.h index f6e788004e..349c84ea3a 100644 --- a/packager/mpd/base/mpd_notifier.h +++ b/packager/mpd/base/mpd_notifier.h @@ -73,7 +73,8 @@ class MpdNotifier { virtual bool NotifySegmentDuration(uint32_t container_id) { return true; } /// Notifies MpdBuilder that there is a new segment ready. For live, this - /// is usually a new segment, for VOD this is usually a subsegment. + /// is usually a new segment, for VOD this is usually a subsegment, for low + /// latency this is the first chunk. /// @param container_id Container ID obtained from calling /// NotifyNewContainer(). /// @param start_time is the start time of the new segment, in units of the @@ -87,6 +88,23 @@ class MpdNotifier { int64_t duration, uint64_t size) = 0; + /// Notifies MpdBuilder that a segment is fully written and provides the + /// segment's complete duration and size. For Low Latency only. Note, size and + /// duration are not known when the low latency segment is first registered + /// with the MPD, so we must update these values after the segment is + /// complete. + /// @param container_id Container ID obtained from calling + /// NotifyNewContainer(). + /// @param duration is the duration of the complete segment, in units of the + /// stream's time scale. + /// @param size is the complete segment size in bytes. + /// @return true on success, false otherwise. + virtual bool NotifyCompletedSegment(uint32_t container_id, + int64_t duration, + uint64_t size) { + return true; + } + /// Notifies MpdBuilder that there is a new CueEvent. /// @param container_id Container ID obtained from calling /// NotifyNewContainer(). diff --git a/packager/mpd/base/representation.cc b/packager/mpd/base/representation.cc index 7aa5fb39c8..9a08c27f96 100644 --- a/packager/mpd/base/representation.cc +++ b/packager/mpd/base/representation.cc @@ -187,6 +187,29 @@ void Representation::AddNewSegment(int64_t start_time, state_change_listener_->OnNewSegmentForRepresentation(start_time, duration); AddSegmentInfo(start_time, duration); + + // Only update the buffer depth and bandwidth estimator when the full segment + // is completed. In the low latency case, only the first chunk in the segment + // has been written at this point. Therefore, we must wait until the entire + // segment has been written before updating buffer depth and bandwidth + // estimator. + if (!mpd_options_.mpd_params.low_latency_dash_mode) { + current_buffer_depth_ += segment_infos_.back().duration; + + bandwidth_estimator_.AddBlock(size, static_cast(duration) / + media_info_.reference_time_scale()); + } +} + +void Representation::UpdateCompletedSegment(int64_t duration, uint64_t size) { + if (!mpd_options_.mpd_params.low_latency_dash_mode) { + LOG(WARNING) + << "UpdateCompletedSegment is only applicable to low latency mode."; + return; + } + + UpdateSegmentInfo(duration); + current_buffer_depth_ += segment_infos_.back().duration; bandwidth_estimator_.AddBlock( @@ -410,6 +433,13 @@ void Representation::AddSegmentInfo(int64_t start_time, int64_t duration) { segment_infos_.push_back({start_time, adjusted_duration, kNoRepeat}); } +void Representation::UpdateSegmentInfo(int64_t duration) { + if (!segment_infos_.empty()) { + // Update the duration in the current segment. + segment_infos_.back().duration = duration; + } +} + bool Representation::ApproximiatelyEqual(int64_t time1, int64_t time2) const { if (!allow_approximate_segment_timeline_) return time1 == time2; diff --git a/packager/mpd/base/representation.h b/packager/mpd/base/representation.h index 97c9e1d499..a1081b2b6f 100644 --- a/packager/mpd/base/representation.h +++ b/packager/mpd/base/representation.h @@ -95,12 +95,25 @@ class Representation { /// @param start_time is the start time for the (sub)segment, in units of the /// stream's time scale. /// @param duration is the duration of the segment, in units of the stream's - /// time scale. - /// @param size of the segment in bytes. + /// time scale. In the low latency case, this duration is that of the + /// first chunk because the full duration is not yet known. + /// @param size of the segment in bytes. In the low latency case, this size is + /// that of the + /// first chunk because the full size is not yet known. virtual void AddNewSegment(int64_t start_time, int64_t duration, uint64_t size); + /// Update a media segment in the Representation. + /// In the low latency case, the segment duration will not be ready until the + /// entire segment has been processed. This allows setting the full duration + /// after the segment has been completed and the true duratio is known. + /// @param duration is the duration of the complete segment, in units of the + /// stream's + /// time scale. + /// @param size of the complete segment in bytes. + virtual void UpdateCompletedSegment(int64_t duration, uint64_t size); + /// Set the sample duration of this Representation. /// Sample duration is not available right away especially for live. This /// allows setting the sample duration after the Representation has been @@ -188,6 +201,11 @@ class Representation { // |allow_approximate_segment_timeline_| is set. void AddSegmentInfo(int64_t start_time, int64_t duration); + // Update the current SegmentInfo. This method is used to update the duration + // value after a low latency segment is complete, and the full segment + // duration is known. + void UpdateSegmentInfo(int64_t duration); + // Check if two timestamps are approximately equal if // |allow_approximate_segment_timeline_| is set; Otherwise check whether the // two times match. diff --git a/packager/mpd/base/representation_unittest.cc b/packager/mpd/base/representation_unittest.cc index 76cc73a5ab..18c28920f8 100644 --- a/packager/mpd/base/representation_unittest.cc +++ b/packager/mpd/base/representation_unittest.cc @@ -444,6 +444,8 @@ class SegmentTemplateTest : public RepresentationTest { public: void SetUp() override { mpd_options_.mpd_type = MpdType::kDynamic; + mpd_options_.mpd_params.low_latency_dash_mode = false; + representation_ = CreateRepresentation(ConvertToMediaInfo(GetDefaultMediaInfo()), kAnyRepresentationId, NoListener()); @@ -458,6 +460,16 @@ class SegmentTemplateTest : public RepresentationTest { SegmentInfo s = {start_time, duration, repeat}; segment_infos_for_expected_out_.push_back(s); + + if (mpd_options_.mpd_params.low_latency_dash_mode) { + // Low latency segments do not repeat, so create 1 new segment and return. + // At this point, only the first chunk of the low latency segment has been + // written. The bandwidth will be updated once the segment is fully + // written and the segment duration and size are known. + representation_->AddNewSegment(start_time, duration, size); + return; + } + if (repeat == 0) { expected_s_elements_ += base::StringPrintf(kSElementTemplateWithoutR, start_time, duration); @@ -474,6 +486,16 @@ class SegmentTemplateTest : public RepresentationTest { } } + void UpdateSegment(int64_t duration, uint64_t size) { + DCHECK(representation_); + DCHECK(!segment_infos_for_expected_out_.empty()); + + segment_infos_for_expected_out_.back().duration = duration; + representation_->UpdateCompletedSegment(duration, size); + bandwidth_estimator_.AddBlock( + size, static_cast(duration) / kDefaultTimeScale); + } + protected: std::string ExpectedXml() { const char kOutputTemplate[] = @@ -510,6 +532,39 @@ TEST_F(SegmentTemplateTest, OneSegmentNormal) { EXPECT_THAT(representation_->GetXml(), XmlNodeEqual(ExpectedXml())); } +TEST_F(SegmentTemplateTest, OneSegmentLowLatency) { + const int64_t kStartTime = 0; + const int64_t kChunkDuration = 5; + const uint64_t kChunkSize = 128; + const int64_t kSegmentDuration = kChunkDuration * 1000; + const uint64_t kSegmentSize = kChunkSize * 1000; + + mpd_options_.mpd_params.low_latency_dash_mode = true; + mpd_options_.mpd_params.target_segment_duration = + kSegmentDuration / representation_->GetMediaInfo().reference_time_scale(); + + // Set values used in LL-DASH MPD attributes + representation_->SetSampleDuration(kChunkDuration); + representation_->SetAvailabilityTimeOffset(); + representation_->SetSegmentDuration(); + + // Register segment after the first chunk is complete + AddSegments(kStartTime, kChunkDuration, kChunkSize, 0); + // Update SegmentInfo after the segment is complete + UpdateSegment(kSegmentDuration, kSegmentSize); + + const char kOutputTemplate[] = + "\n" + " \n" + "\n"; + EXPECT_THAT(representation_->GetXml(), XmlNodeEqual(kOutputTemplate)); +} + TEST_F(SegmentTemplateTest, RepresentationClone) { MediaInfo media_info = ConvertToMediaInfo(GetDefaultMediaInfo()); media_info.set_segment_template_url("$Number$.mp4"); diff --git a/packager/mpd/base/simple_mpd_notifier.cc b/packager/mpd/base/simple_mpd_notifier.cc index 1dbc76f573..76f0384270 100644 --- a/packager/mpd/base/simple_mpd_notifier.cc +++ b/packager/mpd/base/simple_mpd_notifier.cc @@ -119,6 +119,19 @@ bool SimpleMpdNotifier::NotifyNewSegment(uint32_t container_id, return true; } +bool SimpleMpdNotifier::NotifyCompletedSegment(uint32_t container_id, + int64_t duration, + uint64_t size) { + 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->UpdateCompletedSegment(duration, size); + return true; +} + bool SimpleMpdNotifier::NotifyCueEvent(uint32_t container_id, int64_t timestamp) { base::AutoLock auto_lock(lock_); diff --git a/packager/mpd/base/simple_mpd_notifier.h b/packager/mpd/base/simple_mpd_notifier.h index 783605c433..9da46a221a 100644 --- a/packager/mpd/base/simple_mpd_notifier.h +++ b/packager/mpd/base/simple_mpd_notifier.h @@ -44,6 +44,9 @@ class SimpleMpdNotifier : public MpdNotifier { int64_t start_time, int64_t duration, uint64_t size) override; + bool NotifyCompletedSegment(uint32_t container_id, + int64_t duration, + uint64_t size) override; bool NotifyCueEvent(uint32_t container_id, int64_t timestamp) override; bool NotifyEncryptionUpdate(uint32_t container_id, const std::string& drm_uuid,