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
This commit is contained in:
KongQun Yang 2018-11-14 12:47:48 -08:00
parent 061785285e
commit 74df8d30cc
12 changed files with 93 additions and 28 deletions

View File

@ -210,9 +210,7 @@ std::unique_ptr<KeySource> CreateDecryptionKeySource(
return decryption_key_source; return decryption_key_source;
} }
MpdOptions GetMpdOptions(bool on_demand_profile, MpdOptions GetMpdOptions(bool on_demand_profile, const MpdParams& mpd_params) {
const MpdParams& mpd_params,
double target_segment_duration) {
MpdOptions mpd_options; MpdOptions mpd_options;
mpd_options.dash_profile = mpd_options.dash_profile =
on_demand_profile ? DashProfile::kOnDemand : DashProfile::kLive; on_demand_profile ? DashProfile::kOnDemand : DashProfile::kLive;
@ -221,7 +219,6 @@ MpdOptions GetMpdOptions(bool on_demand_profile,
? MpdType::kStatic ? MpdType::kStatic
: MpdType::kDynamic; : MpdType::kDynamic;
mpd_options.mpd_params = mpd_params; mpd_options.mpd_params = mpd_params;
mpd_options.target_segment_duration = target_segment_duration;
return mpd_options; return mpd_options;
} }

View File

@ -45,9 +45,7 @@ std::unique_ptr<KeySource> CreateDecryptionKeySource(
const DecryptionParams& decryption_params); const DecryptionParams& decryption_params);
/// @return MpdOptions from provided inputs. /// @return MpdOptions from provided inputs.
MpdOptions GetMpdOptions(bool on_demand_profile, MpdOptions GetMpdOptions(bool on_demand_profile, const MpdParams& mpd_params);
const MpdParams& mpd_params,
double target_segment_duration);
} // namespace media } // namespace media
} // namespace shaka } // namespace shaka

View File

