Videostreams von IP-Kamera als E-Mail und Live (NVR, h.264, IPC, HLS, FFmpeg)

Begonnen von Torxgewinde, 01 Dezember 2022, 19:17:18

Vorheriges Thema - Nächstes Thema

Torxgewinde

Hallo,
Vor langer Zeit hatte ich mal MJPG-Streamer entwickelt und einen MJPEG-stream ohne Plugin an den Browser senden können. Damit war es auch einfach Snapshots zu machen und diese als E-Mail oder Push Nachricht zu versenden wenn ein Event in FHEM ausgelöst wurde. Die Zeit geht weiter und MJPEG ist mittlerweile nicht mehr die erste Wahl, da es zu wenige Framerate liefert und zu bandbreitenintensiv ist - Heute nimmt mal lieber h.264 oder h.265.

Etabliert, aber leider nicht direkt im Browser darstellbar ist RTSP als Streaming-Protokoll. Im Browser kann man allerdings HLS, DASH oder WebRTC streamen. DASH hat Probleme auf iOS und WebRTC hat wunderbar wenig Latenz, stellt aber an den Server ein paar mehr Anforderungen als HLS. HLS ist ist sehr kompatibel, hat allerdings auch eine heftige Latenz mit der man aber arbeiten kann. Hat man einen HLS Stream kann man diesen mit einem Webserver ausliefern und die meisten Browser können diesen Stream nativ oder mit Javascript darstellen.

Ein HLS-Stream besteht im Grunde aus kurzen Video-Segmenten (*.ts Dateien) und einer M3U8-Playliste. Man kann auch mehrere Auflösungen des Streams anbieten und der Client schaltet dann jeweils auf den Stream um, der ohne zu stocken übertragen werden kann.

Für FHEM ist ein HLS Stream aus einem weiterem Grund interessant: Man kann darin vorwärts und rückwärts gehen und so auch Video von vor einigen Sekunden abrufen. Das ist nützlich, wenn ein Bewegungsmelder in FHEM triggert und auch das Video vor diesem Trigger-Zeitpunkt z.B. per E-Mail verschickt werden soll.

Wenn die IP-Kamera jetzt auch noch direkt Streams per RTSP im h.264 Format anbietet, braucht man nichteinmal das Video transkodieren, sondern es genügt das Video zu remuxen. Das kann auch ein embedded Gerät mit wenig RAM und wenig CPU - in meinem Fall ist es ein WLAN-Router der mit OpenWRT bespielt wurde.

IP-Kamera konfigurieren
Die IP Kamera sollte einen, oder zwei RTSP Streams ausliefern und man sollte die URL kennen. Bei einer Hikvision Kamera ist der erste Stream zum Beispiel "rtsp://username:password@kamera.lan:554/ISAPI/streaming/channels/101/?transportmode=unicast" und der zweite Stream hat die URL "rtsp://username:password@kamera.lan:554/ISAPI/streaming/channels/102/?transportmode=unicast". In den Stream kann man mit VLC oder ffplay reinschauen:
ffplay -rtsp_transport tcp -i rtsp://username:password@kamera.lan:554/ISAPI/streaming/channels/102/?transportmode=unicast
Hinweis #1: -rtsp_transport tcp korrigiert kleinere Fehler im Netzwerk, da der Stream über TCP läuft. Standard ist UDP und da kann es manchmal zu kleinen Fehlern in der Übertragung kommen.
Hinweis #2: Hat die IP-Kamera mehrere Streams, sollte einer mit geringer Bandbreite (kleine Auflösung und/oder kleine Framerate) und der andere hochauflösend sein. In meinem Fall habe ich h.264 als Codec eingestellt und h.264+ oder auch h.265/h.265+ zugunsten der Kompatibilität zu Browsern vermieden.

RTSP-Streams in HLS-Dateien umwandeln
Da ein HLS-Stream aus Dateien besteht muss man diese irgendwo speichern. Der Speicherort sollte von einem HTTP-Server aus erreichbar sein. Auf OpenWRT ist dies zum Beispiel der Ordner /www. Natürlich kann man auch NGinx oder Apache als Webserver installieren. Eigentlich müsste sogar FHEMs eigener Webserver zum ausliefern der HLS-Dateien geeignet sein. Dann kann man mit folgendem Skript die notwendigen Dateien in den Ordner /opt/fhem/www/video speichern und später im Browser abrufen.
#!/bin/bash

#Put HLS output into $WEBFOLDER
WEBFOLDER="/opt/fhem/www/video"

#High resolution RTSP stream
STREAMHIGH="rtsp://username:password@kamera.lan:554/ISAPI/streaming/channels/101/?transportmode=unicast"

#Low resolution RTSP stream
STREAMLOW="rtsp://username:password@kamera.lan:554/ISAPI/streaming/channels/102/?transportmode=unicast"

cd

#download most recent hls.js if not present
if [ ! -f "$WEBFOLDER/hls.js" ]; then
wget "https://cdn.jsdelivr.net/npm/hls.js@latest" -O "$WEBFOLDER/hls.js" || exit 1
fi

#this is a simple webpage with the HLS video.
#Safari plays it without Javascript, Firefox needs hls.js
cat << 'EOM' > "$WEBFOLDER/index.html"
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<script src="hls.js"></script>
<style>
body {
margin: 0;
padding: 0;
}
video {
object-fit: fill;
width: 100%;
height: auto;
}
</style>
</head>
<body>
<video id="video" controls autoplay>
<source src="stream.m3u8" type="application/x-mpegURL">
</video>
<script>
if (Hls.isSupported()) {
var video = document.getElementById('video');

var config = {
autoStartLoad: true,
startPosition: 30,
backBufferLength: 60
};
var hls = new Hls(config);

hls.loadSource('stream.m3u8');
hls.attachMedia(video);

//autoplay, many browsers are configured to only allow autoplay when muted
hls.on(Hls.Events.MANIFEST_PARSED, function() {
video.muted = true;
video.volume = 0;
video.play();
});

//when new fragment loaded, seek to 5 seconds before the current end of stream, total delay is ~10-12s this way
hls.on(Hls.Events.FRAG_LOADED, function() {
video.currentTime = video.duration - 5;
});
}
</script>
</body>
</html>
EOM

