tvstreamrecord icon indicating copy to clipboard operation
tvstreamrecord copied to clipboard

Zeitgleiche Aufnahmen am FritzRepeater

Open danielk117 opened this issue 7 years ago • 43 comments

Hallo,

wie ich bereits in dem Issue https://github.com/Pavion/tvstreamrecord/issues/22 lesen konnte, hat tvstreamrecord keine Begrenzung für zeitgleiche Aufnahmen. Die DVB-C fähigen Geräte von AVM hingegen streamen an ein Gerät aber immer nur einmal.

Ich habe tvstreamrecord auf einer DS415+ laufen. Diese hat zwei Ethernet-Interfaces, welche beide an meiner FritzBox hängen. Zusätzlich habe ich im Netz den FritzRepeater mit DVB-C. Das gleichzeitige Aufnehmen wäre also theoretisch möglich, da ich über zwei verschiedene IPs auf den Repeater zugreifen kann. Funktionieren tut das allerdings mit tvstreamrecord noch nicht. Wahrscheinlich deshalb, weil beide ffmpeg-Prozesse ihre Verbindung über das selbe Interface aufbauen wollen.

Gibt es hier eine Möglichkeit das Problem mit ffmpeg selbst zu lösen oder wäre da eine Lösung über tvstreamrecord möglich? Ein Beispiel: Wenn bereits eine Aufnahme läuft, dann sorgt tvstreamrecord dafür, dass der zweite ffmpeg-Prozess über das zweite Interface arbeitet.

Ich stelle mich auch gern als Versuchskaninchen zur Verfügung.

Beste Grüße Daniel

danielk117 avatar Feb 08 '19 12:02 danielk117

Hallo Daniel,

es ist an sich eine interessante Idee, allerdings ist es eher eine Linux-/Netzwerk-Frage und da kenne ich mich nicht so gut aus. Zudem sind mehrere LANs eher für Link Aggregation gedacht, das ist wieder eine andere Geschichte... Was ich aber gerade gelesen habe, sieht in etwa so aus:

Die Regel, welches Netzwerkadapter verwendet wird (route), kann nur in Linux direkt eingestellt werden; https://superuser.com/questions/1355077/ffmpeg-choose-outbound-ip-eth0-or-eth1 Das sehe dann in etwa so aus:

root@DiskStation:~# route
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
IP1             0.0.0.0         255.255.255.0   U     0      0        0 eth0
IP2             0.0.0.0         255.255.255.0   U     0      0        0 eth1

Diese Route muss aber eigentlich an die Empfänger-Adresse (IP1/IP2) geknüpft werden. Ob man dem Repeater zwei Adressen beibringen kann, wage ich jedoch zu bezweifeln.

Weitere wage Ideen:

  • Proxy-artiges Tool, welches auf DS über eth1 läuft
  • zweites TSR im Docker-Container laufen lassen und den Docker auf eth1 abrichten

Also nach einer Stunde Recherche fand ich leider nichts, was ich umsetzen oder gar testen könnte. Wenn Du weiterführende Ideen hast, melde Dich gern wieder.

Gruß Pav

Pavion avatar Feb 08 '19 14:02 Pavion

Hallo Pavion,

danke für deine Ideen, die Docker-Variante wäre mein letzter Ausweg. Ich habe bereits zwei Möglichkeit erfolgreich testen können. Allerdings gelingt mir in beiden Fällen immer nur der direkte Aufruf von ffmpeg über die Kommandozeile.

Variante 1 https://superuser.com/questions/241178/how-to-use-different-network-interfaces-for-different-processes Über netns einen Interface-Namespace erzeugen, in welchem dann ein ffmpeg-Prozess läuft.

sudo ip netns add eth1_ns
sudo ip link set eth1 netns eth1_ns
sudo ip netns exec eth1_ns sudo ifconfig eth1 10.1.1.10/24 up
sudo ip netns exec eth1_ns sudo ifconfig lo 127.0.0.1/8 up
sudo ip netns exec eth1_ns sudo route add default gw 10.1.1.1
sudo ip netns exec eth1_ns sudo dhclient eth1
sudo ip netns exec eth1_ns sudo /volume1/@appstore/VideoStation/bin/ffmpeg XXXXXXXXXX

Funktioniert, wenn komplett über die Kommandozeile ausgeführt. Anschließend: Alle Schritte bis zum dhclient habe ich direkt über die Kommandozeile gemacht. Den letzten Punkt wollte ich dann über tvstreamrecord starten. Egal ob in der tvstreamrecord.py oder über die Config (ffmpeg-Pfad), ich bekomme immer Meldungen:

