From d54700c9f520fbf3fd8bdd114cb7736c42290a5a Mon Sep 17 00:00:00 2001 From: DenizUgur Date: Sun, 17 Jul 2022 16:51:00 -0700 Subject: [PATCH] add network throttling option to gpac --- Dockerfile | 3 +++ README.md | 3 +-- scripts/entrypoint.sh | 38 ++++++++++++++++++++++++++--- scripts/gpac.sh | 4 ++- server/gpac-dash.js | 57 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 99 insertions(+), 6 deletions(-) diff --git a/Dockerfile b/Dockerfile index dd81c8c..5c45797 100644 --- a/Dockerfile +++ b/Dockerfile @@ -139,5 +139,8 @@ COPY ./config/nginx.prod.conf /etc/nginx/nginx.conf COPY --from=node-builder /server/gpac-dash /usr/local/bin/ COPY --from=node-builder /server/app/build /opt/server +# Copy network profiles +COPY ./simulator/config/profiles /opt/profiles + COPY ./scripts/entrypoint.sh /opt/entrypoint.sh ENTRYPOINT [ "/opt/entrypoint.sh" ] \ No newline at end of file diff --git a/README.md b/README.md index d5be6ca..8e5d27e 100644 --- a/README.md +++ b/README.md @@ -34,8 +34,7 @@ docker run -p 80:80 \ # web server will be accesible on port 80 -w "/home" \ # If you want to use your own video files --name capsc-demo \ # Optional: If you want to name the container ghcr.io/denizugur/capsc \ # Chanege it to `capsc` if you built the image yourself - # A file to stream. Must be relative to current working directory - # Set to true if you want to display the visualizations over the video + --help # Shows the usage instructions for the demo ``` ## Citation diff --git a/scripts/entrypoint.sh b/scripts/entrypoint.sh index db80373..c633803 100755 --- a/scripts/entrypoint.sh +++ b/scripts/entrypoint.sh @@ -1,6 +1,38 @@ #!/bin/bash -INPUT_FILE=$1 -VISUALIZATION=$2 +POSITIONAL_ARGS=() + +while [[ $# -gt 0 ]]; do + case $1 in + -i | --input) + INPUT_FILE="$2" + shift # past argument + shift # past value + ;; + -n | --network-profile) + NETWORK_PROFILE="-use-network-profile $2" + shift # past argument + shift # past value + ;; + -v | --visualization) + VISUALIZATION=YES + shift # past argument + ;; + -h | --help) + echo "Usage: ./entrypoint.sh [-i | --input ] [-n | --network-profile ] [-v | --visualization]" + exit 0 + ;; + -* | --*) + echo "Unknown option $1" + exit 1 + ;; + *) + POSITIONAL_ARGS+=("$1") # save positional arg + shift # past argument + ;; + esac +done + +set -- "${POSITIONAL_ARGS[@]}" # restore positional parameters if [ -z $INPUT_FILE ]; then echo "No input file specified" @@ -18,7 +50,7 @@ nginx # Start GPAC cd /opt -(gpac-dash -chunk-media-segments -cors &) >/dev/null 2>&1 +(gpac-dash -chunk-media-segments -cors $NETWORK_PROFILE &) >/dev/null 2>&1 # Show banner figlet "A-CAPSC Demonstration" diff --git a/scripts/gpac.sh b/scripts/gpac.sh index ca26445..a5bc40c 100755 --- a/scripts/gpac.sh +++ b/scripts/gpac.sh @@ -1,4 +1,6 @@ #!/bin/bash BASE=$(dirname "$0") +export NODE_ENV=development + cd $BASE/../server -forever ./gpac-dash.js -chunk-media-segments -cors \ No newline at end of file +forever ./gpac-dash.js -chunk-media-segments -cors $@ \ No newline at end of file diff --git a/server/gpac-dash.js b/server/gpac-dash.js index e62fb33..52574f3 100755 --- a/server/gpac-dash.js +++ b/server/gpac-dash.js @@ -16,6 +16,7 @@ function usage() { console.log("-segment-marker <4cc> marker for end of segment (default eods)"); console.log("-no-marker-write strip marker of the generated bitstream (default false)"); console.log("-cors add CORS header for all domains") + console.log("-use-network-profile specify the network profile to use (default: not used)"); console.log("-use-watch uses watch instead of watchFile (default: false)"); console.log("-quality-log-file name of a file in which the latest quality requested is logged (default: no log), experimental"); console.log("-incoming-log-file name of a file in which all requests are logged (default: no log)"); @@ -32,6 +33,7 @@ var port = 8000; var quality_log_file = null; var incoming_log_file = null; var logLevel = 0; +var networkProfile = null; /* Boolean controlling the sending of segments fragment-by-fragment as HTTP chunks, requires MP4Box or DashCast to use -segment-marker eods */ @@ -91,9 +93,51 @@ function reportEvent(type, event, filename) { reportMessage(logLevels.DEBUG_BASIC, type + " event (" + event + ") on " + filename + " (size " + file_size + ")"); } +let prevTime = null; +let sessionStartTime = null; +function throttleBandwidth(response, fileData) { + let presetOffset = 10; + let throttleOffset = 5; + + if (sessionStartTime == null || prevTime + 30 * 1000 < getTime()) + sessionStartTime = getTime(); + prevTime = getTime(); + + let current_duration = getTime() - sessionStartTime; + current_duration /= 1000; + + let networkProfile_i = parseInt( + (current_duration + presetOffset) % networkProfile.length + ); + let targetRate = + (networkProfile[networkProfile_i].data.download * 8) / 1000; + targetRate *= 0.75; + + let totalDuration = (getTime() - response.startTime) / 1000; + let size = (8 * fileData.total_sent) / 1000; + let targetDuration = size / targetRate; + + let skipFlag = + totalDuration < 1 || size < 1000 || current_duration < throttleOffset; + if (totalDuration < targetDuration && !skipFlag) { + var diff = targetDuration - totalDuration; + var clamp = Math.min(diff, 0.2); + let targetTime = totalDuration + clamp; + while (true) { + totalDuration = (getTime() - response.startTime) / 1000; + if (totalDuration >= targetTime) { + break; + } + } + } +} + function sendAndUpdateBuffer(response, message, fileData, endpos, noWrite) { var tmpBuffer; fileData.total_sent += endpos; + + if (networkProfile != null) throttleBandwidth(response, fileData); + reportMessage(sendMediaSegmentsFragmented ? logLevels.INFO : logLevels.DEBUG_BASIC, "sendMediaSegmentsFragmented: " + sendMediaSegmentsFragmented + " File " + fileData.filename + ", sending " + message + " data from " + fileData.next_byte_to_send + " to " + (endpos - 1) + " in " + (getTime() - response.startTime) + " ms (total_sent: " + fileData.total_sent + ") at utc " + getTime()); tmpBuffer = fileData.buffer.slice(fileData.next_byte_to_send, endpos); @@ -559,6 +603,19 @@ process.argv.splice(1).forEach(function (val, index, array) { sendMediaSegmentsFragmented = true; } else if (val === "-cors") { allowCors = true; + } else if (val === "-use-network-profile") { + try { + const profilesPath = + process.env["NODE_ENV"] == "development" + ? "../simulator/config/profiles" + : "/opt/profiles"; + networkProfile = require(`${profilesPath}/${ + array[index + 1] + }.json`); + } catch { + console.error("Network profile not found"); + process.exit(-1); + } } else if (val === "-use-watch") { use_watchFile = false; } else if (val === "-quality-log-file") {