# https://ffmpeg.org/ffmpeg.html#Advanced-options
# https://ffmpeg.org/ffmpeg-formats.html#hls-2
# -hide_banner: Hide the infos at startup
# -rtsp_transport tcp: Default is to use UDP, which reveals skipped packets in my network
# -i $STREAMHIGH: the input at high resolution
# -i $STREAMLOW: the input at low resultion
# -codec copy: do not transcode, just remux - this is essential for embedded devices!
# -b:v:0 1024k Describe the bandwidth of video of the first input (index starts at 0)
# -b:v:1 256k  For the low resolution stream the bandwidth is about 256kBit/s
# -map 0:v include the videostream of the first input
# -map 1:v include the videostream of second input as well
# -map 0:a for audio of first input (not used here)
# -copytb 1 Use the demuxer timebase
# -copyts copy timestamps from input to output
# -f hls output is HLS
# -var_stream_map 'v:0,name:hd,agroup:my_stream v:1,name:sd,agroup:my_stream' select first video stream, name it "hd", assign a group + select second video stream, name it "sd", assign it to group
# -hls_time 5 each *.ts file has a length of ~5 seconds
# -hls_allow_cache 0 client must not cache the file
# -hls_list_size 10 limit the number of files to this value
# -hls_flags delete_segments+append_list+program_date_time : delete unreferenced (old) *.ts files, append new ts files to playlist, insert recorded date+time
# -hls_segment_filename '$WEBFOLDER/%v_file%d.ts' pattern for the *.ts filename
# -master_pl_name stream.m3u8 pattern for the the HLS master playlist
# -master_pl_publish_rate 10 publish a current master playlist after creating N *.ts segments
# '$WEBFOLDER/%v_stream.m3u8' the output for HLS playlists

#run ffmpeg
ffmpeg -hide_banner \
  -rtsp_transport tcp -i $STREAMHIGH \
  -rtsp_transport tcp -i $STREAMLOW \
  -codec copy \
  -b:v:0 2M -b:v:1 256k \
  -map 0:v -map 1:v \
  -copytb 1 -copyts \
  -f hls \
  -var_stream_map 'v:0,name:hd,agroup:my_stream v:1,name:sd,agroup:my_stream' \
  -hls_time 5 \
  -hls_allow_cache 0 \
  -hls_list_size 10 \
  -hls_flags delete_segments+append_list+program_date_time \
  -hls_segment_filename "$WEBFOLDER/%v_file%d.ts" \
  -master_pl_name stream.m3u8 \
  -master_pl_publish_rate 10 \
  "$WEBFOLDER/%v_stream.m3u8"

exit 0

Das Skript kopiert die notwendigen Dateien in den $WEBFOLDER und startet ffmpeg mit den passenden Parametern. Ich habe die Parameter kommentiert. Der HD-Stream von der Kamera hat in meinem Fall ca. 2MBit/s und der SD-Stream ca. 256kBit/s. Diese Zahl sollte man an die eigene Kamera anpassen.

Nachdem das Skript läuft, sollte relativ bald die ersten M3U8 Playlisten und TS-Dateien im $WEBFOLDER auftauchen und man sollte mit dem Browser/VLC/ffplay das Livebild streamen können. Im Firefox kann man begrenzte Bandbreiten simulieren und damit prüfen ob auch wirklich zwischen HD und SD Stream umgeschaltet wird (Tastendruck F12 und dann bei den Handy-ansichten kann man das umschalten).

E-Mail bei Bewegung versenden, Video als MOV Datei im Anhang
Ein weiteres Skript nimmt einen Videoschnipsel auf. Dabei wird zwei Segmente in die Zeit vor dem Event zurückgespult und für 30 Sekunden aufgenommen. Da curl auch SMTP zum E-Mail versenden kennt und mit Attachments umgehen kann nutze ich dies:
#!/bin/bash

#generate message with timestamp before running the recording
MESSAGE="Es wurde eine Bewegung erkannt ($(date))"

echo "$(date): recording video..."
#assuming 5 seconds per HLS segment:
#seek backwards two segments = -live_start_index -2 = -10 seconds from current end-of-stream
#record for a duration of 35 seconds               
ffmpeg -y \
-hide_banner \
-loglevel fatal \
-live_start_index -2 \
-i /www/video/sd_stream.m3u8 \
-c copy -map 0 \
-t 35 \
-f mov "/opt/fhem/motion.mov" || exit 1

echo "$(date): sending email now..."
#https://everything.curl.dev/usingcurl/smtp
#https://curl.se/docs/manpage.html
#https://superuser.com/questions/1628906/using-curl-to-send-multipart-alternative-email-with-encoder-quoted-printable-ho
curl --ssl-reqd smtps://mail.server.de:465 \
    --login-options AUTH=PLAIN \
    --user blubb@example.de:password \
    --mail-from blubb@example.de \
    --mail-rcpt bla@example.com \
    --mail-rcpt-allowfails \
    -H "Subject: Bewegung erkannt" \
    -F '=(;type=multipart/alternative' \
    -F "=$MESSAGE" \
    -F "= <body>$MESSAGE</body>;type=text/html" \
    -F '=)' \
    -F "=@/opt/fhem/motion.mov;encoder=base64" || exit 1

rm -f /opt/fhem/motion.mov || exit 1
echo "$(date): done"

exit 0


Triggern kann dieses Skipt ein DOIF:
defmod PIRHaustuer.Action DOIF ( [PIRHaustuer.device:"^motion$"] )\
("bash /opt/fhem/motion_email.sh")
attr PIRHaustuer.Action do always


Zum Darstellen des HLS Streams in FHEMWEB:

defmod Camera.stream weblink htmlCode \
<a href="/fhem?detail=Camera.stream">edit</a><br />\
\
<iframe src="https://meinServer.lan/video/" height="600" width="800"></iframe>


HTH

Edit: Fehler im Skript (siehe Unterhaltung unten) gefixt.

Torxgewinde

Mit FFmpeg kann man auch eine effiziente Szenenanalyse laufen lassen und damit Bewegungen nur aus dem Videostrom erkennen.

Inspiriert ist das Vorgehen von einem Modul für HomeAssitant. Allerdings würde ich nicht md5, sondern CRC32 als Prüfsumme favorisieren (spart nochmal RAM und CPU im Vergleich zu MD5).

Die Parameter:

  • -hide_banner --> ffmpeg schreibt üblichweise Infos aus stdout beim Starten, das brauchen wir hier nicht
  • -rtsp_transport --> Empfange den kleineren RTSP stream von der Kamera mit TCP statt UDP, bügelt kleinere Netzwerkprobleme aus
  • -i --> die URL der Kamera
  • -an --> verwerfe Tonspuren
  • -vf --> VideoFilter
  • select --> wähle nur Bilder bei denen die Suche zutrifft
  • isnan(prev_selected_t)+gte(t-prev_selected_t\,0.5) --> selektiere nur Bilder die einen zeitlichen Abstand von 0.5 Sekunden haben
  • scale --> weiterer Filter, skaliere das Bild runter auf ein Viertel (1/64 wäre noch effizienter, ist aber dann sehr grob)
  • select=gt(scene\,0.05) --> wähle nur Bilder aus dem Videostream bei denen sich er Inhalt mehr als 0.05 geändert hat (Wert kann 0 bis 1.0 sein, 0 lässt alles durch)
  • -f framehash --> Output soll in das Modul 'framehash' gegeben werden
  • -hash crc32 --> Parameter für das Modul 'framehash' um die CRC32 zu berechnen
  • - --> Das Framehash-Modul soll auf STDOUT schreiben, könnte auch in eine Datei geschrieben werden
