Hier soir, je n’arrivais pas à dormir… J’ai donc décidé de regarder la Freebox sur mon portable, en Wifi depuis mon lit…
Le problème en Wifi, c’est qu’il y a beaucoup trop de paquets perdus… et comme le flux est en UDP, ils ne sont pas retransmis… Donc, ça coupe tout le temps et on a toujours plein de gros carrés.
Je me suis donc mis en tête d’écrire un petit proxy RTSP vers HTTP. Mon postulat de départ est simple. En HTTP, grâce a une liaison TCP, la qualité devrait être meilleur, vu que les paquets perdus sont retransmis (en contrepartie, il faut un buffer pour pouvoir attendre que tout les paquet soient arrivé et dans l’ordre avant de les jouer, mais ça c’est le player qui s’en occupe…). D’autre part, le fait de disposer d’un flux http devrais permettre de lire le flux avec des players qui ne gèrent pas le RTSP (comme QuikTime Player ou des périphériques dédié). Enfin, je me suis dit que l’écriture d’un truc comme ça devait être simple est marrante…
J’ai donc commencé à essayer de le faire en C… Puis je me suis rappelé le nombre de lignes de code qu’il faut aligner avant de pouvoir mettre en place un socket, et aussi que la gestion des string était inexistante… J’ai donc décidé d’essayer d’écrire ça en python… Comme je viens de me mettre à ce langage, je me suis dit que ça pouvait être un bon exercice…
L’algorithme est plutôt simple :
- On met en place un socket serveur pour attendre une connexion HTTP de la part du client
- On récupère l’url demandé par le client, et on lui renvois le header de réponse
- On se connecte à la Freebox, et on demande a jouer le flux demandé (via l’url)
- On met en place un socket UDP prêt à recevoir le flux de la Freebox
- Et enfin, on répète toutes les données envoyées sur le port UDP vers le client
Voilà le code que ça donne (Télécharger) :
import socket import sys class server_socket(socket.socket): def __init__(self, arg): if not type(arg)==tuple: self.socket=arg else: self.socket=socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.socket.bind(arg) self.socket.listen(1) def accept(self): s, addr=self.socket.accept() return server_socket(s), addr def readline(self): """receive and return a complete line""" line="" while 1: char = self.recv(1) if not char: if line=="": return False return line if char==" ": return line if char!="
": line += char def __getattr__(self, name): return getattr(self.socket, name) class client_socket(server_socket): def __init__(self, ip, port): self.socket=socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.socket.connect((ip,port)) class basic_rtsp_socket(client_socket): def __init__(self, ip, port): client_socket.__init__(ip, port) self.cseq=1 def request(self, request): print " ".join([">>> %s" % x for x in request.split(" ")]) self.send(request % self.cseq) self.cseq+=1 l=self.readline() print "<<< %s" % l (proto, code)=l.split(" ")[0:2] if proto[0:4]!="RTSP": raise "Error: not a rtsp response", l if code!="200": raise "Error: rtsp error", l l=self.readline() print "<<< %s" % l header={} while l!="" and l!=None: l2=l.split(": ") if len(l2)!=2: raise "Incorrect response", l header[l2[0]]=l2[1] l=self.readline() print "<<< %s" % l if l==None: raise "rtsp connection closed by foreign host", None data=None if "Content-length" in header.keys(): data=read(int(header["Content-length"])) return (header, data) http_listen=server_socket(("", 8082)) http_conn, addr = http_listen.accept() print 'Connected by', addr data = http_conn.readline() if not data: raise "no data on http line", None (request, url, proto)=data.split(" ")[0:3] if request!="GET": raise "Error : not a get request", data if proto[0:4]!="HTTP": raise "Error : not a http request", data l=http_conn.readline() print "<<< %s" %l while l!="" and l!=None: l=http_conn.readline() print "<<< %s" % l if l==None: raise "http connection closed", None http_conn.send("""HTTP/1.0 200 OK Content-type: application/octet-stream Cache-Control: no-cache """) rtsp_session=client_socket("mafreebox.freebox.fr", 554) print "connected to the rtsp session" (header, data)=rtsp_session.request("""OPTIONS rtsp://mafreebox.freebox.fr/freeboxtv%s RTSP/1.0 CSeq: %s User-Agent: rtsp2http v0.0.1 """ % (url, "%d")) (header, data)=rtsp_session.request("""SETUP rtsp://212.27.38.253/freeboxtv%s RTSP/1.0 CSeq: %s Transport: RTP/AVP;unicast;client_port=1662-1663 User-Agent: rtsp2http v0.0.1 """ % (url, "%d")) session=header["Session"] rtsp_data = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) rtsp_data.bind(("", 1662)) (header, data)=rtsp_session.request("""PLAY rtsp://mafreebox.freebox.fr/freeboxtv%s RTSP/1.0 CSeq: %s Session: %s Range: npt=0.000- User-Agent: rtsp2http v0.0.1 """ % (url, "%d", session)) ok=True while ok: data, addr=rtsp_data.recvfrom(1024) if addr[0]==socket.gethostbyname("mafreebox.freebox.fr"): try: http_conn.sendall(data) except: ok=False rtsp_session.request("""TEARDOWN rtsp://mafreebox.freebox.fr/freeboxtv%s RTSP/1.0 CSeq: %s Session: %s User-Agent: rtsp2http v0.0.1 """ % (url, "%d", session)) rtsp_session.close() rtsp_data.close() http_listen.close() http_conn.close()
Pour l’instant, VLC arrive à se connecter, le proxy se connecte à la Freebox et commence à répéter le flux… mais rien a ne s’affiche à l’écran…
Bon il était déjà 2h30 et maintenant, j’avais vraiment envie de dormir… Mission réussie… Je suis aller me coucher 😉
J’ai deux piste à creuser pour essayer de faire marcher ça : Vérifier que toutes les données sont bien retransmises (problème de taille de buffer, ou de vitesse (paquets dropé) par exemple), ou vérifier si je peux bien renvoyer le flux brute de cette façon, et s’il ne faut pas le convertir et el remettre en forme…
Affaire à suivre 😉
Update 12/02/2006 : J’ai débugé le script.
Update 21/02/2006 : Quelques modifications.
Commentaires récents