blob: 0b02941aeb83e4b166a5ac0844d5b9f5a36eef1a [file] [log] [blame]
#!/bin/bash
SEG_DURATION_OUTPUT_FILE=seg_duration_output.csv
TRANS_FRAME_OUTPUT_FILE=trans_frame_output.csv
SEG_FRAME_OUTPUT_FILE=seg_frame_output.csv
TRANS_GOP_OUTPUT_FILE=trans_gop_output.csv
SEG_GOP_OUTPUT_FILE=seg_gop_output.csv
TRANS_PTS_OUTPUT_FILE=trans_pts_output.csv
SEG_PTS_OUTPUT_FILE=seg_pts_output.csv
###################################
function printUsage() {
echo
echo "Usage:"
echo " analyze [path/to/transcoded/file]"
echo
echo "This tool analyzes all *.ts_* HLS segments in the local directory for "
echo "issues. If you pass the path to the transcoded original file as a "
echo "parameter it will analyze that as well for comparison. Issues that "
echo "appear in just the HLS analysis point to a miniclient problem. Issues "
echo "that appear in the latter analysis (or both analyses) point to a "
echo "BroadCom issue."
echo
echo "This tool requires dvbsnoop and ffmpeg! It also needs gnuplot to "
echo "print pretty graphs, but it's optional."
echo
echo " 'apt-get install dvbsnoop ffmpeg gnuplot'"
echo
}
## Parse args and check dependencies
FILE=""
if [ $# -eq 1 ]
then
FILE=$1
fi
RAN_A_TEST=0
## Return true if depedant app (1) exists
dependency_exists() {
command -v $1 >/dev/null 2>&1
}
## Check for deps without killing terminal (as exit does, hence the goto)
dependency_exists ffmpeg || { echo; echo >&2 "This script requires ffmpeg but it's not installed!"; printUsage; exit; }
dependency_exists dvbsnoop || { echo; echo >&2 "This script requires dvbsnoop but it's not installed!"; printUsage; exit; }
## Gnuplot the results given by the parameters
function plotResult() {
if dependency_exists gnuplot
then
PLOTFILE=$1
TITLE=$2
XLABEL=$3
XRANGE=$4
YLABEL=$5
YRANGE=$6
TYPE=$7
gnuplot -persist <<HERE
set title "${TITLE}"
set xlabel '${XLABEL}'
set xrange[${XRANGE}]
set ylabel '${YLABEL}'
set yrange[${YRANGE}]
unset key
plot "${PLOTFILE}" using 1:3 with ${TYPE}
HERE
else
echo " 'gnuplot' is not installed, so no pretty graphs for you today :("
fi
}
## Calculate the GOP between each keyframe given an input file (1) to the given output file (2)
function calculateGOP() {
OUTPUT_FILE=$2
## GOP - Algorithm stolen Shamelessly From Zeev Leiber
PID=$(ffprobe -i $1 2>&1 | grep "Video:" | sed 's/[][]/ /g' | awk '{print "echo $((" $3 "))"}' | bash)
echo " Processing $1 (Video PID $PID)..."
## Searching for random_access of 0 could cause invalid results because of padding adaptation fields
linecount=0
random_access=0
## 14 (1 for match, 12 after, 1 separator) lines per packet to decide if it is a new GOP
GOPs=$(
dvbsnoop -s ts -if $1 $PID | grep -A 12 'Packet data starts' | while read -r h
do
if echo $h | grep 'random_access_indicator: 1' > /dev/null
then
random_access=1
fi
linecount=`expr $linecount + 1`
if [[ $linecount -ge 14 ]]
then
echo $random_access
random_access=0
linecount=0
fi
done
echo $random_access
)
FRAME_COUNT=0
for g in $GOPs
do
if [[ $g -eq 1 ]]
then
if [[ $FRAME_COUNT -gt 0 ]]
then
echo $NUM_GOP $1 $FRAME_COUNT >> "$OUTPUT_FILE"
fi
FRAME_COUNT=1
NUM_GOP=`expr $NUM_GOP + 1`
else
FRAME_COUNT=`expr $FRAME_COUNT + 1`
fi
done
echo $NUM_GOP $1 $FRAME_COUNT >> "$OUTPUT_FILE"
}
## Calculate the PTS delta between each frame given an input file (1) adding results into a single file (2)
function calculatePtsDelta() {
OUTPUT_FILE=$2
PID=$(ffprobe -i $1 2>&1 | grep "Video:" | sed 's/[][]/ /g' | awk '{print "echo $((" $3 "))"}' | bash)
echo " Processing $1 (Video PID $PID)..."
## Algorithm stolen Shamelessly From Jean-Francois Thibert
DELTAs=$(dvbsnoop -s ts -if $1 -tssubdecode $PID | grep PTS | grep Timestamp | awk '{print $3-prev; prev=$3 }')
for d in $DELTAs
do
if [[ $d -lt 10000 ]]
then
echo $NUM_FRAMES $1 $d >> $OUTPUT_FILE
NUM_FRAMES=`expr $NUM_FRAMES + 1`
else
echo " ... Removing bad value $d"
fi
done
}
## Calculate the size of each frame given an input file (1) adding results into a single file (2)
function calculateFrameSize() {
OUTPUT_FILE=$2
echo " Processing $1..."
FRAMEs=$(ffprobe -v quiet -show_frames -select_streams v -i $1 | awk '/pkt_size=/ {sub(/pkt_size=/, "", $1); print $1;}')
for g in $FRAMEs
do
echo $NUM_FRAMES $1 $g >> $OUTPUT_FILE
NUM_FRAMES=`expr $NUM_FRAMES + 1`
done
}
## Calculate duration of each HLS segment in the current folder
function calculateDurations() {
echo "# NUM File Duration" > $SEG_DURATION_OUTPUT_FILE
FILES=$(ls -v *.ts_*)
NUM=1
for f in $FILES
do
echo " Processing $f..."
ffprobe -v quiet -show_packets -select_streams v -i $f > ./temp
duration=$(cat ./temp | awk '/duration_time=0/ {sub(/duration_time=/, "", $1); total = total + $1;}END {print total;}')
echo $NUM $f $duration >> $SEG_DURATION_OUTPUT_FILE
NUM=`expr $NUM + 1`
done
echo
echo "Result written to $SEG_DURATION_OUTPUT_FILE"
plotResult $SEG_DURATION_OUTPUT_FILE "HLS Segment Duration" "Segment" "1:${NUM}-1" "Duration (s)" "0:*" "lines"
}
##########
## Work around fact that you can't pass multi-line input to a function...
## Calculate GOP for each segment adding results into a single file (1)
function calculateSegmentGOPs() {
FILES=$(ls -v *.ts_*)
echo "# NUM File GOP" > $1
NUM_GOP=1
for f in $FILES
do
calculateGOP $f $1
done
echo
echo "Result written to $1"
plotResult $1 "HLS GOP Analysis" "Group" "1:$NUM_GOP-1" "GOP" "0:35" "lines"
}
## Calculate GOP for transcoded file (1) adding results into a single file (2)
function calculateTranscodedGOP() {
echo "# NUM File GOP" > $2
NUM_GOP=1
calculateGOP $1 $2
echo
echo "Result written to $2"
plotResult $2 "Transcoded GOP Analysis" "Group" "1:$NUM_GOP-1" "GOP" "0:35" "lines"
}
## Calculate the PTS delta between each frame in each segment adding results into a single file (1)
function calculateSegmentPTS() {
FILES=$(ls -v *.ts_*)
echo "# NUM File Frame_Size" > $1
NUM_FRAMES=1
for f in $FILES
do
calculatePtsDelta $f $1
done
echo
echo "Result written to $1"
plotResult $1 "HLS PTS Delta Analysis" "Frame" "1:${NUM_FRAMES}-1" "Delta (ms)" "0:*" "lines"
}
## Calculate the PTS delta between each frame given an input file (1) adding results into a single file (2)
function calculateTranscodedPTS() {
echo "# NUM File Frame_Size" > $2
NUM_FRAMES=1
calculatePtsDelta $1 $2
echo
echo "Result written to $2"
plotResult $2 "Transcoded PTS Delta Analysis" "Frame" "1:${NUM_FRAMES}-1" "Delta (ms)" "0:*" "lines"
}
## Calculate the size of each frame in each segment adding results into a single file (1)
function calculateSegmentFS() {
FILES=$(ls -v *.ts_*)
echo "# NUM File Frame_Size" > $1
NUM_FRAMES=1
for f in $FILES
do
calculateFrameSize $f $1
done
echo
echo "Result written to $1"
plotResult $1 "HLS Frame Size Analysis" "Frame" "1:${NUM_FRAMES}-2" "Size (b)" "0:*" "lines"
}
## Calculate the size of each frame given an input file (1) adding results into a single file (2)
function calculateTranscodedFS() {
echo "# NUM File Frame_Size" > $2
NUM_FRAMES=1
calculateFrameSize $1 $2
echo
echo "Result written to $2"
plotResult $2 "Transcoded Frame Size Analysis" "Frame" "1:${NUM_FRAMES}-2" "Size (b)" "0:*" "lines"
}
##########
## Script flow ##
## Segments
echo
echo "Starting segment analysis (.ts_* files)"
ls -v *.ts_* 2>./temp 1>/dev/null
if [[ $? == 0 ]]
then
echo
echo "Analyzing Duration"
calculateDurations $FILES
echo
echo "Analyszing GOP"
calculateSegmentGOPs $SEG_GOP_OUTPUT_FILE
echo
echo "Analyszing PTS"
calculateSegmentPTS $SEG_PTS_OUTPUT_FILE
echo
echo "Analyzing Frame Size"
calculateSegmentFS $SEG_FRAME_OUTPUT_FILE
RAN_A_TEST=1
else
echo " No segments found, skipping these tests."
fi
echo
## Transcoded
echo "Starting transcoded file analysis"
if [[ "$FILE" != "" ]]
then
echo
echo "Analyzing GOP"
calculateTranscodedGOP $FILE $TRANS_GOP_OUTPUT_FILE
echo
echo "Analyzing PTS"
calculateTranscodedPTS $FILE $TRANS_PTS_OUTPUT_FILE
echo
echo "Analyzing Frame Size"
calculateTranscodedFS $FILE $TRANS_FRAME_OUTPUT_FILE
RAN_A_TEST=1
else
echo " No File specified as a parameter, skipping these tests."
fi
## If we didn't test anything, print usage in case the user is new...
if [ $RAN_A_TEST == 0 ]
then
printUsage
fi
if [ -e "./temp" ]
then
rm ./temp
fi
echo