ffmpeg -hide_banner -loglevel fatal \
-rtsp_transport tcp -i rtsp://username:passwort@camera.lan:554/ISAPI/streaming/channels/102/?transportmode=unicast \
-an \
-vf "select=isnan(prev_selected_t)+gte(t-prev_selected_t\,0.5), scale=iw/4:-2:flags=neighbor, select=gt(scene\,0.05)" \
-f framehash -hash crc32 -


Der Output ist dann sowas:
#format: frame checksums
#version: 2
#hash: CRC32
#software: Lavf58.76.100
#tb 0: 1/25
#media_type 0: video
#codec_id 0: rawvideo
#dimensions 0: 480x270
#sar 0: 1/1
#stream#, dts,        pts, duration,     size, hash
0,        473,        473,        1,   194400, ba72f638
0,        686,        686,        1,   194400, 0004a3b1
0,        806,        806,        1,   194400, fc61cca3


Immer wenn sich das Bild deutlich genug ändert (Szenenwechsel), wird eine weitere Zeile mit ausgegeben.

Zum Testen kann man auch eine Webcam direkt am PC nehmen, da kann man dann auch ein Bild im SDL Fenster zeigen:
ffmpeg  -hide_banner -loglevel fatal -f v4l2 -framerate 25 -video_size 640x480 -i /dev/video0 -an -vf "select=isnan(prev_selected_t)+gte(t-prev_selected_t\,0.5), scale=iw/4:ih/4:flags=neighbor, select=gt(scene\,0.05)" -f framehash -hash crc32 - -c:v rawvideo -pix_fmt yuv420p -window_size qcif -f sdl "bla"

Torxgewinde

Man kann den Stream recht einfach als Videodatei speichern. Ein einfaches DOIF ermöglicht mitschneiden des Streams als z.B. *.mov oder *.mkv. Da auch hier nicht transkodiert wird, ist die CPU Last kleiner 5% auf einem einfachen Hardware. Falls man keinen Bedarf an dem HLS Stream hat, kann man alternativ auch hier den RTSP stream mitschneiden. Beim HLS Stream kann man zeitlich in die Vergangenheit springen und das Vergangene aufzeichnen, bei Mitschnitt von RTSP fehlen ab dem Starten der Aufnahme einige Sekunden in der Aufzeichnung.

Da system() zwar ermöglicht einen Prozess in den Hintergrund zu forken, aber leider immer das Log zumüllt mit einem Fehler "-1", habe ich qx() verwendet. Damit qx() nicht den FHEM Prozess pausiert, wird eine mit screen geforkte Session gestartet. Der immense Vorteil dabei ist, dass man auch nachträglich mit screen -dRR NVR nochmal schauen kann was FFmpeg als Ausgabe schreibt.

defmod NVR DOIF ([$SELF:command] eq "start")\
{\
my $WEBFOLDER="/opt/fhem/www/video";;\
my $INPUT="-live_start_index -2 -i /www/video/stream.m3u8";; ##input is HLS stream, seek back two segments, ~10s\
\
my $result = qx(screen -d -S NVR -m bash -c "ffmpeg -y -hide_banner -loglevel info $INPUT -c copy \"$WEBFOLDER/camera.mp4\"" && echo "start OK" || echo "start NOK" );;\
Log(0, "$SELF: $result");;\
}\
DOELSEIF ([$SELF:command] eq "stop")\
{\
##send the keypress "q" to the screen-session named "NVR", this gracefully ends FFmpeg\
my $result = qx( screen -S NVR -p 0 -X stuff q && echo "stop OK" || echo "stop NOK" );;\
Log(0, "$SELF: $result");;\
}\
DOELSEIF ([$SELF:command] eq "prepare")\
{\
##prepare directory for video in FHEMWEB, download hls.js if needed\
my $WEBFOLDER="/opt/fhem/www/video";;\
\
my $result = qx( mkdir -p $WEBFOLDER;; [ ! -f $WEBFOLDER/hls.js ] && wget "https://cdn.jsdelivr.net/npm/hls.js\@latest" -O "$WEBFOLDER/hls.js" );;\
Log(0, "$SELF: $result");;\
}\
DOELSE\
##
attr NVR cmdState Recording|Stopped|prepared|unknown
attr NVR readingList command
attr NVR setList command:start,stop
attr NVR webCmd command
attr NVR widgetOverride command:uzsuSelectRadio,stop,start


Zum Darstellen der MP4 Datei:

defmod CameraView weblink htmlCode \
<a href="/fhem?detail=CameraView">edit</a><br />\
<video id="video" width="640" controls muted autoplay src="/fhem/video/camera.mp4">\
Your browser does not support the video tag.\
</video>

cotecmania

Hi,

ich habe auch das Problem, dass meine alten Kameras (BNC) MJPEG lieferten, was ich in FTUI ohne Probleme anzeigen kann.
Die neueren Kameras bieten aber nur noch RTSP.
Könnte man das "Umleiten" relativ einfach auf dem FHEM Raspy zusätzlich laufen lassen und den FHEM Webserver verwenden ?
Ich möchte so wenig wie möglich zusätzliche Dinge installieren bzw. zusätzliche Hardware verwenden.
Wenn ja könntest Du das genau erklären, da ich in Linux nicht so fit bin.

Gruss
Joe
FHEM auf RaspberryPI B (buster)
2xCUL868 für MAX/Slow_RF, HM-LAN, JeeLink
MAX!/HM-Thermostate, FS20/HM-Rolladenschalter, FS20-EM, LevelJet-Ölstandsmessung, PCA301, IT, KM271, IPCAM, FireTAB10 FTUI

Torxgewinde

Hallo @cotecmania,
Klar, das geht. h.264/RTSP ist wirklich ein Segen im Vergleich zu MJPEG (=JPEG/HTTP). Der Stream ist damit deutlich flüssiger und belegt weniger Bandbreite. HLS hat prinzipbedingt eine spürbare Verzögerung von 30 bis 5 Sekunden, das wird man nur los wenn man auf WebRTC wechseln würde, was aber einen Server wie z.B. "Janus" erfordert! Soviel zur Motivation.

Deinem Post entnehme ich, dass FHEM auf einem RPi läuft. Ich VERMUTE dort läuft das Raspberry-Pi-OS drauf, was praktisch Debian+Extras entspricht. Dort hast du vermutlich FHEM in /opt installiert. Ein Raspberry PI sollte keine Probleme mit der Wandlung von RTSP nach HLS haben (RAM und CPU sollte reichen). Ist der h.264/RTSP-Stream einmal im h.264/HLS-Stream-Format, kann man das gut in FHEM in der WebUI einbinden und anzeigen lassen.

