diff --git a/packager/mpd/base/mpd_builder.cc b/packager/mpd/base/mpd_builder.cc index 522c9ef84d..d28e591754 100644 --- a/packager/mpd/base/mpd_builder.cc +++ b/packager/mpd/base/mpd_builder.cc @@ -347,6 +347,12 @@ class RepresentationStateChangeListenerImpl start_time, duration); } + virtual void OnSetFrameRateForRepresentation(uint32_t frame_duration, + uint32_t timescale) OVERRIDE { + adaptation_set_->OnSetFrameRateForRepresentation(representation_id_, + frame_duration, timescale); + } + private: const uint32_t representation_id_; AdaptationSet* const adaptation_set_; @@ -653,12 +659,8 @@ Representation* AdaptationSet::AddRepresentation(const MediaInfo& media_info) { video_widths_.insert(video_info.width()); video_heights_.insert(video_info.height()); - if (video_info.has_time_scale() && video_info.has_frame_duration()) { - video_frame_rates_[static_cast(video_info.time_scale()) / - video_info.frame_duration()] = - base::IntToString(video_info.time_scale()) + "/" + - base::IntToString(video_info.frame_duration()); - } + if (video_info.has_time_scale() && video_info.has_frame_duration()) + RecordFrameRate(video_info.frame_duration(), video_info.time_scale()); AddPictureAspectRatio(video_info, &picture_aspect_ratio_); } @@ -772,6 +774,14 @@ void AdaptationSet::OnNewSegmentForRepresentation(uint32_t representation_id, CheckSegmentAlignment(representation_id, start_time, duration); } +void AdaptationSet::OnSetFrameRateForRepresentation( + uint32_t /* representation_id */, + uint32_t frame_duration, + uint32_t timescale) { + base::AutoLock scoped_lock(lock_); + RecordFrameRate(frame_duration, timescale); +} + bool AdaptationSet::GetEarliestTimestamp(double* timestamp_seconds) { DCHECK(timestamp_seconds); @@ -864,6 +874,18 @@ void AdaptationSet::CheckSegmentAlignment(uint32_t representation_id, } } +// Since all AdaptationSet cares about is the maxFrameRate, representation_id +// is not passed to this method. +void AdaptationSet::RecordFrameRate(uint32_t frame_duration, + uint32_t timescale) { + if (frame_duration == 0) { + LOG(ERROR) << "Frame duration is 0 and cannot be set."; + return; + } + video_frame_rates_[static_cast(timescale) / frame_duration] = + base::IntToString(timescale) + "/" + base::IntToString(frame_duration); +} + Representation::Representation( const MediaInfo& media_info, const MpdOptions& mpd_options, @@ -949,9 +971,15 @@ void Representation::AddNewSegment(uint64_t start_time, } void Representation::SetSampleDuration(uint32_t sample_duration) { - // Assume single video info. - if (media_info_.has_video_info()) + base::AutoLock scoped_lock(lock_); + + if (media_info_.has_video_info()) { media_info_.mutable_video_info()->set_frame_duration(sample_duration); + if (state_change_listener_) { + state_change_listener_->OnSetFrameRateForRepresentation( + sample_duration, media_info_.video_info().time_scale()); + } + } } // Uses info in |media_info_| and |content_protection_elements_| to create a diff --git a/packager/mpd/base/mpd_builder.h b/packager/mpd/base/mpd_builder.h index 17ed80853c..359d83711f 100644 --- a/packager/mpd/base/mpd_builder.h +++ b/packager/mpd/base/mpd_builder.h @@ -237,6 +237,22 @@ class AdaptationSet { uint64_t start_time, uint64_t duration); + /// Notifies the AdaptationSet instance that the sample duration for the + /// Representation was set. + /// The frame duration for a video Representation might not be specified when + /// a Representation is created (by calling AddRepresentation()). + /// This should be used to notify this instance that the frame rate for a + /// Represenatation has been set. + /// This method is called automatically when + /// Represenatation::SetSampleDuration() is called if the Represenatation + /// instance was created using AddRepresentation(). + /// @param representation_id is the id of the Representation. + /// @frame_duration is the duration of a frame in the Representation. + /// @param timescale is the timescale of the Representation. + void OnSetFrameRateForRepresentation(uint32_t representation_id, + uint32_t frame_duration, + uint32_t timescale); + protected: /// @param adaptation_set_id is an ID number for this AdaptationSet. /// @param lang is the language of this AdaptationSet. Mainly relevant for @@ -286,6 +302,9 @@ class AdaptationSet { FRIEND_TEST_ALL_PREFIXES(StaticMpdBuilderTest, SubSegmentAlignment); FRIEND_TEST_ALL_PREFIXES(StaticMpdBuilderTest, ForceSetSubSegmentAlignment); FRIEND_TEST_ALL_PREFIXES(DynamicMpdBuilderTest, SegmentAlignment); + FRIEND_TEST_ALL_PREFIXES( + CommonMpdBuilderTest, + SetAdaptationFrameRateUsingRepresentationSetSampleDuration); // Gets the earliest, normalized segment timestamp. Returns true if // successful, false otherwise. @@ -301,6 +320,9 @@ class AdaptationSet { uint64_t start_time, uint64_t duration); + // Records the framerate of a Representation. + void RecordFrameRate(uint32_t frame_duration, uint32_t timescale); + std::list content_protection_elements_; std::list representations_; ::STLElementDeleter > representations_deleter_; @@ -360,8 +382,6 @@ class AdaptationSet { DISALLOW_COPY_AND_ASSIGN(AdaptationSet); }; -// TODO(rkuroiwa): OnSetSampleDuration() must also be added to this to notify -// sample duration change to AdaptationSet, to set the right frame rate. class RepresentationStateChangeListener { public: RepresentationStateChangeListener() {} @@ -373,6 +393,13 @@ class RepresentationStateChangeListener { /// @param duration is the duration of the new segment. virtual void OnNewSegmentForRepresentation(uint64_t start_time, uint64_t duration) = 0; + + /// Notifies the instance that the frame rate was set for the + /// Representation. + /// @param frame_duration is the duration of a frame. + /// @param timescale is the timescale of the Representation. + virtual void OnSetFrameRateForRepresentation(uint32_t frame_duration, + uint32_t timescale) = 0; }; /// Representation class contains references to a single media stream, as @@ -412,7 +439,7 @@ class Representation { uint64_t size); /// Set the sample duration of this Representation. - /// In most cases, the sample duration is not available right away. This + /// Sample duration is not available right away especially for live. This /// allows setting the sample duration after the Representation has been /// initialized. /// @param sample_duration is the duration of a sample. @@ -449,8 +476,12 @@ class Representation { FRIEND_TEST_ALL_PREFIXES(CommonMpdBuilderTest, CheckVideoInfoReflectedInXml); FRIEND_TEST_ALL_PREFIXES(CommonMpdBuilderTest, CheckRepresentationId); FRIEND_TEST_ALL_PREFIXES(CommonMpdBuilderTest, SetSampleDuration); - FRIEND_TEST_ALL_PREFIXES(CommonMpdBuilderTest, - RepresentationStateChangeListener); + FRIEND_TEST_ALL_PREFIXES( + CommonMpdBuilderTest, + RepresentationStateChangeListenerOnNewSegmentForRepresentation); + FRIEND_TEST_ALL_PREFIXES( + CommonMpdBuilderTest, + RepresentationStateChangeListenerOnSetFrameRateForRepresentation); bool AddLiveInfo(xml::RepresentationXmlNode* representation); diff --git a/packager/mpd/base/mpd_builder_unittest.cc b/packager/mpd/base/mpd_builder_unittest.cc index 99b9c27b69..9624e346c9 100644 --- a/packager/mpd/base/mpd_builder_unittest.cc +++ b/packager/mpd/base/mpd_builder_unittest.cc @@ -87,6 +87,9 @@ class MockRepresentationStateChangeListener MOCK_METHOD2(OnNewSegmentForRepresentation, void(uint64_t start_time, uint64_t duration)); + + MOCK_METHOD2(OnSetFrameRateForRepresentation, + void(uint32_t frame_duration, uint32_t timescale)); }; } // namespace @@ -435,7 +438,8 @@ TEST_F(CommonMpdBuilderTest, CheckVideoInfoReflectedInXml) { // Make sure RepresentationStateChangeListener::OnNewSegmentForRepresentation() // is called. -TEST_F(CommonMpdBuilderTest, RepresentationStateChangeListener) { +TEST_F(CommonMpdBuilderTest, + RepresentationStateChangeListenerOnNewSegmentForRepresentation) { const char kTestMediaInfo[] = "video_info {\n" " codec: 'avc1'\n" @@ -462,6 +466,37 @@ TEST_F(CommonMpdBuilderTest, RepresentationStateChangeListener) { representation.AddNewSegment(kStartTime, kDuration, 10 /* any size */); } +// Make sure +// RepresentationStateChangeListener::OnSetFrameRateForRepresentation() +// is called. +TEST_F(CommonMpdBuilderTest, + RepresentationStateChangeListenerOnSetFrameRateForRepresentation) { + const char kTestMediaInfo[] = + "video_info {\n" + " codec: 'avc1'\n" + " width: 720\n" + " height: 480\n" + " time_scale: 1000\n" + " frame_duration: 10\n" + " pixel_width: 1\n" + " pixel_height: 1\n" + "}\n" + "container_type: 1\n"; + + const uint64_t kTimeScale = 1000u; + const uint64_t kFrameDuration = 33u; + scoped_ptr listener( + new MockRepresentationStateChangeListener()); + EXPECT_CALL(*listener, + OnSetFrameRateForRepresentation(kFrameDuration, kTimeScale)); + Representation representation( + ConvertToMediaInfo(kTestMediaInfo), MpdOptions(), kAnyRepresentationId, + listener.PassAs()); + EXPECT_TRUE(representation.Init()); + + representation.SetSampleDuration(kFrameDuration); +} + // Verify that content type is set correctly if video info is present in // MediaInfo. TEST_F(CommonMpdBuilderTest, CheckAdaptationSetVideoContentType) { @@ -643,6 +678,77 @@ TEST_F(CommonMpdBuilderTest, AdapatationSetMaxFrameRate) { ExpectAttributeNotSet("frameRate", adaptation_set_xml.get())); } +// Verify that (max)FrameRate can be set by calling +// Representation::SetSampleDuration(). +TEST_F(CommonMpdBuilderTest, + SetAdaptationFrameRateUsingRepresentationSetSampleDuration) { + base::AtomicSequenceNumber sequence_counter; + // Note that frame duration is not set in the MediaInfos. It could be there + // and should not affect the behavior of the program. + // But to make it closer to a real live-profile use case, + // the frame duration is not set in the MediaInfo, instead it is set using + // SetSampleDuration(). + const char k480pMediaInfo[] = + "video_info {\n" + " codec: 'avc1'\n" + " width: 720\n" + " height: 480\n" + " time_scale: 10\n" + " pixel_width: 8\n" + " pixel_height: 9\n" + "}\n" + "container_type: 1\n"; + const char k360pMediaInfo[] = + "video_info {\n" + " codec: 'avc1'\n" + " width: 640\n" + " height: 360\n" + " time_scale: 10\n" + " pixel_width: 1\n" + " pixel_height: 1\n" + "}\n" + "container_type: 1\n"; + + AdaptationSet adaptation_set(kAnyAdaptationSetId, "", MpdOptions(), + MpdBuilder::kStatic, &sequence_counter); + Representation* representation_480p = + adaptation_set.AddRepresentation(ConvertToMediaInfo(k480pMediaInfo)); + Representation* representation_360p = + adaptation_set.AddRepresentation(ConvertToMediaInfo(k360pMediaInfo)); + + // First, make sure that maxFrameRate nor frameRate are set because + // frame durations were not provided in the MediaInfo. + xml::ScopedXmlPtr::type no_frame_rate(adaptation_set.GetXml()); + EXPECT_NO_FATAL_FAILURE( + ExpectAttributeNotSet("maxFrameRate", no_frame_rate.get())); + EXPECT_NO_FATAL_FAILURE( + ExpectAttributeNotSet("frameRate", no_frame_rate.get())); + + // Then set same frame duration for the representations. (Given that the + // time scales match). + const uint32_t kSameFrameDuration = 3u; + representation_480p->SetSampleDuration(kSameFrameDuration); + representation_360p->SetSampleDuration(kSameFrameDuration); + + xml::ScopedXmlPtr::type same_frame_rate(adaptation_set.GetXml()); + EXPECT_NO_FATAL_FAILURE( + ExpectAttributeNotSet("maxFrameRate", same_frame_rate.get())); + EXPECT_NO_FATAL_FAILURE( + ExpectAttributeEqString("frameRate", "10/3", same_frame_rate.get())); + + // Then set 480p to be 5fps (10/2) so that maxFrameRate is set. + const uint32_t k5FPSFrameDuration = 2; + COMPILE_ASSERT(k5FPSFrameDuration < kSameFrameDuration, + frame_duration_must_be_shorter_for_max_frame_rate); + representation_480p->SetSampleDuration(k5FPSFrameDuration); + + xml::ScopedXmlPtr::type max_frame_rate(adaptation_set.GetXml()); + EXPECT_NO_FATAL_FAILURE( + ExpectAttributeEqString("maxFrameRate", "10/2", max_frame_rate.get())); + EXPECT_NO_FATAL_FAILURE( + ExpectAttributeNotSet("frameRate", max_frame_rate.get())); +} + // Verify that if the picture aspect ratio of all the Representations are the // same, @par attribute is present. TEST_F(CommonMpdBuilderTest, AdaptationSetParAllSame) {