| // 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 |