2019-02-09 15:15:44.754 | FFMPEG could not be started. Error: [Errno 2] No such file or directory
2019-02-09 15:15:44.745 | [u'sudo  ip netns exec eth1_ns sudo /volume1/@appstore/VideoStation/bin/ffmpeg',  u'-i',  'rtsp://192.168.170.43:554/?avm=1&freq=346&bw=8&msys=dvbc&mtype=64qam&sr=6900&specinv=1&pids=0,16,17,18,20,1400,1401,1402,1403,1404,1406,1405,1470,1476,2171',  u'-y', u'-t', u'675', u'-loglevel', u'fatal', u'-acodec', u'copy',  u'-vcodec', u'copy', u'/volume2/Aufnahme/20190209151544 -  dasbloghaus_tv___Grafitti_for_Love.ts']

Variante 2 https://daniel-lange.com/archives/53-Binding-applications-to-a-specific-IP.html IP-Adresse des anderen Interfaces angeben und über das bind.so-Skript ausführen.

BIND_ADDR="192.168.170.45" LD_PRELOAD=/usr/lib/bind.so sudo /volume1/@appstore/VideoStation/bin/ffmpeg XXXXXXXXXXXXX

Auch das funktioniert problemlos über Kommandozeile. Doch dann die selben Probleme wie oben. Über tvstreamrecord geht nix...

2019-02-09 18:18:47.514 | FFMPEG could not be started. Error: [Errno 2] No such file or directory
2019-02-09 18:18:47.506 | [u'BIND_ADDR="192.168.170.45"  LD_PRELOAD=/usr/lib/bind.so  /volume1/@appstore/VideoStation/bin/ffmpeg', u'-i',  'rtsp://192.168.170.43:554/?avm=1&freq=314&bw=8&msys=dvbc&mtype=64qam&sr=6900&specinv=1&pids=0,16,17,18,20,200,210,220,221,225,222,230,231,250',  u'-y', u'-t', u'2592', u'-loglevel', u'fatal', u'-acodec', u'copy',  u'-vcodec', u'copy', u'/volume2/Aufnahme/20190209181847 -  Bl_tentr_ume.ts']

Es scheint sich um irgendein Rechte-Problem zu handeln. Kannst du vielleicht weiterhelfen?

VG Daniel

danielk117 avatar Feb 09 '19 18:02 danielk117

Nein. Nur die Leerzeichen im Befehl werden falsch interpretiert. Wenn Du ihn in eine .sh packst, sollte es funktionieren. Schaue ich mir morgen genauer an.

Pavion avatar Feb 09 '19 20:02 Pavion

Die Idee hatte ich auch schon...

Variante 3 Ein Skript welches ich in der Config als Pfad zu ffmpeg angebe. Kompletter Pfad für ffmpeg sh /volume1/@appstore/VideoStation/bin/ffmpegbinder.sh

Das Skript wiederum startet ffmpeg in der bind-Variante:

#!/bin/sh
str="'$*'"
BIND_ADDR="192.168.170.45" LD_PRELOAD=/usr/lib/bind.so /volume1/@appstore/VideoStation/bin/ffmpeg "$str"

Und was soll ich sagen, das Resultat bleibt leider das selbe:

2019-02-09 18:43:14.081 | FFMPEG could not be started. Error: [Errno 2] No such file or directory
2019-02-09 18:43:14.073 | [u'sh  /volume1/homes/admin/ffmpegbinder.sh', u'-i',  'rtsp://192.168.170.43:554/?avm=1&freq=314&bw=8&msys=dvbc&mtype=64qam&sr=6900&specinv=1&pids=0,16,17,18,20,200,210,220,221,225,222,230,231,250',  u'-y', u'-t', u'1125', u'-loglevel', u'fatal', u'-acodec', u'copy',  u'-vcodec', u'copy', u'/volume2/Aufnahme/20190209184314 -  Bl_tentr_ume.ts']

Welche Leerzeichen meinst du?

danielk117 avatar Feb 09 '19 21:02 danielk117

Leerzeichen zwischen sh und /volume1 Vielleicht ./xxx.sh?

Pavion avatar Feb 09 '19 22:02 Pavion

Leerzeichen war ein gutes Stichwort...

Folgendes hab ich an der tvstreamrecord.py angepasst:

attr = [config.cfg_ffmpeg_path,"-i", ('"'+self.url+'"'), '-y', '-t', deltasec] + ffargs + [('"'+fn+'"')]
attr = " ".join(attr)

und

