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:
parent
061785285e
commit
74df8d30cc
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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() {}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.";
|
||||||
|
|
Loading…
Reference in New Issue