blog · git · desktop · images · contact & privacy · gopher


TV-Streaming via SSH und mencoder

Schon seit längerem nutze ich eine kleine Box mit Arch und einer alten WinTV PCI als Videorecorder (zu der Scriptsammlung dafür vielleicht nochmal ein extra Posting), aber ein direktes Streaming hatte ich bis jetzt noch nicht. Warum das also nichtmal probieren? :)

Da ich bei dieser einen Box die Taktik "never touch a running system" verfolge - läuft ja auch wunderbar und bei den häufigen Arch-Updates will ich da nicht so sehr viel riskieren -, ist das System schon "etwas älter". Das heißt, zusätzliche Software aus den Repos installieren, könnte etwas kritisch werden und sollte daher vermieden werden. Zur Verfügung standen eigentlich nur: SSH, xawtv/v4lctl und mencoder. Und siehe da: Das reicht sogar!

– Nachtrag: Mit an Bord ist jetzt auch Timeshift.

Das Grundprinzip ist eine einfache Pipe:

ssh user@host <irgendein Befehl> | <zielProzessAufLokalemRechner>

Das schubst stdout des entfernten Prozesses auf's stdin des lokalen Zielprozesses. Hier greift dann wieder die unglaubliche Flexibilität, welche einem von Konsolentools geboten wird: Man verkupple stdout des mencoders auf dem Fernsehrechner mit stdin eines mplayers auf dem lokalen Rechner - das war's.

Ein paar Sachen sollte man aber noch beachten:

Ein kleines Python-Script hab' ich aber noch drübergestülpt, denn irgendwie will ich auch komfortabel Kanäle auswählen können. Dummerweise hatte ich mich damals für ein unpraktisches "INI"-Format für die Kanalliste entschieden, das diesem Schema folgt:

...
[ZDF]
channel = E6

[ARD]
channel = E7

[3sat]
channel = E12
...

Das kommt aus "scantv ..." heraus und war damals das einfachste, ist aber recht friemelig zum Auswerten. Naja. Für's Lesen dieser Liste kommt wieder dasselbe Prinzip zum Einsatz wie beim Streaming selbst: "cat channels" und eine Pipe zum lokalen Python-Script.

– Nachtrag: Das folgende Script ist jetzt etwas erweitert, sodass einerseits im laufenden Betrieb Kanalwechsel geht und andererseits Timeshift. Für letzteres wird der Output vom mencoder einfach in eine Datei geleitet, die dann irgendwann und irgendwie abgespielt werden kann, bei Bedarf aber auch "live". Pausieren ist auch drin. Natürlich braucht das einiges an Plattenplatz, aber das dürfte heutzutage ja nicht mehr so das Problem sein.

Für den Kanalwechsel ist es wichtig, dass eine ~/.xawtv am Fernsehrechner existiert, in der die richtige Frequenztabelle und Norm eingestellt wird (sonst setzt er die Frequenz immer auf 0.0):

[global]
freqtab = europe-west

[defaults]
input = Television
norm = PAL

Zuguterletzt das Python-Script:

#!/usr/bin/python
# -*- coding: utf8 -*-

import subprocess
import sys


SSH = 'ssh -i ~/.ssh/key-nur-fuer-video video@192.168.13.37'
WIDTH = '768'
HEIGHT = '576'

AUDIO_ENC = '-oac mp3lame -lameopts cbr:br=192 -af volume=6'
VIDEO_ENC = '-ovc lavc -lavcopts vcodec=mjpeg:vqscale=2:autoaspect -vf scale,pp=lb'

LOCAL_CACHE = '-cache 8192 -cache-min 99 -framedrop'
LOCAL_OPTS  = '-softvol -demuxer lavf'

TSFILE = '~/timeshift.avi'


