Hallo,
ich habe ein Problem, zu dem es (nach meinem Anfaengerhaften Wissensstand) zu viele moegliche Ansaetze gibt und ich nicht weiss, welcher der einfachste/effizienteste ist.
Mein Ziel: Wiedergabe von Internetradio via mpg321.
mpg321 ist ein Kommandozeilenplayer unter Linux. Leider kommt er nicht selbst mit HTTP-Streams klar (zum Beispiel somaFM: http://64.236.34.97:80/stream/1018 ), wohl aber kann er Daten, die ueber STDIN eintrudeln, wiedergeben.
Auf der Kommanozeile fuehrt somit ein
wget -qO - http://64.236.34.97:80/stream/1018 | mpg321 -
zu einem Teilerfolg. Teilerfolg deshalb, weil die Daten nicht gepuffert werden und es deshalb oft zu kurzen Aussetzern kommt.
Python benutze ich, weil ich schon das gesamte Rahmenprogramm (inkl. Jukebox fuer lokale MP3s und LCD-Ansteuerung) damit geschrieben habe und es gut funktioniert.
Der Ablauf muesste m.E. so werden:
(1) initialisiere Puffer mit 1/2MB
(2) subprocess: lese HTTP-Stream in Puffer ein
(3) wenn Puffer zu 80% gefuellt:
(4) while Puffer nicht leer:
(4.1) starte 'Pufferinhalt | mpg321 -'
(4.2) gib den Pufferstatus an LCD aus
(5) stop HTTP stream and kill subprocess
Wo kann ich ansetzen?
Sollte ich fuer das Lesen des HTTP Streams des externe wget benutzen oder python-interne Libs benutzen?
Wie kann ich den Puffer realisieren?
Danke,
Blumi
Ansatz gesucht zum gepufferten Pipelining eines HTTP-Streams
- sunmountain
- User
- Beiträge: 89
- Registriert: Montag 13. März 2006, 17:18
Nimm streamripper.
Der druckt die Namen der gerade laufenden Songs auf stdout
aus, speichert die auf Wunsch und kann den Stream als "Proxy" weitergeben.
http://streamripper.sf.net
Vielleicht ist es aber am einfachsten, ein kleines Kommandozeilenpufferprogramm zu schreiben:
wget -qO - http://64.236.34.97:80/stream/1018 | puffer.py | mpg321 -
Der druckt die Namen der gerade laufenden Songs auf stdout
aus, speichert die auf Wunsch und kann den Stream als "Proxy" weitergeben.
http://streamripper.sf.net
Vielleicht ist es aber am einfachsten, ein kleines Kommandozeilenpufferprogramm zu schreiben:
wget -qO - http://64.236.34.97:80/stream/1018 | puffer.py | mpg321 -
@sunmountain: Gute Idee. Das Aufnehmen von Internetradio hatte ich sowieso als "FutureFeature" angedacht, was ich auch per streamripper erledigen wollte.
Ansonsten - hast Du eine Idee, wie "puffer.py" aussehen koennte? os.mkfifo() oder asynchat.fifo()-class oder ...
Blumi
Ansonsten - hast Du eine Idee, wie "puffer.py" aussehen koennte? os.mkfifo() oder asynchat.fifo()-class oder ...
Blumi
Ich würde so einen Puffer mit einem Thread und einer Queue lösen. Die entsprechenden Module sind `threading` und `Queue`. Ungetestet:
Code: Alles auswählen
from __future__ import division
from Queue import Queue
from threading import Thread
from time import sleep
class BufferedReader(object):
def __init__(self, file_obj, max_blocks=1024, blocksize=1024):
self.file_obj = file_obj
self.max_blocks = max_blocks
self.blocksize = blocksize
self.blocks = Queue(max_blocks)
self.eof_reached = False
self.preload_phase = True
Thread(target=self._reader).start()
def _reader(self):
while True:
data = self.file_obj.read(self.blocksize)
self.blocks.put(data)
if not data:
self.eof_reached = True
break
def _get_fill_level(self):
return self.max_blocks / len(self.blocks)
fill_level = property(_get_fill_level)
def get_block(self):
if self.preload_phase:
while not (self.fill_level > 0.8 or self.eof_reached):
sleep(0.01)
self.preload_phase = False
return self.blocks.get()
@BlackJack: Repekt, das ist doch mal ein Ansatz, tausend dank! Ich werde mich also mal in Queue einlesen.
Was hattest Du als Quelle fuer file_obj gedacht? Eine Pipe vom externen wget oder urllib2.urlopen('http://xxx')?
Blumi
Was hattest Du als Quelle fuer file_obj gedacht? Eine Pipe vom externen wget oder urllib2.urlopen('http://xxx')?
Blumi
Irgendein Objekt mit einer `read()`-Methode. Wenn `urlopen()` wie gewünscht funktioniert, spart man sich eine externe Abhängigkeit.blumi hat geschrieben:Was hattest Du als Quelle fuer file_obj gedacht? Eine Pipe vom externen wget oder urllib2.urlopen('http://xxx')?
kurzer Zwischenbericht:
der Ansatz von BlackJack war an sich nicht schlecht - nach ein paar Umstellungen an seinem Code (len(self.blocks) ging z.B. nicht), lief er.
Eingebunden sah es ungefaehr so aus:
Der Ansatz war gut. Allerdings gab es mit der blocksize bei BufferedReader, beim popen und mpg321 sowie der korrekten sleep-Dauer zu viele Variablen dabei, um den Datenstrom allgemeingueltig fluessig zu bekommen. Das gab teilweise lustige Effekte -- bei der Wiedergabe von Sekunde 1 bis 10 kam etwa Sekunde 1,2,3,4,2,5,6,7,8,6,9,10 an. Das lag wahrscheinlich an Pufferueberlaeufen.
Dann probierte ich, lediglich auf die in popen eingebauten Puffer zu setzen:
Dieser Code steht fast 1:1 so in der [url=ttp://docs.python.org/lib/node536.html]Python Doc[/url]. Das Ergebnis war in Anbetracht der Einfachheit des Codes erstaunlich gut, aber oben genannter Effekt war immer noch zu bemerken.
Nach einem kurzen Ausflug zu dem Ansatz "Hintergrundprozess mit streamripper, der einen lokalen HTTP-Relay erstellt, welcher daraufhin mit wget geholt und an mpg321 weitergereicht wird", endete ich bei der Ansteuerung von XMMS, die absolut komplikationslos verlief. Dennoch bin ich nicht ganz gluecklich damit, weil ich eigentlich unabh. vom Windowmanager bleiben wollte.
Nebenbei sei bemerkt, dass XMMS bei der Wiedergabe nur 1%-3% Prozessorlast erzeugt, waehrend mpg321 (das Kommandozeilentool) satte 15%-20% verbrennt(*). Wow. Aber das hat nichts mit Python zu tun.
Wenn ich das komplette Programm rund gemacht habe, werde ich es im Forum vorstellen.
Bis dann,
Blumi
(*) CPU: VIA C3 mit 1GHz
der Ansatz von BlackJack war an sich nicht schlecht - nach ein paar Umstellungen an seinem Code (len(self.blocks) ging z.B. nicht), lief er.
Eingebunden sah es ungefaehr so aus:
Code: Alles auswählen
conn = urllib2.urlopen('http://xxx:8000')
br = BufferedReader(conn, max_blocks=128, blocksize=2048)
r,w,e = popen2.popen3('mpg321 -', bufsize=4096, mode='b')
while not br.eof:
bl = br.get_block()
if (bl == None):
print('BUFFERING')
else:
print('PLAYING')
w.write(bl)
sleep(0.1)
for fd in (conn, r, w, e):
fd.close()
Dann probierte ich, lediglich auf die in popen eingebauten Puffer zu setzen:
Code: Alles auswählen
p1 = subprocess.Popen(["wget", "-qO", "-", "http://xxx:8000"], bufsize=-1, stdout=subprocess.PIPE)
sleep(5)
p2 = subprocess.Popen(["mpg321", "-"], bufsize=-1, stdin=p1.stdout)
output = p2.communicate()[0]
Nach einem kurzen Ausflug zu dem Ansatz "Hintergrundprozess mit streamripper, der einen lokalen HTTP-Relay erstellt, welcher daraufhin mit wget geholt und an mpg321 weitergereicht wird", endete ich bei der Ansteuerung von XMMS, die absolut komplikationslos verlief. Dennoch bin ich nicht ganz gluecklich damit, weil ich eigentlich unabh. vom Windowmanager bleiben wollte.
Nebenbei sei bemerkt, dass XMMS bei der Wiedergabe nur 1%-3% Prozessorlast erzeugt, waehrend mpg321 (das Kommandozeilentool) satte 15%-20% verbrennt(*). Wow. Aber das hat nichts mit Python zu tun.
Wenn ich das komplette Programm rund gemacht habe, werde ich es im Forum vorstellen.
Bis dann,
Blumi
(*) CPU: VIA C3 mit 1GHz