blob: 169054414956323987aa46727f318e1a7080b371 [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/formats/hls/variant_stream.h"
#include <sstream>
#include <string>
#include <vector>
#include "base/containers/flat_set.h"
#include "base/memory/scoped_refptr.h"
#include "base/notreached.h"
#include "media/formats/hls/rendition.h"
#include "media/formats/hls/types.h"
#include "url/gurl.h"
namespace media::hls {
namespace {
// Converts a number representing "bytes per second" into a more human readable
// form for presentation, ie "27 Kbps" or "3.1 Mbps".
//
// When the value would otherwise be a single digit value (such as "2 Kbps"),
// it instead gets formatted to use a few decimal places as well. The `digits`
// parameter is used to control the number of digits that can appear after the
// decimal place. It should be set to 10^(max decimals).
//
// The string representation is never rounded, only truncated.
std::string FormatBandwidth(types::DecimalInteger bandwidth, int digits) {
types::DecimalInteger basis = 1;
if (bandwidth > 1000000) {
basis = 1000000;
} else if (bandwidth > 1000) {
basis = 1000;
}
const types::DecimalInteger decimal_basis = basis / digits;
const types::DecimalInteger left_digits = bandwidth / basis;
std::stringstream bandwidth_text;
bandwidth_text << left_digits;
if (left_digits < 10 && decimal_basis > 1) {
bandwidth_text << "." << ((bandwidth / decimal_basis) % digits);
}
switch (basis) {
case 1:
bandwidth_text << " bps";
break;
case 1000:
bandwidth_text << " Kbps";
break;
case 1000000:
bandwidth_text << " Mbps";
break;
default:
NOTREACHED();
}
return bandwidth_text.str();
}
} // namespace
VariantStream::VariantStream(
GURL primary_rendition_uri,
types::DecimalInteger bandwidth,
std::optional<types::DecimalInteger> average_bandwidth,
std::optional<types::DecimalFloatingPoint> score,
std::optional<std::vector<std::string>> codecs,
std::optional<types::DecimalResolution> resolution,
std::optional<types::DecimalFloatingPoint> frame_rate,
std::unique_ptr<RenditionGroup::View> audio_renditions,
std::unique_ptr<RenditionGroup::View> video_renditions)
: primary_rendition_uri_(std::move(primary_rendition_uri)),
bandwidth_(bandwidth),
average_bandwidth_(average_bandwidth),
score_(score),
codecs_(std::move(codecs)),
resolution_(resolution),
frame_rate_(frame_rate),
audio_renditions_(std::move(audio_renditions)),
video_renditions_(std::move(video_renditions)) {}
VariantStream::VariantStream(VariantStream&&) = default;
VariantStream::~VariantStream() = default;
const std::string VariantStream::Format(
const std::vector<FormatComponent>& components,
uint32_t stream_index) const {
std::stringstream format;
for (FormatComponent component : components) {
if (format.tellp()) {
format << " ";
}
switch (component) {
case FormatComponent::kResolution: {
if (!resolution_) {
continue;
}
format << resolution_->width << "x" << resolution_->height;
break;
}
case FormatComponent::kFrameRate: {
if (!frame_rate_) {
continue;
}
format << frame_rate_.value() << "fps";
break;
}
case FormatComponent::kCodecs: {
if (!codecs_) {
continue;
}
std::string sep = "";
for (const std::string& codec : codecs_.value()) {
// Get a human readable string for the codec.
format << codec << sep;
sep = ", ";
}
break;
}
case FormatComponent::kScore: {
if (!score_) {
continue;
}
format << "*" << score_.value();
break;
}
case FormatComponent::kBandwidth: {
format << FormatBandwidth(bandwidth_, 10);
break;
}
case FormatComponent::kUri: {
format << primary_rendition_uri_.ExtractFileName();
break;
}
case FormatComponent::kIndex: {
format << "Stream: " << stream_index;
break;
}
}
}
return format.str();
}
void VariantStream::UpdateImplicitRenditionMediaTrackName(std::string name) {
video_renditions_->UpdateImplicitRenditionMediaTrackName(name);
}
// static
std::vector<VariantStream::FormatComponent>
VariantStream::OptimalFormatForCollection(
const std::vector<VariantStream>& streams) {
// We have to find some set of properties that differentiates all of the
// supported variants. The most common differentiator is going to be
// video resolution. Resolution is always included in the format unless some
// of the variants are missing it or all variants have the same resolution.
// Framerate is used as a secondary differentiator to resolution, and is only
// used when there are two or more variants of the same resolution that have
// differing frames rates. Bandwidth is used as a tertiary differentiator to
// resolution and framerate.
// If we're still in a scenario where resolution, framerate, and bandwidth are
// all the same, we have to decide to fall back to either codecs, score, uri,
// or index. For now just fall back to stream index, and include resolution if
// there is more than one total size available.
base::flat_set<types::DecimalInteger> resolutions;
base::flat_set<types::DecimalInteger> rates;
base::flat_set<std::string> formatted_bandwidths;
bool missing_resolution = false;
bool missing_frame_rate = false;
for (const VariantStream& stream : streams) {
const auto resolution = stream.GetResolution();
const auto frame_rate = stream.GetFrameRate();
const auto bandwidth = stream.GetBandwidth();
if (resolution.has_value()) {
resolutions.insert(resolution.value().Szudzik());
} else {
missing_resolution = true;
}
if (frame_rate.has_value()) {
// FrameRate x Resolution is a bit tricky. We can't just consider one or
// the other, because {360p, 720p} x {24fps, 60fps} would have four
// variants, but only two resolutions or two frame rates. This isn't an
// issue for bandwidth because it isn't an independent property like these
// two are. To account for the fact that frame rate is only a secondary
// differentiator, we actually hash it with resolution for a better
// signal.
if (frame_rate.value() > 2048) {
// We don't support this high of a frame rate anyway! This data is
// probably invalid, so just fall back to stream index.
return {VariantStream::FormatComponent::kIndex};
}
auto resolution_and_rate = frame_rate.value();
if (resolution.has_value()) {
resolution_and_rate += resolution.value().Szudzik() << 11;
}
rates.insert(resolution_and_rate);
} else {
missing_frame_rate = true;
}
formatted_bandwidths.insert(FormatBandwidth(bandwidth, 10));
}
if (resolutions.size() == streams.size()) {
// There are no duplicates of resolution, and every variant provides one.
return {VariantStream::FormatComponent::kResolution};
}
if (rates.size() == streams.size()) {
// The frame rates are a pure differentiator for variant, but we still
// want to include resolution as well, assuming each variant has one and
// they are not all the same.
if (missing_resolution || resolutions.size() == 1) {
return {VariantStream::FormatComponent::kFrameRate};
}
return {VariantStream::FormatComponent::kResolution,
VariantStream::FormatComponent::kFrameRate};
}
if (formatted_bandwidths.size() == streams.size()) {
if (missing_resolution || resolutions.size() == 1) {
// Don't include resolution. Maybe frame rate?
if (missing_frame_rate || rates.size() == 1) {
return {VariantStream::FormatComponent::kBandwidth};
}
return {VariantStream::FormatComponent::kBandwidth,
VariantStream::FormatComponent::kFrameRate};
}
if (missing_frame_rate || rates.size() == 1) {
// Don't include frame rate, but resolution is ok.
return {VariantStream::FormatComponent::kBandwidth,
VariantStream::FormatComponent::kResolution};
}
return {VariantStream::FormatComponent::kBandwidth,
VariantStream::FormatComponent::kResolution,
VariantStream::FormatComponent::kFrameRate};
}
return {VariantStream::FormatComponent::kIndex};
}
} // namespace media::hls