Der einzige Punkt, den ich nicht hunderpro sagen kann, ist ob der der FHEM eigene Webserver wirklich in der Lage ist die Dateien für den HLS Stream auszuliefern. Wenn man den FHEM eigenen Webserver dazu bewegen möchte, Dateien für einen auszuliefern:

  • legt man einen Ordner im Verzeichnis /opt/fhem/www/ an. Zum Beispiel: mkdir /opt/fhem/www/video. Der Nutzer "fhem" sollte Zugriff auf diesen Ordner haben, also die Rechte prüfen.
  • Jede Datei, die in diesem Ordner liegt, kann man dann im Browser abrufen unter der Adresse: "https://<ip>/fhem/video/". Dort kann man also direkt die Datei "hls.js" hinkopieren, die Firefox hilft den HLS-Stream anzuzeigen. Nativ (also ohne dieses ECMA/Javascript) können das nur iOS Geräte und ich glaube auch Browser wie Chrome. Die hls.js kann man von https://cdn.jsdelivr.net/npm/hls.js@latest runterladen. Wieder auf die Zugriffsrechte achten und ausprobieren, dass der FHEM Webserver nun die Datei unter "https://<ip>/fhem/video/hls.js" auch wirklich an einen Browser ausliefert.

Dann musst du in dem RPi die Programme installieren, vor allem ist dies "ffmpeg". Das sollte mit dem Paketmanager "apt" gelingen: sudo apt -y install ffmpeg screen wget.

In den Kameras sollte man die Einstellungen prüfen, damit auch wirklich ein h.264/RTSP Stream erzeugt wird. Der Codec ist also "h.264", ohne irgendwelche Extras. Hikvision bietet zum Beispiel auch "h.264+" an, das komprimiert zwar besser ist aber zu speziell für einige Browser. Auch "h.265" oder "h.265+" sind zwar toll in sich, aber nicht so kompatibel. Deswegen schau' erstmal ob "h.264" einstellbar ist und stelle das auch ein. Kann die Kamera mehrere Streams anbieten, dann sollte man einen in hoher Auflösung und einen mit geringerer Auflösung einstellen, allerdings auch hier beide auf "h.264" als Codec. Damit man die Kamera von RPi aus auslesen kann, muss die URI bekannt sein, das heißt nicht nur die IP, sondern auch der Pfad und ggf. Parameter. Die URI kann testen mit dem Programm "VLC" oder auch "ffplay". Beide Programme sind sogar für Windows-OS verfügbar.

Wenn die URI bekannt ist, dann kann man das in dem Skript aus dem ersten Post eintragen. Das Skript kann man in den FHEM Ordner kopieren, zum Beispiel nach /opt/fhem/scripts/RTSP-to-HLS.sh. Ich habe hier mal die Pfade angepasst, wie sie VERMUTLICH sein werden:
#!/bin/bash

#Put HLS output into $WEBFOLDER
WEBFOLDER="/opt/fhem/www/video"

#High resolution RTSP stream
STREAMHIGH="rtsp://username:password@kamera.lan:554/ISAPI/streaming/channels/101/?transportmode=unicast"

#Low resolution RTSP stream
STREAMLOW="rtsp://username:password@kamera.lan:554/ISAPI/streaming/channels/102/?transportmode=unicast"

cd

#download most recent hls.js if not present
if [ ! -f "$WEBFOLDER/hls.js" ]; then
wget "https://cdn.jsdelivr.net/npm/hls.js@latest" -O "$WEBFOLDER/hls.js" || exit 1
fi

#this is a simple webpage with the HLS video.
#Safari plays it without Javascript, Firefox needs hls.js
cat << 'EOM' > "$WEBFOLDER/index.html"
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<script src="hls.js"></script>
<style>
body {
margin: 0;
padding: 0;
}
video {
object-fit: fill;
width: 100%;
height: auto;
}
</style>
</head>
<body>
<video id="video" controls autoplay>
<source src="stream.m3u8" type="application/x-mpegURL">
</video>
<script>
if (Hls.isSupported()) {
var video = document.getElementById('video');

var config = {
autoStartLoad: true,
startPosition: 30,
backBufferLength: 60
};
var hls = new Hls(config);

hls.loadSource('stream.m3u8');
hls.attachMedia(video);

//autoplay, many browsers are configured to only allow autoplay when muted
hls.on(Hls.Events.MANIFEST_PARSED, function() {
video.muted = true;
video.volume = 0;
video.play();
});

hls.on(Hls.Events.FRAG_LOADED, function() {
video.currentTime = video.duration - 5;
});

}
</script>
</body>
</html>
EOM

# https://ffmpeg.org/ffmpeg.html#Advanced-options
# https://ffmpeg.org/ffmpeg-formats.html#hls-2
# -hide_banner: Hide the infos at startup
# -rtsp_transport tcp: Default is to use UDP, which reveals skipped packets in my network
# -i $STREAMHIGH: the input at high resolution
# -i $STREAMLOW: the input at low resultion
# -codec copy: do not transcode, just remux - this is essential for embedded devices!
# -b:v:0 1024k Describe the bandwidth of video of the first input (index starts at 0)
# -b:v:1 256k  For the low resolution stream the bandwidth is about 256kBit/s
# -map 0:v include the videostream of the first input
# -map 1:v include the videostream of second input as well
# -map 0:a for audio of first input (not used here)
# -copytb 1 Use the demuxer timebase
# -copyts copy timestamps from input to output
# -f hls output is HLS
# -var_stream_map 'v:0,name:hd,agroup:my_stream v:1,name:sd,agroup:my_stream' select first video stream, name it "hd", assign a group + select second video stream, name it "sd", assign it to group
# -hls_time 5 each *.ts file has a length of ~5 seconds
# -hls_allow_cache 0 client must not cache the file
# -hls_list_size 10 limit the number of files to this value
# -hls_flags delete_segments+append_list+program_date_time : delete unreferenced (old) *.ts files, append new ts files to playlist, insert recorded date+time
# -hls_segment_filename '$WEBFOLDER/%v_file%d.ts' pattern for the *.ts filename
# -master_pl_name stream.m3u8 pattern for the the HLS master playlist
# -master_pl_publish_rate 10 publish a current master playlist after creating N *.ts segments
# '$WEBFOLDER/%v_stream.m3u8' the output for HLS playlists