self.process = subprocess.Popen(attr, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

In der Config als ffmpeg-Pfad:

BIND_ADDR="192.168.170.45"  LD_PRELOAD=/usr/lib/bind.so /volume1/@appstore/VideoStation/bin/ffmpeg

Uns siehe da...

2019-02-10 00:04:26.290 | Record: Stopflag for 'Sportschau' received
2019-02-10 00:03:18.580 | FFMPEG (rtsp) record 'Sportschau' called with:
2019-02-10 00:03:18.580 | BIND_ADDR="192.168.170.45"   LD_PRELOAD=/usr/lib/bind.so /volume1/@appstore/VideoStation/bin/ffmpeg  -i  "rtsp://192.168.170.43:554/?avm=1&freq=306&bw=8&msys=dvbc&mtype=64qam&sr=6900&specinv=1&pids=0,16,17,18,20,100,101,102,103,104,106,84,105,1176,2070,2171"  -y -t 821 -loglevel fatal -acodec copy -vcodec copy  "/volume2/Aufnahme/20190210000318 - Sportschau.ts"

Und in der Fritz steht auch tatsächlich die 192.168.170.45 (mein eth1) als zugreifende IP da... Ich werde das morgen noch ein wenig erweitern und dynamisch machen. Immerhin will ich diesen Modus ja nur für eine zeitgleiche Aufnahme. Theoretisch könnte man diese Methode auch für virtuelle Ethernet-Interfaces nutzen. Man bräuchte also nicht zwingend zusätzliche echte Interfaces... Gut für FritzBoxen mit 4 Tunern. Mein Repeater hat leider nur 2 ;-)

danielk117 avatar Feb 09 '19 23:02 danielk117

So, neue Erkenntnisse und Probleme... Änderungen an der tvstreamrecord.py wie in meinem letzten Kommentar.

In der Config als ffmpeg-Pfad:

sh /volume1/@appstore/VideoStation/bin/ffmpegbinder.sh

Mein ffmpegbinder.sh-Skript sieht so aus:

#!/bin/sh
for i in "192.168.170.21" "192.168.170.45"
do
  runningfile="/volume1/@appstore/tvstreamrecord/running_$i.run"
  if [ -f $runningfile ]; then
    echo "file $runningfile exists"
  else
    echo "file $runningfile not exists"
    echo "running now">$runningfile
    echo "using bind adress $i"
    BIND_ADDR="$i" LD_PRELOAD=/usr/lib/bind.so /volume1/@appstore/VideoStation/bin/ffmpeg "$@"
    rm -- "$runningfile"
    break
  fi
done

Zeitgleiche Aufnahmen funktionieren einwandfrei. Aber das Beenden von Aufnahmen klappt nicht... :-( Anscheinend wird das Skript wegen shell=True nicht als Child-Prozess, sondern als eigener Prozess ausgeführt. Somit greifen terminate() oder kill() nicht.

danielk117 avatar Feb 10 '19 09:02 danielk117

So, jetzt bin ich wach und wieder am PC :)

Ich sehe, Du hast schon sehr viel Arbeit erledigt und sogar die richtige Stelle in meinem Code gefunden! Leider kann ich nicht wirklich viel davon testen, da ich nicht mal einen Tuner mehr habe, daher bleibe ich erstmal bei Theorie und freue mich auf eine Diskussion.

Durch die Verwendung von shell=True wird die Anwendung noch unsicherer, da damit absolut alle möglichen Befehle mit maximalen Admin-Rechten auf DS ausgeführt werden könnten... Dabei gibt es sogar Leute, die das Tool ohne Passwort im Internet freigeben oO

Eigentlich würde ich, wenn, den Pfad zuerst splitten und auf shell=True verzichten:

attr = shlex.split(config.cfg_ffmpeg_path) + ["-i", self.url, '-y', '-t', deltasec] + ffargs + [fn]

Bin mir aber nicht sicher, ob es mit Deinem Befehl funktioniert. Vielleicht könntest Du das testen? Würde es funktionieren, wenn Dein Befehl in einer .sh stünde?

Was die Implementierung der Schaltung angeht, fallen mir zwei Wege ein:

1. Über eine .sh

Eine Umschaltung in einer .sh realisieren (sorry für mein Linux, ist nicht meine Muttersprache ^^):

if [ -f "flag1" ]
then
touch flag2
BIND2
rm flag2
else
touch flag1
BIND1
rm flag1
fi

