#!/bin/sh # # bitrate_calculator.sh # # This script supports two modes: # # 1. Normal (default) mode: # Usage: # bitrate_calculator.sh [fec_ratio] [max_mcs (0-7)] [--cap ] [--gi ] [--max_bw <20|40>] # # If fec_ratio is not provided, it defaults to "8/12". # The script searches (in fixed order: 20 MHz long, 20 MHz short, then 40 MHz long, 40 MHz short) # for the lowest MCS (0..max_mcs, default max_mcs=7) whose computed forward rate is ≥ target. # If the optional "--gi" is provided, only that guard interval is used. # If the optional "--max_bw" is provided, it limits the search to that maximum bandwidth. # # The computed rate is given by: # # final = ( base_rate * 3 * fec_n + (4*fec_k)/2 ) / (4*fec_k) # # where base_rate (in kbps) is looked up from hardcoded tables. # The computed rate is capped at a maximum value (default cap is 20000 kbps, # overrideable via --cap). # # Output is a single line in the format: # ::: # # 2. Backwards mode: # Usage: # bitrate_calculator.sh --backwards [--cap ] # # It computes (and outputs) the forward rate (capped) for the given parameters. # # ------------------------- # Parse optional global arguments # ------------------------- # Default cap is 20000 kbps. CAP=20000 backwards_mode=0 locked_gi="" MAX_BW=40 # new: default maximum bandwidth for candidate search is 40MHz (i.e. search both 20 & 40) other_args="" while [ "$#" -gt 0 ]; do case "$1" in --cap) shift if [ "$#" -eq 0 ]; then echo "Error: --cap requires a value" exit 1 fi CAP="$1" shift ;; --backwards) backwards_mode=1 shift ;; --gi) shift if [ "$#" -eq 0 ]; then echo "Error: --gi requires a value (long or short)" exit 1 fi locked_gi="$1" case "$locked_gi" in long|short) ;; *) echo "Error: --gi must be 'long' or 'short'." ; exit 1 ;; esac shift ;; --max_bw) shift if [ "$#" -eq 0 ]; then echo "Error: --max_bw requires a value (20 or 40)" exit 1 fi if [ "$1" != "20" ] && [ "$1" != "40" ]; then echo "Error: --max_bw must be either 20 or 40" exit 1 fi MAX_BW="$1" shift ;; *) other_args="$other_args $1" shift ;; esac done set -- $other_args # ------------------------- # Function: compute_final # ------------------------- # Given: # bw : Bandwidth (20 or 40) # gi : Guard interval ("long" or "short") # mcs : MCS index (0-7) # fec_n : FEC numerator # fec_k : FEC denominator # # Looks up the base_rate (in kbps) from hardcoded tables: # # 20 MHz: # Long GI: 6500, 13000, 19500, 26000, 39000, 52000, 58500, 65000 # Short GI: 7200, 14400, 21700, 28900, 43300, 57800, 65000, 72200 # # 40 MHz: # Long GI: (20% reduced) 10800, 21600, 32400, 43200, 64800, 86400, 97200, 108000 # Short GI: (20% reduced) 12000, 24000, 36000, 48000, 72000, 96000, 108000, 120000 # # Then computes: # # final = ( base_rate * 3 * fec_n + (4*fec_k)/2 ) / (4*fec_k) # # If final > CAP, it is set to CAP. compute_final() { bw="$1" gi="$2" mcs="$3" fec_n="$4" fec_k="$5" if [ "$bw" -eq 20 ]; then if [ "$gi" = "long" ]; then case "$mcs" in 0) base=6500 ;; 1) base=12000 ;; 2) base=15500 ;; 3) base=20000 ;; 4) base=25000 ;; 5) base=42000 ;; 6) base=47500 ;; 7) base=55000 ;; esac else case "$mcs" in 0) base=7200 ;; 1) base=13400 ;; 2) base=18700 ;; 3) base=21900 ;; 4) base=28300 ;; 5) base=43800 ;; 6) base=50000 ;; 7) base=55200 ;; esac fi elif [ "$bw" -eq 40 ]; then if [ "$gi" = "long" ]; then case "$mcs" in 0) base=9800 ;; 1) base=18600 ;; 2) base=30400 ;; 3) base=40200 ;; 4) base=55800 ;; 5) base=80400 ;; 6) base=90200 ;; 7) base=97000 ;; esac else case "$mcs" in 0) base=12000 ;; 1) base=24000 ;; 2) base=36000 ;; 3) base=48000 ;; 4) base=60000 ;; 5) base=91000 ;; 6) base=980000 ;; 7) base=100000 ;; esac fi else echo "Error: unsupported bandwidth $bw" >&2 exit 1 fi denom=$(( 3 * fec_k )) half_denom=$(( denom / 2 )) num=$(( base * 2 * fec_n )) final=$(( (num + half_denom) / denom )) if [ "$final" -gt "$CAP" ]; then final="$CAP" fi echo "$final" } # ------------------------- # Mode Selection # ------------------------- if [ "$backwards_mode" -eq 1 ]; then # Backwards mode: Expect exactly 4 arguments: bw, mcs, guard, fec. if [ "$#" -ne 4 ]; then echo "Usage: $0 --backwards [--cap ]" exit 1 fi bw="$1" mcs="$2" guard="$3" fec="$4" if [ "$bw" -ne 20 ] && [ "$bw" -ne 40 ]; then echo "Error: bandwidth must be 20 or 40." exit 1 fi case "$mcs" in [0-7]) ;; *) echo "Error: mcs must be between 0 and 7." ; exit 1 ;; esac if [ "$guard" != "long" ] && [ "$guard" != "short" ]; then echo "Error: guard must be 'long' or 'short'." exit 1 fi fec_n=$(echo "$fec" | cut -d'/' -f1) fec_k=$(echo "$fec" | cut -d'/' -f2) if [ -z "$fec_n" ] || [ -z "$fec_k" ]; then echo "Error: fec ratio must be in the form x/y." exit 1 fi final=$(compute_final "$bw" "$guard" "$mcs" "$fec_n" "$fec_k") echo "$final" exit 0 fi # ------------------------- # Normal Mode (Forward Search) # ------------------------- # Usage: # bitrate_calculator.sh [fec_ratio] [max_mcs (0-7)] [--cap ] [--gi ] # # If fec_ratio is not provided, default is "8/12". If max_mcs is not provided, default is 7. if [ "$#" -lt 1 ] || [ "$#" -gt 3 ]; then echo "Usage: $0 [fec_ratio] [max_mcs (0-7)] [--cap ] [--gi ] [--max_bw <20|40>]" exit 1 fi target="$1" shift case "$target" in ''|*[!0-9]*) echo "Error: target must be a positive integer." >&2 exit 1 ;; esac if [ "$#" -ge 1 ]; then case "$1" in */*) fec="$1"; shift;; *) fec="8/12" ;; esac else fec="8/12" fi fec_n=$(echo "$fec" | cut -d'/' -f1) fec_k=$(echo "$fec" | cut -d'/' -f2) if [ -z "$fec_n" ] || [ -z "$fec_k" ]; then echo "Error: fec ratio must be in the form x/y." >&2 exit 1 fi if [ "$#" -ge 1 ]; then max_mcs="$1" shift case "$max_mcs" in [0-7]) ;; *) echo "Error: max_mcs must be between 0 and 7." >&2; exit 1 ;; esac else max_mcs=7 fi # Define which bandwidth values to search based on the --max_bw option. if [ "$MAX_BW" -eq 20 ]; then BW_VALUES="20" else BW_VALUES="20 40" fi candidate_found=0 if [ -n "$locked_gi" ]; then for bw in $BW_VALUES; do for mcs in $(seq 0 $max_mcs); do final=$(compute_final "$bw" "$locked_gi" "$mcs" "$fec_n" "$fec_k") if [ "$final" -ge "$target" ]; then candidate_mcs="$mcs" candidate_bw="$bw" candidate_gi="$locked_gi" candidate_fec="$fec" candidate_found=1 break 2 fi done done else for bw in $BW_VALUES; do for gi in long short; do for mcs in $(seq 0 $max_mcs); do final=$(compute_final "$bw" "$gi" "$mcs" "$fec_n" "$fec_k") if [ "$final" -ge "$target" ]; then candidate_mcs="$mcs" candidate_bw="$bw" candidate_gi="$gi" candidate_fec="$fec" candidate_found=1 break 3 fi done done done fi if [ "$candidate_found" -eq 1 ]; then echo "${candidate_mcs}:${candidate_bw}:${candidate_gi}:${candidate_fec}" else echo "No combination found." >&2 exit 1 fi