#run ffmpeg
ffmpeg -hide_banner \
  -rtsp_transport tcp -i $STREAMHIGH \
  -rtsp_transport tcp -i $STREAMLOW \
  -codec copy \
  -b:v:0 2M -b:v:1 256k \
  -map 0:v -map 1:v \
  -copytb 1 -copyts \
  -f hls \
  -var_stream_map 'v:0,name:hd,agroup:my_stream v:1,name:sd,agroup:my_stream' \
  -hls_time 5 \
  -hls_allow_cache 0 \
  -hls_list_size 10 \
  -hls_flags delete_segments+append_list+program_date_time \
  -hls_segment_filename '$WEBFOLDER/%v_file%d.ts' \
  -master_pl_name stream.m3u8 \
  -master_pl_publish_rate 10 \
  '$WEBFOLDER/%v_stream.m3u8'"

exit 0


Wenn du nun nach "https://<ip>/fhem/video/index.html" surfst, sollte der HLS Stream im Browser angezeigt werden. Will man das in FHEM anzeigen, kann man sich das Leben einfach machen und ein "<iframe>" nutzen:
defmod myCamera.stream weblink htmlCode \
<a href="/fhem?detail=myCamera.stream">edit</a><br />\
\
<iframe src="/fhem/video/index.html" height="455" width="800"></iframe>


Das Skript /opt/fhem/scripts/RTSP-to-HLS.sh anfangs von Hand ausführen, am besten als Nutzer "fhem". Wenn das klappt, kann man es aus FHEM heraus mitstarten und es einfach immer mitlaufen lassen. Da FFMPEG in diesem Fall nicht den Codec ändern muss, muss sich der RPi auch nicht sehr anstrengen und das Skript belegt weder viel CPU noch RAM. Zum Starten aus FHEM heraus:
defmod di_Startup DOIF (0) ## Startup Befehle\
({Log(0, "di_Startup startet")})\
("screen -d -S HLS -m bash -c /opt/fhem/scripts/RTSP-to-HLS.sh")\
({Log(0, "di_Startup beendet")})
attr di_Startup do always
attr di_Startup startup set $SELF cmd_1


HTH!

cotecmania

Hi,

Danke für deine Anleitung.
Ordner ist angelegt und hls.js eingespielt.
Wenn ich http://192.168.1.150:8099/fhem/video/hls.js aufrufe bekomme ich den Inhalt der Datei angezeigt.

Meinen stream rtsp://user:pwd@192.168.1.94:88/videoSub kann ich mit VLC wiedergeben.
Codec ist H264, angezeigt von VLC

Nun nehme ich dein Script und speichere es als index.html im /www/video/ Ordner ???

Wenn ich das nun aufrufe mit http://192.168.1.150:8099/fhem/video/index.html
bekomme ich die Anzeige im Anhang. (Mehrere Browser getestet ...)

Was mache ich falsch ?
Die Dateien habe ich mit einem Windows-Rechner angelegt aufm Raspy. Gibts da Probleme mit Endekennungen/Zeilenumbruch etc. ???

Gruss
Joe


FHEM auf RaspberryPI B (buster)
2xCUL868 für MAX/Slow_RF, HM-LAN, JeeLink
MAX!/HM-Thermostate, FS20/HM-Rolladenschalter, FS20-EM, LevelJet-Ölstandsmessung, PCA301, IT, KM271, IPCAM, FireTAB10 FTUI

Torxgewinde

Hi,
Das Script selbst wird bei dir als Text geöffnet und durch den Webbrowser ausgeliefert, es wird nicht ausgeführt. Das Skript soll eine BASH Datei sein und kann von der Konsole/Terminal/Eingabeaufforderung aus gestartet werden. Es soll dann unendlich lange zusammen mit FHEM laufen um die Wandlung von RTSP in HLS vorzuehmen.


  • Konsole öffnen, Nutzer FHEM sein
  • cd /opt/fhem && mkdir scripts && touch /opt/scripts/RTSP-to-HLS.sh
  • Dort den Text in die Datei RTSP-to-HLS.sh kopieren und die Variablen im Skript anpassen
  • Skript ausführen mit "bash RTSP-to-HLS.sh" oder auch "screen -S HLS -m bash -c /opt/fhem/scripts/RTSP-to-HLS.sh"

Die index.html Datei wird aus dem Script heraus erzeugt, die macht das Script für dich. Bei den Zeilenenden sollte man möglichst nur LF ("\n") und nicht CR+LF ("\r\n") nutzen wenn die Datei auf einem Linux landet https://de.wikipedia.org/wiki/Zeilenumbruch, das sollte allerdings nicht für diesen Fehler verantwortlich sein.

cotecmania

Hallo nochmals,