Wenn Du Dich darin auskennst, könnte ich mir vorstellen, dass so etwas auf mehr Interfaces erweitert und parametrisiert werden könnte. Ggf. könnte man die vorher erforderliche Einrichtung ebenfalls in die gleiche Datei packen und irgendwie nur bei Bedarf (oder erstem Start) ausführen. Das Ziel wäre eine Art Mega-Script, den ein Bediener auf DS downloaden, [ggf. geringfügig anpassen], seinen Pfad in TSR eintragen und sofort nutzen kann. Oder ist es zu viel gehofft?!

2. über TSR

Einführung und Verarbeitung eines neuen Parameters analog zu "Alternate URL": https://github.com/Pavion/tvstreamrecord/blob/f5d7d1bb0973b2d06986f16bd0f3a3a9caf279cc/tvstreamrecord.py#L1122 Vorteil: bequem anzupassen Nachteil: dadurch, dass dennoch einiges auf Linux-Ebene gemacht wird, ist der Vorteil geringer, dafür aber mehr Code und Optionen. Nicht dass ich die Arbeit von mir schieben würde ^^

Pavion avatar Feb 10 '19 09:02 Pavion

Ich habe schon seit einer Stunde an meiner Nachricht geschrieben und wurde gerade fertig, als Du Deine abgeschickt hast...

Pavion avatar Feb 10 '19 09:02 Pavion

Ich sehe, wir kamen auf ähnliche Ideen... Du hast schon den Script und ich habe vielleicht die Lösung für Dein Shell-Problem 🙂

Pavion avatar Feb 10 '19 09:02 Pavion

Sehr gut, fast gleichzeitig geantwortet :D

danielk117 avatar Feb 10 '19 09:02 danielk117

Ich würde deine split-Methode mal testen und shell=True weglassen.

danielk117 avatar Feb 10 '19 09:02 danielk117

Also split-Methode ohne shell=True und als ffmpeg-Pfad sh /volume1/@appstore/VideoStation/bin/ffmpegbinder.sh funktioniert:

2019-02-10 10:39:05.105 | Record: Stopflag for 'NZZ Standpunkte - Die Hydra hebt ihr Haupt - der neue Judenhass' received
2019-02-10 10:38:45.247 | Record: Stopflag for 'Pauline am Strand' received
2019-02-10 10:37:36.328 | FFMPEG (rtsp) record 'Pauline am Strand' called with:
2019-02-10 10:37:36.328 | ['sh',  '/volume1/@appstore/VideoStation/bin/ffmpegbinder.sh', u'-i',  'rtsp://192.168.170.43:554/?avm=1&freq=362&bw=8&msys=dvbc&mtype=64qam&sr=6900&specinv=1&pids=0,16,17,18,20,400,401,402,403,407,408,404,470,1276,2171',  u'-y', u'-t', u'2063', u'-loglevel', u'fatal', u'-acodec', u'copy',  u'-vcodec', u'copy', u'/volume2/Aufnahme/20190210103736 -  Pauline_am_Strand.ts']
2019-02-10 10:37:36.326 | total records: 2 // running records: 2
2019-02-10 10:36:49.971 | FFMPEG (rtsp) record 'NZZ Standpunkte - Die Hydra hebt ihr Haupt - der neue Judenhass' called with:
2019-02-10 10:36:49.971 | ['sh',  '/volume1/@appstore/VideoStation/bin/ffmpegbinder.sh', u'-i',  'rtsp://192.168.170.43:554/?avm=1&freq=314&bw=8&msys=dvbc&mtype=64qam&sr=6900&specinv=1&pids=0,16,17,18,20,200,210,220,221,225,222,230,231,250',  u'-y', u'-t', u'1510', u'-loglevel', u'fatal', u'-acodec', u'copy',  u'-vcodec', u'copy', u'/volume2/Aufnahme/20190210103649 -  NZZ_Standpunkte___Die_Hydra_hebt_ihr_Haupt___der_neue_Judenhass.ts']
2019-02-10 10:36:49.969 | total records: 1 // running records: 1

Beenden funktioniert allerdings trotzdem nicht...

danielk117 avatar Feb 10 '19 09:02 danielk117

Hm. Ich nutze ja terminate(), da sollte nix mehr laufen. Kann es sein, dass Dein Script durch sh im separaten Thread gestartet wird? Geht es nicht mit ./ffmpegbinder.sh?

Pavion avatar Feb 10 '19 09:02 Pavion

Ich vermute auch das mein Skript nicht als Kind-Prozess läuft. Mal gucken warum das so ist...

danielk117 avatar Feb 10 '19 11:02 danielk117

Also, das Skript wird als Kind ausgeführt...

