From 74df8d30cc1b191d2c44a0a0b8a6354df688cdc6 Mon Sep 17 00:00:00 2001 From: KongQun Yang Date: Wed, 14 Nov 2018 12:47:48 -0800 Subject: [PATCH] Exclude short segments from peak bandwidth computation https://tools.ietf.org/html/rfc8216#section-4.1 The peak segment bit rate of a Media Playlist is the largest bit rate of any contiguous set of segments whose total duration is between 0.5 and 1.5 times the target duration. Fixes #498. Change-Id: I1f28972b9cc5977735e47906bdcd88ba3942db5a --- packager/app/packager_util.cc | 5 +--- packager/app/packager_util.h | 4 +-- packager/hls/base/media_playlist.cc | 3 +- packager/hls/public/hls_params.h | 9 ++++++ packager/mpd/base/bandwidth_estimator.cc | 28 ++++++++++++++++++- packager/mpd/base/bandwidth_estimator.h | 3 +- .../mpd/base/bandwidth_estimator_unittest.cc | 19 +++++++++++-- packager/mpd/base/mpd_options.h | 5 ---- packager/mpd/base/representation.cc | 4 ++- packager/mpd/base/representation_unittest.cc | 9 ++++-- packager/mpd/public/mpd_params.h | 8 ++++++ packager/packager.cc | 24 ++++++++++------ 12 files changed, 93 insertions(+), 28 deletions(-) diff --git a/packager/app/packager_util.cc b/packager/app/packager_util.cc index 0d2d4ef16d..afa70c50d8 100644 --- a/packager/app/packager_util.cc +++ b/packager/app/packager_util.cc @@ -210,9 +210,7 @@ std::unique_ptr CreateDecryptionKeySource( return decryption_key_source; } -MpdOptions GetMpdOptions(bool on_demand_profile, - const MpdParams& mpd_params, - double target_segment_duration) { +MpdOptions GetMpdOptions(bool on_demand_profile, const MpdParams& mpd_params) { MpdOptions mpd_options; mpd_options.dash_profile = on_demand_profile ? DashProfile::kOnDemand : DashProfile::kLive; @@ -221,7 +219,6 @@ MpdOptions GetMpdOptions(bool on_demand_profile, ? MpdType::kStatic : MpdType::kDynamic; mpd_options.mpd_params = mpd_params; - mpd_options.target_segment_duration = target_segment_duration; return mpd_options; } diff --git a/packager/app/packager_util.h b/packager/app/packager_util.h index c47c624db0..c5351545de 100644 --- a/packager/app/packager_util.h +++ b/packager/app/packager_util.h @@ -45,9 +45,7 @@ std::unique_ptr CreateDecryptionKeySource( const DecryptionParams& decryption_params); /// @return MpdOptions from provided inputs. -MpdOptions GetMpdOptions(bool on_demand_profile, - const MpdParams& mpd_params, - double target_segment_duration); +MpdOptions GetMpdOptions(bool on_demand_profile, const MpdParams& mpd_params); } // namespace media } // namespace shaka diff --git a/packager/hls/base/media_playlist.cc b/packager/hls/base/media_playlist.cc index bcd6a29d84..b1652eeef8 100644 --- a/packager/hls/base/media_playlist.cc +++ b/packager/hls/base/media_playlist.cc @@ -330,7 +330,8 @@ MediaPlaylist::MediaPlaylist(const HlsParams& hls_params, : hls_params_(hls_params), file_name_(file_name), name_(name), - group_id_(group_id) {} + group_id_(group_id), + bandwidth_estimator_(hls_params_.target_segment_duration) {} MediaPlaylist::~MediaPlaylist() {} diff --git a/packager/hls/public/hls_params.h b/packager/hls/public/hls_params.h index 5811fa7bfa..22c7d882c1 100644 --- a/packager/hls/public/hls_params.h +++ b/packager/hls/public/hls_params.h @@ -46,6 +46,15 @@ struct HlsParams { /// in 'EXT-X-MEDIA' tag. This allows the player to choose the correct default /// language for the content. std::string default_language; + /// This is the target segment duration requested by the user. The actual + /// segment duration may be different to the target segment duration. + /// This parameter is included here to for bandwidth estimator to exclude the + /// segments with duration less than half of the target duration from + /// bandwidth estimation. See + /// https://github.com/google/shaka-packager/issues/498 for details. It will + /// be populated from segment duration specified in ChunkingParams if not + /// specified. + double target_segment_duration = 0; }; } // namespace shaka diff --git a/packager/mpd/base/bandwidth_estimator.cc b/packager/mpd/base/bandwidth_estimator.cc index 82cdcef5a1..85fcb653db 100644 --- a/packager/mpd/base/bandwidth_estimator.cc +++ b/packager/mpd/base/bandwidth_estimator.cc @@ -13,7 +13,9 @@ namespace shaka { -BandwidthEstimator::BandwidthEstimator() = default; +BandwidthEstimator::BandwidthEstimator(double target_segment_duration) + : target_segment_duration_(target_segment_duration) {} + BandwidthEstimator::~BandwidthEstimator() = default; void BandwidthEstimator::AddBlock(uint64_t size_in_bytes, double duration) { @@ -30,6 +32,30 @@ void BandwidthEstimator::AddBlock(uint64_t size_in_bytes, double duration) { total_duration_ += duration; const uint64_t bitrate = static_cast(ceil(size_in_bits / duration)); + + if (duration < 0.5 * target_segment_duration_) { + // https://tools.ietf.org/html/rfc8216#section-4.1 + // The peak segment bit rate of a Media Playlist is the largest bit rate of + // any continuous set of segments whose total duration is between 0.5 + // and 1.5 times the target duration. + // Only the short segments are excluded here as our media playlist generator + // sets the target duration in the playlist to the largest segment duration. + // So although the segment duration could be 1.5 times the user provided + // segment duration, it will never be larger than the actual target + // duration. + // + // TODO(kqyang): Review if we can just stick to the user provided segment + // duration as our target duration. + // + // We also apply the same exclusion to the bandwidth computation for DASH as + // the bitrate for the short segment is not a good signal for peak + // bandwidth. + // See https://github.com/google/shaka-packager/issues/498 for details. + VLOG(1) << "Exclude short segment (duration " << duration + << ", target_duration " << target_segment_duration_ + << ") in peak bandwidth computation."; + return; + } max_bitrate_ = std::max(bitrate, max_bitrate_); } diff --git a/packager/mpd/base/bandwidth_estimator.h b/packager/mpd/base/bandwidth_estimator.h index 5544de129b..5ba06dcbb9 100644 --- a/packager/mpd/base/bandwidth_estimator.h +++ b/packager/mpd/base/bandwidth_estimator.h @@ -13,7 +13,7 @@ namespace shaka { class BandwidthEstimator { public: - BandwidthEstimator(); + explicit BandwidthEstimator(double target_segment_duration); ~BandwidthEstimator(); /// @param size is the size of the block in bytes. Should be positive. @@ -35,6 +35,7 @@ class BandwidthEstimator { BandwidthEstimator(const BandwidthEstimator&) = delete; BandwidthEstimator& operator=(const BandwidthEstimator&) = delete; + const double target_segment_duration_ = 0; uint64_t total_size_in_bits_ = 0; double total_duration_ = 0; uint64_t max_bitrate_ = 0; diff --git a/packager/mpd/base/bandwidth_estimator_unittest.cc b/packager/mpd/base/bandwidth_estimator_unittest.cc index e3632f25ac..a56a6ebb8c 100644 --- a/packager/mpd/base/bandwidth_estimator_unittest.cc +++ b/packager/mpd/base/bandwidth_estimator_unittest.cc @@ -15,9 +15,9 @@ const uint64_t kBitsInByte = 8; } // namespace TEST(BandwidthEstimatorTest, AllBlocks) { - BandwidthEstimator be; - const uint64_t kNumBlocksToAdd = 100; const double kDuration = 1.0; + BandwidthEstimator be(kDuration); + const uint64_t kNumBlocksToAdd = 100; uint64_t total_bytes = 0; for (uint64_t i = 1; i <= kNumBlocksToAdd; ++i) { be.AddBlock(i, kDuration); @@ -31,4 +31,19 @@ TEST(BandwidthEstimatorTest, AllBlocks) { EXPECT_EQ(kMax, be.Max()); } +TEST(BandwidthEstimatorTest, ExcludeShortSegments) { + const double kDuration = 1.0; + BandwidthEstimator be(kDuration); + + // Add 4 blocks with duration 0.1, 0.8, 1.8 and 0.2 respectively. The first + // and the last blocks are excluded as they are too short. + be.AddBlock(1, 0.1 * kDuration); + be.AddBlock(1, 0.8 * kDuration); + be.AddBlock(1, 1.8 * kDuration); + be.AddBlock(1, 0.2 * kDuration); + + const uint64_t kExpectedMax = 1 / 0.8 * kBitsInByte; + EXPECT_EQ(kExpectedMax, be.Max()); +} + } // namespace shaka diff --git a/packager/mpd/base/mpd_options.h b/packager/mpd/base/mpd_options.h index f44dc83f1e..dc261d9718 100644 --- a/packager/mpd/base/mpd_options.h +++ b/packager/mpd/base/mpd_options.h @@ -26,11 +26,6 @@ struct MpdOptions { DashProfile dash_profile = DashProfile::kOnDemand; MpdType mpd_type = MpdType::kStatic; MpdParams mpd_params; - /// This is the target segment duration requested by the user. The actual - /// segment duration may be different to the target segment duration. - /// This parameter is included here to calculate the approximate - /// SegmentTimeline if it is enabled. - double target_segment_duration = 0; }; } // namespace shaka diff --git a/packager/mpd/base/representation.cc b/packager/mpd/base/representation.cc index 35fb9a6b14..f7b90e84e4 100644 --- a/packager/mpd/base/representation.cc +++ b/packager/mpd/base/representation.cc @@ -115,6 +115,7 @@ Representation::Representation( std::unique_ptr state_change_listener) : media_info_(media_info), id_(id), + bandwidth_estimator_(mpd_options.mpd_params.target_segment_duration), mpd_options_(mpd_options), state_change_listener_(std::move(state_change_listener)), allow_approximate_segment_timeline_( @@ -426,7 +427,8 @@ int64_t Representation::AdjustDuration(int64_t duration) const { if (!allow_approximate_segment_timeline_) return duration; const int64_t scaled_target_duration = - mpd_options_.target_segment_duration * media_info_.reference_time_scale(); + mpd_options_.mpd_params.target_segment_duration * + media_info_.reference_time_scale(); return ApproximiatelyEqual(scaled_target_duration, duration) ? scaled_target_duration : duration; diff --git a/packager/mpd/base/representation_unittest.cc b/packager/mpd/base/representation_unittest.cc index 829f1e7016..073f7319cc 100644 --- a/packager/mpd/base/representation_unittest.cc +++ b/packager/mpd/base/representation_unittest.cc @@ -416,6 +416,8 @@ const char kSElementTemplateWithoutR[] = const int kDefaultStartNumber = 1; const uint32_t kDefaultTimeScale = 1000u; const int64_t kScaledTargetSegmentDuration = 10; +const double kTargetSegmentDurationInSeconds = + static_cast(kScaledTargetSegmentDuration) / kDefaultTimeScale; const uint32_t kSampleDuration = 2; std::string GetDefaultMediaInfo() { @@ -441,6 +443,9 @@ std::string GetDefaultMediaInfo() { class SegmentTemplateTest : public RepresentationTest { public: + SegmentTemplateTest() + : bandwidth_estimator_(kTargetSegmentDurationInSeconds) {} + void SetUp() override { mpd_options_.mpd_type = MpdType::kDynamic; representation_ = @@ -715,8 +720,8 @@ class SegmentTimelineTestBase : public SegmentTemplateTest { const std::string& number_template_media_info = base::StringPrintf(kMediaInfo, kDefaultTimeScale); mpd_options_.mpd_type = MpdType::kDynamic; - mpd_options_.target_segment_duration = - static_cast(kScaledTargetSegmentDuration) / kDefaultTimeScale; + mpd_options_.mpd_params.target_segment_duration = + kTargetSegmentDurationInSeconds; representation_ = CreateRepresentation(ConvertToMediaInfo(number_template_media_info), kAnyRepresentationId, NoListener()); diff --git a/packager/mpd/public/mpd_params.h b/packager/mpd/public/mpd_params.h index 0decd3a88c..1b2bfab61e 100644 --- a/packager/mpd/public/mpd_params.h +++ b/packager/mpd/public/mpd_params.h @@ -69,6 +69,14 @@ struct MpdParams { /// Ignored if $Time$ is used in segment template, since $Time$ requires /// accurate Segment Timeline. bool allow_approximate_segment_timeline = false; + /// This is the target segment duration requested by the user. The actual + /// segment duration may be different to the target segment duration. + /// This parameter is included here to calculate the approximate + /// SegmentTimeline if it is enabled. It is also used by the bandwidth + /// estimator to exclude the segments with duration less than half of the + /// target duration from bandwidth estimation. It will be populated from + /// segment duration specified in ChunkingParams if not specified. + double target_segment_duration = 0; }; } // namespace shaka diff --git a/packager/packager.cc b/packager/packager.cc index 6da5164a7e..5a303ac8d5 100644 --- a/packager/packager.cc +++ b/packager/packager.cc @@ -867,18 +867,28 @@ Status Packager::Initialize( return Status(error::INVALID_ARGUMENT, "Failed to create key source."); } - // Store callback params to make it available during packaging. - internal->buffer_callback_params = packaging_params.buffer_callback_params; - - // Update MPD output and HLS output if callback param is specified. + // Update MPD output and HLS output if needed. MpdParams mpd_params = packaging_params.mpd_params; HlsParams hls_params = packaging_params.hls_params; + + // |target_segment_duration| is needed for bandwidth estimation and also for + // DASH approximate segment timeline. + const double target_segment_duration = + packaging_params.chunking_params.segment_duration_in_seconds; + if (mpd_params.target_segment_duration != 0) + mpd_params.target_segment_duration = target_segment_duration; + if (hls_params.target_segment_duration != 0) + hls_params.target_segment_duration = target_segment_duration; + + // Store callback params to make it available during packaging. + internal->buffer_callback_params = packaging_params.buffer_callback_params; if (internal->buffer_callback_params.write_func) { mpd_params.mpd_output = File::MakeCallbackFileName( internal->buffer_callback_params, mpd_params.mpd_output); hls_params.master_playlist_output = File::MakeCallbackFileName( internal->buffer_callback_params, hls_params.master_playlist_output); } + // Both DASH and HLS require language to follow RFC5646 // (https://tools.ietf.org/html/rfc5646), which requires the language to be // in the shortest form. @@ -890,10 +900,8 @@ Status Packager::Initialize( if (!mpd_params.mpd_output.empty()) { const bool on_demand_dash_profile = stream_descriptors.begin()->segment_template.empty(); - const double target_segment_duration = - packaging_params.chunking_params.segment_duration_in_seconds; - const MpdOptions mpd_options = media::GetMpdOptions( - on_demand_dash_profile, mpd_params, target_segment_duration); + const MpdOptions mpd_options = + media::GetMpdOptions(on_demand_dash_profile, mpd_params); internal->mpd_notifier.reset(new SimpleMpdNotifier(mpd_options)); if (!internal->mpd_notifier->Init()) { LOG(ERROR) << "MpdNotifier failed to initialize.";