beim Ausführen des scriptes kommt :
pi@RASPYPI3:/opt/scripts $ sudo bash RTSP-to-HLS.sh
RTSP-to-HLS.sh: Zeile 116: Dateiende beim Suchen nach `"' erreicht.
RTSP-to-HLS.sh: Zeile 120: Syntax Fehler: Unerwartetes Dateiende.

Ich habe das "doppelte Anführungszeichen" hinten beim Aufruf von ffmpeg entfernt, da es m.E. nirgends geöffnet wurde.

Jetzt erscheint beim Aufruf folgendes und das script endet :
pi@RASPYPI3:/opt/scripts $ sudo bash RTSP-to-HLS.sh
[h264 @ 0x1a958c0] non-existing PPS 0 referenced
    Last message repeated 1 times
[h264 @ 0x1a958c0] decode_slice_header error
[h264 @ 0x1a958c0] no frame!
[h264 @ 0x1a958c0] non-existing PPS 0 referenced
    Last message repeated 1 times
[h264 @ 0x1a958c0] decode_slice_header error
[h264 @ 0x1a958c0] no frame!
[h264 @ 0x1a958c0] non-existing PPS 0 referenced
    Last message repeated 1 times
[h264 @ 0x1a958c0] decode_slice_header error
[h264 @ 0x1a958c0] no frame!
[h264 @ 0x1a958c0] non-existing PPS 0 referenced
    Last message repeated 1 times
[h264 @ 0x1a958c0] decode_slice_header error
[h264 @ 0x1a958c0] no frame!
[h264 @ 0x1a958c0] non-existing PPS 0 referenced
    Last message repeated 1 times
[h264 @ 0x1a958c0] decode_slice_header error
[h264 @ 0x1a958c0] no frame!
[h264 @ 0x1a958c0] non-existing PPS 0 referenced
    Last message repeated 1 times
[h264 @ 0x1a958c0] decode_slice_header error
[h264 @ 0x1a958c0] no frame!
[h264 @ 0x1a958c0] non-existing PPS 0 referenced
    Last message repeated 1 times
[h264 @ 0x1a958c0] decode_slice_header error
[h264 @ 0x1a958c0] no frame!
[h264 @ 0x1a958c0] non-existing PPS 0 referenced
    Last message repeated 1 times
[h264 @ 0x1a958c0] decode_slice_header error
[h264 @ 0x1a958c0] no frame!
[h264 @ 0x1a958c0] non-existing PPS 0 referenced
    Last message repeated 1 times
[h264 @ 0x1a958c0] decode_slice_header error
[h264 @ 0x1a958c0] no frame!
[h264 @ 0x1a958c0] non-existing PPS 0 referenced
    Last message repeated 1 times
[h264 @ 0x1a958c0] decode_slice_header error
[h264 @ 0x1a958c0] no frame!
[h264 @ 0x1a958c0] non-existing PPS 0 referenced
    Last message repeated 1 times
[h264 @ 0x1a958c0] decode_slice_header error
[h264 @ 0x1a958c0] no frame!
[h264 @ 0x1a958c0] non-existing PPS 0 referenced
    Last message repeated 1 times
[h264 @ 0x1a958c0] decode_slice_header error
[h264 @ 0x1a958c0] no frame!
[h264 @ 0x1a958c0] non-existing PPS 0 referenced
    Last message repeated 1 times
[h264 @ 0x1a958c0] decode_slice_header error
[h264 @ 0x1a958c0] no frame!
[h264 @ 0x1a958c0] non-existing PPS 0 referenced
    Last message repeated 1 times
[h264 @ 0x1a958c0] decode_slice_header error
[h264 @ 0x1a958c0] no frame!
[h264 @ 0x1a958c0] non-existing PPS 0 referenced
    Last message repeated 1 times
[h264 @ 0x1a958c0] decode_slice_header error
[h264 @ 0x1a958c0] no frame!
[h264 @ 0x1a958c0] non-existing PPS 0 referenced
    Last message repeated 1 times
[h264 @ 0x1a958c0] decode_slice_header error
[h264 @ 0x1a958c0] no frame!
[h264 @ 0x1a958c0] non-existing PPS 0 referenced
    Last message repeated 1 times
[h264 @ 0x1a958c0] decode_slice_header error
[h264 @ 0x1a958c0] no frame!
[h264 @ 0x1a958c0] non-existing PPS 0 referenced
    Last message repeated 1 times
[h264 @ 0x1a958c0] decode_slice_header error
[h264 @ 0x1a958c0] no frame!
[h264 @ 0x1a958c0] non-existing PPS 0 referenced
    Last message repeated 1 times
[h264 @ 0x1a958c0] decode_slice_header error
[h264 @ 0x1a958c0] no frame!
[h264 @ 0x1a958c0] non-existing PPS 0 referenced
    Last message repeated 1 times
[h264 @ 0x1a958c0] decode_slice_header error
[h264 @ 0x1a958c0] no frame!
[h264 @ 0x1a958c0] non-existing PPS 0 referenced
    Last message repeated 1 times
[h264 @ 0x1a958c0] decode_slice_header error
[h264 @ 0x1a958c0] no frame!
[h264 @ 0x1a958c0] non-existing PPS 0 referenced
    Last message repeated 1 times
[h264 @ 0x1a958c0] decode_slice_header error
[h264 @ 0x1a958c0] no frame!
Guessed Channel Layout for Input Stream #0.1 : mono
Input #0, rtsp, from 'rtsp://admin:pwd@192.168.1.94:88/videoMain':
  Metadata:
    title           : IP Camera Video
    comment         : videoMain
  Duration: N/A, start: 0.000000, bitrate: N/A
    Stream #0:0: Video: h264 (Constrained Baseline), yuvj420p(pc, progressive), 1920x1080, 90k tbr, 90k tbn, 180k tbc
    Stream #0:1: Audio: pcm_mulaw, 8000 Hz, mono, s16, 64 kb/s
[h264 @ 0x1ac6e90] non-existing PPS 0 referenced
    Last message repeated 1 times
[h264 @ 0x1ac6e90] decode_slice_header error
[h264 @ 0x1ac6e90] no frame!
[h264 @ 0x1ac6e90] non-existing PPS 0 referenced
    Last message repeated 1 times
[h264 @ 0x1ac6e90] decode_slice_header error
[h264 @ 0x1ac6e90] no frame!
[h264 @ 0x1ac6e90] non-existing PPS 0 referenced
    Last message repeated 1 times
[h264 @ 0x1ac6e90] decode_slice_header error
[h264 @ 0x1ac6e90] no frame!
Guessed Channel Layout for Input Stream #1.1 : mono
Input #1, rtsp, from 'rtsp://admin:pwd@192.168.1.94:88/videoSub':
  Metadata:
    title           : IP Camera Video
    comment         : videoSub
  Duration: N/A, start: 0.000000, bitrate: N/A
    Stream #1:0: Video: h264 (Constrained Baseline), yuvj420p(pc, progressive), 1280x720, 90k tbr, 90k tbn, 180k tbc
    Stream #1:1: Audio: pcm_mulaw, 8000 Hz, mono, s16, 64 kb/s
[hls @ 0x1b9c060] Invalid keyval name:hd
[hls @ 0x1b9c060] Variant stream info update failed with status ffffffea
Could not write header for output file #0 (incorrect codec parameters ?): Invalid argument
Stream mapping:
  Stream #0:0 -> #0:0 (copy)
  Stream #1:0 -> #0:1 (copy)
    Last message repeated 1 times


Eine "index.html" wurde im Videoordner erstellt

Gruss
Joe
FHEM auf RaspberryPI B (buster)
2xCUL868 für MAX/Slow_RF, HM-LAN, JeeLink
MAX!/HM-Thermostate, FS20/HM-Rolladenschalter, FS20-EM, LevelJet-Ölstandsmessung, PCA301, IT, KM271, IPCAM, FireTAB10 FTUI

Torxgewinde

Das doppelte Anführungszeichen gehört da tatsächlich nicht hin, stimmt.

Die Meldungen dürften aus ffmpeg stammen. Er findet IMHO keine Bilddaten (bzw die Beschreibung wie zum Beispiel die Auflösung) in dem Stream. Wenn es ganz dumm läuft, kommt ffmpeg mit dem RTSP Stream nicht klar.
Öffne jeweils die Streams mit ffplay um zu gucken ob ffmpeg damit klarkommt, vielleicht klärt es sich dann auf. Was auch sein kann: Dein Stream hat Audio, meiner nicht. Es kann notwendig sein ffmpeg mit dem Parameter "-an" vorerst anzuweisen Audio zu ignorieren.

Wenn bei dem Skript mehr nachvollziehbar sein soll, kann man es auch mit "bash -vx RTSP-to-HLS.sh" ausführen, dann schreibt Bash immer auf die Konsole was es eingelesen hat und was es genau ausführt - ist also gesprächiger.

Achso, eigentlich sollte "sudo" nicht nötig sein. Das macht hier auch eher Probleme da dann die HLS Dateien dem Nutzer Root gehören und der Nutzer FHEM (und damit der Webserver von FHEM) nicht mehr ohne weiteres darauf zugreifen können.

cotecmania

Ausgabe von ffplay im Anhang. Zuerst kommen Fehlermeldungen aber dann sieht man unten eine Statuszeile die fortschreitet ...
Also sind die ersten Meldungen nur Warnungen bis sich ffmpeg synchronisiert hat

-an bringt nix beim Aufruf von ffmpeg

Ich habe mal folgendes probiert und das funktioniert d.h. die mp4-Datei wird erstellt und der Inhalt kann wiedergegeben werden.
Die Warnungen kommen auch zu Beginn
ffmpeg -i rtsp://admin:pwd@192.168.1.94:88/videoSub -c copy -an /opt/fhem/jotest.mp4

Beim Aufruf mit Deiner Datei erscheint folgender Fehler. Kann hier der Fehler liegen ?
RTSP-to-HLS.sh: Zeile 117: $WEBFOLDER/%v_stream.m3u8: Datei oder Verzeichnis nicht gefunden


Gruss
Joe
FHEM auf RaspberryPI B (buster)
2xCUL868 für MAX/Slow_RF, HM-LAN, JeeLink
MAX!/HM-Thermostate, FS20/HM-Rolladenschalter, FS20-EM, LevelJet-Ölstandsmessung, PCA301, IT, KM271, IPCAM, FireTAB10 FTUI

cotecmania

Ich habe den ffmpeg Aufruf nun mal auf einen Stream reduziert :
ffmpeg -hide_banner \
          -rtsp_transport tcp -i $STREAMLOW \
          -codec copy \
          -b:v:0 1024K \
          -map 0:v \
          -copytb 1 -copyts \
          -f hls \
          -hls_time 5 \
          -hls_allow_cache 0 \
          -hls_list_size 10 \
          -hls_flags delete_segments+append_list+program_date_time \
          -hls_segment_filename '$WEBFOLDER/file%03d.ts' \
          -master_pl_name stream.m3u8 \
          -master_pl_publish_rate 10 \
          -an \
          '$WEBFOLDER/stream.m3u8'


Sieht nun besser aus und jetzt kann er die erste Datei file000.ts nicht schreiben
Input #0, rtsp, from 'rtsp://admin:pwd@192.168.1.94:88/videoSub':
  Metadata:
    title           : IP Camera Video
    comment         : videoSub
  Duration: N/A, start: 0.000000, bitrate: N/A
    Stream #0:0: Video: h264 (Constrained Baseline), yuvj420p(pc, progressive), 1280x720, 90k tbr, 90k tbn, 180k tbc
    Stream #0:1: Audio: pcm_mulaw, 8000 Hz, mono, s16, 64 kb/s
[b][hls @ 0x170e910] Opening '$WEBFOLDER/file000.ts' for writing
Could not write header for output file #0 (incorrect codec parameters ?): No such file or directory[/b]
Stream mapping:
  Stream #0:0 -> #0:0 (copy)
    Last message repeated 1 times
RTSP-to-HLS.sh: Zeile 115: $WEBFOLDER/stream.m3u8: Datei oder Verzeichnis nicht gefunden


Ich habe die Privileges gesetzt auf : sudo chmod -v 777 /opt/fhem/www/video/*.*

WEBFOLDER ist :
#Put HLS output into $WEBFOLDER
WEBFOLDER="/opt/fhem/www/video"


FHEM auf RaspberryPI B (buster)
2xCUL868 für MAX/Slow_RF, HM-LAN, JeeLink
MAX!/HM-Thermostate, FS20/HM-Rolladenschalter, FS20-EM, LevelJet-Ölstandsmessung, PCA301, IT, KM271, IPCAM, FireTAB10 FTUI

Torxgewinde

Das es eine MP4 Datei ergibt ist doch schonmal ein sehr gutes Zeichen.

Zu dem "Datei nicht gefunden"-Fehler: Da ist leider noch ein Fehler der sich in dem Abwandeln fürs Forum eingeschlichen hat, da hast du Recht!

Hier mein echter Aufruf (ich nutze eine Debian-chroot da es bei mir unter OpenWRT läuft):

chroot "$THECHROOT" /bin/bash -c "ffmpeg -hide_banner \
                                  -rtsp_transport tcp -i $STREAMHIGH \
                                  -rtsp_transport tcp -i $STREAMLOW \
                                  -codec copy \
                                  -b:v:0 2M -b:v:1 256k \
                                  -map 0:v -map 1:v \
                                  -copytb 1 -copyts \
                                  -f hls \
                                  -var_stream_map 'v:0,name:hd,agroup:my_stream v:1,name:sd,agroup:my_stream' \
                                  -hls_time 5 \
                                  -hls_allow_cache 0 \
                                  -hls_list_size 10 \
                                  -hls_flags delete_segments+append_list+program_date_time \
                                  -hls_segment_filename '$WEBFOLDER/%v_file%d.ts' \
                                  -master_pl_name stream.m3u8 \
                                  -master_pl_publish_rate 10 \
                                  '$WEBFOLDER/%v_stream.m3u8'"

...und da kam auch das doppelte Anführungszeichen her. Da die BASH-Variable "$WEBFOLDER" nun nur noch in einfach Anführungszeichen steht, wird nichts ersetzt und es wird sprichwörtlich versucht den Ordner mit dem Namen "$WEBFOLDER" zu öffnen.

So müsste es nun gehen:

ffmpeg -hide_banner \
                                  -rtsp_transport tcp -i $STREAMHIGH \
                                  -rtsp_transport tcp -i $STREAMLOW \
                                  -codec copy \
                                  -b:v:0 2M -b:v:1 256k \
                                  -map 0:v -map 1:v \
                                  -copytb 1 -copyts \
                                  -f hls \
                                  -var_stream_map 'v:0,name:hd,agroup:my_stream v:1,name:sd,agroup:my_stream' \
                                  -hls_time 5 \
                                  -hls_allow_cache 0 \
                                  -hls_list_size 10 \
                                  -hls_flags delete_segments+append_list+program_date_time \
                                  -hls_segment_filename $WEBFOLDER/%v_file%d.ts \
                                  -master_pl_name stream.m3u8 \
                                  -master_pl_publish_rate 10 \
                                  $WEBFOLDER/%v_stream.m3u8


Edit zu dem zeitgleichen Post: Da ist das gleiche Problem, die Variable wird nicht durch ihren Wert ersetzt da der String in einfachen Anführungszeichen stand.

cotecmania

So, die .ts Dateien werden erstellt und die index.html auch.

Wenn ich die index.html aufrufe wird aber nichts angzeigt, nur der leere Player ...

In deinem Batchfile wird bei ffmpeg "%v_stream.m3u8"  verwendet,  aber oben im html-Teil nur "stream.m3u8". Passt das ?
Es gibt aber sowieso keine .m3u8 Datei im video Verzeichnis.
FHEM auf RaspberryPI B (buster)
2xCUL868 für MAX/Slow_RF, HM-LAN, JeeLink
MAX!/HM-Thermostate, FS20/HM-Rolladenschalter, FS20-EM, LevelJet-Ölstandsmessung, PCA301, IT, KM271, IPCAM, FireTAB10 FTUI

Torxgewinde

Schade, ich dachte jetzt läuft's, aber leider ist der Weg echt steinig. Ich fürchte du musst noch mehr Geduld aufbringen um es ans laufen zu bringen...

Der Ordnerinhalt sollte so aussehen wie im Dateianhang:

  • Eine "stream.m3u8",
  • eine "sd_stream.m3u8" und
  • eine "hd_stream.m3u8"

Da bei dir nur ein Stream in Verwendung ist, sollte es dann nur eine M3U8 geben.

Die "stream.m3u8" macht dem Player zwei Streams bekannt, einen in hoher und einen in geringer Auflösung. Bei guter Bandbreite schaltet der Player dann auf den HD-Stream um, bei geringer auf SD-Stream. Bei nur einem Stream ist das Prinzip obsolet. Das "%v" wird von ffmpeg mit dem Namen des Streams ersetzt.

Abgewandelt für nur einen Stream wäre das Skript wie folgt (gerade getestet auf Ubuntu Desktop):
#!/bin/bash

#Put HLS output into $WEBFOLDER
WEBFOLDER="/opt/fhem/www/video/"

#High resolution RTSP stream
STREAMHIGH="rtsp://username:password@<IP>:554/ISAPI/streaming/channels/101/?transportmode=unicast"

########################################################################
function cleanup () {
echo "cleaning up..."
exit 0
}
trap cleanup INT EXIT

cd

#download hls.js if not present
if [ ! -f "$WEBFOLDER/hls.js" ]; then
##wget "https://cdn.jsdelivr.net/npm/hls.js@latest" -O "$WEBFOLDER/hls.js" || exit 1
curl "https://cdn.jsdelivr.net/npm/hls.js@latest" --no-progress-meter --output "$WEBFOLDER/hls.js" || exit 1
fi

#this is a simple webpage with the HLS video.
#Safari plays it without Javascript, Firefox needs hls.js
cat << 'EOM' > "$WEBFOLDER/index.html"
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<script src="hls.js"></script>
<style>
body {
margin: 0;
padding: 0;
}
video {
object-fit: fill;
width: 100%;
height: auto;
}
</style>
</head>
<body>
<video id="video" controls autoplay>
<source src="stream.m3u8" type="application/x-mpegURL">
</video>
<script>
if (Hls.isSupported()) {
var video = document.getElementById('video');

var config = {
autoStartLoad: true,
startPosition: 30,
backBufferLength: 60
};
var hls = new Hls(config);

hls.loadSource('stream.m3u8');
hls.attachMedia(video);

//autoplay, many browsers are configured to only allow autoplay when muted
hls.on(Hls.Events.MANIFEST_PARSED, function() {
video.muted = true;
video.volume = 0;
video.play();
});

hls.on(Hls.Events.FRAG_LOADED, function() {
video.currentTime = video.duration - 5;
});
}
</script>
</body>
</html>
EOM

# https://ffmpeg.org/ffmpeg.html#Advanced-options
# https://ffmpeg.org/ffmpeg-formats.html#hls-2
# -hide_banner: Hide the infos at startup
# -rtsp_transport tcp: Default is to use UDP, which reveals skipped packets in my network
# -i $STREAMHIGH: the input at high resolution
# -i $STREAMLOW: the input at low resultion
# -codec copy: do not transcode, just remux - this is essential for embedded devices!
# -b:v:0 1024k Describe the bandwidth of video of the first input (index starts at 0)
# -b:v:1 256k  For the low resolution stream the bandwidth is about 256kBit/s
# -map 0:v include the videostream of the first input
# -map 1:v include the videostream of second input as well
# -map 0:a for audio of first input (not used here)
# -copytb 1 Use the demuxer timebase
# -copyts copy timestamps from input to output
# -f hls output is HLS
# -var_stream_map 'v:0,name:hd,agroup:my_stream v:1,name:sd,agroup:my_stream' select first video stream, name it "hd", assign a group + select second video stream, name it "sd", assign it to group
# -hls_time 5 each *.ts file has a length of ~5 seconds
# -hls_allow_cache 0 client must not cache the file
# -hls_list_size 10 limit the number of files to this value
# -hls_flags delete_segments+append_list+program_date_time : delete unreferenced (old) *.ts files, append new ts files to playlist, insert recorded date+time
# -hls_segment_filename '$WEBFOLDER/%v_file%d.ts' pattern for the *.ts filename
# -master_pl_name stream.m3u8 pattern for the the HLS master playlist
# -master_pl_publish_rate 10 publish a current master playlist after creating N *.ts segments
# '$WEBFOLDER/%v_stream.m3u8' the output for HLS playlists
while true; do
ffmpeg -hide_banner \
  -rtsp_transport tcp -i $STREAMHIGH \
  -codec copy \
  -b:v:0 2M \
  -map 0:v \
  -copytb 1 -copyts \
  -f hls \
  -var_stream_map 'v:0,name:hd,agroup:my_stream' \
  -hls_time 5 \
  -hls_allow_cache 0 \
  -hls_list_size 10 \
  -hls_flags delete_segments+append_list+program_date_time \
  -hls_segment_filename "$WEBFOLDER/%v_file%d.ts" \
  "$WEBFOLDER/stream.m3u8"
sleep 1
done

exit 0


cotecmania

Also mit deiner Version und 2 streams bzw. sobald der Parameter
-var_stream_map 'v:0,name:hd,agroup:my_stream v:1,name:sd,agroup:my_stream'

dabei ist bricht ffmpeg mit folgender Fehlermeldung ab :
[hls @ 0x1634cb0] Invalid keyval name:hd

Mit meiner Version wird die .m3u8 Datei nicht erstellt

Also habe ich den Parameter -var_stream_map in deiner Version einfach entfernt.
Dann läuft die Dateierstellung und die stream.m3u8-Datei wird auch erzeugt.
Allerdings habe ich nur einen stream und zwar 0_stream.m3u8

Wenn ich dann die index.html aufrufe werden immer nur ca. 5 Sekunden Video angezeigt und dann stoppt die Wiedergabe.
Nach einem manuellen Reload fängt es wieder an der selben Stelle an, wieder die 5 Sekunden ...
FHEM auf RaspberryPI B (buster)
2xCUL868 für MAX/Slow_RF, HM-LAN, JeeLink
MAX!/HM-Thermostate, FS20/HM-Rolladenschalter, FS20-EM, LevelJet-Ölstandsmessung, PCA301, IT, KM271, IPCAM, FireTAB10 FTUI