$ sudo pstree -p
        ├─python(26950)─┬─sh(12510)───ffmpeg(12512)
        │               ├─{python}(26960)
        │               ├─{python}(26961)
        │               ├─{python}(26962)
        │               ├─{python}(26963)
        │               ├─{python}(26964)
        │               ├─{python}(26965)
        │               ├─{python}(26966)
        │               ├─{python}(26967)
        │               ├─{python}(26968)
        │               ├─{python}(26969)
        │               ├─{python}(1998)
        │               ├─{python}(12509)
        │               └─{python}(12511)
$ sudo ps -ef | grep ffmpeg && sudo ps -ef | grep python
root     26950     1  0 10:35 ?        00:00:24 python tvstreamrecord.py
root     12510 26950  0 13:14 ?        00:00:00 sh /volume1/@appstore/VideoStation/bin/ffmpegbinder.sh -i rtsp://192.168.170.43:554/?avm=1&freq=306&bw=8&msys=dvbc&mtype=64qam&sr=6900&specinv=1&pids=0,16,17,18,20,100,101,102,103,104,106,84,105,1176,2070,2171 -y -t 18169 -loglevel fatal -acodec copy -vcodec copy /volume2/Aufnahme/20190210131410 - Sportschau.ts
root     12512 12510  2 13:14 ?        00:00:09 /volume1/@appstore/VideoStation/bin/ffmpeg -i rtsp://192.168.170.43:554/?avm=1&freq=306&bw=8&msys=dvbc&mtype=64qam&sr=6900&specinv=1&pids=0,16,17,18,20,100,101,102,103,104,106,84,105,1176,2070,2171 -y -t 18169 -loglevel fatal -acodec copy -vcodec copy /volume2/Aufnahme/20190210131410 - Sportschau.ts

danielk117 avatar Feb 10 '19 12:02 danielk117

Nach dem Beenden der Aufnahmen über tvstreamrecord ist nur noch der ffmpeg-Prozess da. Somit wird das Bash-Skript anscheinend korrekt beendet.

root     20015     1  3 13:33 ?        00:00:01 /volume1/@appstore/VideoStation/bin/ffmpeg -i rtsp://192.168.170.43:554/?avm=1&freq=306&bw=8&msys=dvbc&mtype=64qam&sr=6900&specinv=1&pids=0,16,17,18,20,100,101,102,103,104,106,84,105,1176,2070,2171 -y -t 17011 -loglevel fatal -acodec copy -vcodec copy /volume2/Aufnahme/20190210133328 - Sportschau.ts

danielk117 avatar Feb 10 '19 12:02 danielk117

aber muss man wirklich sh vorschreiben und extra shell erzeugen?

Pavion avatar Feb 10 '19 12:02 Pavion

Mein .terminate() sendet SIGTERM nur an parent: https://unix.stackexchange.com/questions/146756/forward-sigterm-to-child-in-bash Wenn es ohne .sh nicht funktioniert, könnte das die Lösung sein.

Pavion avatar Feb 10 '19 13:02 Pavion

So, funktioniert auch ohne sh:

2019-02-10 13:59:13.147 | ['/volume1/@appstore/VideoStation/bin/ffmpegbinder.sh',  u'-i',  'rtsp://192.168.170.43:554/?avm=1&freq=306&bw=8&msys=dvbc&mtype=64qam&sr=6900&specinv=1&pids=0,16,17,18,20,100,101,102,103,104,106,84,105,1176,2070,2171',  u'-y', u'-t', u'15466', u'-loglevel', u'fatal', u'-acodec', u'copy',  u'-vcodec', u'copy', u'/volume2/Aufnahme/20190210135913 -  Sportschau.ts']
2019-02-10 13:59:13.146 | FFMPEG (rtsp) record 'Sportschau' called with:
$ sudo ps -ef | grep ffmpeg
root     19994     1  0 13:33 ?        00:00:08 python tvstreamrecord.py
root     30193 19994  0 13:59 ?        00:00:00 /bin/sh /volume1/@appstore/VideoStation/bin/ffmpegbinder.sh -i rtsp://192.168.170.43:554/?avm=1&freq=306&bw=8&msys=dvbc&mtype=64qam&sr=6900&specinv=1&pids=0,16,17,18,20,100,101,102,103,104,106,84,105,1176,2070,2171 -y -t 15466 -loglevel fatal -acodec copy -vcodec copy /volume2/Aufnahme/20190210135913 - Sportschau.ts
root     30195 30193  3 13:59 ?        00:00:00 /volume1/@appstore/VideoStation/bin/ffmpeg -i rtsp://192.168.170.43:554/?avm=1&freq=306&bw=8&msys=dvbc&mtype=64qam&sr=6900&specinv=1&pids=0,16,17,18,20,100,101,102,103,104,106,84,105,1176,2070,2171 -y -t 15466 -loglevel fatal -acodec copy -vcodec copy /volume2/Aufnahme/20190210135913 - Sportschau.ts