@ -330,7 +330,8 @@ MediaPlaylist::MediaPlaylist(const HlsParams& hls_params,
: hls_params_(hls_params), : hls_params_(hls_params),
file_name_(file_name), file_name_(file_name),
name_(name), name_(name),
group_id_(group_id) {} group_id_(group_id),
bandwidth_estimator_(hls_params_.target_segment_duration) {}
MediaPlaylist::~MediaPlaylist() {} MediaPlaylist::~MediaPlaylist() {}

View File

@ -46,6 +46,15 @@ struct HlsParams {
/// in 'EXT-X-MEDIA' tag. This allows the player to choose the correct default /// in 'EXT-X-MEDIA' tag. This allows the player to choose the correct default
/// language for the content. /// language for the content.
std::string default_language; 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 } // namespace shaka

View File

@ -13,7 +13,9 @@
namespace shaka { namespace shaka {
BandwidthEstimator::BandwidthEstimator() = default; BandwidthEstimator::BandwidthEstimator(double target_segment_duration)
: target_segment_duration_(target_segment_duration) {}
BandwidthEstimator::~BandwidthEstimator() = default; BandwidthEstimator::~BandwidthEstimator() = default;
void BandwidthEstimator::AddBlock(uint64_t size_in_bytes, double duration) { 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; total_duration_ += duration;
const uint64_t bitrate = static_cast<uint64_t>(ceil(size_in_bits / duration)); const uint64_t bitrate = static_cast<uint64_t>(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_); max_bitrate_ = std::max(bitrate, max_bitrate_);
} }

View File

@ -13,7 +13,7 @@ namespace shaka {
class BandwidthEstimator { class BandwidthEstimator {
public: public:
BandwidthEstimator(); explicit BandwidthEstimator(double target_segment_duration);
~BandwidthEstimator(); ~BandwidthEstimator();
/// @param size is the size of the block in bytes. Should be positive. /// @param size is the size of the block in bytes. Should be positive.
@ -35,6 +35,7 @@ class BandwidthEstimator {
BandwidthEstimator(const BandwidthEstimator&) = delete; BandwidthEstimator(const BandwidthEstimator&) = delete;
BandwidthEstimator& operator=(const BandwidthEstimator&) = delete; BandwidthEstimator& operator=(const BandwidthEstimator&) = delete;
const double target_segment_duration_ = 0;
uint64_t total_size_in_bits_ = 0; uint64_t total_size_in_bits_ = 0;
double total_duration_ = 0; double total_duration_ = 0;
uint64_t max_bitrate_ = 0; uint64_t max_bitrate_ = 0;

View File

@ -15,9 +15,9 @@ const uint64_t kBitsInByte = 8;
} // namespace } // namespace
TEST(BandwidthEstimatorTest, AllBlocks) { TEST(BandwidthEstimatorTest, AllBlocks) {
BandwidthEstimator be;
const uint64_t kNumBlocksToAdd = 100;
const double kDuration = 1.0; const double kDuration = 1.0;
BandwidthEstimator be(kDuration);
const uint64_t kNumBlocksToAdd = 100;
uint64_t total_bytes = 0; uint64_t total_bytes = 0;
for (uint64_t i = 1; i <= kNumBlocksToAdd; ++i) { for (uint64_t i = 1; i <= kNumBlocksToAdd; ++i) {
be.AddBlock(i, kDuration); be.AddBlock(i, kDuration);
@ -31,4 +31,19 @@ TEST(BandwidthEstimatorTest, AllBlocks) {
EXPECT_EQ(kMax, be.Max()); 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 } // namespace shaka

View File

@ -26,11 +26,6 @@ struct MpdOptions {
DashProfile dash_profile = DashProfile::kOnDemand; DashProfile dash_profile = DashProfile::kOnDemand;
MpdType mpd_type = MpdType::kStatic; MpdType mpd_type = MpdType::kStatic;
MpdParams mpd_params; 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 } // namespace shaka

View File

@ -115,6 +115,7 @@ Representation::Representation(
std::unique_ptr<RepresentationStateChangeListener> state_change_listener) std::unique_ptr<RepresentationStateChangeListener> state_change_listener)
: media_info_(media_info), : media_info_(media_info),
id_(id), id_(id),
bandwidth_estimator_(mpd_options.mpd_params.target_segment_duration),
mpd_options_(mpd_options), mpd_options_(mpd_options),
state_change_listener_(std::move(state_change_listener)), state_change_listener_(std::move(state_change_listener)),
allow_approximate_segment_timeline_( allow_approximate_segment_timeline_(
@ -426,7 +427,8 @@ int64_t Representation::AdjustDuration(int64_t duration) const {
if (!allow_approximate_segment_timeline_) if (!allow_approximate_segment_timeline_)
return duration; return duration;
const int64_t scaled_target_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) return ApproximiatelyEqual(scaled_target_duration, duration)
? scaled_target_duration ? scaled_target_duration
: duration; : duration;

View File

@ -416,6 +416,8 @@ const char kSElementTemplateWithoutR[] =
const int kDefaultStartNumber = 1; const int kDefaultStartNumber = 1;
const uint32_t kDefaultTimeScale = 1000u; const uint32_t kDefaultTimeScale = 1000u;
const int64_t kScaledTargetSegmentDuration = 10; const int64_t kScaledTargetSegmentDuration = 10;
const double kTargetSegmentDurationInSeconds =
static_cast<double>(kScaledTargetSegmentDuration) / kDefaultTimeScale;
const uint32_t kSampleDuration = 2; const uint32_t kSampleDuration = 2;
std::string GetDefaultMediaInfo() { std::string GetDefaultMediaInfo() {
@ -441,6 +443,9 @@ std::string GetDefaultMediaInfo() {
class SegmentTemplateTest : public RepresentationTest { class SegmentTemplateTest : public RepresentationTest {
public: public:
SegmentTemplateTest()
: bandwidth_estimator_(kTargetSegmentDurationInSeconds) {}
void SetUp() override { void SetUp() override {
mpd_options_.mpd_type = MpdType::kDynamic; mpd_options_.mpd_type = MpdType::kDynamic;
representation_ = representation_ =
@ -715,8 +720,8 @@ class SegmentTimelineTestBase : public SegmentTemplateTest {
const std::string& number_template_media_info = const std::string& number_template_media_info =
base::StringPrintf(kMediaInfo, kDefaultTimeScale); base::StringPrintf(kMediaInfo, kDefaultTimeScale);
mpd_options_.mpd_type = MpdType::kDynamic; mpd_options_.mpd_type = MpdType::kDynamic;
mpd_options_.target_segment_duration = mpd_options_.mpd_params.target_segment_duration =
static_cast<double>(kScaledTargetSegmentDuration) / kDefaultTimeScale; kTargetSegmentDurationInSeconds;
representation_ = representation_ =
CreateRepresentation(ConvertToMediaInfo(number_template_media_info), CreateRepresentation(ConvertToMediaInfo(number_template_media_info),
kAnyRepresentationId, NoListener()); kAnyRepresentationId, NoListener());

View File

@ -69,6 +69,14 @@ struct MpdParams {
/// Ignored if $Time$ is used in segment template, since $Time$ requires /// Ignored if $Time$ is used in segment template, since $Time$ requires
/// accurate Segment Timeline. /// accurate Segment Timeline.
bool allow_approximate_segment_timeline = false; 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 } // namespace shaka

View File

@ -867,18 +867,28 @@ Status Packager::Initialize(
return Status(error::INVALID_ARGUMENT, "Failed to create key source."); return Status(error::INVALID_ARGUMENT, "Failed to create key source.");
} }
// Store callback params to make it available during packaging. // Update MPD output and HLS output if needed.
internal->buffer_callback_params = packaging_params.buffer_callback_params;
// Update MPD output and HLS output if callback param is specified.
MpdParams mpd_params = packaging_params.mpd_params; MpdParams mpd_params = packaging_params.mpd_params;
HlsParams hls_params = packaging_params.hls_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) { if (internal->buffer_callback_params.write_func) {
mpd_params.mpd_output = File::MakeCallbackFileName( mpd_params.mpd_output = File::MakeCallbackFileName(
internal->buffer_callback_params, mpd_params.mpd_output); internal->buffer_callback_params, mpd_params.mpd_output);
hls_params.master_playlist_output = File::MakeCallbackFileName( hls_params.master_playlist_output = File::MakeCallbackFileName(
internal->buffer_callback_params, hls_params.master_playlist_output); internal->buffer_callback_params, hls_params.master_playlist_output);
} }
// Both DASH and HLS require language to follow RFC5646 // Both DASH and HLS require language to follow RFC5646
// (https://tools.ietf.org/html/rfc5646), which requires the language to be // (https://tools.ietf.org/html/rfc5646), which requires the language to be
// in the shortest form. // in the shortest form.
@ -890,10 +900,8 @@ Status Packager::Initialize(
if (!mpd_params.mpd_output.empty()) { if (!mpd_params.mpd_output.empty()) {
const bool on_demand_dash_profile = const bool on_demand_dash_profile =
stream_descriptors.begin()->segment_template.empty(); stream_descriptors.begin()->segment_template.empty();
const double target_segment_duration = const MpdOptions mpd_options =
packaging_params.chunking_params.segment_duration_in_seconds; media::GetMpdOptions(on_demand_dash_profile, mpd_params);
const MpdOptions mpd_options = media::GetMpdOptions(
on_demand_dash_profile, mpd_params, target_segment_duration);
internal->mpd_notifier.reset(new SimpleMpdNotifier(mpd_options)); internal->mpd_notifier.reset(new SimpleMpdNotifier(mpd_options));
if (!internal->mpd_notifier->Init()) { if (!internal->mpd_notifier->Init()) {
LOG(ERROR) << "MpdNotifier failed to initialize."; LOG(ERROR) << "MpdNotifier failed to initialize.";