diff --git a/board/raspberrypi/motioneye-modules/streameyectl.py b/board/raspberrypi/motioneye-modules/streameyectl.py old mode 100644 new mode 100755 index c006e20c72..7a4ee03c23 --- a/board/raspberrypi/motioneye-modules/streameyectl.py +++ b/board/raspberrypi/motioneye-modules/streameyectl.py @@ -412,9 +412,13 @@ def _get_streameye_settings(camera_id): 'seProto': 'mjpeg', 'seAuthMode': 'disabled', 'sePort': 8081, - 'seRTSPPort': 554 + 'seRTSPPort': 554, + 'seMJPEGWidth': 640, + 'seMJPEGHeight': 480, + 'seMJPEGFramerate': 5, + 'seMJPEGBitrate': 800000 } - + if os.path.exists(STREAMEYE_CONF): logging.debug('reading streameye settings from %s' % STREAMEYE_CONF) @@ -442,15 +446,40 @@ def _get_streameye_settings(camera_id): if m: s['seProto'] = m[0] + m = re.findall('^MJPEG_WIDTH="?(\w+)"?', line) + if m: + s['seMJPEGWidth'] = m[0] + + m = re.findall('^MJPEG_HEIGHT="?(\w+)"?', line) + if m: + s['seMJPEGHeight'] = m[0] + + m = re.findall('^MJPEG_FRAMERATE="?(\w+)"?', line) + if m: + s['seMJPEGFramerate'] = m[0] + + m = re.findall('^MJPEG_BITRATE="?(\w+)"?', line) + if m: + s['seMJPEGBitrate'] = m[0] + + s['seMJPEGRes'] = '%sx%s' % (s.pop('seMJPEGWidth'), s.pop('seMJPEGHeight')) + return s def _set_streameye_settings(camera_id, s): s = dict(s) + s['seMJPEGWidth'] = int(s['seMJPEGRes'].split('x')[0]) + s['seMJPEGHeight'] = int(s.pop('seMJPEGRes').split('x')[1]) + s.setdefault('sePort', 8081) s.setdefault('seRTSPPort', 554) s.setdefault('seAuthMode', 'disabled') - + s.setdefault('seMJPEGWidth', 640) + s.setdefault('seMJPEGHeight', 480) + s.setdefault('seMJPEGFramerate', 5) + s.setdefault('seMJPEGBitrate', 800000) + main_config = config.get_main() username = main_config['@normal_username'] password = main_config['@normal_password'] @@ -462,6 +491,10 @@ def _set_streameye_settings(camera_id, s): 'PROTO="%s"' % s['seProto'], 'PORT="%s"' % s['sePort'], 'RTSP_PORT="%s"' % s['seRTSPPort'], + 'MJPEG_WIDTH="%s"' % s['seMJPEGWidth'], + 'MJPEG_HEIGHT="%s"' % s['seMJPEGHeight'], + 'MJPEG_FRAMERATE="%s"' % s['seMJPEGFramerate'], + 'MJPEG_BITRATE="%s"' % s['seMJPEGBitrate'], 'AUTH="%s"' % s['seAuthMode'], 'CREDENTIALS="%s:%s:%s"' % (username, password, realm) ] @@ -1158,6 +1191,27 @@ def seProto(): } +@additional_config +def seRTSPPort(): + if not _get_streameye_enabled(): + return None + + return { + 'label': 'Streaming RTSP Port', + 'description': 'sets the TCP port on which the webcam streaming server listens', + 'type': 'number', + 'min': 0, + 'max': 65535, + 'section': 'streaming', + 'camera': True, + 'required': True, + 'depends': ['seProto==rtsp'], + 'get': _get_streameye_settings, + 'set': _set_streameye_settings, + 'get_set_dict': True + } + + @additional_config def sePort(): if not _get_streameye_enabled(): @@ -1172,7 +1226,6 @@ def sePort(): 'section': 'streaming', 'camera': True, 'required': True, - 'depends': ['seProto==mjpeg'], 'get': _get_streameye_settings, 'set': _set_streameye_settings, 'get_set_dict': True @@ -1180,16 +1233,60 @@ def sePort(): @additional_config -def seRTSPPort(): +def seMJPEGRes(): if not _get_streameye_enabled(): return None return { - 'label': 'Streaming Port', - 'description': 'sets the TCP port on which the webcam streaming server listens', + 'label': 'MJPEG Resolution', + 'description': 'the MJPEG resolution fed to the frontend and used for motion detection on remote machines when streaming RTSP', + 'type': 'choices', + 'choices': RESOLUTION_CHOICES, + 'section': 'streaming', + 'camera': True, + 'required': True, + 'depends': ['seProto==rtsp'], + 'get': _get_streameye_settings, + 'set': _set_streameye_settings, + 'get_set_dict': True + } + + +@additional_config +def seMJPEGFramerate(): + if not _get_streameye_enabled(): + return None + + return { + 'label': 'MJPEG Frame Rate', + 'description': 'the MJPEG Frame Rate fed to the frontend and used for motion detection on remote machines when streaming RTSP; for motion detection, 5 works good', + 'type': 'range', + 'min': 2, + 'max': 30, + 'snap': 0, + 'ticks': "2|5|10|15|20|25|30", + 'decimals': 0, + 'section': 'streaming', + 'camera': True, + 'required': True, + 'depends': ['seProto==rtsp'], + 'get': _get_streameye_settings, + 'set': _set_streameye_settings, + 'get_set_dict': True + } + + +@additional_config +def seMJPEGBitrate(): + if not _get_streameye_enabled(): + return None + + return { + 'label': 'MJPEG Bitrate', + 'description': 'the MJPEG Bitrate fed to the frontend and used for motion detection on remote machines when streaming RTSP; for motion detection, 800000 works good', 'type': 'number', 'min': 0, - 'max': 65535, + 'max': 25000000, 'section': 'streaming', 'camera': True, 'required': True, @@ -1218,4 +1315,3 @@ def seAuthMode(): 'set': _set_streameye_settings, 'get_set_dict': True } - diff --git a/board/raspberrypi/overlay/usr/bin/streameye.sh b/board/raspberrypi/overlay/usr/bin/streameye.sh index 9f63710c3c..83950bdb7b 100755 --- a/board/raspberrypi/overlay/usr/bin/streameye.sh +++ b/board/raspberrypi/overlay/usr/bin/streameye.sh @@ -2,7 +2,7 @@ RASPIMJPEG_CONF=/data/etc/raspimjpeg.conf RASPIMJPEG_LOG=/var/log/raspimjpeg.log -GSTREAMER_LOG=/var/log/gstreamer.log +RTSPSERVER_LOG=/var/log/rtspserver.log MOTIONEYE_CONF=/data/etc/motioneye.conf STREAMEYE_CONF=/data/etc/streameye.conf STREAMEYE_LOG=/var/log/streameye.log @@ -17,122 +17,22 @@ function watch() { while true; do sleep 5 if [ "${PROTO}" = "rtsp" ]; then - if ! ps aux | grep test-launch | grep -v grep &>/dev/null; then + if ! ps aux | grep v4l2multi_stream_mmal | grep -v grep &>/dev/null; then + logger -t streameye -s "not running, respawning" + start + elif ! ps aux | grep v4l2rtspserver | grep -v grep &>/dev/null; then logger -t streameye -s "not running, respawning" start fi else if ! ps aux | grep raspimjpeg.py | grep -v grep &>/dev/null; then - logger -t streameye -s "not running, respawning" + logger -t streameye -s "raspimjpeg.py not running, respawning" start fi fi done } -function configure_v4l2_cam() { - video_arg="-d $1" - - horizontal_flip="0" - hflip=$(grep -e ^hflip ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - if [ "${hflip}" = "true" ]; then horizontal_flip="1"; fi - v4l2-ctl ${video_arg} --set-ctrl=horizontal_flip=${horizontal_flip} &>/dev/null - - vertical_flip="0" - vflip=$(grep -e ^vflip ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - if [ "${vflip}" = "true" ]; then vertical_flip="1"; fi - v4l2-ctl ${video_arg} --set-ctrl=vertical_flip=${vertical_flip} &>/dev/null - - rotate=$(grep -e ^rotation ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - if [ -z "${rotate}" ]; then rotate="0"; fi - v4l2-ctl ${video_arg} --set-ctrl=rotate=${rotate} &>/dev/null - - brightness=$(grep -e ^brightness ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - if [ -z "${brightness}" ]; then brightness="50"; fi - v4l2-ctl ${video_arg} --set-ctrl=brightness=${brightness} &>/dev/null - - contrast=$(grep -e ^contrast ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - if [ -z "${contrast}" ]; then contrast="0"; fi - v4l2-ctl ${video_arg} --set-ctrl=contrast=${contrast} &>/dev/null - - saturation=$(grep -e ^saturation ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - if [ -z "${saturation}" ]; then saturation="0"; fi - v4l2-ctl ${video_arg} --set-ctrl=saturation=${saturation} &>/dev/null - - sharpness=$(grep -e ^sharpness ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - if [ -z "${sharpness}" ]; then sharpness="0"; fi - v4l2-ctl ${video_arg} --set-ctrl=sharpness=${sharpness} &>/dev/null - - vstab=$(grep -e ^vstab ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - if [ -z "${vstab}" ]; then vstab="0"; fi - v4l2-ctl ${video_arg} --set-ctrl=image_stabilization=${vstab} &>/dev/null - - shutter=$(grep -e ^shutter ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - if [ -z "${shutter}" ]; then shutter="0"; fi - if [ "${#shutter}" -gt 2 ]; then shutter=$(echo ${shutter/%??/}); else shutter="0"; fi - v4l2-ctl ${video_arg} --set-ctrl=exposure_time_absolute=${shutter} &>/dev/null - - iso=$(grep -e ^iso ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - if [ -z "${iso}" ]; then iso="0"; fi - if [ "${iso}" -ge 800 ]; then iso="4" - elif [ "${iso}" -ge 400 ]; then iso="3" - elif [ "${iso}" -ge 200 ]; then iso="2" - elif [ "${iso}" -ge 100 ]; then iso="1" - else iso="0" - fi - v4l2-ctl ${video_arg} --set-ctrl=iso_sensitivity=${iso} &>/dev/null - - awb=$(grep -e ^awb ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - if [ "${awb}" = "off" ]; then awb="0" - elif [ "${awb}" = "auto" ]; then awb="1" - elif [ "${awb}" = "sunlight" ]; then awb="6" - elif [ "${awb}" = "cloudy" ]; then awb="8" - elif [ "${awb}" = "shade" ]; then awb="9" - elif [ "${awb}" = "tungsten" ]; then awb="4" - elif [ "${awb}" = "fluorescent" ]; then awb="3" - elif [ "${awb}" = "incandescent" ]; then awb="2" - elif [ "${awb}" = "flash" ]; then awb="7" - elif [ "${awb}" = "horizon" ]; then awb="5" - else awb="1" - fi - v4l2-ctl ${video_arg} --set-ctrl=white_balance_auto_preset=${awb} &>/dev/null - - metering=$(grep -e ^metering ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - if [ "${metering}" = "spot" ]; then metering="2" - elif [ "${metering}" = "center" ]; then metering="1" - else metering="0" - fi - v4l2-ctl ${video_arg} --set-ctrl=exposure_metering_mode=${awb} &>/dev/null - - scene="0" - exposure=$(grep -e ^exposure ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - if [ "${exposure}" = "off" ]; then - exposure="1" - else - if [ "${exposure}" = "night" ]; then scene="8" - elif [ "${exposure}" = "backlight" ]; then scene="1" - elif [ "${exposure}" = "sports" ]; then scene="11" - elif [ "${exposure}" = "snow" ]; then scene="2" - elif [ "${exposure}" = "beach" ]; then scene="2" - elif [ "${exposure}" = "fireworks" ]; then scene="6" - fi - exposure="0" - fi - v4l2-ctl ${video_arg} --set-ctrl=auto_exposure=${exposure} &>/dev/null - v4l2-ctl ${video_arg} --set-ctrl=scene_mode=${scene} &>/dev/null - - ev=$(grep -e ^ev ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - if [ -z "${ev}" ]; then ev="0"; fi - ev=$(expr ${ev} + 25) - ev=$(expr ${ev} / 2) - if [ "${ev}" -gt 24 ]; then ev="24"; fi - v4l2-ctl ${video_arg} --set-ctrl=auto_exposure_bias=${ev} &>/dev/null - - video_bitrate=$(grep -e ^bitrate ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - if [ -z "${video_bitrate}" ]; then video_bitrate="1000000"; fi - v4l2-ctl ${video_arg} --set-ctrl=video_bitrate=${video_bitrate} &>/dev/null -} - function invalid_opt() { local e match="$1" shift @@ -143,70 +43,106 @@ function invalid_opt() { function start() { source ${STREAMEYE_CONF} streameye_opts="-p ${PORT}" + rtspserver_opts="" if [ -n "${CREDENTIALS}" ] && [ "${AUTH}" = "basic" ]; then streameye_opts="${streameye_opts} -a basic -c ${CREDENTIALS}" + # TODO: Add RTSP Auth fi - + if [ "${PROTO}" = "rtsp" ]; then - pid=$(ps | grep test-launch | grep -v grep | tr -s ' ' | sed -e 's/^\s//' | cut -d ' ' -f 1) - if [ -n "${pid}" ]; then - return + v4l2rtspserver_pid=$(ps | grep v4l2rtspserver | grep -v grep | tr -s ' ' | sed -e 's/^\s//' | cut -d ' ' -f 1) + v4l2multi_stream_mmal_pid=$(ps | grep v4l2multi_stream_mmal | grep -v grep | tr -s ' ' | sed -e 's/^\s//' | cut -d ' ' -f 1) + if [ -n "${v4l2rtspserver_pid}" ]; then + if [ -n "${v4l2multi_stream_mmal_pid}" ]; then + return + fi fi - + RTSP_PORT=${RTSP_PORT:-554} + MJPEG_WIDTH=${MJPEG_WIDTH:-640} + MJPEG_HEIGHT=${MJPEG_HEIGHT:-480} + MJPEG_FRAMERATE=${MJPEG_FRAMERATE:-5} + MJPEG_BITRATE=${MJPEG_BITRATE:-800000} - iptables -A INPUT -p tcp -s localhost --dport ${RTSP_PORT} -j ACCEPT - iptables -A INPUT -p tcp --dport ${RTSP_PORT} -j DROP - + audio_path="" audio_opts="" if [ -n "${AUDIO_DEV}" ]; then + audio_path=",${AUDIO_DEV}" + + # Audio bitrate: default 44100 audio_bitrate=$(grep -e ^audio_bitrate ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) + # Audio channels: default 2 audio_channels=$(grep -e ^audio_channels ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - audio_extras="" + # Valid audio formats: S16_BE, S16_LE, S24_BE, S24_LE, S32_BE, S32_LE, ALAW, MULAW, S8, MPEG + audio_format=$(grep -e ^audio_format ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) if [ -n "${audio_bitrate}" ]; then - audio_extras="${audio_extras},rate=${audio_bitrate}" + audio_opts="${audio_opts} -A ${audio_bitrate}" fi if [ -n "${audio_channels}" ]; then - audio_extras="${audio_extras},channels=${audio_channels}" + audio_opts="${audio_opts} -C ${audio_channels}" + fi + if [ -n "${audio_format}" ]; then + audio_opts="${audio_opts} -a ${audio_format}" fi - audio_opts="alsasrc device=${AUDIO_DEV} ! audioresample ! audio/x-raw${audio_extras} ! queue ! voaacenc ! rtpmp4gpay pt=97 name=pay1" fi - video_path="/dev/video0" - if [ -n "${VIDEO_DEV}" ]; then - video_path=${VIDEO_DEV} + # hardcode to 90 for now + vidid=90 + video_path="/dev/video${vidid}" + if lsmod | grep v4l2loopback &> /dev/null ; then + rmmod v4l2loopback fi + modprobe v4l2loopback video_nr=${vidid} if [ -e "${video_path}" ]; then - # Only configure camera if it is a Pi Cam - if v4l2-ctl -d ${video_path} -D | grep -q 'bm2835 mmal'; then - configure_v4l2_cam ${video_path} + valid_opts=("analoggain" "awb" "awbgains" "bitrate" "brightness" "colfx" "contrast" "denoise" "digitalgain" "drc" "ev" "exposure" "flicker" "framerate" "height" "hflip" "imxfx" "intra" "irefresh" "level" "metering" "profile" "roi" "rotation" "saturation" "sharpness" "shutter" "vflip" "vstab" "width" "mjpegbitrate" "mjpegframerate" "mjpegwidth" "mjpegheight") + raspimjpeg_opts="--videoout ${video_path}" + while read line; do + key=$(echo ${line} | cut -d ' ' -f 1) + if invalid_opt "${key}" "${valid_opts[@]}"; then + continue + fi + if echo "${line}" | grep false &>/dev/null; then + continue + fi + if echo "${line}" | grep true &>/dev/null; then + line=${key} + fi + raspimjpeg_opts="${raspimjpeg_opts} --${line}" + done < ${RASPIMJPEG_CONF} + + video_iso=$(grep -e ^iso ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) + if [ -n "${video_iso}" ]; then + raspimjpeg_opts="${raspimjpeg_opts} --ISO ${video_iso}" fi - width=$(grep -e ^width ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - height=$(grep -e ^height ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - framerate=$(grep -e ^framerate ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - video_extras="" - if [ -n "${width}" ]; then - video_extras="${video_extras},width=${width}" - fi - if [ -n "${height}" ]; then - video_extras="${video_extras},height=${height}" - fi - if [ -n "${framerate}" ]; then - video_extras="${video_extras},framerate=${framerate}/1" - fi - video_opts="v4l2src device=${video_path} ! video/x-h264${video_extras} ! h264parse ! rtph264pay name=pay0 pt=96" if [ -r ${MOTIONEYE_CONF} ] && grep 'log-level debug' ${MOTIONEYE_CONF} >/dev/null; then + raspimjpeg_opts="${raspimjpeg_opts} -v" streameye_opts="${streameye_opts} -d" fi - test-launch -p ${RTSP_PORT} -m h264 "\"( ${video_opts} ${audio_opts} )\"" &>${GSTREAMER_LOG} & - sleep 10 - gst-launch-1.0 -v rtspsrc location=rtsp://127.0.0.1:${RTSP_PORT}/h264 latency=0 drop-on-latency=1 ! rtph264depay ! h264parse ! omxh264dec ! videorate ! video/x-raw,framerate=5/1 ! jpegenc ! filesink location=/dev/stdout | streameye ${streameye_opts} &>${STREAMEYE_LOG} & - sleep 5 - fi + rtspserver_opts="${rtspserver_opts} -P ${RTSP_PORT} -u h264" + video_framerate=$(grep -e ^framerate ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) + video_intra=$(grep -e ^intra ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) + if [ -n "${video_framerate}" ]; then + rtspserver_opts="${rtspserver_opts} -F ${video_framerate}" + if [ -z "${video_intra}" ]; then + let "video_intra=$((video_framerate))*2" + raspimjpeg_opts="${raspimjpeg_opts} --intra ${video_intra}" + fi + fi - iptables -D INPUT -p tcp --dport ${RTSP_PORT} -j DROP - iptables -D INPUT -p tcp -s localhost --dport ${RTSP_PORT} -j ACCEPT + mjpeg_opts="--mjpegbitrate ${MJPEG_BITRATE} --mjpegwidth ${MJPEG_WIDTH} --mjpegheight ${MJPEG_HEIGHT} --mjpegframerate ${MJPEG_FRAMERATE}" + raspimjpeg_opts="${raspimjpeg_opts} ${mjpeg_opts}" + + if [ -z "${v4l2multi_stream_mmal_pid}" ]; then + v4l2multi_stream_mmal -v ${raspimjpeg_opts} -o - 2>${RTSPSERVER_LOG} | streameye ${streameye_opts} &>${STREAMEYE_LOG} & + sleep 10 + fi + + if [ -z "${v4l2rtspserver_pid}" ]; then + v4l2rtspserver ${rtspserver_opts} ${audio_opts} ${video_path}${audio_path} &>${RTSPSERVER_LOG} & + sleep 5 + fi + fi else pid=$(ps | grep raspimjpeg.py | grep -v grep | tr -s ' ' | sed -e 's/^\s//' | cut -d ' ' -f 1) @@ -244,41 +180,25 @@ function stop() { # stop the streameye background watch process ps | grep streameye.sh | grep -v $$ | grep -v S94streameye| grep -v grep | tr -s ' ' | sed -e 's/^\s//' | cut -d ' ' -f 1 | xargs -r kill - # stop the raspimjpeg process - raspimjpeg_pid=$(ps | grep raspimjpeg.py | grep -v grep | tr -s ' ' | sed -e 's/^\s//' | cut -d ' ' -f 1) - # stop the gst-launch-1.0 process - gst_launch_pid=$(ps | grep gst-launch-1.0 | grep -v grep | tr -s ' ' | sed -e 's/^\s//' | cut -d ' ' -f 1) - # stop the test-launch process - test_launch_pid=$(ps | grep test-launch | grep -v grep | tr -s ' ' | sed -e 's/^\s//' | cut -d ' ' -f 1) + # stop the running streaming process + processes=( "raspimjpeg.py" "v4l2rtspserver" "v4l2multi_stream_mmal" ) + for i in "${processes[@]}" + do + pid=$(ps | grep $i | grep -v grep | tr -s ' ' | sed -e 's/^\s//' | cut -d ' ' -f 1) + if [ -n "${pid}" ]; then + kill -HUP "${pid}" &>/dev/null + count=0 + while kill -0 "${pid}" &>/dev/null && [ ${count} -lt 5 ]; do + sleep 1 + count=$((${count} + 1)) + done + kill -KILL "${pid}" &>/dev/null || true + fi + done - if [ -n "${raspimjpeg_pid}" ]; then - kill -HUP "${raspimjpeg_pid}" &>/dev/null - count=0 - while kill -0 "${raspimjpeg_pid}" &>/dev/null && [ ${count} -lt 5 ]; do - sleep 1 - count=$((${count} + 1)) - done - kill -KILL "${raspimjpeg_pid}" &>/dev/null || true - fi - - if [ -n "${gst_launch_pid}" ]; then - kill -HUP "${gst_launch_pid}" &>/dev/null - count=0 - while kill -0 "${gst_launch_pid}" &>/dev/null && [ ${count} -lt 5 ]; do - sleep 1 - count=$((${count} + 1)) - done - kill -KILL "${gst_launch_pid}" &>/dev/null || true - fi - - if [ -n "${test_launch_pid}" ]; then - kill -HUP "${test_launch_pid}" &>/dev/null - count=0 - while kill -0 "${test_launch_pid}" &>/dev/null && [ ${count} -lt 5 ]; do - sleep 1 - count=$((${count} + 1)) - done - kill -KILL "${test_launch_pid}" &>/dev/null || true + # stop the loopback device + if lsmod | grep v4l2loopback &> /dev/null ; then + rmmod v4l2loopback fi } @@ -302,4 +222,3 @@ case "$1" in echo $"Usage: $0 {start|stop|restart}" exit 1 esac - diff --git a/board/raspberrypi2/motioneye-modules/streameyectl.py b/board/raspberrypi2/motioneye-modules/streameyectl.py old mode 100644 new mode 100755 index c006e20c72..7a4ee03c23 --- a/board/raspberrypi2/motioneye-modules/streameyectl.py +++ b/board/raspberrypi2/motioneye-modules/streameyectl.py @@ -412,9 +412,13 @@ def _get_streameye_settings(camera_id): 'seProto': 'mjpeg', 'seAuthMode': 'disabled', 'sePort': 8081, - 'seRTSPPort': 554 + 'seRTSPPort': 554, + 'seMJPEGWidth': 640, + 'seMJPEGHeight': 480, + 'seMJPEGFramerate': 5, + 'seMJPEGBitrate': 800000 } - + if os.path.exists(STREAMEYE_CONF): logging.debug('reading streameye settings from %s' % STREAMEYE_CONF) @@ -442,15 +446,40 @@ def _get_streameye_settings(camera_id): if m: s['seProto'] = m[0] + m = re.findall('^MJPEG_WIDTH="?(\w+)"?', line) + if m: + s['seMJPEGWidth'] = m[0] + + m = re.findall('^MJPEG_HEIGHT="?(\w+)"?', line) + if m: + s['seMJPEGHeight'] = m[0] + + m = re.findall('^MJPEG_FRAMERATE="?(\w+)"?', line) + if m: + s['seMJPEGFramerate'] = m[0] + + m = re.findall('^MJPEG_BITRATE="?(\w+)"?', line) + if m: + s['seMJPEGBitrate'] = m[0] + + s['seMJPEGRes'] = '%sx%s' % (s.pop('seMJPEGWidth'), s.pop('seMJPEGHeight')) + return s def _set_streameye_settings(camera_id, s): s = dict(s) + s['seMJPEGWidth'] = int(s['seMJPEGRes'].split('x')[0]) + s['seMJPEGHeight'] = int(s.pop('seMJPEGRes').split('x')[1]) + s.setdefault('sePort', 8081) s.setdefault('seRTSPPort', 554) s.setdefault('seAuthMode', 'disabled') - + s.setdefault('seMJPEGWidth', 640) + s.setdefault('seMJPEGHeight', 480) + s.setdefault('seMJPEGFramerate', 5) + s.setdefault('seMJPEGBitrate', 800000) + main_config = config.get_main() username = main_config['@normal_username'] password = main_config['@normal_password'] @@ -462,6 +491,10 @@ def _set_streameye_settings(camera_id, s): 'PROTO="%s"' % s['seProto'], 'PORT="%s"' % s['sePort'], 'RTSP_PORT="%s"' % s['seRTSPPort'], + 'MJPEG_WIDTH="%s"' % s['seMJPEGWidth'], + 'MJPEG_HEIGHT="%s"' % s['seMJPEGHeight'], + 'MJPEG_FRAMERATE="%s"' % s['seMJPEGFramerate'], + 'MJPEG_BITRATE="%s"' % s['seMJPEGBitrate'], 'AUTH="%s"' % s['seAuthMode'], 'CREDENTIALS="%s:%s:%s"' % (username, password, realm) ] @@ -1158,6 +1191,27 @@ def seProto(): } +@additional_config +def seRTSPPort(): + if not _get_streameye_enabled(): + return None + + return { + 'label': 'Streaming RTSP Port', + 'description': 'sets the TCP port on which the webcam streaming server listens', + 'type': 'number', + 'min': 0, + 'max': 65535, + 'section': 'streaming', + 'camera': True, + 'required': True, + 'depends': ['seProto==rtsp'], + 'get': _get_streameye_settings, + 'set': _set_streameye_settings, + 'get_set_dict': True + } + + @additional_config def sePort(): if not _get_streameye_enabled(): @@ -1172,7 +1226,6 @@ def sePort(): 'section': 'streaming', 'camera': True, 'required': True, - 'depends': ['seProto==mjpeg'], 'get': _get_streameye_settings, 'set': _set_streameye_settings, 'get_set_dict': True @@ -1180,16 +1233,60 @@ def sePort(): @additional_config -def seRTSPPort(): +def seMJPEGRes(): if not _get_streameye_enabled(): return None return { - 'label': 'Streaming Port', - 'description': 'sets the TCP port on which the webcam streaming server listens', + 'label': 'MJPEG Resolution', + 'description': 'the MJPEG resolution fed to the frontend and used for motion detection on remote machines when streaming RTSP', + 'type': 'choices', + 'choices': RESOLUTION_CHOICES, + 'section': 'streaming', + 'camera': True, + 'required': True, + 'depends': ['seProto==rtsp'], + 'get': _get_streameye_settings, + 'set': _set_streameye_settings, + 'get_set_dict': True + } + + +@additional_config +def seMJPEGFramerate(): + if not _get_streameye_enabled(): + return None + + return { + 'label': 'MJPEG Frame Rate', + 'description': 'the MJPEG Frame Rate fed to the frontend and used for motion detection on remote machines when streaming RTSP; for motion detection, 5 works good', + 'type': 'range', + 'min': 2, + 'max': 30, + 'snap': 0, + 'ticks': "2|5|10|15|20|25|30", + 'decimals': 0, + 'section': 'streaming', + 'camera': True, + 'required': True, + 'depends': ['seProto==rtsp'], + 'get': _get_streameye_settings, + 'set': _set_streameye_settings, + 'get_set_dict': True + } + + +@additional_config +def seMJPEGBitrate(): + if not _get_streameye_enabled(): + return None + + return { + 'label': 'MJPEG Bitrate', + 'description': 'the MJPEG Bitrate fed to the frontend and used for motion detection on remote machines when streaming RTSP; for motion detection, 800000 works good', 'type': 'number', 'min': 0, - 'max': 65535, + 'max': 25000000, 'section': 'streaming', 'camera': True, 'required': True, @@ -1218,4 +1315,3 @@ def seAuthMode(): 'set': _set_streameye_settings, 'get_set_dict': True } - diff --git a/board/raspberrypi2/overlay/usr/bin/streameye.sh b/board/raspberrypi2/overlay/usr/bin/streameye.sh index 9f63710c3c..83950bdb7b 100755 --- a/board/raspberrypi2/overlay/usr/bin/streameye.sh +++ b/board/raspberrypi2/overlay/usr/bin/streameye.sh @@ -2,7 +2,7 @@ RASPIMJPEG_CONF=/data/etc/raspimjpeg.conf RASPIMJPEG_LOG=/var/log/raspimjpeg.log -GSTREAMER_LOG=/var/log/gstreamer.log +RTSPSERVER_LOG=/var/log/rtspserver.log MOTIONEYE_CONF=/data/etc/motioneye.conf STREAMEYE_CONF=/data/etc/streameye.conf STREAMEYE_LOG=/var/log/streameye.log @@ -17,122 +17,22 @@ function watch() { while true; do sleep 5 if [ "${PROTO}" = "rtsp" ]; then - if ! ps aux | grep test-launch | grep -v grep &>/dev/null; then + if ! ps aux | grep v4l2multi_stream_mmal | grep -v grep &>/dev/null; then + logger -t streameye -s "not running, respawning" + start + elif ! ps aux | grep v4l2rtspserver | grep -v grep &>/dev/null; then logger -t streameye -s "not running, respawning" start fi else if ! ps aux | grep raspimjpeg.py | grep -v grep &>/dev/null; then - logger -t streameye -s "not running, respawning" + logger -t streameye -s "raspimjpeg.py not running, respawning" start fi fi done } -function configure_v4l2_cam() { - video_arg="-d $1" - - horizontal_flip="0" - hflip=$(grep -e ^hflip ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - if [ "${hflip}" = "true" ]; then horizontal_flip="1"; fi - v4l2-ctl ${video_arg} --set-ctrl=horizontal_flip=${horizontal_flip} &>/dev/null - - vertical_flip="0" - vflip=$(grep -e ^vflip ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - if [ "${vflip}" = "true" ]; then vertical_flip="1"; fi - v4l2-ctl ${video_arg} --set-ctrl=vertical_flip=${vertical_flip} &>/dev/null - - rotate=$(grep -e ^rotation ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - if [ -z "${rotate}" ]; then rotate="0"; fi - v4l2-ctl ${video_arg} --set-ctrl=rotate=${rotate} &>/dev/null - - brightness=$(grep -e ^brightness ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - if [ -z "${brightness}" ]; then brightness="50"; fi - v4l2-ctl ${video_arg} --set-ctrl=brightness=${brightness} &>/dev/null - - contrast=$(grep -e ^contrast ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - if [ -z "${contrast}" ]; then contrast="0"; fi - v4l2-ctl ${video_arg} --set-ctrl=contrast=${contrast} &>/dev/null - - saturation=$(grep -e ^saturation ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - if [ -z "${saturation}" ]; then saturation="0"; fi - v4l2-ctl ${video_arg} --set-ctrl=saturation=${saturation} &>/dev/null - - sharpness=$(grep -e ^sharpness ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - if [ -z "${sharpness}" ]; then sharpness="0"; fi - v4l2-ctl ${video_arg} --set-ctrl=sharpness=${sharpness} &>/dev/null - - vstab=$(grep -e ^vstab ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - if [ -z "${vstab}" ]; then vstab="0"; fi - v4l2-ctl ${video_arg} --set-ctrl=image_stabilization=${vstab} &>/dev/null - - shutter=$(grep -e ^shutter ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - if [ -z "${shutter}" ]; then shutter="0"; fi - if [ "${#shutter}" -gt 2 ]; then shutter=$(echo ${shutter/%??/}); else shutter="0"; fi - v4l2-ctl ${video_arg} --set-ctrl=exposure_time_absolute=${shutter} &>/dev/null - - iso=$(grep -e ^iso ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - if [ -z "${iso}" ]; then iso="0"; fi - if [ "${iso}" -ge 800 ]; then iso="4" - elif [ "${iso}" -ge 400 ]; then iso="3" - elif [ "${iso}" -ge 200 ]; then iso="2" - elif [ "${iso}" -ge 100 ]; then iso="1" - else iso="0" - fi - v4l2-ctl ${video_arg} --set-ctrl=iso_sensitivity=${iso} &>/dev/null - - awb=$(grep -e ^awb ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - if [ "${awb}" = "off" ]; then awb="0" - elif [ "${awb}" = "auto" ]; then awb="1" - elif [ "${awb}" = "sunlight" ]; then awb="6" - elif [ "${awb}" = "cloudy" ]; then awb="8" - elif [ "${awb}" = "shade" ]; then awb="9" - elif [ "${awb}" = "tungsten" ]; then awb="4" - elif [ "${awb}" = "fluorescent" ]; then awb="3" - elif [ "${awb}" = "incandescent" ]; then awb="2" - elif [ "${awb}" = "flash" ]; then awb="7" - elif [ "${awb}" = "horizon" ]; then awb="5" - else awb="1" - fi - v4l2-ctl ${video_arg} --set-ctrl=white_balance_auto_preset=${awb} &>/dev/null - - metering=$(grep -e ^metering ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - if [ "${metering}" = "spot" ]; then metering="2" - elif [ "${metering}" = "center" ]; then metering="1" - else metering="0" - fi - v4l2-ctl ${video_arg} --set-ctrl=exposure_metering_mode=${awb} &>/dev/null - - scene="0" - exposure=$(grep -e ^exposure ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - if [ "${exposure}" = "off" ]; then - exposure="1" - else - if [ "${exposure}" = "night" ]; then scene="8" - elif [ "${exposure}" = "backlight" ]; then scene="1" - elif [ "${exposure}" = "sports" ]; then scene="11" - elif [ "${exposure}" = "snow" ]; then scene="2" - elif [ "${exposure}" = "beach" ]; then scene="2" - elif [ "${exposure}" = "fireworks" ]; then scene="6" - fi - exposure="0" - fi - v4l2-ctl ${video_arg} --set-ctrl=auto_exposure=${exposure} &>/dev/null - v4l2-ctl ${video_arg} --set-ctrl=scene_mode=${scene} &>/dev/null - - ev=$(grep -e ^ev ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - if [ -z "${ev}" ]; then ev="0"; fi - ev=$(expr ${ev} + 25) - ev=$(expr ${ev} / 2) - if [ "${ev}" -gt 24 ]; then ev="24"; fi - v4l2-ctl ${video_arg} --set-ctrl=auto_exposure_bias=${ev} &>/dev/null - - video_bitrate=$(grep -e ^bitrate ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - if [ -z "${video_bitrate}" ]; then video_bitrate="1000000"; fi - v4l2-ctl ${video_arg} --set-ctrl=video_bitrate=${video_bitrate} &>/dev/null -} - function invalid_opt() { local e match="$1" shift @@ -143,70 +43,106 @@ function invalid_opt() { function start() { source ${STREAMEYE_CONF} streameye_opts="-p ${PORT}" + rtspserver_opts="" if [ -n "${CREDENTIALS}" ] && [ "${AUTH}" = "basic" ]; then streameye_opts="${streameye_opts} -a basic -c ${CREDENTIALS}" + # TODO: Add RTSP Auth fi - + if [ "${PROTO}" = "rtsp" ]; then - pid=$(ps | grep test-launch | grep -v grep | tr -s ' ' | sed -e 's/^\s//' | cut -d ' ' -f 1) - if [ -n "${pid}" ]; then - return + v4l2rtspserver_pid=$(ps | grep v4l2rtspserver | grep -v grep | tr -s ' ' | sed -e 's/^\s//' | cut -d ' ' -f 1) + v4l2multi_stream_mmal_pid=$(ps | grep v4l2multi_stream_mmal | grep -v grep | tr -s ' ' | sed -e 's/^\s//' | cut -d ' ' -f 1) + if [ -n "${v4l2rtspserver_pid}" ]; then + if [ -n "${v4l2multi_stream_mmal_pid}" ]; then + return + fi fi - + RTSP_PORT=${RTSP_PORT:-554} + MJPEG_WIDTH=${MJPEG_WIDTH:-640} + MJPEG_HEIGHT=${MJPEG_HEIGHT:-480} + MJPEG_FRAMERATE=${MJPEG_FRAMERATE:-5} + MJPEG_BITRATE=${MJPEG_BITRATE:-800000} - iptables -A INPUT -p tcp -s localhost --dport ${RTSP_PORT} -j ACCEPT - iptables -A INPUT -p tcp --dport ${RTSP_PORT} -j DROP - + audio_path="" audio_opts="" if [ -n "${AUDIO_DEV}" ]; then + audio_path=",${AUDIO_DEV}" + + # Audio bitrate: default 44100 audio_bitrate=$(grep -e ^audio_bitrate ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) + # Audio channels: default 2 audio_channels=$(grep -e ^audio_channels ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - audio_extras="" + # Valid audio formats: S16_BE, S16_LE, S24_BE, S24_LE, S32_BE, S32_LE, ALAW, MULAW, S8, MPEG + audio_format=$(grep -e ^audio_format ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) if [ -n "${audio_bitrate}" ]; then - audio_extras="${audio_extras},rate=${audio_bitrate}" + audio_opts="${audio_opts} -A ${audio_bitrate}" fi if [ -n "${audio_channels}" ]; then - audio_extras="${audio_extras},channels=${audio_channels}" + audio_opts="${audio_opts} -C ${audio_channels}" + fi + if [ -n "${audio_format}" ]; then + audio_opts="${audio_opts} -a ${audio_format}" fi - audio_opts="alsasrc device=${AUDIO_DEV} ! audioresample ! audio/x-raw${audio_extras} ! queue ! voaacenc ! rtpmp4gpay pt=97 name=pay1" fi - video_path="/dev/video0" - if [ -n "${VIDEO_DEV}" ]; then - video_path=${VIDEO_DEV} + # hardcode to 90 for now + vidid=90 + video_path="/dev/video${vidid}" + if lsmod | grep v4l2loopback &> /dev/null ; then + rmmod v4l2loopback fi + modprobe v4l2loopback video_nr=${vidid} if [ -e "${video_path}" ]; then - # Only configure camera if it is a Pi Cam - if v4l2-ctl -d ${video_path} -D | grep -q 'bm2835 mmal'; then - configure_v4l2_cam ${video_path} + valid_opts=("analoggain" "awb" "awbgains" "bitrate" "brightness" "colfx" "contrast" "denoise" "digitalgain" "drc" "ev" "exposure" "flicker" "framerate" "height" "hflip" "imxfx" "intra" "irefresh" "level" "metering" "profile" "roi" "rotation" "saturation" "sharpness" "shutter" "vflip" "vstab" "width" "mjpegbitrate" "mjpegframerate" "mjpegwidth" "mjpegheight") + raspimjpeg_opts="--videoout ${video_path}" + while read line; do + key=$(echo ${line} | cut -d ' ' -f 1) + if invalid_opt "${key}" "${valid_opts[@]}"; then + continue + fi + if echo "${line}" | grep false &>/dev/null; then + continue + fi + if echo "${line}" | grep true &>/dev/null; then + line=${key} + fi + raspimjpeg_opts="${raspimjpeg_opts} --${line}" + done < ${RASPIMJPEG_CONF} + + video_iso=$(grep -e ^iso ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) + if [ -n "${video_iso}" ]; then + raspimjpeg_opts="${raspimjpeg_opts} --ISO ${video_iso}" fi - width=$(grep -e ^width ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - height=$(grep -e ^height ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - framerate=$(grep -e ^framerate ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - video_extras="" - if [ -n "${width}" ]; then - video_extras="${video_extras},width=${width}" - fi - if [ -n "${height}" ]; then - video_extras="${video_extras},height=${height}" - fi - if [ -n "${framerate}" ]; then - video_extras="${video_extras},framerate=${framerate}/1" - fi - video_opts="v4l2src device=${video_path} ! video/x-h264${video_extras} ! h264parse ! rtph264pay name=pay0 pt=96" if [ -r ${MOTIONEYE_CONF} ] && grep 'log-level debug' ${MOTIONEYE_CONF} >/dev/null; then + raspimjpeg_opts="${raspimjpeg_opts} -v" streameye_opts="${streameye_opts} -d" fi - test-launch -p ${RTSP_PORT} -m h264 "\"( ${video_opts} ${audio_opts} )\"" &>${GSTREAMER_LOG} & - sleep 10 - gst-launch-1.0 -v rtspsrc location=rtsp://127.0.0.1:${RTSP_PORT}/h264 latency=0 drop-on-latency=1 ! rtph264depay ! h264parse ! omxh264dec ! videorate ! video/x-raw,framerate=5/1 ! jpegenc ! filesink location=/dev/stdout | streameye ${streameye_opts} &>${STREAMEYE_LOG} & - sleep 5 - fi + rtspserver_opts="${rtspserver_opts} -P ${RTSP_PORT} -u h264" + video_framerate=$(grep -e ^framerate ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) + video_intra=$(grep -e ^intra ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) + if [ -n "${video_framerate}" ]; then + rtspserver_opts="${rtspserver_opts} -F ${video_framerate}" + if [ -z "${video_intra}" ]; then + let "video_intra=$((video_framerate))*2" + raspimjpeg_opts="${raspimjpeg_opts} --intra ${video_intra}" + fi + fi - iptables -D INPUT -p tcp --dport ${RTSP_PORT} -j DROP - iptables -D INPUT -p tcp -s localhost --dport ${RTSP_PORT} -j ACCEPT + mjpeg_opts="--mjpegbitrate ${MJPEG_BITRATE} --mjpegwidth ${MJPEG_WIDTH} --mjpegheight ${MJPEG_HEIGHT} --mjpegframerate ${MJPEG_FRAMERATE}" + raspimjpeg_opts="${raspimjpeg_opts} ${mjpeg_opts}" + + if [ -z "${v4l2multi_stream_mmal_pid}" ]; then + v4l2multi_stream_mmal -v ${raspimjpeg_opts} -o - 2>${RTSPSERVER_LOG} | streameye ${streameye_opts} &>${STREAMEYE_LOG} & + sleep 10 + fi + + if [ -z "${v4l2rtspserver_pid}" ]; then + v4l2rtspserver ${rtspserver_opts} ${audio_opts} ${video_path}${audio_path} &>${RTSPSERVER_LOG} & + sleep 5 + fi + fi else pid=$(ps | grep raspimjpeg.py | grep -v grep | tr -s ' ' | sed -e 's/^\s//' | cut -d ' ' -f 1) @@ -244,41 +180,25 @@ function stop() { # stop the streameye background watch process ps | grep streameye.sh | grep -v $$ | grep -v S94streameye| grep -v grep | tr -s ' ' | sed -e 's/^\s//' | cut -d ' ' -f 1 | xargs -r kill - # stop the raspimjpeg process - raspimjpeg_pid=$(ps | grep raspimjpeg.py | grep -v grep | tr -s ' ' | sed -e 's/^\s//' | cut -d ' ' -f 1) - # stop the gst-launch-1.0 process - gst_launch_pid=$(ps | grep gst-launch-1.0 | grep -v grep | tr -s ' ' | sed -e 's/^\s//' | cut -d ' ' -f 1) - # stop the test-launch process - test_launch_pid=$(ps | grep test-launch | grep -v grep | tr -s ' ' | sed -e 's/^\s//' | cut -d ' ' -f 1) + # stop the running streaming process + processes=( "raspimjpeg.py" "v4l2rtspserver" "v4l2multi_stream_mmal" ) + for i in "${processes[@]}" + do + pid=$(ps | grep $i | grep -v grep | tr -s ' ' | sed -e 's/^\s//' | cut -d ' ' -f 1) + if [ -n "${pid}" ]; then + kill -HUP "${pid}" &>/dev/null + count=0 + while kill -0 "${pid}" &>/dev/null && [ ${count} -lt 5 ]; do + sleep 1 + count=$((${count} + 1)) + done + kill -KILL "${pid}" &>/dev/null || true + fi + done - if [ -n "${raspimjpeg_pid}" ]; then - kill -HUP "${raspimjpeg_pid}" &>/dev/null - count=0 - while kill -0 "${raspimjpeg_pid}" &>/dev/null && [ ${count} -lt 5 ]; do - sleep 1 - count=$((${count} + 1)) - done - kill -KILL "${raspimjpeg_pid}" &>/dev/null || true - fi - - if [ -n "${gst_launch_pid}" ]; then - kill -HUP "${gst_launch_pid}" &>/dev/null - count=0 - while kill -0 "${gst_launch_pid}" &>/dev/null && [ ${count} -lt 5 ]; do - sleep 1 - count=$((${count} + 1)) - done - kill -KILL "${gst_launch_pid}" &>/dev/null || true - fi - - if [ -n "${test_launch_pid}" ]; then - kill -HUP "${test_launch_pid}" &>/dev/null - count=0 - while kill -0 "${test_launch_pid}" &>/dev/null && [ ${count} -lt 5 ]; do - sleep 1 - count=$((${count} + 1)) - done - kill -KILL "${test_launch_pid}" &>/dev/null || true + # stop the loopback device + if lsmod | grep v4l2loopback &> /dev/null ; then + rmmod v4l2loopback fi } @@ -302,4 +222,3 @@ case "$1" in echo $"Usage: $0 {start|stop|restart}" exit 1 esac - diff --git a/board/raspberrypi3/motioneye-modules/streameyectl.py b/board/raspberrypi3/motioneye-modules/streameyectl.py old mode 100644 new mode 100755 index c006e20c72..7a4ee03c23 --- a/board/raspberrypi3/motioneye-modules/streameyectl.py +++ b/board/raspberrypi3/motioneye-modules/streameyectl.py @@ -412,9 +412,13 @@ def _get_streameye_settings(camera_id): 'seProto': 'mjpeg', 'seAuthMode': 'disabled', 'sePort': 8081, - 'seRTSPPort': 554 + 'seRTSPPort': 554, + 'seMJPEGWidth': 640, + 'seMJPEGHeight': 480, + 'seMJPEGFramerate': 5, + 'seMJPEGBitrate': 800000 } - + if os.path.exists(STREAMEYE_CONF): logging.debug('reading streameye settings from %s' % STREAMEYE_CONF) @@ -442,15 +446,40 @@ def _get_streameye_settings(camera_id): if m: s['seProto'] = m[0] + m = re.findall('^MJPEG_WIDTH="?(\w+)"?', line) + if m: + s['seMJPEGWidth'] = m[0] + + m = re.findall('^MJPEG_HEIGHT="?(\w+)"?', line) + if m: + s['seMJPEGHeight'] = m[0] + + m = re.findall('^MJPEG_FRAMERATE="?(\w+)"?', line) + if m: + s['seMJPEGFramerate'] = m[0] + + m = re.findall('^MJPEG_BITRATE="?(\w+)"?', line) + if m: + s['seMJPEGBitrate'] = m[0] + + s['seMJPEGRes'] = '%sx%s' % (s.pop('seMJPEGWidth'), s.pop('seMJPEGHeight')) + return s def _set_streameye_settings(camera_id, s): s = dict(s) + s['seMJPEGWidth'] = int(s['seMJPEGRes'].split('x')[0]) + s['seMJPEGHeight'] = int(s.pop('seMJPEGRes').split('x')[1]) + s.setdefault('sePort', 8081) s.setdefault('seRTSPPort', 554) s.setdefault('seAuthMode', 'disabled') - + s.setdefault('seMJPEGWidth', 640) + s.setdefault('seMJPEGHeight', 480) + s.setdefault('seMJPEGFramerate', 5) + s.setdefault('seMJPEGBitrate', 800000) + main_config = config.get_main() username = main_config['@normal_username'] password = main_config['@normal_password'] @@ -462,6 +491,10 @@ def _set_streameye_settings(camera_id, s): 'PROTO="%s"' % s['seProto'], 'PORT="%s"' % s['sePort'], 'RTSP_PORT="%s"' % s['seRTSPPort'], + 'MJPEG_WIDTH="%s"' % s['seMJPEGWidth'], + 'MJPEG_HEIGHT="%s"' % s['seMJPEGHeight'], + 'MJPEG_FRAMERATE="%s"' % s['seMJPEGFramerate'], + 'MJPEG_BITRATE="%s"' % s['seMJPEGBitrate'], 'AUTH="%s"' % s['seAuthMode'], 'CREDENTIALS="%s:%s:%s"' % (username, password, realm) ] @@ -1158,6 +1191,27 @@ def seProto(): } +@additional_config +def seRTSPPort(): + if not _get_streameye_enabled(): + return None + + return { + 'label': 'Streaming RTSP Port', + 'description': 'sets the TCP port on which the webcam streaming server listens', + 'type': 'number', + 'min': 0, + 'max': 65535, + 'section': 'streaming', + 'camera': True, + 'required': True, + 'depends': ['seProto==rtsp'], + 'get': _get_streameye_settings, + 'set': _set_streameye_settings, + 'get_set_dict': True + } + + @additional_config def sePort(): if not _get_streameye_enabled(): @@ -1172,7 +1226,6 @@ def sePort(): 'section': 'streaming', 'camera': True, 'required': True, - 'depends': ['seProto==mjpeg'], 'get': _get_streameye_settings, 'set': _set_streameye_settings, 'get_set_dict': True @@ -1180,16 +1233,60 @@ def sePort(): @additional_config -def seRTSPPort(): +def seMJPEGRes(): if not _get_streameye_enabled(): return None return { - 'label': 'Streaming Port', - 'description': 'sets the TCP port on which the webcam streaming server listens', + 'label': 'MJPEG Resolution', + 'description': 'the MJPEG resolution fed to the frontend and used for motion detection on remote machines when streaming RTSP', + 'type': 'choices', + 'choices': RESOLUTION_CHOICES, + 'section': 'streaming', + 'camera': True, + 'required': True, + 'depends': ['seProto==rtsp'], + 'get': _get_streameye_settings, + 'set': _set_streameye_settings, + 'get_set_dict': True + } + + +@additional_config +def seMJPEGFramerate(): + if not _get_streameye_enabled(): + return None + + return { + 'label': 'MJPEG Frame Rate', + 'description': 'the MJPEG Frame Rate fed to the frontend and used for motion detection on remote machines when streaming RTSP; for motion detection, 5 works good', + 'type': 'range', + 'min': 2, + 'max': 30, + 'snap': 0, + 'ticks': "2|5|10|15|20|25|30", + 'decimals': 0, + 'section': 'streaming', + 'camera': True, + 'required': True, + 'depends': ['seProto==rtsp'], + 'get': _get_streameye_settings, + 'set': _set_streameye_settings, + 'get_set_dict': True + } + + +@additional_config +def seMJPEGBitrate(): + if not _get_streameye_enabled(): + return None + + return { + 'label': 'MJPEG Bitrate', + 'description': 'the MJPEG Bitrate fed to the frontend and used for motion detection on remote machines when streaming RTSP; for motion detection, 800000 works good', 'type': 'number', 'min': 0, - 'max': 65535, + 'max': 25000000, 'section': 'streaming', 'camera': True, 'required': True, @@ -1218,4 +1315,3 @@ def seAuthMode(): 'set': _set_streameye_settings, 'get_set_dict': True } - diff --git a/board/raspberrypi3/overlay/usr/bin/streameye.sh b/board/raspberrypi3/overlay/usr/bin/streameye.sh index 9f63710c3c..83950bdb7b 100755 --- a/board/raspberrypi3/overlay/usr/bin/streameye.sh +++ b/board/raspberrypi3/overlay/usr/bin/streameye.sh @@ -2,7 +2,7 @@ RASPIMJPEG_CONF=/data/etc/raspimjpeg.conf RASPIMJPEG_LOG=/var/log/raspimjpeg.log -GSTREAMER_LOG=/var/log/gstreamer.log +RTSPSERVER_LOG=/var/log/rtspserver.log MOTIONEYE_CONF=/data/etc/motioneye.conf STREAMEYE_CONF=/data/etc/streameye.conf STREAMEYE_LOG=/var/log/streameye.log @@ -17,122 +17,22 @@ function watch() { while true; do sleep 5 if [ "${PROTO}" = "rtsp" ]; then - if ! ps aux | grep test-launch | grep -v grep &>/dev/null; then + if ! ps aux | grep v4l2multi_stream_mmal | grep -v grep &>/dev/null; then + logger -t streameye -s "not running, respawning" + start + elif ! ps aux | grep v4l2rtspserver | grep -v grep &>/dev/null; then logger -t streameye -s "not running, respawning" start fi else if ! ps aux | grep raspimjpeg.py | grep -v grep &>/dev/null; then - logger -t streameye -s "not running, respawning" + logger -t streameye -s "raspimjpeg.py not running, respawning" start fi fi done } -function configure_v4l2_cam() { - video_arg="-d $1" - - horizontal_flip="0" - hflip=$(grep -e ^hflip ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - if [ "${hflip}" = "true" ]; then horizontal_flip="1"; fi - v4l2-ctl ${video_arg} --set-ctrl=horizontal_flip=${horizontal_flip} &>/dev/null - - vertical_flip="0" - vflip=$(grep -e ^vflip ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - if [ "${vflip}" = "true" ]; then vertical_flip="1"; fi - v4l2-ctl ${video_arg} --set-ctrl=vertical_flip=${vertical_flip} &>/dev/null - - rotate=$(grep -e ^rotation ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - if [ -z "${rotate}" ]; then rotate="0"; fi - v4l2-ctl ${video_arg} --set-ctrl=rotate=${rotate} &>/dev/null - - brightness=$(grep -e ^brightness ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - if [ -z "${brightness}" ]; then brightness="50"; fi - v4l2-ctl ${video_arg} --set-ctrl=brightness=${brightness} &>/dev/null - - contrast=$(grep -e ^contrast ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - if [ -z "${contrast}" ]; then contrast="0"; fi - v4l2-ctl ${video_arg} --set-ctrl=contrast=${contrast} &>/dev/null - - saturation=$(grep -e ^saturation ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - if [ -z "${saturation}" ]; then saturation="0"; fi - v4l2-ctl ${video_arg} --set-ctrl=saturation=${saturation} &>/dev/null - - sharpness=$(grep -e ^sharpness ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - if [ -z "${sharpness}" ]; then sharpness="0"; fi - v4l2-ctl ${video_arg} --set-ctrl=sharpness=${sharpness} &>/dev/null - - vstab=$(grep -e ^vstab ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - if [ -z "${vstab}" ]; then vstab="0"; fi - v4l2-ctl ${video_arg} --set-ctrl=image_stabilization=${vstab} &>/dev/null - - shutter=$(grep -e ^shutter ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - if [ -z "${shutter}" ]; then shutter="0"; fi - if [ "${#shutter}" -gt 2 ]; then shutter=$(echo ${shutter/%??/}); else shutter="0"; fi - v4l2-ctl ${video_arg} --set-ctrl=exposure_time_absolute=${shutter} &>/dev/null - - iso=$(grep -e ^iso ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - if [ -z "${iso}" ]; then iso="0"; fi - if [ "${iso}" -ge 800 ]; then iso="4" - elif [ "${iso}" -ge 400 ]; then iso="3" - elif [ "${iso}" -ge 200 ]; then iso="2" - elif [ "${iso}" -ge 100 ]; then iso="1" - else iso="0" - fi - v4l2-ctl ${video_arg} --set-ctrl=iso_sensitivity=${iso} &>/dev/null - - awb=$(grep -e ^awb ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - if [ "${awb}" = "off" ]; then awb="0" - elif [ "${awb}" = "auto" ]; then awb="1" - elif [ "${awb}" = "sunlight" ]; then awb="6" - elif [ "${awb}" = "cloudy" ]; then awb="8" - elif [ "${awb}" = "shade" ]; then awb="9" - elif [ "${awb}" = "tungsten" ]; then awb="4" - elif [ "${awb}" = "fluorescent" ]; then awb="3" - elif [ "${awb}" = "incandescent" ]; then awb="2" - elif [ "${awb}" = "flash" ]; then awb="7" - elif [ "${awb}" = "horizon" ]; then awb="5" - else awb="1" - fi - v4l2-ctl ${video_arg} --set-ctrl=white_balance_auto_preset=${awb} &>/dev/null - - metering=$(grep -e ^metering ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - if [ "${metering}" = "spot" ]; then metering="2" - elif [ "${metering}" = "center" ]; then metering="1" - else metering="0" - fi - v4l2-ctl ${video_arg} --set-ctrl=exposure_metering_mode=${awb} &>/dev/null - - scene="0" - exposure=$(grep -e ^exposure ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - if [ "${exposure}" = "off" ]; then - exposure="1" - else - if [ "${exposure}" = "night" ]; then scene="8" - elif [ "${exposure}" = "backlight" ]; then scene="1" - elif [ "${exposure}" = "sports" ]; then scene="11" - elif [ "${exposure}" = "snow" ]; then scene="2" - elif [ "${exposure}" = "beach" ]; then scene="2" - elif [ "${exposure}" = "fireworks" ]; then scene="6" - fi - exposure="0" - fi - v4l2-ctl ${video_arg} --set-ctrl=auto_exposure=${exposure} &>/dev/null - v4l2-ctl ${video_arg} --set-ctrl=scene_mode=${scene} &>/dev/null - - ev=$(grep -e ^ev ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - if [ -z "${ev}" ]; then ev="0"; fi - ev=$(expr ${ev} + 25) - ev=$(expr ${ev} / 2) - if [ "${ev}" -gt 24 ]; then ev="24"; fi - v4l2-ctl ${video_arg} --set-ctrl=auto_exposure_bias=${ev} &>/dev/null - - video_bitrate=$(grep -e ^bitrate ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - if [ -z "${video_bitrate}" ]; then video_bitrate="1000000"; fi - v4l2-ctl ${video_arg} --set-ctrl=video_bitrate=${video_bitrate} &>/dev/null -} - function invalid_opt() { local e match="$1" shift @@ -143,70 +43,106 @@ function invalid_opt() { function start() { source ${STREAMEYE_CONF} streameye_opts="-p ${PORT}" + rtspserver_opts="" if [ -n "${CREDENTIALS}" ] && [ "${AUTH}" = "basic" ]; then streameye_opts="${streameye_opts} -a basic -c ${CREDENTIALS}" + # TODO: Add RTSP Auth fi - + if [ "${PROTO}" = "rtsp" ]; then - pid=$(ps | grep test-launch | grep -v grep | tr -s ' ' | sed -e 's/^\s//' | cut -d ' ' -f 1) - if [ -n "${pid}" ]; then - return + v4l2rtspserver_pid=$(ps | grep v4l2rtspserver | grep -v grep | tr -s ' ' | sed -e 's/^\s//' | cut -d ' ' -f 1) + v4l2multi_stream_mmal_pid=$(ps | grep v4l2multi_stream_mmal | grep -v grep | tr -s ' ' | sed -e 's/^\s//' | cut -d ' ' -f 1) + if [ -n "${v4l2rtspserver_pid}" ]; then + if [ -n "${v4l2multi_stream_mmal_pid}" ]; then + return + fi fi - + RTSP_PORT=${RTSP_PORT:-554} + MJPEG_WIDTH=${MJPEG_WIDTH:-640} + MJPEG_HEIGHT=${MJPEG_HEIGHT:-480} + MJPEG_FRAMERATE=${MJPEG_FRAMERATE:-5} + MJPEG_BITRATE=${MJPEG_BITRATE:-800000} - iptables -A INPUT -p tcp -s localhost --dport ${RTSP_PORT} -j ACCEPT - iptables -A INPUT -p tcp --dport ${RTSP_PORT} -j DROP - + audio_path="" audio_opts="" if [ -n "${AUDIO_DEV}" ]; then + audio_path=",${AUDIO_DEV}" + + # Audio bitrate: default 44100 audio_bitrate=$(grep -e ^audio_bitrate ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) + # Audio channels: default 2 audio_channels=$(grep -e ^audio_channels ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - audio_extras="" + # Valid audio formats: S16_BE, S16_LE, S24_BE, S24_LE, S32_BE, S32_LE, ALAW, MULAW, S8, MPEG + audio_format=$(grep -e ^audio_format ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) if [ -n "${audio_bitrate}" ]; then - audio_extras="${audio_extras},rate=${audio_bitrate}" + audio_opts="${audio_opts} -A ${audio_bitrate}" fi if [ -n "${audio_channels}" ]; then - audio_extras="${audio_extras},channels=${audio_channels}" + audio_opts="${audio_opts} -C ${audio_channels}" + fi + if [ -n "${audio_format}" ]; then + audio_opts="${audio_opts} -a ${audio_format}" fi - audio_opts="alsasrc device=${AUDIO_DEV} ! audioresample ! audio/x-raw${audio_extras} ! queue ! voaacenc ! rtpmp4gpay pt=97 name=pay1" fi - video_path="/dev/video0" - if [ -n "${VIDEO_DEV}" ]; then - video_path=${VIDEO_DEV} + # hardcode to 90 for now + vidid=90 + video_path="/dev/video${vidid}" + if lsmod | grep v4l2loopback &> /dev/null ; then + rmmod v4l2loopback fi + modprobe v4l2loopback video_nr=${vidid} if [ -e "${video_path}" ]; then - # Only configure camera if it is a Pi Cam - if v4l2-ctl -d ${video_path} -D | grep -q 'bm2835 mmal'; then - configure_v4l2_cam ${video_path} + valid_opts=("analoggain" "awb" "awbgains" "bitrate" "brightness" "colfx" "contrast" "denoise" "digitalgain" "drc" "ev" "exposure" "flicker" "framerate" "height" "hflip" "imxfx" "intra" "irefresh" "level" "metering" "profile" "roi" "rotation" "saturation" "sharpness" "shutter" "vflip" "vstab" "width" "mjpegbitrate" "mjpegframerate" "mjpegwidth" "mjpegheight") + raspimjpeg_opts="--videoout ${video_path}" + while read line; do + key=$(echo ${line} | cut -d ' ' -f 1) + if invalid_opt "${key}" "${valid_opts[@]}"; then + continue + fi + if echo "${line}" | grep false &>/dev/null; then + continue + fi + if echo "${line}" | grep true &>/dev/null; then + line=${key} + fi + raspimjpeg_opts="${raspimjpeg_opts} --${line}" + done < ${RASPIMJPEG_CONF} + + video_iso=$(grep -e ^iso ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) + if [ -n "${video_iso}" ]; then + raspimjpeg_opts="${raspimjpeg_opts} --ISO ${video_iso}" fi - width=$(grep -e ^width ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - height=$(grep -e ^height ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - framerate=$(grep -e ^framerate ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - video_extras="" - if [ -n "${width}" ]; then - video_extras="${video_extras},width=${width}" - fi - if [ -n "${height}" ]; then - video_extras="${video_extras},height=${height}" - fi - if [ -n "${framerate}" ]; then - video_extras="${video_extras},framerate=${framerate}/1" - fi - video_opts="v4l2src device=${video_path} ! video/x-h264${video_extras} ! h264parse ! rtph264pay name=pay0 pt=96" if [ -r ${MOTIONEYE_CONF} ] && grep 'log-level debug' ${MOTIONEYE_CONF} >/dev/null; then + raspimjpeg_opts="${raspimjpeg_opts} -v" streameye_opts="${streameye_opts} -d" fi - test-launch -p ${RTSP_PORT} -m h264 "\"( ${video_opts} ${audio_opts} )\"" &>${GSTREAMER_LOG} & - sleep 10 - gst-launch-1.0 -v rtspsrc location=rtsp://127.0.0.1:${RTSP_PORT}/h264 latency=0 drop-on-latency=1 ! rtph264depay ! h264parse ! omxh264dec ! videorate ! video/x-raw,framerate=5/1 ! jpegenc ! filesink location=/dev/stdout | streameye ${streameye_opts} &>${STREAMEYE_LOG} & - sleep 5 - fi + rtspserver_opts="${rtspserver_opts} -P ${RTSP_PORT} -u h264" + video_framerate=$(grep -e ^framerate ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) + video_intra=$(grep -e ^intra ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) + if [ -n "${video_framerate}" ]; then + rtspserver_opts="${rtspserver_opts} -F ${video_framerate}" + if [ -z "${video_intra}" ]; then + let "video_intra=$((video_framerate))*2" + raspimjpeg_opts="${raspimjpeg_opts} --intra ${video_intra}" + fi + fi - iptables -D INPUT -p tcp --dport ${RTSP_PORT} -j DROP - iptables -D INPUT -p tcp -s localhost --dport ${RTSP_PORT} -j ACCEPT + mjpeg_opts="--mjpegbitrate ${MJPEG_BITRATE} --mjpegwidth ${MJPEG_WIDTH} --mjpegheight ${MJPEG_HEIGHT} --mjpegframerate ${MJPEG_FRAMERATE}" + raspimjpeg_opts="${raspimjpeg_opts} ${mjpeg_opts}" + + if [ -z "${v4l2multi_stream_mmal_pid}" ]; then + v4l2multi_stream_mmal -v ${raspimjpeg_opts} -o - 2>${RTSPSERVER_LOG} | streameye ${streameye_opts} &>${STREAMEYE_LOG} & + sleep 10 + fi + + if [ -z "${v4l2rtspserver_pid}" ]; then + v4l2rtspserver ${rtspserver_opts} ${audio_opts} ${video_path}${audio_path} &>${RTSPSERVER_LOG} & + sleep 5 + fi + fi else pid=$(ps | grep raspimjpeg.py | grep -v grep | tr -s ' ' | sed -e 's/^\s//' | cut -d ' ' -f 1) @@ -244,41 +180,25 @@ function stop() { # stop the streameye background watch process ps | grep streameye.sh | grep -v $$ | grep -v S94streameye| grep -v grep | tr -s ' ' | sed -e 's/^\s//' | cut -d ' ' -f 1 | xargs -r kill - # stop the raspimjpeg process - raspimjpeg_pid=$(ps | grep raspimjpeg.py | grep -v grep | tr -s ' ' | sed -e 's/^\s//' | cut -d ' ' -f 1) - # stop the gst-launch-1.0 process - gst_launch_pid=$(ps | grep gst-launch-1.0 | grep -v grep | tr -s ' ' | sed -e 's/^\s//' | cut -d ' ' -f 1) - # stop the test-launch process - test_launch_pid=$(ps | grep test-launch | grep -v grep | tr -s ' ' | sed -e 's/^\s//' | cut -d ' ' -f 1) + # stop the running streaming process + processes=( "raspimjpeg.py" "v4l2rtspserver" "v4l2multi_stream_mmal" ) + for i in "${processes[@]}" + do + pid=$(ps | grep $i | grep -v grep | tr -s ' ' | sed -e 's/^\s//' | cut -d ' ' -f 1) + if [ -n "${pid}" ]; then + kill -HUP "${pid}" &>/dev/null + count=0 + while kill -0 "${pid}" &>/dev/null && [ ${count} -lt 5 ]; do + sleep 1 + count=$((${count} + 1)) + done + kill -KILL "${pid}" &>/dev/null || true + fi + done - if [ -n "${raspimjpeg_pid}" ]; then - kill -HUP "${raspimjpeg_pid}" &>/dev/null - count=0 - while kill -0 "${raspimjpeg_pid}" &>/dev/null && [ ${count} -lt 5 ]; do - sleep 1 - count=$((${count} + 1)) - done - kill -KILL "${raspimjpeg_pid}" &>/dev/null || true - fi - - if [ -n "${gst_launch_pid}" ]; then - kill -HUP "${gst_launch_pid}" &>/dev/null - count=0 - while kill -0 "${gst_launch_pid}" &>/dev/null && [ ${count} -lt 5 ]; do - sleep 1 - count=$((${count} + 1)) - done - kill -KILL "${gst_launch_pid}" &>/dev/null || true - fi - - if [ -n "${test_launch_pid}" ]; then - kill -HUP "${test_launch_pid}" &>/dev/null - count=0 - while kill -0 "${test_launch_pid}" &>/dev/null && [ ${count} -lt 5 ]; do - sleep 1 - count=$((${count} + 1)) - done - kill -KILL "${test_launch_pid}" &>/dev/null || true + # stop the loopback device + if lsmod | grep v4l2loopback &> /dev/null ; then + rmmod v4l2loopback fi } @@ -302,4 +222,3 @@ case "$1" in echo $"Usage: $0 {start|stop|restart}" exit 1 esac - diff --git a/board/raspberrypi4/motioneye-modules/streameyectl.py b/board/raspberrypi4/motioneye-modules/streameyectl.py old mode 100644 new mode 100755 index c006e20c72..7a4ee03c23 --- a/board/raspberrypi4/motioneye-modules/streameyectl.py +++ b/board/raspberrypi4/motioneye-modules/streameyectl.py @@ -412,9 +412,13 @@ def _get_streameye_settings(camera_id): 'seProto': 'mjpeg', 'seAuthMode': 'disabled', 'sePort': 8081, - 'seRTSPPort': 554 + 'seRTSPPort': 554, + 'seMJPEGWidth': 640, + 'seMJPEGHeight': 480, + 'seMJPEGFramerate': 5, + 'seMJPEGBitrate': 800000 } - + if os.path.exists(STREAMEYE_CONF): logging.debug('reading streameye settings from %s' % STREAMEYE_CONF) @@ -442,15 +446,40 @@ def _get_streameye_settings(camera_id): if m: s['seProto'] = m[0] + m = re.findall('^MJPEG_WIDTH="?(\w+)"?', line) + if m: + s['seMJPEGWidth'] = m[0] + + m = re.findall('^MJPEG_HEIGHT="?(\w+)"?', line) + if m: + s['seMJPEGHeight'] = m[0] + + m = re.findall('^MJPEG_FRAMERATE="?(\w+)"?', line) + if m: + s['seMJPEGFramerate'] = m[0] + + m = re.findall('^MJPEG_BITRATE="?(\w+)"?', line) + if m: + s['seMJPEGBitrate'] = m[0] + + s['seMJPEGRes'] = '%sx%s' % (s.pop('seMJPEGWidth'), s.pop('seMJPEGHeight')) + return s def _set_streameye_settings(camera_id, s): s = dict(s) + s['seMJPEGWidth'] = int(s['seMJPEGRes'].split('x')[0]) + s['seMJPEGHeight'] = int(s.pop('seMJPEGRes').split('x')[1]) + s.setdefault('sePort', 8081) s.setdefault('seRTSPPort', 554) s.setdefault('seAuthMode', 'disabled') - + s.setdefault('seMJPEGWidth', 640) + s.setdefault('seMJPEGHeight', 480) + s.setdefault('seMJPEGFramerate', 5) + s.setdefault('seMJPEGBitrate', 800000) + main_config = config.get_main() username = main_config['@normal_username'] password = main_config['@normal_password'] @@ -462,6 +491,10 @@ def _set_streameye_settings(camera_id, s): 'PROTO="%s"' % s['seProto'], 'PORT="%s"' % s['sePort'], 'RTSP_PORT="%s"' % s['seRTSPPort'], + 'MJPEG_WIDTH="%s"' % s['seMJPEGWidth'], + 'MJPEG_HEIGHT="%s"' % s['seMJPEGHeight'], + 'MJPEG_FRAMERATE="%s"' % s['seMJPEGFramerate'], + 'MJPEG_BITRATE="%s"' % s['seMJPEGBitrate'], 'AUTH="%s"' % s['seAuthMode'], 'CREDENTIALS="%s:%s:%s"' % (username, password, realm) ] @@ -1158,6 +1191,27 @@ def seProto(): } +@additional_config +def seRTSPPort(): + if not _get_streameye_enabled(): + return None + + return { + 'label': 'Streaming RTSP Port', + 'description': 'sets the TCP port on which the webcam streaming server listens', + 'type': 'number', + 'min': 0, + 'max': 65535, + 'section': 'streaming', + 'camera': True, + 'required': True, + 'depends': ['seProto==rtsp'], + 'get': _get_streameye_settings, + 'set': _set_streameye_settings, + 'get_set_dict': True + } + + @additional_config def sePort(): if not _get_streameye_enabled(): @@ -1172,7 +1226,6 @@ def sePort(): 'section': 'streaming', 'camera': True, 'required': True, - 'depends': ['seProto==mjpeg'], 'get': _get_streameye_settings, 'set': _set_streameye_settings, 'get_set_dict': True @@ -1180,16 +1233,60 @@ def sePort(): @additional_config -def seRTSPPort(): +def seMJPEGRes(): if not _get_streameye_enabled(): return None return { - 'label': 'Streaming Port', - 'description': 'sets the TCP port on which the webcam streaming server listens', + 'label': 'MJPEG Resolution', + 'description': 'the MJPEG resolution fed to the frontend and used for motion detection on remote machines when streaming RTSP', + 'type': 'choices', + 'choices': RESOLUTION_CHOICES, + 'section': 'streaming', + 'camera': True, + 'required': True, + 'depends': ['seProto==rtsp'], + 'get': _get_streameye_settings, + 'set': _set_streameye_settings, + 'get_set_dict': True + } + + +@additional_config +def seMJPEGFramerate(): + if not _get_streameye_enabled(): + return None + + return { + 'label': 'MJPEG Frame Rate', + 'description': 'the MJPEG Frame Rate fed to the frontend and used for motion detection on remote machines when streaming RTSP; for motion detection, 5 works good', + 'type': 'range', + 'min': 2, + 'max': 30, + 'snap': 0, + 'ticks': "2|5|10|15|20|25|30", + 'decimals': 0, + 'section': 'streaming', + 'camera': True, + 'required': True, + 'depends': ['seProto==rtsp'], + 'get': _get_streameye_settings, + 'set': _set_streameye_settings, + 'get_set_dict': True + } + + +@additional_config +def seMJPEGBitrate(): + if not _get_streameye_enabled(): + return None + + return { + 'label': 'MJPEG Bitrate', + 'description': 'the MJPEG Bitrate fed to the frontend and used for motion detection on remote machines when streaming RTSP; for motion detection, 800000 works good', 'type': 'number', 'min': 0, - 'max': 65535, + 'max': 25000000, 'section': 'streaming', 'camera': True, 'required': True, @@ -1218,4 +1315,3 @@ def seAuthMode(): 'set': _set_streameye_settings, 'get_set_dict': True } - diff --git a/board/raspberrypi4/overlay/usr/bin/streameye.sh b/board/raspberrypi4/overlay/usr/bin/streameye.sh index 9f63710c3c..83950bdb7b 100755 --- a/board/raspberrypi4/overlay/usr/bin/streameye.sh +++ b/board/raspberrypi4/overlay/usr/bin/streameye.sh @@ -2,7 +2,7 @@ RASPIMJPEG_CONF=/data/etc/raspimjpeg.conf RASPIMJPEG_LOG=/var/log/raspimjpeg.log -GSTREAMER_LOG=/var/log/gstreamer.log +RTSPSERVER_LOG=/var/log/rtspserver.log MOTIONEYE_CONF=/data/etc/motioneye.conf STREAMEYE_CONF=/data/etc/streameye.conf STREAMEYE_LOG=/var/log/streameye.log @@ -17,122 +17,22 @@ function watch() { while true; do sleep 5 if [ "${PROTO}" = "rtsp" ]; then - if ! ps aux | grep test-launch | grep -v grep &>/dev/null; then + if ! ps aux | grep v4l2multi_stream_mmal | grep -v grep &>/dev/null; then + logger -t streameye -s "not running, respawning" + start + elif ! ps aux | grep v4l2rtspserver | grep -v grep &>/dev/null; then logger -t streameye -s "not running, respawning" start fi else if ! ps aux | grep raspimjpeg.py | grep -v grep &>/dev/null; then - logger -t streameye -s "not running, respawning" + logger -t streameye -s "raspimjpeg.py not running, respawning" start fi fi done } -function configure_v4l2_cam() { - video_arg="-d $1" - - horizontal_flip="0" - hflip=$(grep -e ^hflip ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - if [ "${hflip}" = "true" ]; then horizontal_flip="1"; fi - v4l2-ctl ${video_arg} --set-ctrl=horizontal_flip=${horizontal_flip} &>/dev/null - - vertical_flip="0" - vflip=$(grep -e ^vflip ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - if [ "${vflip}" = "true" ]; then vertical_flip="1"; fi - v4l2-ctl ${video_arg} --set-ctrl=vertical_flip=${vertical_flip} &>/dev/null - - rotate=$(grep -e ^rotation ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - if [ -z "${rotate}" ]; then rotate="0"; fi - v4l2-ctl ${video_arg} --set-ctrl=rotate=${rotate} &>/dev/null - - brightness=$(grep -e ^brightness ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - if [ -z "${brightness}" ]; then brightness="50"; fi - v4l2-ctl ${video_arg} --set-ctrl=brightness=${brightness} &>/dev/null - - contrast=$(grep -e ^contrast ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - if [ -z "${contrast}" ]; then contrast="0"; fi - v4l2-ctl ${video_arg} --set-ctrl=contrast=${contrast} &>/dev/null - - saturation=$(grep -e ^saturation ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - if [ -z "${saturation}" ]; then saturation="0"; fi - v4l2-ctl ${video_arg} --set-ctrl=saturation=${saturation} &>/dev/null - - sharpness=$(grep -e ^sharpness ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - if [ -z "${sharpness}" ]; then sharpness="0"; fi - v4l2-ctl ${video_arg} --set-ctrl=sharpness=${sharpness} &>/dev/null - - vstab=$(grep -e ^vstab ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - if [ -z "${vstab}" ]; then vstab="0"; fi - v4l2-ctl ${video_arg} --set-ctrl=image_stabilization=${vstab} &>/dev/null - - shutter=$(grep -e ^shutter ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - if [ -z "${shutter}" ]; then shutter="0"; fi - if [ "${#shutter}" -gt 2 ]; then shutter=$(echo ${shutter/%??/}); else shutter="0"; fi - v4l2-ctl ${video_arg} --set-ctrl=exposure_time_absolute=${shutter} &>/dev/null - - iso=$(grep -e ^iso ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - if [ -z "${iso}" ]; then iso="0"; fi - if [ "${iso}" -ge 800 ]; then iso="4" - elif [ "${iso}" -ge 400 ]; then iso="3" - elif [ "${iso}" -ge 200 ]; then iso="2" - elif [ "${iso}" -ge 100 ]; then iso="1" - else iso="0" - fi - v4l2-ctl ${video_arg} --set-ctrl=iso_sensitivity=${iso} &>/dev/null - - awb=$(grep -e ^awb ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - if [ "${awb}" = "off" ]; then awb="0" - elif [ "${awb}" = "auto" ]; then awb="1" - elif [ "${awb}" = "sunlight" ]; then awb="6" - elif [ "${awb}" = "cloudy" ]; then awb="8" - elif [ "${awb}" = "shade" ]; then awb="9" - elif [ "${awb}" = "tungsten" ]; then awb="4" - elif [ "${awb}" = "fluorescent" ]; then awb="3" - elif [ "${awb}" = "incandescent" ]; then awb="2" - elif [ "${awb}" = "flash" ]; then awb="7" - elif [ "${awb}" = "horizon" ]; then awb="5" - else awb="1" - fi - v4l2-ctl ${video_arg} --set-ctrl=white_balance_auto_preset=${awb} &>/dev/null - - metering=$(grep -e ^metering ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - if [ "${metering}" = "spot" ]; then metering="2" - elif [ "${metering}" = "center" ]; then metering="1" - else metering="0" - fi - v4l2-ctl ${video_arg} --set-ctrl=exposure_metering_mode=${awb} &>/dev/null - - scene="0" - exposure=$(grep -e ^exposure ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - if [ "${exposure}" = "off" ]; then - exposure="1" - else - if [ "${exposure}" = "night" ]; then scene="8" - elif [ "${exposure}" = "backlight" ]; then scene="1" - elif [ "${exposure}" = "sports" ]; then scene="11" - elif [ "${exposure}" = "snow" ]; then scene="2" - elif [ "${exposure}" = "beach" ]; then scene="2" - elif [ "${exposure}" = "fireworks" ]; then scene="6" - fi - exposure="0" - fi - v4l2-ctl ${video_arg} --set-ctrl=auto_exposure=${exposure} &>/dev/null - v4l2-ctl ${video_arg} --set-ctrl=scene_mode=${scene} &>/dev/null - - ev=$(grep -e ^ev ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - if [ -z "${ev}" ]; then ev="0"; fi - ev=$(expr ${ev} + 25) - ev=$(expr ${ev} / 2) - if [ "${ev}" -gt 24 ]; then ev="24"; fi - v4l2-ctl ${video_arg} --set-ctrl=auto_exposure_bias=${ev} &>/dev/null - - video_bitrate=$(grep -e ^bitrate ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - if [ -z "${video_bitrate}" ]; then video_bitrate="1000000"; fi - v4l2-ctl ${video_arg} --set-ctrl=video_bitrate=${video_bitrate} &>/dev/null -} - function invalid_opt() { local e match="$1" shift @@ -143,70 +43,106 @@ function invalid_opt() { function start() { source ${STREAMEYE_CONF} streameye_opts="-p ${PORT}" + rtspserver_opts="" if [ -n "${CREDENTIALS}" ] && [ "${AUTH}" = "basic" ]; then streameye_opts="${streameye_opts} -a basic -c ${CREDENTIALS}" + # TODO: Add RTSP Auth fi - + if [ "${PROTO}" = "rtsp" ]; then - pid=$(ps | grep test-launch | grep -v grep | tr -s ' ' | sed -e 's/^\s//' | cut -d ' ' -f 1) - if [ -n "${pid}" ]; then - return + v4l2rtspserver_pid=$(ps | grep v4l2rtspserver | grep -v grep | tr -s ' ' | sed -e 's/^\s//' | cut -d ' ' -f 1) + v4l2multi_stream_mmal_pid=$(ps | grep v4l2multi_stream_mmal | grep -v grep | tr -s ' ' | sed -e 's/^\s//' | cut -d ' ' -f 1) + if [ -n "${v4l2rtspserver_pid}" ]; then + if [ -n "${v4l2multi_stream_mmal_pid}" ]; then + return + fi fi - + RTSP_PORT=${RTSP_PORT:-554} + MJPEG_WIDTH=${MJPEG_WIDTH:-640} + MJPEG_HEIGHT=${MJPEG_HEIGHT:-480} + MJPEG_FRAMERATE=${MJPEG_FRAMERATE:-5} + MJPEG_BITRATE=${MJPEG_BITRATE:-800000} - iptables -A INPUT -p tcp -s localhost --dport ${RTSP_PORT} -j ACCEPT - iptables -A INPUT -p tcp --dport ${RTSP_PORT} -j DROP - + audio_path="" audio_opts="" if [ -n "${AUDIO_DEV}" ]; then + audio_path=",${AUDIO_DEV}" + + # Audio bitrate: default 44100 audio_bitrate=$(grep -e ^audio_bitrate ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) + # Audio channels: default 2 audio_channels=$(grep -e ^audio_channels ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - audio_extras="" + # Valid audio formats: S16_BE, S16_LE, S24_BE, S24_LE, S32_BE, S32_LE, ALAW, MULAW, S8, MPEG + audio_format=$(grep -e ^audio_format ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) if [ -n "${audio_bitrate}" ]; then - audio_extras="${audio_extras},rate=${audio_bitrate}" + audio_opts="${audio_opts} -A ${audio_bitrate}" fi if [ -n "${audio_channels}" ]; then - audio_extras="${audio_extras},channels=${audio_channels}" + audio_opts="${audio_opts} -C ${audio_channels}" + fi + if [ -n "${audio_format}" ]; then + audio_opts="${audio_opts} -a ${audio_format}" fi - audio_opts="alsasrc device=${AUDIO_DEV} ! audioresample ! audio/x-raw${audio_extras} ! queue ! voaacenc ! rtpmp4gpay pt=97 name=pay1" fi - video_path="/dev/video0" - if [ -n "${VIDEO_DEV}" ]; then - video_path=${VIDEO_DEV} + # hardcode to 90 for now + vidid=90 + video_path="/dev/video${vidid}" + if lsmod | grep v4l2loopback &> /dev/null ; then + rmmod v4l2loopback fi + modprobe v4l2loopback video_nr=${vidid} if [ -e "${video_path}" ]; then - # Only configure camera if it is a Pi Cam - if v4l2-ctl -d ${video_path} -D | grep -q 'bm2835 mmal'; then - configure_v4l2_cam ${video_path} + valid_opts=("analoggain" "awb" "awbgains" "bitrate" "brightness" "colfx" "contrast" "denoise" "digitalgain" "drc" "ev" "exposure" "flicker" "framerate" "height" "hflip" "imxfx" "intra" "irefresh" "level" "metering" "profile" "roi" "rotation" "saturation" "sharpness" "shutter" "vflip" "vstab" "width" "mjpegbitrate" "mjpegframerate" "mjpegwidth" "mjpegheight") + raspimjpeg_opts="--videoout ${video_path}" + while read line; do + key=$(echo ${line} | cut -d ' ' -f 1) + if invalid_opt "${key}" "${valid_opts[@]}"; then + continue + fi + if echo "${line}" | grep false &>/dev/null; then + continue + fi + if echo "${line}" | grep true &>/dev/null; then + line=${key} + fi + raspimjpeg_opts="${raspimjpeg_opts} --${line}" + done < ${RASPIMJPEG_CONF} + + video_iso=$(grep -e ^iso ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) + if [ -n "${video_iso}" ]; then + raspimjpeg_opts="${raspimjpeg_opts} --ISO ${video_iso}" fi - width=$(grep -e ^width ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - height=$(grep -e ^height ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - framerate=$(grep -e ^framerate ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) - video_extras="" - if [ -n "${width}" ]; then - video_extras="${video_extras},width=${width}" - fi - if [ -n "${height}" ]; then - video_extras="${video_extras},height=${height}" - fi - if [ -n "${framerate}" ]; then - video_extras="${video_extras},framerate=${framerate}/1" - fi - video_opts="v4l2src device=${video_path} ! video/x-h264${video_extras} ! h264parse ! rtph264pay name=pay0 pt=96" if [ -r ${MOTIONEYE_CONF} ] && grep 'log-level debug' ${MOTIONEYE_CONF} >/dev/null; then + raspimjpeg_opts="${raspimjpeg_opts} -v" streameye_opts="${streameye_opts} -d" fi - test-launch -p ${RTSP_PORT} -m h264 "\"( ${video_opts} ${audio_opts} )\"" &>${GSTREAMER_LOG} & - sleep 10 - gst-launch-1.0 -v rtspsrc location=rtsp://127.0.0.1:${RTSP_PORT}/h264 latency=0 drop-on-latency=1 ! rtph264depay ! h264parse ! omxh264dec ! videorate ! video/x-raw,framerate=5/1 ! jpegenc ! filesink location=/dev/stdout | streameye ${streameye_opts} &>${STREAMEYE_LOG} & - sleep 5 - fi + rtspserver_opts="${rtspserver_opts} -P ${RTSP_PORT} -u h264" + video_framerate=$(grep -e ^framerate ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) + video_intra=$(grep -e ^intra ${RASPIMJPEG_CONF} | cut -d ' ' -f 2) + if [ -n "${video_framerate}" ]; then + rtspserver_opts="${rtspserver_opts} -F ${video_framerate}" + if [ -z "${video_intra}" ]; then + let "video_intra=$((video_framerate))*2" + raspimjpeg_opts="${raspimjpeg_opts} --intra ${video_intra}" + fi + fi - iptables -D INPUT -p tcp --dport ${RTSP_PORT} -j DROP - iptables -D INPUT -p tcp -s localhost --dport ${RTSP_PORT} -j ACCEPT + mjpeg_opts="--mjpegbitrate ${MJPEG_BITRATE} --mjpegwidth ${MJPEG_WIDTH} --mjpegheight ${MJPEG_HEIGHT} --mjpegframerate ${MJPEG_FRAMERATE}" + raspimjpeg_opts="${raspimjpeg_opts} ${mjpeg_opts}" + + if [ -z "${v4l2multi_stream_mmal_pid}" ]; then + v4l2multi_stream_mmal -v ${raspimjpeg_opts} -o - 2>${RTSPSERVER_LOG} | streameye ${streameye_opts} &>${STREAMEYE_LOG} & + sleep 10 + fi + + if [ -z "${v4l2rtspserver_pid}" ]; then + v4l2rtspserver ${rtspserver_opts} ${audio_opts} ${video_path}${audio_path} &>${RTSPSERVER_LOG} & + sleep 5 + fi + fi else pid=$(ps | grep raspimjpeg.py | grep -v grep | tr -s ' ' | sed -e 's/^\s//' | cut -d ' ' -f 1) @@ -244,41 +180,25 @@ function stop() { # stop the streameye background watch process ps | grep streameye.sh | grep -v $$ | grep -v S94streameye| grep -v grep | tr -s ' ' | sed -e 's/^\s//' | cut -d ' ' -f 1 | xargs -r kill - # stop the raspimjpeg process - raspimjpeg_pid=$(ps | grep raspimjpeg.py | grep -v grep | tr -s ' ' | sed -e 's/^\s//' | cut -d ' ' -f 1) - # stop the gst-launch-1.0 process - gst_launch_pid=$(ps | grep gst-launch-1.0 | grep -v grep | tr -s ' ' | sed -e 's/^\s//' | cut -d ' ' -f 1) - # stop the test-launch process - test_launch_pid=$(ps | grep test-launch | grep -v grep | tr -s ' ' | sed -e 's/^\s//' | cut -d ' ' -f 1) + # stop the running streaming process + processes=( "raspimjpeg.py" "v4l2rtspserver" "v4l2multi_stream_mmal" ) + for i in "${processes[@]}" + do + pid=$(ps | grep $i | grep -v grep | tr -s ' ' | sed -e 's/^\s//' | cut -d ' ' -f 1) + if [ -n "${pid}" ]; then + kill -HUP "${pid}" &>/dev/null + count=0 + while kill -0 "${pid}" &>/dev/null && [ ${count} -lt 5 ]; do + sleep 1 + count=$((${count} + 1)) + done + kill -KILL "${pid}" &>/dev/null || true + fi + done - if [ -n "${raspimjpeg_pid}" ]; then - kill -HUP "${raspimjpeg_pid}" &>/dev/null - count=0 - while kill -0 "${raspimjpeg_pid}" &>/dev/null && [ ${count} -lt 5 ]; do - sleep 1 - count=$((${count} + 1)) - done - kill -KILL "${raspimjpeg_pid}" &>/dev/null || true - fi - - if [ -n "${gst_launch_pid}" ]; then - kill -HUP "${gst_launch_pid}" &>/dev/null - count=0 - while kill -0 "${gst_launch_pid}" &>/dev/null && [ ${count} -lt 5 ]; do - sleep 1 - count=$((${count} + 1)) - done - kill -KILL "${gst_launch_pid}" &>/dev/null || true - fi - - if [ -n "${test_launch_pid}" ]; then - kill -HUP "${test_launch_pid}" &>/dev/null - count=0 - while kill -0 "${test_launch_pid}" &>/dev/null && [ ${count} -lt 5 ]; do - sleep 1 - count=$((${count} + 1)) - done - kill -KILL "${test_launch_pid}" &>/dev/null || true + # stop the loopback device + if lsmod | grep v4l2loopback &> /dev/null ; then + rmmod v4l2loopback fi } @@ -302,4 +222,3 @@ case "$1" in echo $"Usage: $0 {start|stop|restart}" exit 1 esac -