Aber nach dem Beenden:

$ sudo ps -ef | grep ffmpeg
root     30193 19994  0 13:59 ?        00:00:00 [ffmpegbinder.sh] <defunct>
root     30195     1  2 13:59 ?        00:00:04 /volume1/@appstore/VideoStation/bin/ffmpeg -i rtsp://192.168.170.43:554/?avm=1&freq=306&bw=8&msys=dvbc&mtype=64qam&sr=6900&specinv=1&pids=0,16,17,18,20,100,101,102,103,104,106,84,105,1176,2070,2171 -y -t 15466 -loglevel fatal -acodec copy -vcodec copy /volume2/Aufnahme/20190210135913 - Sportschau.ts

Der ffmpeg-Prozess will ums Verrecken nicht mit sterben... Da ich selbst mit Linux, bash und Co. nicht so fit bin, werde ich mich mal hier einlesen: https://riccomini.name/kill-subprocesses-linux-bash

danielk117 avatar Feb 10 '19 13:02 danielk117

Mein .terminate() sendet SIGTERM nur an parent: https://unix.stackexchange.com/questions/146756/forward-sigterm-to-child-in-bash Wenn es ohne .sh nicht funktioniert, könnte das die Lösung sein.

Wird probiert ;-)

danielk117 avatar Feb 10 '19 13:02 danielk117

So... Änderungen am Skript:

#!/bin/sh

_term() {
  echo "caught SIGTERM signal"
  echo "$(date)"
  pkill -9 -P "$$" 2>/dev/null
}

trap _term SIGTERM

for i in "192.168.170.21" "192.168.170.45"
do
  runningfile="/volume1/@appstore/tvstreamrecord/running_$i.run"
  if [ -f $runningfile ]; then
    echo "file $runningfile exists"
  else
    echo "file $runningfile not exists"
    echo "running now" >> $runningfile
    echo "using bind adress $i"
    BIND_ADDR="$i" LD_PRELOAD=/usr/lib/bind.so /volume1/@appstore/VideoStation/bin/ffmpeg "$@"
    rm -- "$runningfile"
    break
  fi
done

Beim Beenden über tvstreamrecord passiert nix. Alles läuft weiter. _term() wird anscheinend gar nicht aufgerufen. Rufe ich direkt in der Kommandozeile sudo pkill -9 -P $PID auf, dann wird die _term()-Funktion aufgerufen (erkenne ich an der dem Zeitstempel den mir die Funktion ausgibt).

danielk117 avatar Feb 10 '19 19:02 danielk117

Wird Dein Script wieder sh aufgerufen? Damit wäre noch ein Shell drüber, wenn ich es richtig verstehe. Ansonsten bin ich mit meinem Latein am Ende...

Pavion avatar Feb 10 '19 19:02 Pavion

Egal ob mit oder ohne sh, das Verhalten bleibt gleich. Anscheinend wird der SIGTERM-Befehl nicht korrekt gesendet oder empfangen...

danielk117 avatar Feb 10 '19 19:02 danielk117

So, ein erster Erfolg. Wie weiter oben angekündigt wollte ich mich hier https://riccomini.name/kill-subprocesses-linux-bash mal einlesen. Gesagt, getan und die dort beschriebene kill_child_processes() mittels trap angesprochen und es funktioniert.

#!/bin/sh

kill_child_processes() {
    isTopmost=$1
    curPid=$2
    childPids=`ps -o pid --no-headers --ppid ${curPid}`
    for childPid in $childPids
    do
        echo "try to kill child $childPid"
        kill_child_processes 0 $childPid
    done
    if [ $isTopmost -eq 0 ]; then
        echo "try to kill parent $curPid"
        kill -9 $curPid 2> /dev/null
    fi
    if [ -f $runningfile ]; then
        echo "try to delete $runningfile"
        rm -- "$runningfile"
    fi
}

trap "kill_child_processes 1 $$; exit 0" 0

for i in "192.168.170.21" "192.168.170.45"
do
  runningfile="/volume1/@appstore/tvstreamrecord/running_$i.run"
  if [ -f $runningfile ]; then
    echo "file $runningfile exists"
  else
    echo "file $runningfile not exists"
    echo "running now" >> $runningfile

    echo "using bind adress $i"
    BIND_ADDR="$i" LD_PRELOAD=/usr/lib/bind.so /volume1/@appstore/VideoStation/bin/ffmpeg "$@"
    rm -- "$runningfile"
    # exit
    break
  fi
