#!/bin/sh
. /lib/rcscripts/sh/error.sh
. /lib/rcscripts/sh/rc-std2parse.sh
. /lib/rcscripts/sh/files.sh
LOG="logger -s -t${0##*/}[$$]"
LOG_I="$LOG -pinfo"
LOG_E="$LOG -perr"
PROFILES_COUNT=-1
# Dynamic configuration containing ids of existing pipelines.
CONF_FILE=/etc/dynamic/mcast-always.conf
LOCK_FILE=/var/lock/mcast-always
# Check that a session id is only made of figures.
is_session_id() {
[ $# -eq 1 ] && [ "$1" ] || {
$LOG_E "is_session_id: missing or empty argument."
return 1
}
case $1 in
*[!0-9]*)
$LOG_E "is_session_id: invalid argument."
return 1
;
esac
}
# Set CM_PORT in the range provided by the user. Since the error conditions
# are unlikely to happen, and to avoid a complex port lookup, we simply ask
# the user to set a port manually.
# $1 is a shift in port numbering (video: 0 audio:2)
set_cm_port() {
local used_port ports_needed i=0
case $1 in
0|2)
;
*)
$LOG_E "set_cm_port: invalid argument."
return 1
;
esac
# The RTP port is always even (RTCP odd).
CM_PORT=$(($STD2_NETWORK_RTP_STARTPORT + $1 + $CHANNEL * 4))
[ $(($CM_PORT % 2)) -eq 0 ] || CM_PORT=$(($CM_PORT + 1))
# Verify that the port is within range
[ $CM_PORT -lt $STD2_NETWORK_RTP_ENDPORT ] || {
$LOG_E "Automatic port setting failed. Please set the" \
"video port manually for channel $CHANNEL."
return 1
}
# Verify that the new port is not used by another channel.
while [ $i -lt $STD2_NETWORK_RTP_NBROFRTPGROUPS ]; do
[ $i -ne $CHANNEL ] || {
i=$(($i + 1))
continue
}
case $MEDIA_TYPE in
video)
eval used_port=\$STD2_NETWORK_RTP_R${i}_VIDEOPORT
;
audio)
eval used_port=\$STD2_NETWORK_RTP_R${i}_AUDIOPORT
;
esac
[ $CM_PORT -ne $used_port ] || {
$LOG_E "Port conflict between channels $CHANNEL and" \
"$i. Please set the port manually for" \
"channel $CHANNEL."
return 1
}
i=$(($i + 1))
done
}
# DBUS variables and functions
# DBUS functions display specific dbus errors in case of failure.
DBUS="dbus-send --system --dest=com.axis.Streamer --print-reply --type=method_call"
DOBJ_PATH=/com/axis/Streamer/RTP
METHOD_PREFIX=com.axis.Streamer.RTP
session_create() {
# media=$1, profile=$2, group=$3, port=$4 ttl=$5
[ $# -eq 5 ] || {
$LOG_E "session_create: wrong number of arguments."
return 1
}
case $1 in
audio)
mediaopt="audio=1&video=0"
;
video)
mediaopt="audio=0&video=1"
;
*)
$LOG_E "session_create: invalid media type $1"
return 1
;
esac
# Leave the rest of arguments validation to $DBUS.
# Tell the streamer to create a multicast pipeline.
$DBUS $DOBJ_PATH $METHOD_PREFIX.CreatePipeline \
string:"/axis-media/media.amp" \
string:"$2&camera=$CAMERA&$mediaopt" \
string:"$3" \
int32:$4 \
int32:$5 2>&1
}
session_play() {
is_session_id "$1" &&
$DBUS $DOBJ_PATH/Pipeline/$1 $METHOD_PREFIX.Pipeline.Play >/dev/null 2>&1
}
session_destroy() {
is_session_id "$1" &&
$DBUS $DOBJ_PATH/Pipeline/$1 $METHOD_PREFIX.Pipeline.Destroy >/dev/null 2>&1
}
# Start and stop continuous multicast.
start_continuous_multicast() {
local cm_profile_name cm_group trackID creation_msg session_id cm_profile
# Set variables necessary to build a pipeline.
case $MEDIA_TYPE in
video)
eval cm_profile=\$STD2_NETWORK_RTP_R${CHANNEL}_ALWAYSMULTICASTPROFILE
eval cm_group=\$STD2_NETWORK_RTP_R${CHANNEL}_VIDEOADDRESS
eval CM_PORT=\$STD2_NETWORK_RTP_R${CHANNEL}_VIDEOPORT
# videoport == 0 means automatic port setting.
[ $CM_PORT -ne 0 ] || set_cm_port 0
media=video
;
audio)
eval cm_profile=\$STD2_NETWORK_RTP_R${CHANNEL}_ALWAYSMULTICASTPROFILE
eval cm_group=\$STD2_NETWORK_RTP_R${CHANNEL}_AUDIOADDRESS
eval CM_PORT=\$STD2_NETWORK_RTP_R${CHANNEL}_AUDIOPORT
[ $CM_PORT -ne 0 ] || set_cm_port 2
media=audio
;
esac
eval ttl=\$STD2_NETWORK_RTP_R${CHANNEL}_TTL
# Create the multicast pipeline using the DBus API.
creation_msg=$(session_create $media "$cm_profile" "$cm_group" "$CM_PORT" $ttl) || {
$LOG_E $creation_msg
return 1
}
# If session_create is successful, stdout contains our precious pipeline id.
session_id=$(echo $creation_msg | sed -rne 's|^.*Pipeline/([^"]*)"$|\1|p')
session_play $session_id || {
$LOG_E "Could not play $MEDIA_TYPE RTP pipeline."
return 1
}
# Save the session id to stop the stream when necessary.
# Only copy the CONF_FILE if it exist.
if [ -f $CONF_FILE ]; then
cp $CONF_FILE $CONF_FILE.tmp || {
session_destroy $session_id || $LOG_E "Destroy of" \
"session '$session_id' failed"
$LOG_E "Failed to copy '$CONF_FILE'. Destroy session"
return 1
}
fi
echo "$CHANNEL $MEDIA_TYPE id$session_id" >> $CONF_FILE.tmp || {
session_destroy $session_id || $LOG_E "Destroy of" \
"session '$session_id' failed"
$LOG_E "Failed to save multicast session '$session_id'" \
"to configuration file. Destroy session."
return 1
}
fsynced_write_or_cleanup $CONF_FILE.tmp $CONF_FILE &&
$LOG_I "multicast ($MEDIA_TYPE $cm_profile) started at" \
"$cm_group:$CM_PORT camera $CAMERA" || {
session_destroy $session_id || $LOG_E "Destroy of" \
"session '$session_id' failed"
$LOG_E "Failed to save multicast session to configuration" \
"file. Destroy '$session_id'."
return 1
}
}
stop_continuous_multicast() {
local id
# Destroy pipeline.
[ -r $CONF_FILE ] || {
$LOG_E "stop_continuous_multicast: " \
"$CONF_FILE is not readable."
return 1
}
id=$(sed -rne "s|^$CHANNEL $MEDIA_TYPE id(.*)$|\1|p" $CONF_FILE) || {
$LOG_E "could not retrieve session id from $CONF_FILE."
return 1
}
# This can only fail if no pipeline exist, can happen when restarting
# monolith. Thus it does not matter if this fails or succeedes, the conf
# should always be deleted
session_destroy $id || $LOG_E "Failed to destroy multicast session $id."
# Delete session infos from configuration.
[ -w $CONF_FILE ] || {
$LOG_E "$CONF_FILE is not writable," \
"Always Multicast infos can not be deleted."
return 1
}
sed -i -re "/^$CHANNEL $MEDIA_TYPE.*/d" $CONF_FILE || {
$LOG_E "stop_continuous_multicast: " \
"could not delete session infos."
return 1
}
}
# We never know if a call from parhand means start or stop. It means "toggle".
toggle_continuous_multicast() {
local enabled grep_res
# Look if toggle means start or stop!
case $MEDIA_TYPE in
video)
eval enabled=\$STD2_NETWORK_RTP_R${CHANNEL}_ALWAYSMULTICASTVIDEO
;
audio)
eval enabled=\$STD2_NETWORK_RTP_R${CHANNEL}_ALWAYSMULTICASTAUDIO
;
esac
grep -q "$CHANNEL[[:blank:]]\+$MEDIA_TYPE[[:blank:]]\+id" $CONF_FILE
grep_res=$?
case $enabled in
yes)
# Channel already started || start channel.
[ $grep_res -eq 0 ] || start_continuous_multicast
;
no)
# Channel already stopped || stop channel.
[ $grep_res -ne 0 ] || stop_continuous_multicast
;
esac
}
# This script is called from mcast-always initscript (options start/stop) or
# as a parhand parameter setter (option toggle).
# This script is called for exactly one channel and one media type at a time.
CHANNEL=$2
case $CHANNEL in
*[!0-9]*)
error "Invalid channel '$CHANNEL'"
;
esac
# The media type is video or audio
MEDIA_TYPE=$3
case $MEDIA_TYPE in
audio|video)
;
*)
error "Invalid media type '$MEDIA_TYPE'"
;
esac
# Retrieve the global RTP configuration variables.
# Note that std2parse has its own error handling, exiting if necessary.
std2parse /etc/sysconfig/rtp.conf
# Look for allview.
std2parse /etc/sysconfig/image_global.conf
eval source=\$STD2_IMAGE_I${CHANNEL}_SOURCE
if [ $source = "quad" ]; then
CAMERA=all
else
CAMERA=$(($CHANNEL + 1))
fi
(flock 9 || exit 1
case $1 in
start)
start_continuous_multicast
;
stop)
stop_continuous_multicast
;
toggle)
#Called from parhand
toggle_continuous_multicast
;
*)
$LOG_E "Usage: $0 {start|stop|toggle} <channel> <audio|video>"
;
esac
) 9>$LOCK_FILE