-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
368 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
#pragma once | ||
|
||
#include "PlaylistParser.hpp" | ||
#include "NetworkDiagnoser.hpp" | ||
#include <vector> | ||
#include <algorithm> | ||
|
||
class ABRManager { | ||
public: | ||
ABRManager(boost::asio::io_context &ioc, const std::string &master_url) | ||
: ioc_(ioc), | ||
ssl_ctx_(boost::asio::ssl::context::sslv23), | ||
parser_(ioc, ssl_ctx_, master_url), | ||
network_(ioc, master_url) {} | ||
|
||
void selectBestBitrate() { | ||
if (!parser_.fetchMasterPlaylist()) { | ||
std::cerr << "[ERROR] Failed to fetch master playlist.\n"; | ||
return; | ||
} | ||
|
||
NetworkStats stats = network_.diagnoseNetworkSpeed(); | ||
if (stats.latency < 0) { | ||
std::cerr << "[ERROR] Network diagnosis failed.\n"; | ||
return; | ||
} | ||
|
||
std::map<int, std::string> playlists = parser_.getBitratePlaylists(); | ||
if (playlists.empty()) { | ||
std::cerr << "[ERROR] No available bitrates in playlist.\n"; | ||
return; | ||
} | ||
|
||
// Extract bitrates and sort them in ascending order | ||
std::vector<int> available_bitrates; | ||
for (const auto &[bitrate, _] : playlists) { | ||
available_bitrates.push_back(bitrate); | ||
} | ||
std::sort(available_bitrates.begin(), available_bitrates.end()); | ||
|
||
int selected_bitrate = determineBestBitrate(stats, available_bitrates); | ||
std::cout << "[INFO] Selected Best Bitrate: " << selected_bitrate << " kbps\n"; | ||
} | ||
|
||
private: | ||
boost::asio::io_context &ioc_; | ||
boost::asio::ssl::context ssl_ctx_; | ||
PlaylistParser parser_; | ||
NetworkDiagnoser network_; | ||
|
||
/** | ||
* Determines the best bitrate based on network statistics. | ||
*/ | ||
int determineBestBitrate(const NetworkStats &stats, const std::vector<int> &bitrates) { | ||
if (bitrates.empty()) return 64000; // Default minimum bitrate | ||
|
||
// Define network thresholds | ||
constexpr int LOW_LATENCY = 80; | ||
constexpr int MEDIUM_LATENCY = 150; | ||
constexpr int HIGH_LATENCY = 250; | ||
constexpr double MAX_PACKET_LOSS = 20.0; | ||
constexpr double MAX_JITTER = 50.0; | ||
|
||
int selected_bitrate = bitrates.front(); // Start with the lowest bitrate | ||
|
||
for (int bitrate : bitrates) { | ||
if (stats.packet_loss > MAX_PACKET_LOSS || stats.jitter > MAX_JITTER) { | ||
// High packet loss or jitter -> stick to lowest bitrate | ||
break; | ||
} | ||
if (stats.latency < LOW_LATENCY) { | ||
// Low latency -> Choose highest available bitrate | ||
selected_bitrate = bitrate; | ||
} | ||
else if (stats.latency < MEDIUM_LATENCY) { | ||
// Medium latency -> Choose medium bitrate | ||
if (bitrate <= bitrates[bitrates.size() / 2]) { | ||
selected_bitrate = bitrate; | ||
} | ||
} | ||
else if (stats.latency < HIGH_LATENCY) { | ||
// High latency -> Choose lowest available bitrate | ||
selected_bitrate = bitrates.front(); | ||
break; | ||
} | ||
} | ||
|
||
return selected_bitrate; | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
CPP := g++ | ||
SRC := main.cpp | ||
LIBS := -lboost_log -lboost_log_setup -DBOOST_LOG_DLL -lboost_filesystem -lboost_system -lssl -lcrypto | ||
BIN := abr | ||
|
||
abr: | ||
$(CPP) -o $(BIN) $(SRC) $(LIBS) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
#pragma once | ||
|
||
#include <boost/asio.hpp> | ||
#include <iostream> | ||
#include <chrono> | ||
#include <random> | ||
|
||
namespace net = boost::asio; | ||
using tcp = net::ip::tcp; | ||
using namespace std::chrono; | ||
|
||
struct NetworkStats { | ||
int latency; // In milliseconds | ||
double jitter; // In milliseconds | ||
double packet_loss; // Percentage (0-100%) | ||
}; | ||
|
||
class NetworkDiagnoser { | ||
public: | ||
NetworkDiagnoser(net::io_context &ioc, const std::string &server_url) | ||
: resolver_(ioc), socket_(ioc), server_url_(server_url) {} | ||
|
||
NetworkStats diagnoseNetworkSpeed() { | ||
std::string host, port, target; | ||
parseUrl(server_url_, host, port, target); | ||
|
||
NetworkStats stats = { -1, 0.0, 0.0 }; // Default invalid values | ||
|
||
try { | ||
std::cout << "[INFO] Diagnosing network speed to: " << host << "\n"; | ||
auto const results = resolver_.resolve(host, port); | ||
|
||
std::vector<int> latencies; | ||
|
||
for (int i = 0; i < 5; ++i) { // Send 5 probes to calculate jitter | ||
auto start = high_resolution_clock::now(); | ||
socket_.connect(*results.begin()); | ||
auto end = high_resolution_clock::now(); | ||
int ping_time = duration_cast<milliseconds>(end - start).count(); | ||
latencies.push_back(ping_time); | ||
socket_.close(); | ||
} | ||
|
||
stats.latency = calculateAverage(latencies); | ||
stats.jitter = calculateJitter(latencies); | ||
stats.packet_loss = simulatePacketLoss(); // Simulating packet loss | ||
|
||
std::cout << "[INFO] Network Latency: " << stats.latency << " ms\n"; | ||
std::cout << "[INFO] Network Jitter: " << stats.jitter << " ms\n"; | ||
std::cout << "[INFO] Packet Loss: " << stats.packet_loss << "%\n"; | ||
|
||
} catch (const std::exception &e) { | ||
std::cerr << "[ERROR] Network speed diagnosis failed: " << e.what() << "\n"; | ||
} | ||
|
||
return stats; | ||
} | ||
|
||
private: | ||
tcp::resolver resolver_; | ||
tcp::socket socket_; | ||
std::string server_url_; | ||
|
||
void parseUrl(const std::string &url, std::string &host, std::string &port, std::string &target) { | ||
size_t pos = url.find("//"); | ||
size_t start = (pos == std::string::npos) ? 0 : pos + 2; | ||
size_t end = url.find('/', start); | ||
|
||
std::string full_host = url.substr(start, end - start); | ||
size_t port_pos = full_host.find(':'); | ||
|
||
if (port_pos != std::string::npos) { | ||
host = full_host.substr(0, port_pos); | ||
port = full_host.substr(port_pos + 1); | ||
} else { | ||
host = full_host; | ||
port = "443"; // Default HTTPS port | ||
} | ||
|
||
target = (end == std::string::npos) ? "/" : url.substr(end); | ||
} | ||
|
||
int calculateAverage(const std::vector<int>& values) { | ||
int sum = 0; | ||
for (int v : values) sum += v; | ||
return values.empty() ? -1 : sum / values.size(); | ||
} | ||
|
||
double calculateJitter(const std::vector<int>& latencies) { | ||
if (latencies.size() < 2) return 0.0; | ||
|
||
double sum = 0.0; | ||
for (size_t i = 1; i < latencies.size(); ++i) { | ||
sum += std::abs(latencies[i] - latencies[i - 1]); | ||
} | ||
return sum / (latencies.size() - 1); | ||
} | ||
|
||
double simulatePacketLoss() { | ||
std::random_device rd; | ||
std::mt19937 gen(rd()); | ||
std::uniform_real_distribution<> dist(0.0, 10.0); // Simulating 0-10% loss | ||
return dist(gen); | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
#pragma once | ||
|
||
#include <boost/asio.hpp> | ||
#include <boost/asio/ssl.hpp> | ||
#include <boost/beast.hpp> | ||
#include <iostream> | ||
#include <map> | ||
#include <sstream> | ||
#include <string> | ||
|
||
namespace beast = boost::beast; | ||
namespace http = beast::http; | ||
namespace net = boost::asio; | ||
namespace ssl = boost::asio::ssl; | ||
using tcp = net::ip::tcp; | ||
|
||
class PlaylistParser { | ||
public: | ||
PlaylistParser(net::io_context &ioc, ssl::context &ssl_ctx, const std::string &url) | ||
: resolver_(ioc), stream_(ioc, ssl_ctx), master_url_(url) {} | ||
|
||
bool fetchMasterPlaylist() { | ||
std::string host, port, target; | ||
parseUrl(master_url_, host, port, target); | ||
|
||
std::cout << "[INFO] Fetching master playlist from: " << master_url_ << "\n"; | ||
|
||
try { | ||
std::cout << "[INFO] Resolving host: " << host << " on port " << port << "\n"; | ||
auto const results = resolver_.resolve(host, port); | ||
|
||
std::cout << "[INFO] Connecting to: " << host << ":" << port << "\n"; | ||
beast::get_lowest_layer(stream_).connect(results); | ||
stream_.handshake(ssl::stream_base::client); | ||
|
||
http::request<http::string_body> req{http::verb::get, target, 11}; | ||
req.set(http::field::host, host); | ||
req.set(http::field::user_agent, "Boost.Beast"); | ||
|
||
std::cout << "[INFO] Sending HTTP GET request...\n"; | ||
http::write(stream_, req); | ||
|
||
beast::flat_buffer buffer; | ||
http::response<http::dynamic_body> res; | ||
http::read(stream_, buffer, res); | ||
|
||
std::cout << "[INFO] Received HTTP Response. Status: " << res.result_int() << "\n"; | ||
beast::error_code ec; | ||
stream_.shutdown(ec); | ||
if (ec && ec != beast::errc::not_connected) { | ||
std::cerr << "[WARN] SSL Shutdown failed: " << ec.message() << "\n"; | ||
} | ||
|
||
std::string body = beast::buffers_to_string(res.body().data()); | ||
|
||
parsePlaylist(body); | ||
return true; | ||
} catch (const std::exception &e) { | ||
std::cerr << "[ERROR] Error fetching master playlist: " << e.what() << "\n"; | ||
return false; | ||
} | ||
} | ||
|
||
std::map<int, std::string> getBitratePlaylists() const { | ||
return bitrate_playlists_; | ||
} | ||
|
||
private: | ||
net::ip::tcp::resolver resolver_; | ||
ssl::stream<beast::tcp_stream> stream_; | ||
std::string master_url_; | ||
std::map<int, std::string> bitrate_playlists_; | ||
|
||
void parseUrl(const std::string &url, std::string &host, std::string &port, std::string &target) { | ||
size_t pos = url.find("//"); | ||
size_t start = (pos == std::string::npos) ? 0 : pos + 2; | ||
size_t end = url.find('/', start); | ||
|
||
std::string full_host = url.substr(start, end - start); | ||
size_t port_pos = full_host.find(':'); | ||
|
||
if (port_pos != std::string::npos) { | ||
host = full_host.substr(0, port_pos); | ||
port = full_host.substr(port_pos + 1); | ||
} else { | ||
host = full_host; | ||
port = "443"; // Default HTTPS port | ||
} | ||
|
||
target = (end == std::string::npos) ? "/" : url.substr(end); | ||
std::cout << "[DEBUG] Parsed Host: " << host << ", Port: " << port << ", Target: " << target << "\n"; | ||
} | ||
|
||
void parsePlaylist(const std::string &playlist) { | ||
std::istringstream ss(playlist); | ||
std::string line; | ||
int current_bitrate = 0; | ||
|
||
while (std::getline(ss, line)) { | ||
if (line.find("#EXT-X-STREAM-INF") != std::string::npos) { | ||
size_t bitrate_pos = line.find("BANDWIDTH="); | ||
if (bitrate_pos != std::string::npos) { | ||
size_t start = bitrate_pos + 9; // Position after "BANDWIDTH=" | ||
size_t end = line.find(',', start); // Find next comma | ||
|
||
std::string bitrate_str = line.substr(start, end - start); | ||
|
||
// Remove any '=' sign if it appears (edge case) | ||
bitrate_str.erase(std::remove(bitrate_str.begin(), bitrate_str.end(), '='), bitrate_str.end()); | ||
|
||
std::cout << "[DEBUG] Extracted Bitrate String: '" << bitrate_str << "'\n"; | ||
|
||
// Ensure it's numeric | ||
if (!bitrate_str.empty() && std::all_of(bitrate_str.begin(), bitrate_str.end(), ::isdigit)) { | ||
try { | ||
current_bitrate = std::stoi(bitrate_str); | ||
std::cout << "[INFO] Parsed Bitrate: " << current_bitrate << " kbps\n"; | ||
} catch (const std::exception &e) { | ||
std::cerr << "[ERROR] Failed to parse bitrate: " << e.what() << "\n"; | ||
continue; | ||
} | ||
} else { | ||
std::cerr << "[ERROR] Invalid bitrate format: '" << bitrate_str << "'\n"; | ||
continue; | ||
} | ||
} | ||
} else if (!line.empty() && line[0] != '#') { | ||
if (current_bitrate > 0) { | ||
bitrate_playlists_[current_bitrate] = line; | ||
std::cout << "[INFO] Added bitrate playlist: " << current_bitrate << " -> " << line << "\n"; | ||
} else { | ||
std::cerr << "[ERROR] Playlist URL found but no valid bitrate!\n"; | ||
} | ||
} | ||
} | ||
} | ||
}; |
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
#include "ABRManager.hpp" | ||
#include <boost/asio.hpp> | ||
|
||
auto main(int argc, char* argv[]) -> int | ||
{ | ||
if (argc != 2) | ||
{ | ||
std::cerr << "Usage: " << argv[0] << " <network-stream>" << std::endl; | ||
return EXIT_FAILURE; | ||
} | ||
try | ||
{ | ||
boost::asio::io_context ioc; | ||
|
||
// Replace with your HLS master playlist URL | ||
std::string master_url = argv[1]; | ||
|
||
ABRManager abr_manager(ioc, master_url); | ||
abr_manager.selectBestBitrate(); | ||
} | ||
catch (const std::exception& e) | ||
{ | ||
std::cerr << "[ERROR] Exception: " << e.what() << "\n"; | ||
return EXIT_FAILURE; | ||
} | ||
|
||
return EXIT_SUCCESS; | ||
} |