done

Die kill_child_processes()-Funktion killt erst alle Kinder, dann den Hauptprozess und in meinem Fall wird noch die Running-Datei gelöscht. Sonderlich schön ist das Skript aktuell nicht, aber es funktioniert. Ich kann zwei gleichzeitge Aufnahmen über tvstreamrecord starten und beenden. Gleichzeitig sorgt das Skript dafür, dass die angegebenen IPs nur einmal für diesem Zweck verwendet werden können. Normalerweise würden sich mehrere ffmpeg-Prozesse über die gleiche IP zum FritzGerät nämlich in die Quere kommen und immer gegenseitig zum Abbruch führen.

Meine nächster Idee ist jetzt, mein eth1 um ein virtuelles Interface mit eigener IP zu erweitern und dann diese beiden Interfaces für die Aufnahmen zu nutzen. Somit wird eth0 nicht mit den Aufnahmen belastet und steht komplett für die anderen Aufgaben des NAS zur Verfügung.

danielk117 avatar Feb 11 '19 10:02 danielk117

Ich will nicht behaupten, alles verstanden zu haben, aber das klingt doch cool! :)

  • Hast Du schon überlegt, ob Du Dein Script mit einer kurzen Anleitung bei GitHub einstellst, damit auch andere davon profitieren könnten?
  • Was passiert eigentlich, wenn eine dritte Aufnahme gestartet wird? Verstehe ich es richtig, dass sie dann gar nicht erst gestartet würde? Eigentlich müsste dann die 1. Aufnahme abgebrochen werden....
  • Was passiert, wenn die Flags nicht gelöscht werden (z.B. weil DS während einer Aufnahme heruntergefahren wurde)?

Pavion avatar Feb 11 '19 11:02 Pavion

  1. Ja klar, wenn ich komplett fertig bin. 😉
  2. Die dritte Aufnahme wird über tvstreamrecord zwar theoretisch gestartet, aber es startet nie ein ffmpeg, da mein Skript aufgrund der vorhanden Tickets normal zu Ende läuft. Danach greift aktuell deine Retry-Logik. Wenn es x-mal erfolglos probiert wurde, hört tvstreamrecord ja dann auf es zu probieren. Hier bin ich aktuell aber noch am Testen.
2019-02-11 12:55:57.017 | Something went wrong with 'Punkt 12 - Das RTL-Mittagsjournal', retry 1/20 in 10 seconds
2019-02-11 12:55:57.016 | FFMPEG record 'Punkt 12 - Das RTL-Mittagsjournal' ended
  1. Theoretisch sollte auch hier die kill_child_processes() getriggert und die Tickets gelöscht werden. Kann ich aber bei Gelegenheit mal testen.

danielk117 avatar Feb 11 '19 13:02 danielk117

Also auch mit virtuellen Interfaces (über ein reales Interface) klappt das wunderbar.

Einfach Interfaces erstellen:

sudo ifconfig eth1:1 192.168.170.46
sudo ifconfig eth1:2 192.168.170.47

In meinem Skript verwende ich dann die beiden neuen IPs und das klappt wunderbar. Der gesamte Traffic läuft nun ausschließlich über eth1. 😄

Anderes Thema: Kannst du mit dieser Meldung etwas anfangen?

FFMPEG could not be started. Error: 'ascii' codec can't decode byte 0xe2 in position 275: ordinal not in range(128)

Die wird ab und zu mal geworfen und dann kommt ein erfolgreicher Retry.

danielk117 avatar Feb 11 '19 14:02 danielk117

coole Sache! ^^
Ich bin gespannt, wenn das mal den Endstand erreicht hat.

Die Fehlermeldung kommt mir schon bekannt vor und sollte eig. nur kommen, wenn der Titel unzulässige Zeichen enthält, und nur unter Python 2.x

Pavion avatar Feb 11 '19 16:02 Pavion

So, da will ich mich auch mal wieder zu Wort melden... Mein Skript ist gewachsen, und ich erklär natürlich auch warum. 😉

Probleme

Es gibt einige Themen die sich schwierig gestaltet haben:

  • Man müsste nach jedem Boot die virtuellen Interfaces per ifconfig neu erzeugen.
  • Mit ifconfig wären die IPs vorgegeben und man muss vorher prüfen ob diese auch noch im Netz frei sind.
  • Bisher musste man die IPs fest in meinem Skript vorgeben.
  • Gleichzeitige Aufnahmen auf dem selben Sender belegen beide IPs, aber ein Tuner ist in meinem Fall noch frei und könnte über eine dritte IP genutzt werden (passiert wenn man 2 Sendungen hintereinander aufnimmt und diese überschneiden sich z.B. 4 Minuten).