# Aufruf:
#        tv.py [-s | -t | -p] [channel]
#
# Was soll erledigt werden, exklusive Optionen:
#   -t  Timeshift, d.h. sende den Output nach <TSFILE>
#   -p  Spiele das Timeshift-Video ab
#   -s  Nur Kanalwechsel
#
# Der Kanal selbst kann dabei als letztes angegeben werden. Wird er
# ausgelassen, dann erscheint ein Menü zur Auswahl.
# Wird -s, -t oder -p nicht angegeben, wird direkt ein mplayer gestartet,
# der direkt mit den TV-Daten gefüttert wird.

cmd = 0
if (len(sys.argv) >= 2 and sys.argv[1] == "-t"):

    cmd = 1
    sys.argv.pop(1)
elif (len(sys.argv) >= 2 and sys.argv[1] == "-s"):
    cmd = 2
    sys.argv.pop(1)
elif (len(sys.argv) >= 2 and sys.argv[1] == "-p"):
    # Timeshift abspielen. cat nötig, um kontinuierlich abspielen zu können.
    subprocess.call('cat ' + TSFILE + ' | mplayer ' + LOCAL_CACHE + ' ' + LOCAL_OPTS + ' -', shell = True)
    sys.exit(0)


# Jetzt weiter: Kanal als Parameter gegeben oder auswählen?

if (len(sys.argv) >= 2):
    channel = sys.argv[1]
else:
    # Hole Liste der Kanäle vom Server und baue eine Choice-Line draus.
    # Erwartet Liste, die z.B. mit
    #    $ scantv -n PAL -f europe-west -o ~/channels
    # erzeugt wurde.
    toggle  = 0
    choices = ''

    p = subprocess.Popen(SSH + ' cat channels',
            shell = True,
            stdout = subprocess.PIPE).stdout

    while True:
        line = p.readline()
        if not line: break
        line = line.strip()
        if line:
            if (line[0] == '[' and line != '[global]' and line != '[defaults]'):
                toggle = 1
                lastdesc = line[1:-1]
            elif (toggle == 1):
                toggle = 0
                choices += '"' + line[10:] + '" "' + lastdesc + '" '

    # Dialog anzeigen, Auswahl von stderr lesen.
    p = subprocess.Popen('dialog --menu "Kanal auswählen" 0 0 0 ' + choices,
            shell = True,
            stderr = subprocess.PIPE)

    retval = p.wait()

    if (retval == 0):
        channel = p.stderr.read()
    else:
        sys.exit(1)


# Befehle bauen und absetzen

recordercmd = SSH + ' mencoder -really-quiet tv:// -tv driver=v4l2:device=/dev/video0:input=0:outfmt=yuy2:fps=25:alsa:amode=1:forcechan=2:chanlist=europe-west:channel=' + channel + ':width=' + WIDTH + ':height=' + HEIGHT + ' ' + AUDIO_ENC + ' ' + VIDEO_ENC + ' -o -'

if (cmd == 0):
    # Player und Streaming starten.
    pushcmd = recordercmd + ' | mplayer - ' + LOCAL_CACHE + ' ' + LOCAL_OPTS

elif (cmd == 1):
    # Timeshift, also Ausgabe in Datei umleiten
    pushcmd = recordercmd + ' > ' + TSFILE

elif (cmd == 2):
    # Nur Befehl absetzen für Kanalwechsel
    pushcmd = SSH + ' v4lctl setchannel ' + channel

# Okay, mach das jetzt.
subprocess.call(pushcmd, shell = True)

Gegebenenfalls muss die Cachegröße oder Kompression noch erhöht oder die Auflösung verringert werden, denn stellenweise läuft das arg ans Limit ran - und abbrechen soll es ja nicht. ;) Vielleicht finde ich da auch noch bessere Settings, denn der arme Athlon XP 2200+ läuft bei 80-90% Last.

– Kurzer Edit: Scheint jetzt durch "-cache-min" und "-demuxer lavf" besser geworden sein. "Cache in use" ist jetzt großteils um 50%, was laut Manpage der Normalwert ist.