diff --git a/AampConfig.cpp b/AampConfig.cpp index 406f754a3..33aa93cba 100644 --- a/AampConfig.cpp +++ b/AampConfig.cpp @@ -31,6 +31,7 @@ #include "PlayerRfc.h" #include "PlayerExternalsInterface.h" #include "PlayerSecInterface.h" +#include "abr.h" #include #include //////////////// CAUTION !!!! STOP !!! Read this before you proceed !!!!!!! ///////////// @@ -470,7 +471,7 @@ static const ConfigLookupEntryInt mConfigLookupTableInt[AAMPCONFIG_INT_COUNT+CON {DEFAULT_PROGRESS_LOGGING_DIVISOR,"progressLoggingDivisor",eAAMPConfig_ProgressLoggingDivisor,false}, {DEFAULT_MONITOR_AV_REPORTING_INTERVAL, "monitorAVReportingInterval", eAAMPConfig_MonitorAVReportingInterval, false}, {DEFAULT_UTC_SYNC_MIN_INTERVAL_SEC,"utcSyncMinIntervalSec",eAAMPConfig_UTCSyncMinIntervalSec,true }, - + {DEFAULT_ABR_BANDWIDTH_ESTIMATOR_TYPE, "abrBandwidthEstimator", eAAMPConfig_ABRBandwidthEstimator, false, eCONFIG_RANGE_ANY}, // Add new integer config entries above this line, before the aliases section. // // Aliases, kept for backwards compatibility diff --git a/AampConfig.h b/AampConfig.h index af0aff4be..9b82bfcdb 100644 --- a/AampConfig.h +++ b/AampConfig.h @@ -313,6 +313,7 @@ typedef enum eAAMPConfig_ProgressLoggingDivisor, /**< Divisor to avoid printing the progress report too frequently in the log */ eAAMPConfig_MonitorAVReportingInterval, /**< Timeout in milliseconds for reporting MonitorAV events */ eAAMPConfig_UTCSyncMinIntervalSec, /**< Minimum interval between sync attempts */ + eAAMPConfig_ABRBandwidthEstimator, /**< Select ABR bandwidth estimator */ eAAMPConfig_IntMaxValue /**< Max value of int config always last element*/ } AAMPConfigSettingInt; #define AAMPCONFIG_INT_COUNT (eAAMPConfig_IntMaxValue) diff --git a/AampDefine.h b/AampDefine.h index 744240033..7d60c4fd8 100644 --- a/AampDefine.h +++ b/AampDefine.h @@ -60,6 +60,7 @@ #define DEFAULT_BUFFER_HEALTH_MONITOR_INTERVAL 5 #define DEFAULT_ABR_CACHE_LENGTH 3 /**< Default ABR cache length */ #define DEFAULT_ABR_BUFFER_COUNTER 4 /**< Default ABR Buffer Counter */ +#define DEFAULT_ABR_BANDWIDTH_ESTIMATOR_TYPE 0 /**< Default ABR Bandwidth Estimator Type */ #define DEFAULT_REPORT_PROGRESS_INTERVAL 1 /**< Progress event reporting interval: 1sec */ #define DEFAULT_PROGRESS_LOGGING_DIVISOR 4 /**< Divisor of progress logging frequency to print logging */ #define DEFAULT_LICENSE_REQ_RETRY_WAIT_TIME 500 /**< Wait time in milliseconds before retrying for DRM license */ diff --git a/CMakeLists.txt b/CMakeLists.txt index bd8492681..b2f6a963a 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -389,7 +389,9 @@ set(LIBAAMP_SOURCES AampCMCDCollector.cpp AampCMCDCollector.h downloader/AampCurlDownloader.cpp downloader/AampCurlDownloader.h ID3Metadata.cpp ID3Metadata.hpp - abr/abr.cpp abr/abr.h abr/NetworkBandwidthEstimator.cpp abr/NetworkBandwidthEstimator.h + abr/abr.cpp abr/abr.h + abr/RMOBandwidthEstimator.cpp abr/RMOBandwidthEstimator.h + abr/HarmonicEWMAEstimator.cpp abr/HarmonicEWMAEstimator.h dash/xml/DomDocument.cpp dash/xml/DomElement.cpp dash/xml/DomNode.cpp @@ -586,6 +588,9 @@ install(FILES LangCodePreference.h StreamOutputFormat.h VideoZoomMode.h StreamSink.h TimedMetadata.h AampDemuxDataTypes.h abr/abr.h + abr/RMOBandwidthEstimator.h + abr/HarmonicEWMAEstimator.h + abr/BandwidthEstimatorBase.h DESTINATION include ) diff --git a/abr/BandwidthEstimatorBase.h b/abr/BandwidthEstimatorBase.h new file mode 100644 index 000000000..dd0e12692 --- /dev/null +++ b/abr/BandwidthEstimatorBase.h @@ -0,0 +1,155 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2026 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef AAMP_ABR_BANDWIDTH_ESTIMATOR_BASE_H +#define AAMP_ABR_BANDWIDTH_ESTIMATOR_BASE_H + +#include + +#include "AampMediaType.h" + +/** + * @brief Plain Old Data (POD) structure for profiling information from a + * given CURL instance. + */ +struct DownloadMetrics +{ + std::size_t m_size_download_bytes; // CURLINFO_SIZE_DOWNLOAD + double m_total_time_seconds; // CURLINFO_TOTAL_TIME + double m_time_to_first_byte_seconds; // CURLINFO_STARTTRANSFER_TIME +}; + +/** + * @brief POD structure for in-flight progress updates (xferinfo-style). + */ +struct DownloadProgressInfo +{ + double m_now_seconds; + std::size_t m_total_bytes; + std::size_t m_now_bytes; +}; + +/** + * @brief Optional configuration for bandwidth estimators. + * + * Not all estimators use every field. Estimators that do not support + * configuration should ignore it. + */ +struct BandwidthEstimatorConfig +{ + int mAbrCacheLife; + int mAbrCacheLength; + int mAbrCacheOutlier; + std::size_t mLowLatencyCacheLength; + + BandwidthEstimatorConfig() + : mAbrCacheLife(0), + mAbrCacheLength(0), + mAbrCacheOutlier(0), + mLowLatencyCacheLength(0) + { + } + + BandwidthEstimatorConfig( + int abrCacheLife, + int abrCacheLength, + int abrCacheOutlier, + std::size_t lowLatencyCacheLength) + : mAbrCacheLife(abrCacheLife), + mAbrCacheLength(abrCacheLength), + mAbrCacheOutlier(abrCacheOutlier), + mLowLatencyCacheLength(lowLatencyCacheLength) + { + } +}; + +/** + * @brief Base interface for bandwidth estimation algorithms. + */ +class BandwidthEstimatorBase +{ +public: + virtual ~BandwidthEstimatorBase() {} + + /** + * @brief Human-readable algorithm name. + */ + virtual const char* GetNetworkEstimatorName() const = 0; + + /** + * @brief Reset all internal state. + */ + virtual void Reset() = 0; + + /** + * @brief Add a bandwidth sample (bits per second). + */ + virtual void AddBandwidthSample(BitsPerSecond downloadbps, bool lowLatencyMode) = 0; + + /** + * @brief Add download metrics derived from curl (optional). + */ + virtual void UpdateDownloadMetrics(const DownloadMetrics &metrics ) = 0; + + /** + * @brief Provide in-flight download progress updates (optional). + */ + virtual void UpdateDownloadProgress(const DownloadProgressInfo &progressInfo) + { + } + + /** + * @brief Update estimator configuration (optional). + * + * Default implementation is a no-op. + */ + virtual void SetConfig(const BandwidthEstimatorConfig &config) + { + } + + /** + * @brief Return current bandwidth estimate (bits per second). + * + * @return Estimated bandwidth in bits per second, or -1 if unavailable. + */ + virtual BitsPerSecond GetBandwidthBitsPerSecond() = 0; + + /** + * @brief Return current robust throughput estimate (bytes/s). + */ + virtual double GetThroughputBytesPerSecond() = 0; + + /** + * @brief Return current overhead (TTFB) estimate (seconds). + */ + virtual double GetTimeToFirstByteSeconds() = 0; + + /** + * @brief Predict completion time for a new segment. + */ + virtual double GetPredictedDownloadTimeSeconds(std::size_t segment_size_bytes) = 0; + + /** + * @brief Reset only the currently available bandwidth estimate. + */ + virtual void ResetCurrentlyAvailableBandwidth() + { + } +}; + +#endif diff --git a/abr/HarmonicEWMAEstimator.cpp b/abr/HarmonicEWMAEstimator.cpp new file mode 100644 index 000000000..163804201 --- /dev/null +++ b/abr/HarmonicEWMAEstimator.cpp @@ -0,0 +1,425 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2026 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include +#include +#include +#include +#include +#include + +#include "HarmonicEWMAEstimator.h" + +static constexpr double epsilon = 1e-6; +static constexpr double BLEND_WEIGHT_HARMONIC = 0.6; // 60% harmonic, 40% EWMA +static constexpr size_t MAX_HISTORY = 24; // how far back in rolling window samples to consider for bandwidth estimate + +/** + * @brief get clock time as a floating point monotonic value + */ +double GetCurrentTimeMonotonicSeconds() +{ + using clock = std::chrono::steady_clock; + return std::chrono::duration(clock::now().time_since_epoch()).count(); +} + +/** + * @brief given a vector of floating point values, retrieve the median value + * This function uses std::nth_element for O(n) time complexity instead of sorting. + * For performance reasons, we don't make a copy of input, and so order in values may change as side effect of calling this function. + * + * @param values Input vector of floating point values; order may change after calling + * @return The median value, or 0.0 if the input vector is empty + */ +double GetMedian(std::vector &values) +{ + if (values.empty()) + { + return 0.0; + } + + const size_t n = values.size(); + const size_t mid = n / 2; + if (n % 2) + { // Odd number of elements - find the middle element + std::nth_element(values.begin(), values.begin() + mid, values.end()); + return values[mid]; + } + else + { // Even number of elements - average the two middle elements + // Find the element at mid position + std::nth_element(values.begin(), values.begin() + mid, values.end()); + const double upper = values[mid]; + + // Find the element at mid-1 position (the max of the lower half) + std::nth_element(values.begin(), values.begin() + (mid - 1), values.end()); + const double lower = values[mid - 1]; + + return 0.5 * (lower + upper); + } +} + +/** + * @brief Recompute median TTFB and harmonic mean from history; this requires iterating through all recent samples + */ +void HarmonicEWMAEstimator::RecomputeHarmonicMeanAndMedianTTFB() +{ + // Overhead = median TTFB from all samples + std::vector ttfb; + ttfb.reserve(m_history.size()); + for (const auto &s : m_history) + { + ttfb.push_back(s.GetTimeToFirstByteSeconds()); + } + m_estimated_TTFB_seconds = GetMedian(ttfb); + + // Harmonic mean of throughput over last harmonic_window samples + const size_t n = m_history.size(); + const size_t start = (n > harmonic_window) ? (n - harmonic_window) : 0; + double denominator = 0.0; + size_t count = 0; + for (size_t i = start; i < n; i++) + { + const double payloadBytesPerSecond = + m_history[i].GetPayloadBytesPerSecond(); + if (payloadBytesPerSecond > epsilon) + { + denominator += 1.0 / payloadBytesPerSecond; + count++; + } + } + m_harmonic_BytesPerSecond = (count > 0 && denominator > 0.0) + ? (static_cast(count) / denominator) + : 0.0; +} + +/** + * @brief Add download metrics derived from curl (optional). + * @param[in] downloadMetrics Download metrics. + */ +void HarmonicEWMAEstimator::UpdateDownloadMetrics(const DownloadMetrics &downloadMetrics) +{ + Sample sample(downloadMetrics); + const double payload_bytes_per_second = sample.GetPayloadBytesPerSecond(); + m_history.emplace_back(sample); + + if (m_history.size() > MAX_HISTORY) + { // Trim history to avoid unbounded growth + m_history.erase( + m_history.begin(), + m_history.begin() + (m_history.size() - MAX_HISTORY)); + } + + // EWMA updates + if (m_ewma_fast_BytesPerSecond > 0.0) + { + m_ewma_fast_BytesPerSecond = ALPHA_FAST * payload_bytes_per_second + + (1.0 - ALPHA_FAST) * m_ewma_fast_BytesPerSecond; + } + else + { + m_ewma_fast_BytesPerSecond = payload_bytes_per_second; + } + if (m_ewma_slow_BytesPerSecond > 0.0) + { + m_ewma_slow_BytesPerSecond = ALPHA_SLOW * payload_bytes_per_second + + (1.0 - ALPHA_SLOW) * m_ewma_slow_BytesPerSecond; + } + else + { + m_ewma_slow_BytesPerSecond = payload_bytes_per_second; + } + RecomputeHarmonicMeanAndMedianTTFB(); +} + +/** + * @brief Human-readable algorithm name. + * @return Algorithm name. + */ +const char *HarmonicEWMAEstimator::GetNetworkEstimatorName() const +{ + return "Harmonic_Mean_Exponentially_Weighted_Moving_Average_Blend"; +} + +/** + * @brief Reset internal state. + */ +void HarmonicEWMAEstimator::Reset() +{ + m_history.clear(); + m_estimated_TTFB_seconds = 0.0; + m_ewma_fast_BytesPerSecond = 0.0; + m_ewma_slow_BytesPerSecond = 0.0; + m_harmonic_BytesPerSecond = 0.0; + m_progressContextValid = false; + m_progressBytesPerSecond = 0.0; + m_progressLastNowSeconds = 0.0; + m_progressLastNowBytes = 0; + m_progressLastTotalBytes = 0; + m_progressHasSample = false; +} + +/** + * @brief Add a bandwidth sample. + * @param[in] downloadbps Download bandwidth in bits per second. + * @param[in] lowLatencyMode Whether low latency mode is enabled. + */ +void HarmonicEWMAEstimator::AddBandwidthSample(BitsPerSecond downloadbps, bool lowLatencyMode) +{ + (void)downloadbps; + (void)lowLatencyMode; +} + +/** + * @brief Provide in-flight download progress updates (optional). + * @param[in] progressInfo Download progress information. + */ +void HarmonicEWMAEstimator::UpdateDownloadProgress( + const DownloadProgressInfo &progressInfo) +{ + const double now = progressInfo.m_now_seconds; + const std::size_t dltotal = progressInfo.m_total_bytes; + const std::size_t dlnow = progressInfo.m_now_bytes; + + if (!m_progressContextValid) + { + m_progressContext.Reset(now); + m_progressContextValid = true; + } + else if (now < m_progressLastNowSeconds || dlnow < m_progressLastNowBytes) + { + m_progressContext.Reset(now); + m_progressBytesPerSecond = 0.0; + m_progressHasSample = false; + } + else if (dltotal != 0 && + dltotal != m_progressLastTotalBytes && + dlnow == 0) + { + m_progressContext.Reset(now); + m_progressBytesPerSecond = 0.0; + m_progressHasSample = false; + } + + if (m_progressContext.xferinfo(now, dltotal, dlnow)) + { + m_progressBytesPerSecond = + m_progressContext.GetEstimatedThroughputBytesPerSecond(); + m_progressHasSample = (m_progressBytesPerSecond > 0.0); + } + + m_progressLastNowSeconds = now; + m_progressLastNowBytes = dlnow; + m_progressLastTotalBytes = dltotal; +} + +/** + * @brief Get current bandwidth estimate. + * @return Estimated bandwidth in bits per second, or -1 if unavailable. + */ +BitsPerSecond HarmonicEWMAEstimator::GetBandwidthBitsPerSecond() +{ + return static_cast(GetThroughputBytesPerSecond() * 8.0); +} + +/** + * @brief Get current robust throughput estimate. + * @return Estimated throughput in bytes per second. + */ +double HarmonicEWMAEstimator::GetThroughputBytesPerSecond() +{ + double ewma_min = (m_ewma_fast_BytesPerSecond > 0.0 && + m_ewma_slow_BytesPerSecond > 0.0) + ? std::min(m_ewma_fast_BytesPerSecond, m_ewma_slow_BytesPerSecond) + : std::max(m_ewma_fast_BytesPerSecond, m_ewma_slow_BytesPerSecond); + if (ewma_min > 0.0) + { + if (m_harmonic_BytesPerSecond > 0.0) + { + const double blended = + BLEND_WEIGHT_HARMONIC * m_harmonic_BytesPerSecond + + (1.0 - BLEND_WEIGHT_HARMONIC) * ewma_min; + return (m_progressHasSample) + ? std::min(blended, m_progressBytesPerSecond) + : blended; + } + else + { + return (m_progressHasSample) + ? std::min(ewma_min, m_progressBytesPerSecond) + : ewma_min; + } + } + else + { + const double baseline = m_harmonic_BytesPerSecond; + if (baseline > 0.0) + { + return (m_progressHasSample) + ? std::min(baseline, m_progressBytesPerSecond) + : baseline; + } + return (m_progressHasSample) ? m_progressBytesPerSecond : 0.0; + } +} + +/** + * @brief Get current overhead (TTFB) estimate. + * @return Estimated time to first byte in seconds. + */ +double HarmonicEWMAEstimator::GetTimeToFirstByteSeconds() +{ + return m_estimated_TTFB_seconds; +} + +/** + * @brief Predict completion time for a new segment. + * @param[in] segment_size_bytes Size of the segment in bytes. + * @return Predicted download time in seconds. + */ +double HarmonicEWMAEstimator::GetPredictedDownloadTimeSeconds( + size_t segment_size_bytes) +{ + const double throughput = GetThroughputBytesPerSecond(); + if (throughput > 0.0) + { + return m_estimated_TTFB_seconds + + (static_cast(segment_size_bytes) / throughput); + } + else + { + return 0.0; + } +} + +/** + * @brief Reset the download context. + * @param now Current time in seconds. + */ +void DownloadContext::Reset(const double now) +{ + m_ewma_bytes_per_second = 0.0; + m_dltotal = 0; + m_dlnow_prev = 0; + m_time_prev = now; +} + +/** + * @brief Get estimated remaining time for download. + * @return Estimated remaining time in seconds. + */ +double DownloadContext::GetEstimatedRemainingTime() const +{ + double remaining_time_seconds = 0.0; + const size_t remaining_bytes = m_dltotal - m_dlnow_prev; + if (m_ewma_bytes_per_second > 0.0) + { + remaining_time_seconds = remaining_bytes / m_ewma_bytes_per_second; + } + return remaining_time_seconds; +} + +/** + * @brief Get estimated throughput in bytes per second. + * @return Estimated throughput in bytes per second. + */ +double DownloadContext::GetEstimatedThroughputBytesPerSecond() const +{ + return m_ewma_bytes_per_second; +} + +/** + * @brief Update transfer information. + * @param now Current time in seconds. + * @param dltotal Total bytes to download. + * @param dlnow Bytes downloaded so far. + * @return True if the context was updated, false otherwise. + */ +bool DownloadContext::xferinfo(const double now, size_t dltotal, size_t dlnow) +{ + bool rc = false; + const size_t delta_bytes = dlnow - m_dlnow_prev; + const double delta_time = now - m_time_prev; + m_dltotal = dltotal; + if (delta_bytes > 0) + { // some data has trickled in + if (delta_time > epsilon) + { + const double bytesPerSecond = + static_cast(delta_bytes) / delta_time; + if (m_ewma_bytes_per_second > 0.0) + { + m_ewma_bytes_per_second = m_ewma_short_window_weight * + bytesPerSecond + + (1.0 - m_ewma_short_window_weight) * + m_ewma_bytes_per_second; + } + else + { + m_ewma_bytes_per_second = bytesPerSecond; + } + m_time_prev = now; + m_dlnow_prev = dlnow; + rc = true; + } + } + return rc; +} + +/** + * @brief Construct a Sample from DownloadMetrics + * @param downloadMetrics Download metrics. + */ +Sample::Sample(const DownloadMetrics &downloadMetrics) : m_downloadMetrics(downloadMetrics) +{ + // compute derived values + m_payload_download_time_seconds = std::max( + epsilon, + downloadMetrics.m_total_time_seconds - downloadMetrics.m_time_to_first_byte_seconds); + + m_payload_bytes_per_second = + static_cast(downloadMetrics.m_size_download_bytes) / + m_payload_download_time_seconds; +} + +/** + * @brief Get time to first byte from sample. + * @return Time to first byte in seconds. + */ +double Sample::GetTimeToFirstByteSeconds() const +{ + return m_downloadMetrics.m_time_to_first_byte_seconds; +} + +/** + * @brief Get payload bytes per second from sample. + * @return Payload bytes per second. + */ +double Sample::GetPayloadBytesPerSecond() const +{ + return m_payload_bytes_per_second; +} + +/** + * @brief Get total time from sample. + * @return Total download time in seconds. + */ +double Sample::GetTotalTimeSeconds() const +{ + return m_downloadMetrics.m_total_time_seconds; +} diff --git a/abr/HarmonicEWMAEstimator.h b/abr/HarmonicEWMAEstimator.h new file mode 100644 index 000000000..e57a629c9 --- /dev/null +++ b/abr/HarmonicEWMAEstimator.h @@ -0,0 +1,135 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2026 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef THROUGHPUT_ESTIMATOR_HARMONIC_EWMA_ESTIMATOR_H +#define THROUGHPUT_ESTIMATOR_HARMONIC_EWMA_ESTIMATOR_H + +#include +#include + +#include "BandwidthEstimatorBase.h" + +/** + * @brief Compute the median of the given values in-place. + * + * This function may reorder the contents of @p values for performance + * reasons (for example, by using partial sorting algorithms). Callers + * must not rely on the original order of @p values after this call. + * + * @param values Vector of numeric samples to compute the median from. + * The vector is modified and its element order is not preserved. + * + * @return The median value computed from @p values. + */ +double GetMedian(std::vector &values); +double GetCurrentTimeMonotonicSeconds(); + +/** + * @brief encapsulate performance information for a given http download + */ +class Sample +{ +private: + DownloadMetrics m_downloadMetrics; + + // total_time - time_to_first_byte + double m_payload_download_time_seconds = 0.0; + + // size_download / payload_download_time + double m_payload_bytes_per_second = 0.0; + +public: + /** + * @brief constructor - populate sample metrics + * @param downloadMetrics Download profiling data + */ + Sample(const DownloadMetrics &downloadMetrics); + double GetTimeToFirstByteSeconds() const; + double GetPayloadBytesPerSecond() const; + double GetTotalTimeSeconds() const; +}; + +class DownloadContext +{ +private: + static constexpr double m_ewma_short_window_weight = 0.4; + double m_ewma_bytes_per_second = 0.0; + std::size_t m_dltotal = 0; + std::size_t m_dlnow_prev = 0; + double m_time_prev = 0.0; + +public: + void Reset(const double now); + + double GetEstimatedRemainingTime() const; + double GetEstimatedThroughputBytesPerSecond() const; + + bool xferinfo(const double now, std::size_t dltotal, std::size_t dlnow); +}; + +/** + * @class HarmonicEWMAEstimator + * @brief Maintains network bandwidth state and predicts download performance. + */ +class HarmonicEWMAEstimator + : public BandwidthEstimatorBase +{ +private: + DownloadContext m_progressContext; + bool m_progressContextValid = false; + double m_progressBytesPerSecond = 0.0; + double m_progressLastNowSeconds = 0.0; + std::size_t m_progressLastNowBytes = 0; + std::size_t m_progressLastTotalBytes = 0; + bool m_progressHasSample = false; + + // Rolling history & stats + std::vector m_history; + + // Robust per-request overhead Time to First Byte (TTFB) estimate + double m_estimated_TTFB_seconds = 0.0; // median TTFB - computed brute force + + // Robust throughput estimates (bytes/s) + double m_ewma_fast_BytesPerSecond = 0.0; // reacts quickly + double m_ewma_slow_BytesPerSecond = 0.0; // stable + double m_harmonic_BytesPerSecond = 0.0; // conservative + + // Exponentially Weighted Moving Average (EWMA) tuning + static constexpr double ALPHA_FAST = 0.5; + static constexpr double ALPHA_SLOW = 0.2; + + // Harmonic mean over last N samples. + static constexpr std::size_t harmonic_window = 8; + + void RecomputeHarmonicMeanAndMedianTTFB(); + +public: + HarmonicEWMAEstimator() = default; + + const char *GetNetworkEstimatorName() const override; + void Reset() override; + void AddBandwidthSample(BitsPerSecond downloadbps, bool lowLatencyMode) override; + void UpdateDownloadMetrics(const DownloadMetrics &curl) override; + void UpdateDownloadProgress(const DownloadProgressInfo &progressInfo) override; + BitsPerSecond GetBandwidthBitsPerSecond() override; + double GetThroughputBytesPerSecond() override; + double GetTimeToFirstByteSeconds() override; + double GetPredictedDownloadTimeSeconds(std::size_t segment_size_bytes) override; +}; + +#endif diff --git a/abr/NetworkBandwidthEstimator.cpp b/abr/NetworkBandwidthEstimator.cpp deleted file mode 100644 index 18d058cf1..000000000 --- a/abr/NetworkBandwidthEstimator.cpp +++ /dev/null @@ -1,265 +0,0 @@ -/* - * If not stated otherwise in this file or this component's license file the - * following copyright and licenses apply: - * - * Copyright 2026 RDK Management - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include -#include -#include -#include -#include -#include - -#include "NetworkBandwidthEstimator.h" - -static constexpr double epsilon = 1e-6; -static constexpr double BLEND_WEIGHT_HARMONIC = 0.6; // 60% harmonic, 40% EWMA -static constexpr size_t MAX_HISTORY = 24; // how far back in rolling window samples to consider for bandwidth estimate - -/** - * @brief get clock time as a floating point monotonic value - */ -double GetCurrentTimeMonotonicSeconds() -{ - using clock = std::chrono::steady_clock; - return std::chrono::duration(clock::now().time_since_epoch()).count(); -} - -/** - * @brief given a vector of floating point values, retrieve the median value - * This function uses std::nth_element for O(n) time complexity instead of sorting. - * For performance reasons, we don't make a copy of input, and so order in values may change as side effect of calling this function. - * - * @param values Input vector of floating point values; order may change after calling - * @return The median value, or 0.0 if the input vector is empty - */ -double GetMedian( std::vector &values ) -{ - if( values.empty() ) - { - return 0.0; - } - - const size_t n = values.size(); - const size_t mid = n / 2; - if( n % 2 ) - { // Odd number of elements - find the middle element - std::nth_element(values.begin(), values.begin() + mid, values.end()); - return values[mid]; - } - else - { // Even number of elements - average the two middle elements - // Find the element at mid position - std::nth_element(values.begin(), values.begin() + mid, values.end()); - const double upper = values[mid]; - - // Find the element at mid-1 position (the max of the lower half) - std::nth_element(values.begin(), values.begin() + (mid - 1), values.end()); - const double lower = values[mid - 1]; - - return 0.5 * (lower + upper); - } -} - -Sample::Sample( const CurlInfo &curlInfo ) : m_curlInfo(curlInfo) -{ - // compute derived values - m_payload_download_time_seconds = std::max(epsilon, curlInfo.m_total_time_seconds - curlInfo.m_time_to_first_byte_seconds); - - m_payload_bytes_per_second = static_cast(curlInfo.m_size_download_bytes) / m_payload_download_time_seconds; -} - -double Sample::GetTimeToFirstByteSeconds() const -{ - return m_curlInfo.m_time_to_first_byte_seconds; -} - -double Sample::GetPayloadBytesPerSecond() const -{ - return m_payload_bytes_per_second; -} - -double Sample::GetTotalTimeSeconds() const -{ - return m_curlInfo.m_total_time_seconds; -} - -/** - * @brief Recompute median TTFB and harmonic mean from history; this requires iterating through all recent samples - */ -void NetworkBandwidthEstimator::RecomputeHarmonicMeanAndMedianTTFB() -{ - // Overhead = median TTFB from all samples - std::vector ttfb; - ttfb.reserve(m_history.size()); - for( const auto& s : m_history ) - { - ttfb.push_back(s.GetTimeToFirstByteSeconds() ); - } - m_estimated_TTFB_seconds = GetMedian(ttfb); - - // Harmonic mean of throughput over last harmonic_window samples - const size_t n = m_history.size(); - const size_t start = (n > harmonic_window) ? (n - harmonic_window) : 0; - double denominator = 0.0; - size_t count = 0; - for( size_t i = start; i < n; i++ ) - { - const double payloadBytesPerSecond = m_history[i].GetPayloadBytesPerSecond(); - if( payloadBytesPerSecond > epsilon ) - { - denominator += 1.0/payloadBytesPerSecond; - count++; - } - } - m_harmonic_BytesPerSecond = (count > 0 && denominator > 0.0) ? (static_cast(count) / denominator) : 0.0; -} - -void NetworkBandwidthEstimator::UpdateDownloadMetrics( const CurlInfo &curlInfo ) -{ - Sample sample(curlInfo); - // extract derived payload_bytes_per_second from sample - const double payload_bytes_per_second = sample.GetPayloadBytesPerSecond(); - m_history.emplace_back(sample); - - if (m_history.size() > MAX_HISTORY) - { // Trim history to avoid unbounded growth - m_history.erase(m_history.begin(), m_history.begin() + (m_history.size() - MAX_HISTORY)); - } - - // EWMA updates - if( m_ewma_fast_BytesPerSecond > 0.0 ) - { - m_ewma_fast_BytesPerSecond = ALPHA_FAST * payload_bytes_per_second + (1.0 - ALPHA_FAST) * m_ewma_fast_BytesPerSecond; - } - else - { - m_ewma_fast_BytesPerSecond = payload_bytes_per_second; - } - if( m_ewma_slow_BytesPerSecond > 0.0 ) - { - m_ewma_slow_BytesPerSecond = ALPHA_SLOW * payload_bytes_per_second + (1.0 - ALPHA_SLOW) * m_ewma_slow_BytesPerSecond; - } - else - { - m_ewma_slow_BytesPerSecond = payload_bytes_per_second; - } - RecomputeHarmonicMeanAndMedianTTFB(); -} - -/** - * @brief return current robust throughput estimate (bytes/s), buffer-agnostic - */ -double NetworkBandwidthEstimator::GetThroughputBytesPerSecond() const -{ - double ewma_min = (m_ewma_fast_BytesPerSecond > 0.0 && m_ewma_slow_BytesPerSecond > 0.0) - ? std::min(m_ewma_fast_BytesPerSecond, m_ewma_slow_BytesPerSecond) - : std::max(m_ewma_fast_BytesPerSecond, m_ewma_slow_BytesPerSecond); - if( ewma_min > 0.0 ) - { - if (m_harmonic_BytesPerSecond > 0.0) - { - return BLEND_WEIGHT_HARMONIC * m_harmonic_BytesPerSecond + (1.0 - BLEND_WEIGHT_HARMONIC) * ewma_min; - } - else - { - return ewma_min; - } - } - else - { - return m_harmonic_BytesPerSecond; - } -} - -/** - * @brief return current overhead (TTFB) estimate (seconds) - */ -double NetworkBandwidthEstimator::GetTimeToFirstByteSeconds() const -{ - return m_estimated_TTFB_seconds; -} - -/** - * @brief predict completion time for a new segment - */ -double NetworkBandwidthEstimator::GetPredictedDownloadTimeSeconds(size_t segment_size_bytes) const -{ - const double throughput = GetThroughputBytesPerSecond(); - if( throughput > 0.0 ) - { - return m_estimated_TTFB_seconds + (static_cast(segment_size_bytes) / throughput); - } - else - { // no history data to make estimate - return 0.0; - } -} - -void DownloadContext::Reset( const double now ) -{ - m_ewma_bytes_per_second = 0.0; - m_dltotal = 0; - m_dlnow_prev = 0; - m_time_prev = now; -} - -double DownloadContext::GetEstimatedRemainingTime() const -{ - double remaining_time_seconds = 0.0; - const size_t remaining_bytes = m_dltotal - m_dlnow_prev; - if( m_ewma_bytes_per_second > 0.0 ) - { - remaining_time_seconds = remaining_bytes / m_ewma_bytes_per_second; - } - return remaining_time_seconds; -} - -double DownloadContext::GetEstimatedThroughputBytesPerSecond() const -{ - return m_ewma_bytes_per_second; -} - -/** - * @param dltotal total bytes to download - * @param dlnow downloaded bytes so far - */ -bool DownloadContext::xferinfo( const double now, size_t dltotal, size_t dlnow ) -{ - bool rc = false; - const size_t delta_bytes = dlnow - m_dlnow_prev; - const double delta_time = now - m_time_prev; - m_dltotal = dltotal; - if( delta_bytes > 0 ) - { // some data has trickled in - if( delta_time > epsilon ) - { - const double bytesPerSecond = static_cast(delta_bytes)/delta_time; - if( m_ewma_bytes_per_second > 0.0 ) - { - m_ewma_bytes_per_second = m_ewma_short_window_weight * bytesPerSecond + (1.0 - m_ewma_short_window_weight) * m_ewma_bytes_per_second; - } - else - { - m_ewma_bytes_per_second = bytesPerSecond; - } - m_time_prev = now; - m_dlnow_prev = dlnow; - rc = true; - } - } - return rc; -} diff --git a/abr/NetworkBandwidthEstimator.h b/abr/NetworkBandwidthEstimator.h deleted file mode 100644 index 65a0964a9..000000000 --- a/abr/NetworkBandwidthEstimator.h +++ /dev/null @@ -1,182 +0,0 @@ -/* - * If not stated otherwise in this file or this component's license file the - * following copyright and licenses apply: - * - * Copyright 2026 RDK Management - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#ifndef THROUGHPUT_ESTIMATOR_NETWORK_BANDWIDTH_ESTIMATOR_H -#define THROUGHPUT_ESTIMATOR_NETWORK_BANDWIDTH_ESTIMATOR_H - -#include -#include - -/** - * @brief Compute the median of the given values in-place. - * - * This function may reorder the contents of @p values for performance - * reasons (for example, by using partial sorting algorithms). Callers - * must not rely on the original order of @p values after this call. - * - * @param values Vector of numeric samples to compute the median from. - * The vector is modified and its element order is not preserved. - * - * @return The median value computed from @p values. - */ -double GetMedian( std::vector &values ); -double GetCurrentTimeMonotonicSeconds(); - -/** - * @brief Plain Old Data (POD) structure for profiling information from a given CURL instance - */ -struct CurlInfo -{ - std::size_t m_size_download_bytes; // CURLINFO_SIZE_DOWNLOAD - double m_total_time_seconds; // CURLINFO_TOTAL_TIME - double m_time_to_first_byte_seconds; // CURLINFO_STARTTRANSFER_TIME -}; - -/** - * @brief encapsulate performance information for a given http download - */ -class Sample -{ -private: - CurlInfo m_curlInfo; - - // total_time - time_to_first_byte - double m_payload_download_time_seconds = 0.0; - - // size_download / payload_download_time - double m_payload_bytes_per_second = 0.0; - -public: - /** - * @brief constructor - populate sample metrics - * @param curlInfo Curl Handle profiling data - */ - Sample( const CurlInfo &curlInfo ); - double GetTimeToFirstByteSeconds() const; - double GetPayloadBytesPerSecond() const; - double GetTotalTimeSeconds() const; -}; - -/** - * @class NetworkBandwidthEstimator - * @brief Maintains network bandwidth state and predicts download performance. - * - * This class collects per-request download samples (size, total time and - * time-to-first-byte) and derives robust throughput estimates that can be - * consumed by Adaptive Bitrate (ABR) decision logic. It maintains two - * Exponentially Weighted Moving Average (EWMA) filters over the measured - * payload throughput: a fast EWMA that reacts quickly to changes and a - * slow EWMA that provides a more stable baseline. - * - * In addition, it computes a conservative harmonic-mean throughput over a - * sliding window of recent samples and blends this with the EWMA-based - * estimates. The combination of EWMA smoothing and harmonic mean blending - * yields a bandwidth estimate that is responsive to real network changes - * while remaining resilient to outliers, and is used to predict segment - * download times and guide ABR bitrate selection. - */ -class NetworkBandwidthEstimator -{ -private: - // Rolling history & stats - std::vector m_history; - - // Robust per-request overhead Time to First Byte (TTFB) estimate - double m_estimated_TTFB_seconds = 0.0; // median TTFB - computed brute force - - // Robust throughput estimates (bytes/s) - double m_ewma_fast_BytesPerSecond = 0.0; // reacts quickly - double m_ewma_slow_BytesPerSecond = 0.0; // stable - double m_harmonic_BytesPerSecond = 0.0; // conservative - - // Exponentially Weighted Moving Average (EWMA) tuning - static constexpr double ALPHA_FAST = 0.5; - static constexpr double ALPHA_SLOW = 0.2; - - // Harmonic mean over last N samples. - // A window of 8 samples provides a balance between responsiveness and - // stability: it smooths out short spikes while still tracking recent - // network conditions closely enough for ABR decisions. - static constexpr std::size_t harmonic_window = 8; - - /** - * @brief Recompute median TTFB and harmonic mean from history; this requires iterating through all recent samples - */ - void RecomputeHarmonicMeanAndMedianTTFB(); - -public: - NetworkBandwidthEstimator() = default; - - void UpdateDownloadMetrics( const CurlInfo &curl ); - - /** - * @brief return current robust throughput estimate (bytes/s), buffer-agnostic - */ - double GetThroughputBytesPerSecond() const; - - /** - * @brief return current overhead (TTFB) estimate (seconds) - */ - double GetTimeToFirstByteSeconds() const; - - /** - * @brief predict completion time for a new segment - */ - double GetPredictedDownloadTimeSeconds(std::size_t segment_size_bytes) const; -}; - -class DownloadContext -{ -private: - /** - * @brief Smoothing factor for short-window EWMA throughput estimate. - * - * This weight acts as the alpha parameter in an Exponentially Weighted - * Moving Average that tracks the instantaneous download rate reported - * by periodic progress callbacks (see xferinfo()). - * - * A value of 0.4 biases the estimate toward the most recent progress - * interval (real-time behavior) while still retaining enough history - * to dampen noise from very short spikes or stalls. This trade-off - * was chosen to make the remaining-time and throughput estimates react - * quickly to genuine bandwidth changes without causing large UI jumps - * between successive callbacks. - */ - static constexpr double m_ewma_short_window_weight = 0.4; - double m_ewma_bytes_per_second = 0.0; - std::size_t m_dltotal = 0; - std::size_t m_dlnow_prev = 0; - double m_time_prev = 0.0; - -public: - void Reset( const double now ); - - double GetEstimatedRemainingTime() const; - double GetEstimatedThroughputBytesPerSecond() const; - - /** - * @brief monitor download progress - * @note: name and parameters are based on CURLOPT_XFERINFOFUNCTION - * - * @param dltotal total bytes to download - * @param dlnow downloaded bytes so far - * @retval true if more bytes have been transferred since last call - */ - bool xferinfo( const double now, std::size_t dltotal, std::size_t dlnow ); -}; -#endif diff --git a/abr/RMOBandwidthEstimator.cpp b/abr/RMOBandwidthEstimator.cpp new file mode 100644 index 000000000..eb4d1fa9c --- /dev/null +++ b/abr/RMOBandwidthEstimator.cpp @@ -0,0 +1,275 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2026 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "RMOBandwidthEstimator.h" + +#include +#include + +static constexpr size_t DEFAULT_ABR_CHUNK_CACHE_LENGTH = 10; + +/** + * @brief Get current time in milliseconds. + * @return Current time in milliseconds. + */ +long long RMOBandwidthEstimator::GetCurrentTimeMs() +{ + struct timeval t; + gettimeofday(&t, NULL); + return static_cast(t.tv_sec * 1e3 + t.tv_usec * 1e-3); +} + +/** + * @brief Constructor. + */ +RMOBandwidthEstimator::RMOBandwidthEstimator() + : mConfig(0, 0, 0, DEFAULT_ABR_CHUNK_CACHE_LENGTH), + mAbrBitrateData() +{ +} + +/** + * @brief Constructor with configuration. + * @param[in] config Configuration parameters. + */ +RMOBandwidthEstimator::RMOBandwidthEstimator(const BandwidthEstimatorConfig &config) + : mConfig(config), + mAbrBitrateData() +{ +} + +/** + * @brief Set configuration parameters. + * @param[in] config Configuration parameters. + */ +void RMOBandwidthEstimator::SetConfig(const BandwidthEstimatorConfig &config) +{ + mConfig = config; +} + +/** + * @brief Get current configuration parameters. + * @return Configuration parameters. + */ +BandwidthEstimatorConfig RMOBandwidthEstimator::GetConfig() const +{ + return mConfig; +} + +/** + * @brief Update ABR bitrate data based on cache length. + * @param[in] downloadbps Download bandwidth in bits per second. + * @param[in] lowLatencyMode Whether low latency mode is enabled. + */ +void RMOBandwidthEstimator::UpdateABRBitrateDataBasedOnCacheLength(BitsPerSecond downloadbps, bool lowLatencyMode) +{ + mAbrBitrateData.push_back(std::make_pair(GetCurrentTimeMs(), downloadbps)); + + if (lowLatencyMode) + { + if (mAbrBitrateData.size() > mConfig.mLowLatencyCacheLength) + { + mAbrBitrateData.erase(mAbrBitrateData.begin()); + } + } + else + { + if (mAbrBitrateData.size() > static_cast(mConfig.mAbrCacheLength)) + { + mAbrBitrateData.erase(mAbrBitrateData.begin()); + } + } +} + +/** + * @brief Update ABR bitrate data based on cache life. + * @param[out] tmpData Temporary vector to store valid bitrate samples. + */ +void RMOBandwidthEstimator::UpdateABRBitrateDataBasedOnCacheLife(std::vector &tmpData) +{ + long long presentTime = GetCurrentTimeMs(); + for (auto bitrateIter = mAbrBitrateData.begin(); + bitrateIter != mAbrBitrateData.end();) + { + if ((bitrateIter->first <= 0) || + (presentTime - bitrateIter->first > + mConfig.mAbrCacheLife)) + { + bitrateIter = mAbrBitrateData.erase(bitrateIter); + } + else + { + tmpData.push_back(bitrateIter->second); + bitrateIter++; + } + } +} + +/** + * @brief Update ABR bitrate data based on cache outlier. + * @param[in,out] tmpData Temporary vector containing valid bitrate samples. + * @return Calculated average bitrate after removing outliers, or -1 if unavailable. + */ +BitsPerSecond RMOBandwidthEstimator::UpdateABRBitrateDataBasedOnCacheOutlier(std::vector &tmpData) +{ + BitsPerSecond ret = -1; + BitsPerSecond medianbps = 0; + + std::sort(tmpData.begin(), tmpData.end()); + if (tmpData.size() % 2) + { + medianbps = tmpData.at(tmpData.size() / 2); + } + else + { + BitsPerSecond m1 = tmpData.at(tmpData.size() / 2 - 1); + BitsPerSecond m2 = tmpData.at(tmpData.size() / 2); + medianbps = (m1 + m2) / 2; + } + + long diffOutlier = 0; + BitsPerSecond avg = 0; + int abrOutlierDiffBytes = mConfig.mAbrCacheOutlier; + for (auto tmpDataIter = tmpData.begin(); + tmpDataIter != tmpData.end();) + { + diffOutlier = (*tmpDataIter) > medianbps + ? (*tmpDataIter) - medianbps + : medianbps - (*tmpDataIter); + if (diffOutlier > abrOutlierDiffBytes) + { + tmpDataIter = tmpData.erase(tmpDataIter); + } + else + { + avg += (*tmpDataIter); + tmpDataIter++; + } + } + + if (tmpData.size()) + { + ret = (avg / static_cast(tmpData.size())); + } + else + { + ret = -1; + } + + return ret; +} + +/** + * @brief Get human-readable algorithm name. + * @return Algorithm name. + */ +const char *RMOBandwidthEstimator::GetNetworkEstimatorName() const +{ + return "Rolling_Median_Outlier_Average"; +} + +/** + * @brief Reset internal state. + */ +void RMOBandwidthEstimator::Reset() +{ + mAbrBitrateData.clear(); +} + +/** + * @brief Add a bandwidth sample. + * @param[in] downloadbps Download bandwidth in bits per second. + * @param[in] lowLatencyMode Whether low latency mode is enabled. + */ +void RMOBandwidthEstimator::AddBandwidthSample( + BitsPerSecond downloadbps, bool lowLatencyMode) +{ + UpdateABRBitrateDataBasedOnCacheLength(downloadbps, lowLatencyMode); +} + +/** + * @brief Add download metrics derived from curl (optional). + * @param[in] metrics Download metrics. + */ +void RMOBandwidthEstimator::UpdateDownloadMetrics( + const DownloadMetrics &metrics) +{ + (void)metrics; +} + +/** + * @brief Get current bandwidth estimate. + * @return Estimated bandwidth in bits per second, or -1 if unavailable. + */ +BitsPerSecond RMOBandwidthEstimator::GetBandwidthBitsPerSecond() +{ + std::vector tmpData; + UpdateABRBitrateDataBasedOnCacheLife(tmpData); + if (tmpData.empty()) + { + return -1; + } + return UpdateABRBitrateDataBasedOnCacheOutlier(tmpData); +} + +/** + * @brief Get current robust throughput estimate. + * @return Estimated throughput in bytes per second. + */ +double RMOBandwidthEstimator::GetThroughputBytesPerSecond() +{ + const BitsPerSecond bandwidthBitsPerSecond = GetBandwidthBitsPerSecond(); + if (bandwidthBitsPerSecond <= 0) + { + return 0.0; + } + return static_cast(bandwidthBitsPerSecond) / 8.0; +} + +/** + * @brief Get current overhead (TTFB) estimate. + * @return Estimated time to first byte in seconds. + */ +double RMOBandwidthEstimator::GetTimeToFirstByteSeconds() +{ + return 0.0; +} + +/** + * @brief Predict completion time for a new segment. + * @param[in] segment_size_bytes Size of the segment in bytes. + * @return Predicted download time in seconds. + */ +double RMOBandwidthEstimator::GetPredictedDownloadTimeSeconds(std::size_t segment_size_bytes) +{ + const BitsPerSecond bandwidthBitsPerSecond = GetBandwidthBitsPerSecond(); + if (bandwidthBitsPerSecond <= 0) + { + return 0.0; + } + return (static_cast(segment_size_bytes) * 8.0) / + static_cast(bandwidthBitsPerSecond); +} + +/** + * @brief Reset only the currently available bandwidth estimate. + */ +void RMOBandwidthEstimator::ResetCurrentlyAvailableBandwidth() +{ + mAbrBitrateData.clear(); +} \ No newline at end of file diff --git a/abr/RMOBandwidthEstimator.h b/abr/RMOBandwidthEstimator.h new file mode 100644 index 000000000..92d2dbf23 --- /dev/null +++ b/abr/RMOBandwidthEstimator.h @@ -0,0 +1,58 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2026 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef ROLLING_MEDIAN_OUTLIER_BANDWIDTH_ESTIMATOR_H +#define ROLLING_MEDIAN_OUTLIER_BANDWIDTH_ESTIMATOR_H + +#include +#include + +#include "BandwidthEstimatorBase.h" + +/** + * @brief Bandwidth estimator using the legacy ABR algorithm: + * rolling-window samples + median + outlier rejection + average. + */ +class RMOBandwidthEstimator : public BandwidthEstimatorBase +{ +private: + BandwidthEstimatorConfig mConfig; + std::vector> mAbrBitrateData; + + static long long GetCurrentTimeMs(); + void UpdateABRBitrateDataBasedOnCacheLength(BitsPerSecond downloadbps, bool lowLatencyMode); + void UpdateABRBitrateDataBasedOnCacheLife(std::vector &tmpData); + BitsPerSecond UpdateABRBitrateDataBasedOnCacheOutlier(std::vector &tmpData); + +public: + RMOBandwidthEstimator(); + explicit RMOBandwidthEstimator(const BandwidthEstimatorConfig &config); + void SetConfig(const BandwidthEstimatorConfig &config) override; + BandwidthEstimatorConfig GetConfig() const; + const char *GetNetworkEstimatorName() const override; + void Reset() override; + void AddBandwidthSample(BitsPerSecond downloadbps, bool lowLatencyMode) override; + void UpdateDownloadMetrics(const DownloadMetrics &metrics) override; + BitsPerSecond GetBandwidthBitsPerSecond() override; + double GetThroughputBytesPerSecond() override; + double GetTimeToFirstByteSeconds() override; + double GetPredictedDownloadTimeSeconds(std::size_t segment_size_bytes) override; + void ResetCurrentlyAvailableBandwidth() override; +}; + +#endif diff --git a/abr/abr.cpp b/abr/abr.cpp index 446d8d14b..4b4f259a8 100644 --- a/abr/abr.cpp +++ b/abr/abr.cpp @@ -20,6 +20,9 @@ ***************************************************/ #include "abr.h" +#include "RMOBandwidthEstimator.h" +#include "HarmonicEWMAEstimator.h" +#include "BandwidthEstimatorBase.h" #include #include #include @@ -42,6 +45,165 @@ ABRManager::AampAbrConfig eAAMPAbrConfig; BitsPerSecond ABRManager::mPersistBandwidth = 0; long long ABRManager::mPersistBandwidthUpdatedTime = 0; +ABRManager::ABRManager() + : bLowLatencyStartABR(false), + bLowLatencyServiceConfigured(false), + mProfiles(), + mSortedBWProfileList(), + mProfileLock(), + mBandwidthState(), + mBandwidthEstimatorType(BANDWIDTH_ESTIMATOR_TYPE_ROLLING_MEDIAN_OUTLIER), + mBandwidthEstimator(), + mBandwidthEstimatorLock() +{ + SetBandwidthEstimatorType(mBandwidthEstimatorType); +} + +ABRManager::~ABRManager() = default; + +void ABRManager::SetBandwidthEstimatorType(BandwidthEstimatorType type) +{ + std::lock_guard lock(mBandwidthEstimatorLock); + mBandwidthEstimatorType = type; + + switch (type) + { + case BANDWIDTH_ESTIMATOR_TYPE_HARMONIC_EWMA: + mBandwidthEstimator.reset(new HarmonicEWMAEstimator()); + break; + + case BANDWIDTH_ESTIMATOR_TYPE_ROLLING_MEDIAN_OUTLIER: + default: + { + BandwidthEstimatorConfig config; + config.mAbrCacheLife = eAAMPAbrConfig.abrCacheLife; + config.mAbrCacheLength = eAAMPAbrConfig.abrCacheLength; + config.mAbrCacheOutlier = eAAMPAbrConfig.abrCacheOutlier; + config.mLowLatencyCacheLength = DEFAULT_ABR_CHUNK_CACHE_LENGTH; + mBandwidthEstimator.reset(new RMOBandwidthEstimator(config)); + } + break; + } + AAMPLOG_WARN("Setting ABR Bandwidth Estimator type to %s", mBandwidthEstimator->GetNetworkEstimatorName()); + + // This is to initialize the bandwidth state from the newly created estimator + (void)UpdateBandwidthStateFromEstimatorLocked(); +} + +BandwidthEstimatorType ABRManager::GetBandwidthEstimatorType() const +{ + std::lock_guard lock(mBandwidthEstimatorLock); + return mBandwidthEstimatorType; +} + +void ABRManager::AddBandwidthSample(BitsPerSecond downloadbps, bool lowLatencyMode) +{ + std::lock_guard lock(mBandwidthEstimatorLock); + if (mBandwidthEstimator) + { + mBandwidthEstimator->AddBandwidthSample(downloadbps, lowLatencyMode); + (void)UpdateBandwidthStateFromEstimatorLocked(); + } +} + +void ABRManager::ReportDownloadComplete( + BitsPerSecond downloadbps, + bool lowLatencyMode, + const DownloadMetrics &metrics) +{ + std::lock_guard lock(mBandwidthEstimatorLock); + if (!mBandwidthEstimator) + { + return; + } + if (downloadbps > 0) + { + mBandwidthEstimator->AddBandwidthSample(downloadbps, lowLatencyMode); + } + mBandwidthEstimator->UpdateDownloadMetrics(metrics); + (void)UpdateBandwidthStateFromEstimatorLocked(); +} + +void ABRManager::ReportDownloadProgress( + BitsPerSecond downloadbps, + bool lowLatencyMode, + const DownloadProgressInfo &progressInfo) +{ + std::lock_guard lock(mBandwidthEstimatorLock); + if (!mBandwidthEstimator) + { + return; + } + mBandwidthEstimator->UpdateDownloadProgress(progressInfo); + if (downloadbps > 0) + { + mBandwidthEstimator->AddBandwidthSample(downloadbps, lowLatencyMode); + } + (void)UpdateBandwidthStateFromEstimatorLocked(); +} + +BitsPerSecond ABRManager::UpdateBandwidthStateFromEstimatorLocked() +{ + if (!mBandwidthEstimator) + { + mBandwidthState.availableBandwidth = static_cast(-1); + return mBandwidthState.availableBandwidth; + } + + const BitsPerSecond estimate = mBandwidthEstimator->GetBandwidthBitsPerSecond(); + mBandwidthState.availableBandwidth = estimate; + if (estimate != static_cast(-1)) + { + mBandwidthState.networkBandwidth = estimate; + } + return estimate; +} + +void ABRManager::SetInitialBandwidthForProfile(BitsPerSecond bitsPerSecond, bool trickPlay, int profile) +{ + (void)trickPlay; + (void)profile; + + std::lock_guard lock(mBandwidthEstimatorLock); + mBandwidthState.availableBandwidth = bitsPerSecond; + mBandwidthState.networkBandwidth = bitsPerSecond; + + if (mBandwidthEstimator) + { + mBandwidthEstimator->Reset(); + if (bitsPerSecond > 0) + { + mBandwidthEstimator->AddBandwidthSample(bitsPerSecond, false); + } + (void)UpdateBandwidthStateFromEstimatorLocked(); + } +} + +void ABRManager::ResetCurrentlyAvailableBandwidth() +{ + std::lock_guard lock(mBandwidthEstimatorLock); + mBandwidthEstimator->ResetCurrentlyAvailableBandwidth(); +} + +BitsPerSecond ABRManager::GetCurrentlyAvailableBandwidth() +{ + std::lock_guard lock(mBandwidthEstimatorLock); + return UpdateBandwidthStateFromEstimatorLocked(); +} + +BitsPerSecond ABRManager::GetNetworkBandwidth() +{ + std::lock_guard lock(mBandwidthEstimatorLock); + (void)UpdateBandwidthStateFromEstimatorLocked(); + return mBandwidthState.networkBandwidth; +} + +bool ABRManager::HasBandwidthEstimator() const +{ + std::lock_guard lock(mBandwidthEstimatorLock); + return (mBandwidthEstimator.get() != nullptr); +} + /** * @brief Get initial profile index, choose the medium profile or * the profile whose bitrate >= the default bitrate. @@ -706,6 +868,7 @@ void ABRManager::ReadPlayerConfig(AampAbrConfig *mAampAbrConfig) eAAMPAbrConfig.abrMinBuffer = mAampAbrConfig->abrMinBuffer; eAAMPAbrConfig.abrCacheOutlier = mAampAbrConfig->abrCacheOutlier; eAAMPAbrConfig.abrBufferCounter = mAampAbrConfig->abrBufferCounter; + eAAMPAbrConfig.bandwidthEstimatorType = mAampAbrConfig->bandwidthEstimatorType; //Logging Level @@ -713,7 +876,29 @@ void ABRManager::ReadPlayerConfig(AampAbrConfig *mAampAbrConfig) eAAMPAbrConfig.debuglogging = mAampAbrConfig->debuglogging; eAAMPAbrConfig.tracelogging = mAampAbrConfig->tracelogging; eAAMPAbrConfig.warnlogging = mAampAbrConfig->warnlogging; - AAMPLOG_MIL("ABRCacheLife %d, ABRCacheLength %d, ABRSkipDuration %d, ABRNwConsistency %d, ABRThresholdSize %d, ABRMaxBuffer %d, ABRMinBuffer %d ABRCacheOutlier %d ABRBufferCounter %d ",eAAMPAbrConfig.abrCacheLife,eAAMPAbrConfig.abrCacheLength,eAAMPAbrConfig.abrSkipDuration,eAAMPAbrConfig.abrNwConsistency,eAAMPAbrConfig.abrThresholdSize,eAAMPAbrConfig.abrMaxBuffer,eAAMPAbrConfig.abrMinBuffer,eAAMPAbrConfig.abrCacheOutlier,eAAMPAbrConfig.abrBufferCounter); + AAMPLOG_MIL("ABRCacheLife %d, ABRCacheLength %d, ABRSkipDuration %d, ABRNwConsistency %d, ABRThresholdSize %d, ABRMaxBuffer %d, ABRMinBuffer %d ABRCacheOutlier %d ABRBufferCounter %d ABRBandwidthEstimator %d",eAAMPAbrConfig.abrCacheLife,eAAMPAbrConfig.abrCacheLength,eAAMPAbrConfig.abrSkipDuration,eAAMPAbrConfig.abrNwConsistency,eAAMPAbrConfig.abrThresholdSize,eAAMPAbrConfig.abrMaxBuffer,eAAMPAbrConfig.abrMinBuffer,eAAMPAbrConfig.abrCacheOutlier,eAAMPAbrConfig.abrBufferCounter,eAAMPAbrConfig.bandwidthEstimatorType); + + if (eAAMPAbrConfig.bandwidthEstimatorType < BANDWIDTH_ESTIMATOR_TYPE_MAX) + { + bool needNewEstimator = false; + { + std::lock_guard lock(mBandwidthEstimatorLock); + needNewEstimator = (!mBandwidthEstimator || (mBandwidthEstimatorType != static_cast(eAAMPAbrConfig.bandwidthEstimatorType))); + if (!needNewEstimator) + { + BandwidthEstimatorConfig config; + config.mAbrCacheLife = eAAMPAbrConfig.abrCacheLife; + config.mAbrCacheLength = eAAMPAbrConfig.abrCacheLength; + config.mAbrCacheOutlier = eAAMPAbrConfig.abrCacheOutlier; + config.mLowLatencyCacheLength = DEFAULT_ABR_CHUNK_CACHE_LENGTH; + mBandwidthEstimator->SetConfig(config); + } + } + if (needNewEstimator) + { + SetBandwidthEstimatorType(static_cast(eAAMPAbrConfig.bandwidthEstimatorType)); + } + } } /** @@ -745,101 +930,6 @@ BitsPerSecond ABRManager::CheckAbrThresholdSize(int bufferlen, int downloadTimeM return downloadbps; } -/** - * @brief Function to Update Persisted Recent Download Statistics Based on Cache Length - * @return none - */ -void ABRManager::UpdateABRBitrateDataBasedOnCacheLength(std::vector > &mAbrBitrateData, BitsPerSecond downloadbps, bool LowLatencyMode) -{ - mAbrBitrateData.push_back(std::make_pair(ABRGetCurrentTimeMS(), downloadbps)); - if(LowLatencyMode) - { - if(mAbrBitrateData.size() > DEFAULT_ABR_CHUNK_CACHE_LENGTH) - mAbrBitrateData.erase(mAbrBitrateData.begin()); - } - else - { - if(mAbrBitrateData.size() > eAAMPAbrConfig.abrCacheLength) - mAbrBitrateData.erase(mAbrBitrateData.begin()); - } -} - -/** - * @brief Function to Update Persisted Recent Download Statistics Based on abrCacheLife - * @return none - */ -void ABRManager::UpdateABRBitrateDataBasedOnCacheLife(std::vector> &mAbrBitrateData, std::vector &tmpData) -{ - std::vector>::iterator bitrateIter; - long long presentTime = ABRGetCurrentTimeMS(); - for (bitrateIter = mAbrBitrateData.begin(); bitrateIter != mAbrBitrateData.end();) - { - if ((bitrateIter->first <= 0) || (presentTime - bitrateIter->first > eAAMPAbrConfig.abrCacheLife)) - { - bitrateIter = mAbrBitrateData.erase(bitrateIter); - } - else - { - tmpData.push_back(bitrateIter->second); - bitrateIter++; - } - } -} - -/** - * @brief Function to Update Persisted Recent Download Statistics Based on ABRCacheOutlier and calculate bw - * @return Available bandwidth in bps - */ -BitsPerSecond ABRManager::UpdateABRBitrateDataBasedOnCacheOutlier(std::vector &tmpData) -{ - BitsPerSecond ret = -1; - std::vector::iterator tmpDataIter; - BitsPerSecond medianbps=0; - - std::sort(tmpData.begin(),tmpData.end()); - if (tmpData.size()%2) - { // odd - pick middle - medianbps = tmpData.at(tmpData.size()/2); - } - else - { - BitsPerSecond m1 = tmpData.at(tmpData.size()/2 - 1); - BitsPerSecond m2 = tmpData.at(tmpData.size()/2); - medianbps = (m1+m2)/2; - } - - long diffOutlier = 0; - BitsPerSecond avg = 0; - int abrOutlierDiffBytes = eAAMPAbrConfig.abrCacheOutlier ; - for (tmpDataIter = tmpData.begin();tmpDataIter != tmpData.end();) - { - diffOutlier = (*tmpDataIter) > medianbps ? (*tmpDataIter) - medianbps : medianbps - (*tmpDataIter); - if (diffOutlier > abrOutlierDiffBytes) - { - //AAMPLOG_WARN("Outlier found[%ld]>[%ld] erasing ....",diffOutlier,abrOutlierDiffBytes); - tmpDataIter = tmpData.erase(tmpDataIter); - } - else - { - avg += (*tmpDataIter); - tmpDataIter++; - } - } - if (tmpData.size()) - { - //AAMPLOG_WARN("NwBW with newlogic size[%d] avg[%ld] ",tmpData.size(), avg/tmpData.size()); - ret = (avg/tmpData.size()); - //Store the PersistBandwidth and UpdatedTime on ABRManager - //Bitrate Update only for foreground player - } - else - { - //AAMPLOG_WARN("No prior data available for abr, return -1 "); - ret = -1; - } - return ret; -} - /* * @brief Function for ABR check for each segment download * @return bool true if profilechange needed else false diff --git a/abr/abr.h b/abr/abr.h index aa64e5870..6b3ce06b3 100644 --- a/abr/abr.h +++ b/abr/abr.h @@ -26,13 +26,42 @@ #include #include #include +#include #include #include "AampMediaType.h" +#include "BandwidthEstimatorBase.h" +class BandwidthEstimatorBase; + +enum BandwidthEstimatorType +{ + BANDWIDTH_ESTIMATOR_TYPE_ROLLING_MEDIAN_OUTLIER = 0, + BANDWIDTH_ESTIMATOR_TYPE_HARMONIC_EWMA = 1, + BANDWIDTH_ESTIMATOR_TYPE_MAX = 2 +}; class ABRManager { public: - ~ABRManager() = default; + ABRManager(); + ~ABRManager(); + + void SetBandwidthEstimatorType(BandwidthEstimatorType type); + BandwidthEstimatorType GetBandwidthEstimatorType() const; + + void AddBandwidthSample(BitsPerSecond downloadbps, bool lowLatencyMode); + void ReportDownloadComplete( + BitsPerSecond downloadbps, + bool lowLatencyMode, + const DownloadMetrics &metrics); + void ReportDownloadProgress( + BitsPerSecond downloadbps, + bool lowLatencyMode, + const DownloadProgressInfo &progressInfo); + void SetInitialBandwidthForProfile(BitsPerSecond bitsPerSecond, bool trickPlay, int profile = 0); + void ResetCurrentlyAvailableBandwidth(); + BitsPerSecond GetCurrentlyAvailableBandwidth(); + BitsPerSecond GetNetworkBandwidth(); + bool HasBandwidthEstimator() const; struct ProfileInfo { /** @@ -334,13 +363,18 @@ class ABRManager * @brief Enables Debug Logging */ bool debuglogging; + + /** + * @brief Sets Bandwidth Estimator Configurations + */ + int bandwidthEstimatorType; // Constructor to initialize all members AampAbrConfig() : abrCacheLife(0), abrCacheLength(0), abrSkipDuration(0), abrNwConsistency(0), abrThresholdSize(0), abrMaxBuffer(0), abrMinBuffer(0), abrCacheOutlier(0), abrBufferCounter(0), infologging(false), tracelogging(false), - warnlogging(false), debuglogging(false) {} + warnlogging(false), debuglogging(false), bandwidthEstimatorType(0) {} }; /** @@ -390,29 +424,6 @@ class ABRManager */ BitsPerSecond CheckAbrThresholdSize(int bufferlen, int downloadTimeMs, BitsPerSecond currentProfilebps, int fragmentDurationMs, CurlAbortReason abortReason); - /** - * @brief to update Bitrate Data - * @param mAbrBitrateData collection of recent (timestamp, estimated network bandwidth) samples - * @param downloadbps most recent estimate of network bandwidth - * @param lowLatencyMode true if playing low-latency stream - */ - void UpdateABRBitrateDataBasedOnCacheLength(std::vector> &mAbrBitrateData, BitsPerSecond downloadbps, bool LowLatencyMode ); - - /** - * @brief Update Bitrate Data based on ABR CacheLife - * @param BitrateData vector - * @param tmpData vector - * @return none - */ - void UpdateABRBitrateDataBasedOnCacheLife(std::vector> &mAbrBitrateData, std::vector &tmpData); - - /** - * @brief Update Bitrate Data based on ABRCacheOutlier - * @param tmpData vector - * @return none - */ - BitsPerSecond UpdateABRBitrateDataBasedOnCacheOutlier(std::vector &tmpData); - /** * @brief Checks if a profile change is needed based on the most recently recorded network bandwidth samples and total fetched fragment duration. * @param totalFetchedDuration - Total fragment fetched duration @@ -604,5 +615,24 @@ class ABRManager * @brief Mutex for make internal structures thread safe */ std::mutex mProfileLock; + + struct BandwidthState + { + BandwidthState() + : availableBandwidth(0) + , networkBandwidth(0) + { + } + + BitsPerSecond availableBandwidth; + BitsPerSecond networkBandwidth; + }; + + BitsPerSecond UpdateBandwidthStateFromEstimatorLocked(); + BandwidthState mBandwidthState; + BandwidthEstimatorType mBandwidthEstimatorType; + + std::unique_ptr mBandwidthEstimator; + mutable std::mutex mBandwidthEstimatorLock; }; #endif // !ABR_H diff --git a/fragmentcollector_hls.cpp b/fragmentcollector_hls.cpp index f90b98ec9..a6a3e4425 100644 --- a/fragmentcollector_hls.cpp +++ b/fragmentcollector_hls.cpp @@ -3347,7 +3347,7 @@ AAMPStatusType StreamAbstractionAAMP_HLS::Init(TuneType tuneType) { if (!newTune) { - long persistedBandwidth = aamp->GetPersistedBandwidth(); + long persistedBandwidth = static_cast(aamp->mhAbrManager.GetNetworkBandwidth()); long defaultBitRate = aamp->GetDefaultBitrate(); //We were tuning to a lesser profile previously, so we use it as starting profile // If bitrate to be persisted during trickplay is true, set persisted BW as default init BW @@ -3439,7 +3439,7 @@ AAMPStatusType StreamAbstractionAAMP_HLS::Init(TuneType tuneType) currentProfileIndex = GetDesiredProfile(false); HlsStreamInfo *streamInfo = (HlsStreamInfo*)GetStreamInfo(currentProfileIndex); BitsPerSecond bandwidthBitsPerSecond = streamInfo->bandwidthBitsPerSecond; - aamp->ResetCurrentlyAvailableBandwidth(bandwidthBitsPerSecond, trickplayMode, currentProfileIndex); + aamp->mhAbrManager.ResetCurrentlyAvailableBandwidth(); aamp->profiler.SetBandwidthBitsPerSecondVideo(bandwidthBitsPerSecond); AAMPLOG_INFO("Selected BitRate: %" BITSPERSECOND_FORMAT ", Max BitRate: %" BITSPERSECOND_FORMAT, bandwidthBitsPerSecond, GetStreamInfo(GetMaxBWProfile())->bandwidthBitsPerSecond); } @@ -3523,9 +3523,7 @@ AAMPStatusType StreamAbstractionAAMP_HLS::Init(TuneType tuneType) }else if (video->playlist.GetLen()){ BitsPerSecond bandwidthBitsPerSecond = GetStreamInfo(currentProfileIndex)->bandwidthBitsPerSecond; - aamp->ResetCurrentlyAvailableBandwidth( - bandwidthBitsPerSecond, - trickplayMode,currentProfileIndex); + aamp->mhAbrManager.ResetCurrentlyAvailableBandwidth(); aamp->profiler.SetBandwidthBitsPerSecondVideo( bandwidthBitsPerSecond); AAMPLOG_INFO("Selected BitRate: %" BITSPERSECOND_FORMAT ", Max BitRate: %" BITSPERSECOND_FORMAT, diff --git a/fragmentcollector_mpd.cpp b/fragmentcollector_mpd.cpp index 6c6f0150f..e39cea113 100644 --- a/fragmentcollector_mpd.cpp +++ b/fragmentcollector_mpd.cpp @@ -7520,7 +7520,7 @@ AAMPStatusType StreamAbstractionAAMP_MPD::UpdateTrackInfo(bool modifyDefaultBW, } if (modifyDefaultBW) { // Not for new tune ( for Seek / Trickplay) - long persistedBandwidth = aamp->GetPersistedBandwidth(); + BitsPerSecond persistedBandwidth = aamp->mhAbrManager.GetNetworkBandwidth(); // If Bitrate persisted over trickplay is true, set persisted BW as default init BW if (persistedBandwidth > 0 && (persistedBandwidth < defaultBitrate || aamp->IsBitRatePersistedOverSeek())) { @@ -7606,7 +7606,7 @@ AAMPStatusType StreamAbstractionAAMP_MPD::UpdateTrackInfo(bool modifyDefaultBW, pMediaStreamContext->adaptationSetId = pMediaStreamContext->adaptationSet->GetId(); IRepresentation *selectedRepresentation = pMediaStreamContext->adaptationSet->GetRepresentation().at(pMediaStreamContext->representationIndex); // for the profile selected ,reset the abr values with default bandwidth values - aamp->ResetCurrentlyAvailableBandwidth(selectedRepresentation->GetBandwidth(),trickplayMode,currentProfileIndex); + aamp->mhAbrManager.ResetCurrentlyAvailableBandwidth(); aamp->profiler.SetBandwidthBitsPerSecondVideo(selectedRepresentation->GetBandwidth()); } else @@ -12829,7 +12829,7 @@ void StreamAbstractionAAMP_MPD::MonitorLatency() bufferLowHit = true; bufferLowHitCount++; /** Buffer Low hit so push the data to telemetry*/ - aamp->profiler.SetLLDLowBufferParam(static_cast(currentLatency), bufferValue, currPlaybackRate, aamp->mNetworkBandwidth, bufferLowHitCount); + aamp->profiler.SetLLDLowBufferParam(static_cast(currentLatency), bufferValue, currPlaybackRate, aamp->mhAbrManager.GetNetworkBandwidth(), bufferLowHitCount); bufferLowCount = 0; } } diff --git a/priv_aamp.cpp b/priv_aamp.cpp index fbfd488ea..4f9ce8c02 100644 --- a/priv_aamp.cpp +++ b/priv_aamp.cpp @@ -594,6 +594,26 @@ static bool replace(std::string &str, const char *existingSubStringToReplace, co return rc; } +void PrivateInstanceAAMP::UpdatePersistBandwidth(BitsPerSecond bandwidth) +{ + // Store the PersistBandwidth and UpdatedTime on ABRManager. + // Bitrate update only for foreground player. + if (!( + ISCONFIGSET_PRIV(eAAMPConfig_PersistLowNetworkBandwidth) || + ISCONFIGSET_PRIV(eAAMPConfig_PersistHighNetworkBandwidth))) + { + return; + } + + if (bandwidth <= 0 || !mbPlayEnabled) + { + return; + } + + ABRManager::setPersistBandwidth(bandwidth); + ABRManager::mPersistBandwidthUpdatedTime = aamp_GetCurrentTimeMS(); +} + /** * @brief convert https to https in recordedUrl part of manifestUrl @@ -1346,11 +1366,15 @@ int PrivateInstanceAAMP::HandleSSLProgressCallback ( void *clientp, double dltot aamp->mhAbrManager.SetLowLatencyStartABR(true); } - if(downloadbps) - { - std::lock_guard guard(context->aamp->mLock); - aamp->mhAbrManager.UpdateABRBitrateDataBasedOnCacheLength(context->aamp->mAbrBitrateData,downloadbps,true); - } + std::lock_guard guard(context->aamp->mLock); + DownloadProgressInfo progressInfo; + progressInfo.m_now_seconds = NOW_STEADY_TS_MS / 1000.0; + progressInfo.m_total_bytes = + static_cast(dltotal); + progressInfo.m_now_bytes = + static_cast(dlnow); + aamp->mhAbrManager.ReportDownloadProgress( + downloadbps, true, progressInfo); } } @@ -1452,7 +1476,7 @@ int PrivateInstanceAAMP::HandleSSLProgressCallback ( void *clientp, double dltot /** * @brief PrivateInstanceAAMP Constructor */ -PrivateInstanceAAMP::PrivateInstanceAAMP(AampConfig *config) : mReportProgressPosn(0.0), mLastTelemetryTimeMS(0), mDiscontinuityFound(false), mTelemetryInterval(0), mAbrBitrateData(), mLock(), +PrivateInstanceAAMP::PrivateInstanceAAMP(AampConfig *config) : mReportProgressPosn(0.0), mLastTelemetryTimeMS(0), mDiscontinuityFound(false), mTelemetryInterval(0), mLock(), mpStreamAbstractionAAMP(NULL), mInitSuccess(false), mVideoFormat(FORMAT_INVALID), mAudioFormat(FORMAT_INVALID), mDownloadsDisabled(), mDownloadsEnabled(true), profiler(), licenceFromManifest(false), previousAudioType(eAUDIO_UNKNOWN),isPreferredDRMConfigured(false), mbDownloadsBlocked(false), streamerIsActive(false), mFogTSBEnabled(false), mIscDVR(false), mLiveOffset(AAMP_LIVE_OFFSET), @@ -1461,7 +1485,7 @@ PrivateInstanceAAMP::PrivateInstanceAAMP(AampConfig *config) : mReportProgressPo mEventListener(NULL), mNewSeekInfo(), discardEnteringLiveEvt(false), mIsRetuneInProgress(false), mCondDiscontinuity(), mDiscontinuityTuneOperationId(0), mIsVSS(false), m_fd(-1), mIsLive(false), mIsAudioContextSkipped(false), mLogTune(false), mTuneCompleted(false), mFirstTune(true), mfirstTuneFmt(-1), mTuneAttempts(0), mPlayerLoadTime(0), - mState(eSTATE_RELEASED), mMediaFormat(eMEDIAFORMAT_HLS), mPersistedProfileIndex(0), mAvailableBandwidth(0), + mState(eSTATE_RELEASED), mMediaFormat(eMEDIAFORMAT_HLS), mPersistedProfileIndex(0), mDiscontinuityTuneOperationInProgress(false), mContentType(ContentType_UNKNOWN), mTunedEventPending(false), mSeekOperationInProgress(false), mTrickplayInProgress(false), mPendingAsyncEvents(), mCustomHeaders(), mManifestUrl(""), mTunedManifestUrl(""), mOrigManifestUrl(), mServiceZone(), mVssVirtualStreamId(), @@ -1483,7 +1507,6 @@ PrivateInstanceAAMP::PrivateInstanceAAMP(AampConfig *config) : mReportProgressPo ,mDrmDecryptFailCount(MAX_SEG_DRM_DECRYPT_FAIL_COUNT) ,mPlaylistTimeoutMs(-1) ,mMutexPlaystart() - ,mNetworkBandwidth(0) ,mTimeToTopProfile(0) , fragmentCdmEncrypted(false) ,drmParserMutex(), aesCtrAttrDataList() , createDRMSessionThreadID() @@ -2555,10 +2578,10 @@ void PrivateInstanceAAMP::MonitorProgress(bool sync, bool beginningOfStream) } } - if(GetCurrentlyAvailableBandwidth() != -1) - { - mNetworkBandwidth = GetCurrentlyAvailableBandwidth(); - } + const BitsPerSecond availableBandwidth = mhAbrManager.GetCurrentlyAvailableBandwidth(); + const BitsPerSecond networkBandwidth = mhAbrManager.GetNetworkBandwidth(); + + UpdatePersistBandwidth(availableBandwidth); double currentRate; if(pipeline_paused) @@ -2592,7 +2615,7 @@ void PrivateInstanceAAMP::MonitorProgress(bool sync, bool beginningOfStream) bps = mpStreamAbstractionAAMP->GetVideoBitrate(); } - ProgressEventPtr evt = std::make_shared(duration, reportFormattedCurrPos, start, end, speed, videoPTS, videoBufferedDuration, audioBufferedDuration, seiTimecode.c_str(), latency, bps, mNetworkBandwidth, currentRate, GetSessionId()); + ProgressEventPtr evt = std::make_shared(duration, reportFormattedCurrPos, start, end, speed, videoPTS, videoBufferedDuration, audioBufferedDuration, seiTimecode.c_str(), latency, bps, networkBandwidth, currentRate, GetSessionId()); if (trickStartUTCMS >= 0 && (bProcessEvent || mFirstProgress)) { @@ -2637,7 +2660,7 @@ void PrivateInstanceAAMP::MonitorProgress(bool sync, bool beginningOfStream) (double)(latency / 1000.0), seiTimecode.c_str(), bps, - mNetworkBandwidth, + networkBandwidth, currentRate); } } @@ -2647,7 +2670,7 @@ void PrivateInstanceAAMP::MonitorProgress(bool sync, bool beginningOfStream) if(mTelemetryInterval > 0 && (diff > mTelemetryInterval)) { mLastTelemetryTimeMS = currTimeMS; - profiler.SetLatencyParam(latency, (double)(videoBufferedDuration/1000.0), currentRate, mNetworkBandwidth); + profiler.SetLatencyParam(latency, (double)(videoBufferedDuration/1000.0), currentRate, networkBandwidth); profiler.GetTelemetryParam(); } @@ -4200,62 +4223,6 @@ AampCurlInstance PrivateInstanceAAMP::GetPlaylistCurlInstance(AampMediaType type return retType; } -/** - * @brief Reset bandwidth value - * Artificially resetting the bandwidth. Low for quicker tune times - */ -void PrivateInstanceAAMP::ResetCurrentlyAvailableBandwidth(BitsPerSecond bitsPerSecond , bool trickPlay,int profile) -{ - std::lock_guard guard(mLock); - if (mAbrBitrateData.size()) - { - mAbrBitrateData.erase(mAbrBitrateData.begin(),mAbrBitrateData.end()); - } -} - -/** - * @brief Get the current network bandwidth - * using most recently recorded 3 samples - * @return Available bandwidth in bps - */ -BitsPerSecond PrivateInstanceAAMP::GetCurrentlyAvailableBandwidth(void) -{ - // 1. Check for any old bitrate beyond threshold time . remove those before calculation - // 2. Sort and get median - // 3. if any outliers , remove those entries based on a threshold value. - // 4. Get the average of remaining data. - // 5. if no item in the list , return -1 . Caller to ignore bandwidth based processing - - std::vector tmpData; - long ret = -1; - { - std::lock_guard guard(mLock); - mhAbrManager.UpdateABRBitrateDataBasedOnCacheLife(mAbrBitrateData,tmpData); - } - if (tmpData.size()) - { - //AAMPLOG_WARN("NwBW with newlogic size[%d] avg[%ld] ",tmpData.size(), avg/tmpData.size()); - ret =mhAbrManager.UpdateABRBitrateDataBasedOnCacheOutlier(tmpData); - mAvailableBandwidth = ret; - //Store the PersistBandwidth and UpdatedTime on ABRManager - //Bitrate Update only for foreground player - if(ISCONFIGSET_PRIV(eAAMPConfig_PersistLowNetworkBandwidth)||ISCONFIGSET_PRIV(eAAMPConfig_PersistHighNetworkBandwidth)) - { - if(mAvailableBandwidth > 0 && mbPlayEnabled) - { - ABRManager::setPersistBandwidth(mAvailableBandwidth ); - ABRManager::mPersistBandwidthUpdatedTime = aamp_GetCurrentTimeMS(); - } - } - } - else - { - //AAMPLOG_WARN("No prior data available for abr , return -1 "); - ret = -1; - } - return ret; -} - /** * @brief Set track data for CMCD data collection * @@ -4875,7 +4842,16 @@ bool PrivateInstanceAAMP::GetFile( std::string remoteUrl, AampMediaType mediaTyp long downloadbps = (long)mhAbrManager.CheckAbrThresholdSize((int)buffer->GetLen(),downloadTimeMS,currentProfilebps,fragmentDurationMs,hybridabortReason); { std::lock_guard guard(mLock); - mhAbrManager.UpdateABRBitrateDataBasedOnCacheLength(mAbrBitrateData,downloadbps,false); + DownloadMetrics downloadMetrics; +#if LIBCURL_VERSION_NUM >= 0x073700 // CURL version >= 7.55.0 + downloadMetrics.m_size_download_bytes = aamp_CurlEasyGetinfoOffset(curl, CURLINFO_SIZE_DOWNLOAD_T); +#else +#warning LIBCURL_VERSION<7.55.0 + downloadMetrics.m_size_download_bytes = aamp_CurlEasyGetinfoDouble(curl, CURLINFO_SIZE_DOWNLOAD); +#endif + downloadMetrics.m_total_time_seconds = aamp_CurlEasyGetinfoDouble(curl, CURLINFO_TOTAL_TIME); + downloadMetrics.m_time_to_first_byte_seconds = aamp_CurlEasyGetinfoDouble(curl, CURLINFO_STARTTRANSFER_TIME); + mhAbrManager.ReportDownloadComplete(downloadbps, false, downloadMetrics); } } } @@ -8053,7 +8029,7 @@ void PrivateInstanceAAMP::Stop( bool isDestructing ) bufferedDuration = mpStreamAbstractionAAMP->GetBufferedVideoDurationSec(); } double latency = GetCurrentLatency(); - profiler.SetLatencyParam(latency, bufferedDuration, rate, mNetworkBandwidth); + profiler.SetLatencyParam(latency, bufferedDuration, rate, mhAbrManager.GetNetworkBandwidth()); profiler.GetTelemetryParam(); mTelemetryInterval = 0; } @@ -13755,6 +13731,7 @@ void PrivateInstanceAAMP::LoadAampAbrConfig() mhAampAbrConfig.abrMinBuffer = GETCONFIGVALUE_PRIV(eAAMPConfig_MinABRNWBufferRampDown); mhAampAbrConfig.abrCacheOutlier = GETCONFIGVALUE_PRIV(eAAMPConfig_ABRCacheOutlier); mhAampAbrConfig.abrBufferCounter = GETCONFIGVALUE_PRIV(eAAMPConfig_ABRBufferCounter); + mhAampAbrConfig.bandwidthEstimatorType = GETCONFIGVALUE_PRIV(eAAMPConfig_ABRBandwidthEstimator); // Logging level support on aampabr diff --git a/priv_aamp.h b/priv_aamp.h index 19e6bb76e..4433c1395 100644 --- a/priv_aamp.h +++ b/priv_aamp.h @@ -884,7 +884,6 @@ class PrivateInstanceAAMP : public DrmCallbacks, public std::enable_shared_from_ bool mDiscontinuityFound; int mTelemetryInterval; - std::vector< std::pair> mAbrBitrateData; std::recursive_mutex mLock; std::recursive_mutex mParallelPlaylistFetchLock; /**< mutex lock for parallel fetch */ @@ -936,7 +935,6 @@ class PrivateInstanceAAMP : public DrmCallbacks, public std::enable_shared_from_ int mManifestTimeoutMs; int mPlaylistTimeoutMs; bool mAsyncTuneEnabled; - BitsPerSecond mNetworkBandwidth; std::string mTsbType; int mTsbDepthMs; int mDownloadDelay; @@ -1613,14 +1611,28 @@ class PrivateInstanceAAMP : public DrmCallbacks, public std::enable_shared_from_ * @param[in] bandwidth - Bandwidth in bps * @return void */ - void SetPersistedBandwidth(BitsPerSecond bandwidth) {mAvailableBandwidth = bandwidth;} + void SetPersistedBandwidth(BitsPerSecond bandwidth) + { + mhAbrManager.SetInitialBandwidthForProfile(bandwidth, false, 0); + } /** * @brief Get persisted bandwidth * * @return Bandwidth */ - BitsPerSecond GetPersistedBandwidth(){return mAvailableBandwidth;} + BitsPerSecond GetPersistedBandwidth() + { + return mhAbrManager.GetNetworkBandwidth(); + } + + /** + * @brief Update ABR persisted bandwidth/time (across tunes) if enabled. + * + * @param[in] bandwidth - Available bandwidth in bps + * @return void + */ + void UpdatePersistBandwidth(BitsPerSecond bandwidth); /** * @fn UpdateDuration @@ -2118,21 +2130,6 @@ class PrivateInstanceAAMP : public DrmCallbacks, public std::enable_shared_from_ */ double GetSeekBase(void); - /** - * @fn ResetCurrentlyAvailableBandwidth - * - * @param[in] bitsPerSecond - bps - * @param[in] trickPlay - Is trickplay mode - * @param[in] profile - Profile id. - * @return void - */ - void ResetCurrentlyAvailableBandwidth(BitsPerSecond bitsPerSecond,bool trickPlay,int profile=0); - - /** - * @fn GetCurrentlyAvailableBandwidth - */ - BitsPerSecond GetCurrentlyAvailableBandwidth(void); - /** * @fn DisableDownloads * @@ -4175,7 +4172,6 @@ class PrivateInstanceAAMP : public DrmCallbacks, public std::enable_shared_from_ bool mbTrackDownloadsBlocked[AAMP_TRACK_COUNT]; DrmHelperPtr mCurrentDrm; int mPersistedProfileIndex; - BitsPerSecond mAvailableBandwidth; bool mProcessingDiscontinuity[AAMP_TRACK_COUNT]; bool mIsDiscontinuityIgnored[AAMP_TRACK_COUNT]; bool mDiscontinuityTuneOperationInProgress; diff --git a/streamabstraction.cpp b/streamabstraction.cpp index 9e8f011ec..3492d3b7f 100644 --- a/streamabstraction.cpp +++ b/streamabstraction.cpp @@ -49,7 +49,6 @@ static constexpr uint32_t TRICKMODE_TIMESCALE{100000}; using namespace std; - AampMediaType TrackTypeToMediaType( TrackType trackType ) { switch( trackType ) @@ -2582,7 +2581,8 @@ int StreamAbstractionAAMP::GetDesiredProfileBasedOnCache(void) else { long currentBandwidth = GetStreamInfo(currentProfileIndex)->bandwidthBitsPerSecond; - long networkBandwidth = aamp->GetCurrentlyAvailableBandwidth(); + const BitsPerSecond networkBandwidth = aamp->mhAbrManager.GetCurrentlyAvailableBandwidth(); + aamp->UpdatePersistBandwidth(networkBandwidth); int nwConsistencyCnt = (mNwConsistencyBypass)?1:mABRNwConsistency; if(aamp->GetLLDashServiceData()->lowLatencyMode) { @@ -2692,7 +2692,8 @@ bool StreamAbstractionAAMP::RampDownProfile(int http_error) { stAbrInfo.currentBandwidth = streamInfocurrent->bandwidthBitsPerSecond; stAbrInfo.desiredBandwidth = streamInfodesired->bandwidthBitsPerSecond; - stAbrInfo.networkBandwidth = aamp->GetCurrentlyAvailableBandwidth(); + stAbrInfo.networkBandwidth = aamp->mhAbrManager.GetCurrentlyAvailableBandwidth(); + aamp->UpdatePersistBandwidth(stAbrInfo.networkBandwidth); stAbrInfo.errorType = AAMPNetworkErrorHttp; stAbrInfo.errorCode = http_error; @@ -2714,7 +2715,7 @@ bool StreamAbstractionAAMP::RampDownProfile(int http_error) if(video) { video->SetCurrentBandWidth( newBW ); - aamp->ResetCurrentlyAvailableBandwidth(newBW,false,profileIdxForBandwidthNotification); + aamp->mhAbrManager.ResetCurrentlyAvailableBandwidth(); mBitrateReason = eAAMP_BITRATE_CHANGE_BY_RAMPDOWN; // Send abr notification @@ -2939,7 +2940,8 @@ bool StreamAbstractionAAMP::UpdateProfileBasedOnFragmentCache() MediaTrack *video = GetMediaTrack(eTRACK_VIDEO); int desiredProfileIndex = currentProfileIndex; double totalFetchedDuration = video->GetTotalFetchedDuration(); - long availBW = aamp->GetCurrentlyAvailableBandwidth(); + const BitsPerSecond availBW = aamp->mhAbrManager.GetCurrentlyAvailableBandwidth(); + aamp->UpdatePersistBandwidth(availBW); bool checkProfileChange = aamp->mhAbrManager.CheckProfileChange(totalFetchedDuration,currentProfileIndex,availBW); //For LLD, it's necessary to initiate a rampdown process when there is a consistent download delay in order to construct the buffer. if (aamp->GetLLDashServiceData()->lowLatencyMode && !checkProfileChange && (aamp->mDownloadDelay >= (int)(floor(aamp->mLiveOffset / 2)))) @@ -2969,7 +2971,8 @@ bool StreamAbstractionAAMP::UpdateProfileBasedOnFragmentCache() stAbrInfo.desiredProfileIndex = desiredProfileIndex; stAbrInfo.currentBandwidth = GetStreamInfo(currentProfileIndex)->bandwidthBitsPerSecond; stAbrInfo.desiredBandwidth = GetStreamInfo(desiredProfileIndex)->bandwidthBitsPerSecond; - stAbrInfo.networkBandwidth = aamp->GetCurrentlyAvailableBandwidth(); + stAbrInfo.networkBandwidth = aamp->mhAbrManager.GetCurrentlyAvailableBandwidth(); + aamp->UpdatePersistBandwidth(stAbrInfo.networkBandwidth); stAbrInfo.errorType = AAMPNetworkErrorNone; AampLogManager::LogABRInfo(&stAbrInfo); @@ -2982,7 +2985,7 @@ bool StreamAbstractionAAMP::UpdateProfileBasedOnFragmentCache() video->ABRProfileChanged(); long newBW = GetStreamInfo(profileIdxForBandwidthNotification)->bandwidthBitsPerSecond; video->SetCurrentBandWidth(newBW); - aamp->ResetCurrentlyAvailableBandwidth(newBW,false,profileIdxForBandwidthNotification); + aamp->mhAbrManager.ResetCurrentlyAvailableBandwidth(); mABRLowBufferCounter = 0 ; mABRHighBufferCounter = 0; retVal = true; diff --git a/test/utests/drm/mocks/aampMocks.cpp b/test/utests/drm/mocks/aampMocks.cpp index 83c9377be..4a68a6fe0 100644 --- a/test/utests/drm/mocks/aampMocks.cpp +++ b/test/utests/drm/mocks/aampMocks.cpp @@ -780,10 +780,6 @@ void PrivateInstanceAAMP::ReportTimedMetadata(long long timeMilliseconds, const { } -void PrivateInstanceAAMP::ResetCurrentlyAvailableBandwidth(BitsPerSecond bitsPerSecond, - bool trickPlay, int profile) -{ -} void PrivateInstanceAAMP::ResumeTrackInjection(AampMediaType type) { @@ -926,11 +922,6 @@ uint32_t PrivateInstanceAAMP::GetSubTimeScale(void) return 0u; } -BitsPerSecond PrivateInstanceAAMP::GetCurrentlyAvailableBandwidth(void) -{ - return 0; -} - BitsPerSecond PrivateInstanceAAMP::GetIframeBitrate() { return 0; diff --git a/test/utests/fakes/FakeABR.cpp b/test/utests/fakes/FakeABR.cpp index 513416f78..945305f1f 100644 --- a/test/utests/fakes/FakeABR.cpp +++ b/test/utests/fakes/FakeABR.cpp @@ -20,11 +20,83 @@ #include "abr.h" #include "MockABRManager.h" -long ABRManager::mPersistBandwidth = 0; +BitsPerSecond ABRManager::mPersistBandwidth = 0; long long ABRManager::mPersistBandwidthUpdatedTime = 0; MockABRManager *g_mockABRManager = nullptr; +ABRManager::ABRManager() : bLowLatencyStartABR(false) , bLowLatencyServiceConfigured(false) , mBandwidthEstimatorType(BANDWIDTH_ESTIMATOR_TYPE_ROLLING_MEDIAN_OUTLIER) +{ +} + +ABRManager::~ABRManager() +{ +} + +void ABRManager::SetBandwidthEstimatorType(BandwidthEstimatorType type) +{ + std::lock_guard lock(mBandwidthEstimatorLock); + mBandwidthEstimatorType = type; +} + +BandwidthEstimatorType ABRManager::GetBandwidthEstimatorType() const +{ + std::lock_guard lock(mBandwidthEstimatorLock); + return mBandwidthEstimatorType; +} + +void ABRManager::AddBandwidthSample(BitsPerSecond downloadbps, bool lowLatencyMode) +{ +} + +void ABRManager::ReportDownloadComplete(BitsPerSecond downloadbps, bool lowLatencyMode, const DownloadMetrics &metrics) +{ + std::lock_guard lock(mBandwidthEstimatorLock); + if (downloadbps > 0) + { + mBandwidthState.availableBandwidth = downloadbps; + mBandwidthState.networkBandwidth = downloadbps; + } +} + +void ABRManager::ReportDownloadProgress(BitsPerSecond downloadbps, bool lowLatencyMode, const DownloadProgressInfo &progressInfo) +{ + std::lock_guard lock(mBandwidthEstimatorLock); + if (downloadbps > 0) + { + mBandwidthState.availableBandwidth = downloadbps; + mBandwidthState.networkBandwidth = downloadbps; + } +} + +void ABRManager::SetInitialBandwidthForProfile(BitsPerSecond bitsPerSecond, bool trickPlay, int profile) +{ + std::lock_guard lock(mBandwidthEstimatorLock); + mBandwidthState.availableBandwidth = bitsPerSecond; + mBandwidthState.networkBandwidth = bitsPerSecond; +} + +void ABRManager::ResetCurrentlyAvailableBandwidth() +{ +} + +BitsPerSecond ABRManager::GetCurrentlyAvailableBandwidth() +{ + std::lock_guard lock(mBandwidthEstimatorLock); + return mBandwidthState.availableBandwidth; +} + +BitsPerSecond ABRManager::GetNetworkBandwidth() +{ + std::lock_guard lock(mBandwidthEstimatorLock); + return mBandwidthState.networkBandwidth; +} + +bool ABRManager::HasBandwidthEstimator() const +{ + return true; +} + int ABRManager::getProfileCount() { if (g_mockABRManager) @@ -49,6 +121,12 @@ BitsPerSecond ABRManager::getBandwidthOfProfile(int profileIndex) return 0; } +int ABRManager::getProfileOfBandwidth(BitsPerSecond bandwidth) +{ + (void)bandwidth; + return 0; +} + void ABRManager::clearProfiles() { return; @@ -72,8 +150,9 @@ int ABRManager::getUserDataOfProfile(int currentProfileIndex) return 0; } -void ABRManager::setDefaultInitBitrate(long defaultInitBitrate) +void ABRManager::setDefaultInitBitrate(BitsPerSecond defaultInitBitrate) { + (void)defaultInitBitrate; } void ABRManager::updateProfile() @@ -110,8 +189,9 @@ bool ABRManager::isProfileIndexBitrateLowest(int currentProfileIndex, const std: return true; } -void ABRManager::setDefaultIframeBitrate(long defaultIframeBitrate) +void ABRManager::setDefaultIframeBitrate(BitsPerSecond defaultIframeBitrate) { + (void)defaultIframeBitrate; } int ABRManager::removeProfiles(std::vector profileBPS, int currentProfileIndex, const std::string& periodId) @@ -124,8 +204,9 @@ int ABRManager::getProfileIndexForLowestBandwidth() return 0; } -int ABRManager::getClosestProfileIndexByBandwidth( long inputBandwidth ) +int ABRManager::getClosestProfileIndexByBandwidth(BitsPerSecond inputBandwidth) { + (void)inputBandwidth; return 0; } @@ -133,26 +214,21 @@ void ABRManager::ReadPlayerConfig(AampAbrConfig *mAampAbrConfig) { } -long ABRManager::CheckAbrThresholdSize(int bufferlen, int downloadTimeMs ,long currentProfilebps ,int fragmentDurationMs , CurlAbortReason abortReason) -{ - return 0; -} - -void ABRManager::UpdateABRBitrateDataBasedOnCacheLength(std::vector < std::pair > &mAbrBitrateData,long downloadbps,bool LowLatencyMode) -{ -} - -void ABRManager::UpdateABRBitrateDataBasedOnCacheLife(std::vector < std::pair > &mAbrBitrateData , std::vector &tmpData) -{ -} - -long ABRManager::UpdateABRBitrateDataBasedOnCacheOutlier(std::vector &tmpData) +BitsPerSecond ABRManager::CheckAbrThresholdSize(int bufferlen, int downloadTimeMs, BitsPerSecond currentProfilebps, int fragmentDurationMs, CurlAbortReason abortReason) { + (void)bufferlen; + (void)downloadTimeMs; + (void)currentProfilebps; + (void)fragmentDurationMs; + (void)abortReason; return 0; } -bool ABRManager::CheckProfileChange(double totalFetchedDuration ,int currProfileIndex , long availBW) +bool ABRManager::CheckProfileChange(double totalFetchedDuration, int currProfileIndex, BitsPerSecond availBW) { + (void)totalFetchedDuration; + (void)currProfileIndex; + (void)availBW; return false; } @@ -161,8 +237,16 @@ void ABRManager::GetDesiredProfileOnBuffer(int currProfileIndex,int &newProfileI } -void ABRManager::CheckRampupFromSteadyState(int currProfileIndex,int &newProfileIndex,long nwBandwidth,double bufferValue,long newBandwidth,BitrateChangeReason &mhBitrateReason,int &mMaxBufferCountCheck,const std::string& periodId) +void ABRManager::CheckRampupFromSteadyState(int currProfileIndex, int &newProfileIndex, BitsPerSecond nwBandwidth, double bufferValue, BitsPerSecond newBandwidth, BitrateChangeReason &mhBitrateReason, int &mMaxBufferCountCheck, const std::string& periodId) { + (void)currProfileIndex; + (void)newProfileIndex; + (void)nwBandwidth; + (void)bufferValue; + (void)newBandwidth; + (void)mhBitrateReason; + (void)mMaxBufferCountCheck; + (void)periodId; } void ABRManager::CheckRampdownFromSteadyState(int currProfileIndex, int &newProfileIndex,BitrateChangeReason &mBitrateReason,int mABRLowBufferCounter,const std::string& periodId) @@ -197,11 +281,19 @@ bool ABRManager::IsABRDataGoodToEstimate(long time_diff) return false; } -void ABRManager::CheckLLDashABRSpeedStoreSize(struct SpeedCache *speedcache,long &bitsPerSecond,long time_now,long total_dl_diff,long time_diff,long currentTotalDownloaded) +void ABRManager::CheckLLDashABRSpeedStoreSize(struct SpeedCache *speedcache, BitsPerSecond &bitsPerSecond, long time_now, long total_dl_diff, long time_diff, long currentTotalDownloaded) { + (void)speedcache; + (void)bitsPerSecond; + (void)time_now; + (void)total_dl_diff; + (void)time_diff; + (void)currentTotalDownloaded; } -long ABRManager::FragmentfailureRampdown(int buffer,int currentprofileindex) +BitsPerSecond ABRManager::FragmentfailureRampdown(int currentBuffer, int currentProfileIndex) { + (void)currentBuffer; + (void)currentProfileIndex; return 0; } diff --git a/test/utests/fakes/FakePrivateInstanceAAMP.cpp b/test/utests/fakes/FakePrivateInstanceAAMP.cpp index 34ed13d29..0218c6e9d 100644 --- a/test/utests/fakes/FakePrivateInstanceAAMP.cpp +++ b/test/utests/fakes/FakePrivateInstanceAAMP.cpp @@ -22,6 +22,8 @@ #include "AampMPDDownloader.h" #include "AampStreamSinkManager.h" +#include "BandwidthEstimatorBase.h" + #include "ID3Metadata.hpp" #include "AampSegmentInfo.hpp" @@ -49,7 +51,6 @@ PrivateInstanceAAMP::PrivateInstanceAAMP(AampConfig *config) : mIsAudioContextSkipped(false), mMediaFormat(eMEDIAFORMAT_HLS), mPersistedProfileIndex(0), - mAvailableBandwidth(0), mContentType(ContentType_UNKNOWN), mManifestUrl(""), mServiceZone(), @@ -144,6 +145,10 @@ PrivateInstanceAAMP::~PrivateInstanceAAMP() { } +void PrivateInstanceAAMP::UpdatePersistBandwidth(BitsPerSecond bandwidth) +{ +} + double PrivateInstanceAAMP::RecalculatePTS(AampMediaType mediaType, const void *ptr, size_t len) { double pts = 0.0; @@ -882,9 +887,6 @@ void PrivateInstanceAAMP::ReportTimedMetadata(long long timeMilliseconds, const { } -void PrivateInstanceAAMP::ResetCurrentlyAvailableBandwidth(BitsPerSecond bitsPerSecond , bool trickPlay,int profile) -{ -} void PrivateInstanceAAMP::ResumeTrackInjection(AampMediaType type) { @@ -1029,11 +1031,6 @@ uint32_t PrivateInstanceAAMP::GetSubTimeScale(void) return 0u; } -BitsPerSecond PrivateInstanceAAMP::GetCurrentlyAvailableBandwidth(void) -{ - return 0; -} - BitsPerSecond PrivateInstanceAAMP::GetIframeBitrate() { return 0; diff --git a/test/utests/tests/AampAbrTests/AbrTests.cpp b/test/utests/tests/AampAbrTests/AbrTests.cpp index 4a7dfec23..bd5625d80 100644 --- a/test/utests/tests/AampAbrTests/AbrTests.cpp +++ b/test/utests/tests/AampAbrTests/AbrTests.cpp @@ -84,6 +84,8 @@ TEST_F(AampAbrTests,LoadAampAbrConfig) .WillRepeatedly(Return(10000)); EXPECT_CALL(*g_mockAampConfig, GetConfigValue(eAAMPConfig_ABRBufferCounter)) .WillRepeatedly(Return(4)); + EXPECT_CALL(*g_mockAampConfig, GetConfigValue(eAAMPConfig_ABRBandwidthEstimator)) + .WillRepeatedly(Return(BANDWIDTH_ESTIMATOR_TYPE_HARMONIC_EWMA)); aamp->LoadAampAbrConfig(); @@ -96,4 +98,5 @@ TEST_F(AampAbrTests,LoadAampAbrConfig) EXPECT_EQ(eAAMPAbrConfig.abrMinBuffer,10); EXPECT_EQ(eAAMPAbrConfig.abrCacheOutlier,10000); EXPECT_EQ(eAAMPAbrConfig.abrBufferCounter,4); + EXPECT_EQ(eAAMPAbrConfig.bandwidthEstimatorType,BANDWIDTH_ESTIMATOR_TYPE_HARMONIC_EWMA); } diff --git a/test/utests/tests/AampAbrTests/CMakeLists.txt b/test/utests/tests/AampAbrTests/CMakeLists.txt index 010d390ff..485745969 100644 --- a/test/utests/tests/AampAbrTests/CMakeLists.txt +++ b/test/utests/tests/AampAbrTests/CMakeLists.txt @@ -29,7 +29,13 @@ include(${CMAKE_CURRENT_LIST_DIR}/../CommonTestIncludes.cmake) set(TEST_SOURCES AbrTests.cpp AAMPAbrTests.cpp) -set(AAMP_SOURCES ${AAMP_ROOT}/priv_aamp.cpp ${AAMP_ROOT}/abr/abr.cpp ${AAMP_ROOT}/CachedFragment.cpp) +set(AAMP_SOURCES + ${AAMP_ROOT}/priv_aamp.cpp + ${AAMP_ROOT}/abr/abr.cpp + ${AAMP_ROOT}/abr/RMOBandwidthEstimator.cpp + ${AAMP_ROOT}/abr/HarmonicEWMAEstimator.cpp + ${AAMP_ROOT}/CachedFragment.cpp +) add_executable(${EXEC_NAME} ${TEST_SOURCES} ${AAMP_SOURCES}) diff --git a/test/utests/tests/AampDrmLegacy/CMakeLists.txt b/test/utests/tests/AampDrmLegacy/CMakeLists.txt index 9acf8c875..e03162826 100644 --- a/test/utests/tests/AampDrmLegacy/CMakeLists.txt +++ b/test/utests/tests/AampDrmLegacy/CMakeLists.txt @@ -47,7 +47,6 @@ set(FAKE_SOURCES ${AAMP_ROOT}/test/utests/fakes/FakeThunderAccessPlayer.cpp set(MOCK_SOURCES ${DRM_ROOT}/mocks/aampMocks.cpp ${DRM_ROOT}/mocks/FakeID3Metadata.cpp - ${DRM_ROOT}/mocks/FakeABRManager.cpp ${DRM_ROOT}/mocks/FakeAampStreamSinkManager.cpp ${DRM_ROOT}/mocks/curlMocks.c ${DRM_ROOT}/mocks/pthreadMocks.c @@ -60,6 +59,7 @@ set(FAKE_SOURCES ${UTESTS_ROOT}/fakes/FakeAampRfc.cpp ${AAMP_ROOT}/test/utests/fakes/FakeContentSecurityManager.cpp ${AAMP_ROOT}/test/utests/fakes/FakePlayerScheduler.cpp ${AAMP_ROOT}/test/utests/fakes/FakeBase64.cpp + ${AAMP_ROOT}/test/utests/fakes/FakeABR.cpp ${AAMP_ROOT}/test/utests/fakes/FakePlayerLogManager.cpp) set(AAMP_SOURCES ${AAMP_ROOT}/AampConfig.cpp diff --git a/test/utests/tests/NetworkBandwidthEstimator/CMakeLists.txt b/test/utests/tests/NetworkBandwidthEstimator/CMakeLists.txt index 05b676a00..895dbd71d 100644 --- a/test/utests/tests/NetworkBandwidthEstimator/CMakeLists.txt +++ b/test/utests/tests/NetworkBandwidthEstimator/CMakeLists.txt @@ -19,7 +19,7 @@ include(GoogleTest) set(AAMP_ROOT "../../../../") set(UTESTS_ROOT "../../") -set(EXEC_NAME NetworkBandwidthEstimator) +set(EXEC_NAME HarmonicEWMAEstimator) # Include common test directories include(${CMAKE_CURRENT_LIST_DIR}/../CommonTestIncludes.cmake) @@ -27,7 +27,7 @@ include(${CMAKE_CURRENT_LIST_DIR}/../CommonTestIncludes.cmake) set(TEST_SOURCES NetworkBandwidthEstimatorTests.cpp FunctionalTests.cpp) -set(AAMP_SOURCES ${AAMP_ROOT}/abr/NetworkBandwidthEstimator.cpp ) +set(AAMP_SOURCES ${AAMP_ROOT}/abr/HarmonicEWMAEstimator.cpp ) add_executable(${EXEC_NAME} ${TEST_SOURCES} diff --git a/test/utests/tests/NetworkBandwidthEstimator/FunctionalTests.cpp b/test/utests/tests/NetworkBandwidthEstimator/FunctionalTests.cpp index 2b86338e4..5fa0c5153 100644 --- a/test/utests/tests/NetworkBandwidthEstimator/FunctionalTests.cpp +++ b/test/utests/tests/NetworkBandwidthEstimator/FunctionalTests.cpp @@ -18,7 +18,7 @@ */ #include -#include "NetworkBandwidthEstimator.h" +#include "HarmonicEWMAEstimator.h" #include #include #include @@ -89,18 +89,18 @@ TEST_F(FunctionalTests, ThroughputPredictionTest) { 0.054673,1380828.841638,0.136119,112463,0.116275,0.051314 }, { 0.052516,1433386.044995,0.130976,112463,0.116544,0.051999 } }}; - NetworkBandwidthEstimator networkBandwidthEstimator; + HarmonicEWMAEstimator networkBandwidthEstimator; for( const auto& data : test_data ) { EXPECT_NEAR( data.timeToFirstByteSeconds, networkBandwidthEstimator.GetTimeToFirstByteSeconds(), epsilon ); EXPECT_NEAR( data.throughputBytesPerSecond, networkBandwidthEstimator.GetThroughputBytesPerSecond(), epsilon ); EXPECT_NEAR( data.predictedDownloadTimeSeconds, networkBandwidthEstimator.GetPredictedDownloadTimeSeconds(segment_size_bytes), epsilon ); - - CurlInfo curlInfo; - curlInfo.m_size_download_bytes = data.download_bytes; - curlInfo.m_total_time_seconds = data.total_time_seconds; - curlInfo.m_time_to_first_byte_seconds = data.time_to_first_byte_seconds; - networkBandwidthEstimator.UpdateDownloadMetrics(curlInfo); + + DownloadMetrics downloadMetrics; + downloadMetrics.m_size_download_bytes = data.download_bytes; + downloadMetrics.m_total_time_seconds = data.total_time_seconds; + downloadMetrics.m_time_to_first_byte_seconds = data.time_to_first_byte_seconds; + networkBandwidthEstimator.UpdateDownloadMetrics(downloadMetrics); } } @@ -135,3 +135,39 @@ TEST_F(FunctionalTests, MidDownloadMonitoringTest) EXPECT_NEAR( data.bytesPerSecond, downloadContext.GetEstimatedThroughputBytesPerSecond(), epsilon ); } } + +TEST_F(FunctionalTests, EstimatorProgressUpdateTest) +{ + const double epsilon = 1e-6; + struct TestData + { + double now; + size_t dlnow; + size_t dltotal; + double bytesPerSecond; + }; + const std::array test_data{{ + {271850.024298,0,0,0.0}, + {271850.227916,15908,112463,78126.688199}, + {271850.259620,32274,112463,253360.998893}, + {271850.259914,48640,112463,22418685.647001}, + {271850.294995,63960,112463,13625892.839653}, + {271850.295399,80326,112463,24379496.450812}, + {271850.317906,96692,112463,14918558.491420}, + {271850.326387,112463,112463,9694962.476504} + }}; + + HarmonicEWMAEstimator estimator; + for(const auto& data : test_data) + { + DownloadProgressInfo progressInfo; + progressInfo.m_now_seconds = data.now; + progressInfo.m_total_bytes = data.dltotal; + progressInfo.m_now_bytes = data.dlnow; + estimator.UpdateDownloadProgress(progressInfo); + EXPECT_NEAR( + data.bytesPerSecond, + estimator.GetThroughputBytesPerSecond(), + epsilon); + } +} diff --git a/test/utests/tests/PrivAampTests/PrivAampTests.cpp b/test/utests/tests/PrivAampTests/PrivAampTests.cpp index 494ec6020..675e28d2d 100644 --- a/test/utests/tests/PrivAampTests/PrivAampTests.cpp +++ b/test/utests/tests/PrivAampTests/PrivAampTests.cpp @@ -44,6 +44,7 @@ #include "MockCurl.h" #include "MockAampCurlStore.h" #include "MockAampJsonObject.h" +#include "MockAampUtils.h" #include "MockTSBSessionManager.h" #include "MockTSBStore.h" #include "fragmentcollector_mpd.h" @@ -102,6 +103,7 @@ class PrivAampTests : public ::testing::Test g_mockPlayerCCManager = std::make_shared>(); g_mockMediaStreamContext = new NiceMock(); g_mockIsoBmffBuffer = new NiceMock(); + g_mockAampUtils = new NiceMock(); } void TearDown() override @@ -146,6 +148,9 @@ class PrivAampTests : public ::testing::Test delete g_mockIsoBmffBuffer; g_mockIsoBmffBuffer = nullptr; + delete g_mockAampUtils; + g_mockAampUtils = nullptr; + delete (int*)mCurlEasyHandle; mCurlEasyHandle = nullptr; @@ -2141,47 +2146,6 @@ TEST_F(PrivAampTests,GetPlaylistCurlInstanceTest_2) EXPECT_EQ(6,retVar); } -TEST_F(PrivAampTests,ResetCurrentlyAvailableBandwidthTest) -{ - p_aamp->ResetCurrentlyAvailableBandwidth(123564756,true,15); - EXPECT_EQ(p_aamp->mAbrBitrateData.size(),0); -} - -TEST_F(PrivAampTests,ResetCurrentlyAvailableBWTest_1) -{ - long bitsPerSecond = 123564756; - bool trickPlay = true; - int profile = 15; - p_aamp->ResetCurrentlyAvailableBandwidth(bitsPerSecond,trickPlay,profile); -} - -TEST_F(PrivAampTests,ResetCurrentlyAvailableBWTest_2) -{ - long bitsPerSecond = 123564756; - bool trickPlay = true; - int profile = 15; - std::vector< std::pair > mAbrBitrateData; - mAbrBitrateData.push_back(std::make_pair(243475656835,433554345343)); - - p_aamp->ResetCurrentlyAvailableBandwidth(bitsPerSecond,trickPlay,profile); -} - -TEST_F(PrivAampTests,GetCurrentlyAvailableBandwidthTest) -{ - long val = p_aamp->GetCurrentlyAvailableBandwidth(); - EXPECT_NE(0,val); -} - -TEST_F(PrivAampTests,GetCurrentlyAvailableBandwidthTest_1) -{ - std::vector tmpData; - tmpData.push_back(13242352); - tmpData.push_back(13312242352); - - long val = p_aamp->GetCurrentlyAvailableBandwidth(); - EXPECT_NE(0,val); -} - TEST_F(PrivAampTests,GetFileTest) { const char *url; @@ -5283,6 +5247,94 @@ struct GetStreamFormatTestParams { } }; + +/** + * @brief Validate UpdatePersistBandwidth updates ABR statics when enabled. + */ +TEST_F(PrivAampTests, UpdatePersistBandwidth_ConfigEnabledAndPlayEnabled_UpdatesAbrStatics) +{ + ABRManager::mPersistBandwidth = 0; + ABRManager::mPersistBandwidthUpdatedTime = 0; + + ON_CALL(*g_mockAampConfig, IsConfigSet(_)).WillByDefault(Return(false)); + ON_CALL(*g_mockAampConfig, IsConfigSet(eAAMPConfig_PersistLowNetworkBandwidth)) + .WillByDefault(Return(true)); + + EXPECT_CALL(*g_mockAampUtils, aamp_GetCurrentTimeMS()) + .WillOnce(Return(1234)); + + p_aamp->mbPlayEnabled = true; + p_aamp->UpdatePersistBandwidth(5000); + + EXPECT_EQ(ABRManager::getPersistBandwidth(), 5000); + EXPECT_EQ(ABRManager::mPersistBandwidthUpdatedTime, 1234); +} + +/** + * @brief Validate UpdatePersistBandwidth does nothing when config disabled. + */ +TEST_F(PrivAampTests, UpdatePersistBandwidth_ConfigDisabled_DoesNotUpdateAbrStatics) +{ + ABRManager::mPersistBandwidth = 123; + ABRManager::mPersistBandwidthUpdatedTime = 999; + + ON_CALL(*g_mockAampConfig, IsConfigSet(_)).WillByDefault(Return(false)); + ON_CALL(*g_mockAampConfig, IsConfigSet(eAAMPConfig_PersistLowNetworkBandwidth)) + .WillByDefault(Return(false)); + ON_CALL(*g_mockAampConfig, IsConfigSet(eAAMPConfig_PersistHighNetworkBandwidth)) + .WillByDefault(Return(false)); + + EXPECT_CALL(*g_mockAampUtils, aamp_GetCurrentTimeMS()).Times(0); + + p_aamp->mbPlayEnabled = true; + p_aamp->UpdatePersistBandwidth(5000); + + EXPECT_EQ(ABRManager::getPersistBandwidth(), 123); + EXPECT_EQ(ABRManager::mPersistBandwidthUpdatedTime, 999); +} + +/** + * @brief Validate UpdatePersistBandwidth does nothing when playback disabled. + */ +TEST_F(PrivAampTests, UpdatePersistBandwidth_PlaybackDisabled_DoesNotUpdateAbrStatics) +{ + ABRManager::mPersistBandwidth = 123; + ABRManager::mPersistBandwidthUpdatedTime = 999; + + ON_CALL(*g_mockAampConfig, IsConfigSet(_)).WillByDefault(Return(false)); + ON_CALL(*g_mockAampConfig, IsConfigSet(eAAMPConfig_PersistLowNetworkBandwidth)) + .WillByDefault(Return(true)); + + EXPECT_CALL(*g_mockAampUtils, aamp_GetCurrentTimeMS()).Times(0); + + p_aamp->mbPlayEnabled = false; + p_aamp->UpdatePersistBandwidth(5000); + + EXPECT_EQ(ABRManager::getPersistBandwidth(), 123); + EXPECT_EQ(ABRManager::mPersistBandwidthUpdatedTime, 999); +} + +/** + * @brief Validate UpdatePersistBandwidth does nothing when bandwidth invalid. + */ +TEST_F(PrivAampTests, UpdatePersistBandwidth_ZeroBandwidth_DoesNotUpdateAbrStatics) +{ + ABRManager::mPersistBandwidth = 123; + ABRManager::mPersistBandwidthUpdatedTime = 999; + + ON_CALL(*g_mockAampConfig, IsConfigSet(_)).WillByDefault(Return(false)); + ON_CALL(*g_mockAampConfig, IsConfigSet(eAAMPConfig_PersistLowNetworkBandwidth)) + .WillByDefault(Return(true)); + + EXPECT_CALL(*g_mockAampUtils, aamp_GetCurrentTimeMS()).Times(0); + + p_aamp->mbPlayEnabled = true; + p_aamp->UpdatePersistBandwidth(0); + + EXPECT_EQ(ABRManager::getPersistBandwidth(), 123); + EXPECT_EQ(ABRManager::mPersistBandwidthUpdatedTime, 999); +} + // This function is used by Google Test to print the parameter value. void PrintTo(const GetStreamFormatTestParams& params, ::std::ostream* os) {