Insgesamt alles "verbesserungswürdig"...

Was tut mein neues Skript?

  1. Exit-Händler definieren, falls Aufnahmen vorzeitig beendet werden soll.

    1. Alle Kind-Prozesse killen (z.B. ffmpeg)
    2. running-Ticket für IP löschen
    3. Den Hauptprozess killen
  2. Prüfen ob meine virtuellen Interfaces vorhanden sind.

    1. Interfaces nicht vorhanden
      1. Es erzeugt die vier virtuellen Interfaces vrt0 bis vrt3, welche allesamt über das reale Interface eth1 laufen. Alle vier haben unterschiedliche Dummy-MAC-Adressen (eigene MAC-Adressen sind notwendig, damit der Router per DCHP eigenständige IPs vergibt).
      2. Es startet für jedes Interface einen dhclient um per DHCP eine IP vom DHCP-Server zu bekommen.
    2. Interfaces vorhanden
      1. IPs auslesen.
  3. Freie IP (aus den ausgelesen vom zweiten Schritt) für Aufnahme finden anhand running-Ticket.

    1. running-Ticket für IP vorhanden, dann nächste IP prüfen.
    2. Kein running-Ticket vorhanden...
      1. Das running-Ticket schreiben
      2. Aufnahme starten
      3. Wenn Aufnahme normal beendet wird, dann wird das Ticket wieder gelöscht und der Prozess beendet.

Resultat

Ich kann also nun 4 Aufnahmen auf 2 unterschiedlich Sendern gleichzeitig durchführen. Sollte eine der 4 Aufnahme einen dritten Sender aufnahmen wollen, schlägt dieser ffmpeg-Prozess fehl und die Sendung wird nicht aufgenommen. Die anderen Prozesse laufen normal weiter. Es gilt: Wer zuerst kommt... 😄

#!/bin/sh
 
# exit or kill handler
kill_child_processes() {
    isTopmost=$1
    curPid=$2
    childPids=`ps -o pid --no-headers --ppid ${curPid}`
 
    for childPid in $childPids
    do
        echo "kill child $childPid"
        kill_child_processes 0 $childPid
    done
 
    if [ $isTopmost -eq 0 ]; then
        echo "kill parent $curPid"
        kill -9 $curPid 2> /dev/null
    fi
 
    if [ ! -z "$deletefile" ] && [ -f $deletefile ]; then
        echo "delete $deletefile"
        rm -- "$deletefile"
    fi
}
trap "kill_child_processes 1 $$; exit 0" 0
 
 
# create virtual interfaces
vrtip=$(ifconfig | awk -F"[: ]+" '/vrt/{getline;  {print $4}}')
if [ -z "$vrtip" ]; then
    echo "no vrt interfaces found"
    for i in "0" "1" "2" "3"; do
        echo "create vrt$i"
        ip link add link eth1 address 00:11:22:33:44:"$i$i" vrt"$i" type macvlan
        /usr/sbin/dhclient -4 -nw -lf /tmp/dhcpv4.leases.vrt"$i" -pf /tmp/dhcpcd-vrt"$i".pid -sf /var/run/dhclient-script vrt"$i"
    done
    sleep 10
    vrtip=$(ifconfig | awk -F"[: ]+" '/vrt/{getline;  {print $4}}')
fi
echo "vrtip: $vrtip"
 
 
# find free ip and try to record
for i in $vrtip; do
  runningfile="running_$i.run"
  if [ -f $runningfile ]; then
    echo "$runningfile exists"
  else
    echo "$runningfile not exists"
    echo "running now" >> $runningfile
 
    echo "using bind adress $i"
    deletefile="$runningfile"
    BIND_ADDR="$i" LD_PRELOAD=/usr/lib/bind.so /volume1/\@appstore/ffmpeg/bin/ffmpeg "$@"
    rm -- "$runningfile"
    unset deletefile
 
    break
  fi
done

Schlusswort

Das ist aber noch nicht die Endfassung des Skriptes. Aktuell benutze ich ein ffmpeg über eine Community-Quelle und das ist fest im Skript angegeben. Dieses Setting könnte auch als Argument an das Skript gegeben werden. Außerdem könnte man auch die Anzahl der zu erzeugenden virtuellen Interfaces als Argument verarbeiten. Es ist also immer noch Luft noch oben.

Ich hoffe man blickt einigermaßen durch.

VG Daniel

danielk117 avatar Feb 20 '19 08:02 danielk117