Commit 10004907 authored by Cyril Deguet's avatar Cyril Deguet
Browse files

* libvlc binding for python

* new VideoLAN RTSP Server, using libvlc.
  Not yet fully rfc2326-compliant, but it works ;)
parent e1522ec4
all:
python setup.py build
install:
python setup.py install
clean:
rm -Rf build
from distutils.core import setup, Extension
FFMPEG_DIR = '/home/cyril/ffmpeg'
vlc = Extension('vlc',
sources = ['vlcmodule.c'],
libraries = ['vlc', 'rt', 'dl' , 'pthread', 'ffmpeg', 'm',
'avcodec'],
library_dirs = ['../lib', '../modules/codec/ffmpeg',
FFMPEG_DIR + '/libavcodec'])
setup (name = 'PackageName',
version = '1.0',
description = 'This is a demo package',
ext_modules = [vlc])
#include <Python.h>
#include <vlc/vlc.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
static PyObject *vlc_create(PyObject *self, PyObject *args)
{
int iRc;
iRc = VLC_Create();
return Py_BuildValue("i", iRc);
}
static PyObject *vlc_init(PyObject *self, PyObject *args)
{
int iVlc;
char *file;
char *pArgv[] = { "vlc", "--sout", NULL };
int iRc;
if (!PyArg_ParseTuple(args, "iss", &iVlc, &file, &pArgv[2]))
return NULL;
iRc = VLC_Init(iVlc, 3, pArgv);
if (iRc >= 0)
iRc = VLC_AddTarget(iVlc, file, PLAYLIST_APPEND, PLAYLIST_END);
return Py_BuildValue("i", iRc);
}
static PyObject *vlc_play(PyObject *self, PyObject *args)
{
int iVlc;
int iRc;
if (!PyArg_ParseTuple(args, "i", &iVlc))
return NULL;
iRc = VLC_Play(iVlc);
return Py_BuildValue("i", iRc);
}
static PyObject *vlc_stop(PyObject *self, PyObject *args)
{
int iVlc;
int iRc;
if (!PyArg_ParseTuple(args, "i", &iVlc))
return NULL;
iRc = VLC_Stop(iVlc);
return Py_BuildValue("i", iRc);
}
static PyObject *vlc_pause(PyObject *self, PyObject *args)
{
int iVlc;
int iRc;
if (!PyArg_ParseTuple(args, "i", &iVlc))
return NULL;
iRc = VLC_Pause(iVlc);
return Py_BuildValue("i", iRc);
}
static PyMethodDef VlcMethods[] = {
{"create", vlc_create, METH_VARARGS, "Create a vlc thread."},
{"init", vlc_init, METH_VARARGS, "Initialize a vlc thread."},
{"play", vlc_play, METH_VARARGS, "Play"},
{"stop", vlc_stop, METH_VARARGS, "Stop"},
{"pause", vlc_pause, METH_VARARGS, "Pause"},
{NULL, NULL, 0, NULL} /* Sentinel */
};
void initvlc(void)
{
Py_InitModule("vlc", VlcMethods);
}
# Nice kludge to share global variables ;-)
pass
#!/usr/bin/python -O
#
# VideoLAN RTSP Server
#
# Author: Cyril Deguet <asmax@via.ecp.fr>
import cfg, string, threading
class PlayList:
"Contains the media playlist"
def __init__(self):
self.lock = threading.Lock()
def readConfig(self, filename):
"Read the playlist file"
f = open(filename)
newList = {}
while 1:
line = string.strip(f.readline())
if line == "":
break
items = string.split(line, '\t')
newList[items[0]] = {'file':items[1], 'name':items[2], 'addr':items[3]}
self.lock.acquire()
self.list = newList
self.lock.release()
def getMedia(self, uri):
"Return the description of an item in the playlist"
self.lock.acquire()
if self.list.has_key(uri):
media = self.list[uri]
else:
media = None
self.lock.release()
return media
#!/usr/bin/python
#
# VideoLAN RTSP Server
#
# Author: Cyril Deguet <asmax@via.ecp.fr>
#
# See: RFC 2326 Real Time Streaming Protocol
# RFC 2327 Session Description Protocol
import cfg, mimetools, re, socket, time, SocketServer, string, sys
def ntpTime():
"return the current time in NTP decimal format"
return "%d" % (int(time.time()) + 2208988800L)
class SdpMessage:
"Build a SDP message"
uri = "http://www.videolan.org"
def __init__(self, sessionName, address, uri):
"Build the message"
self.sessionName = sessionName
self.address = address
self.uri = uri
def getMessage(self):
"Return the SDP message"
msg = "v=0\r\n" + \
"o=asmax " + ntpTime() + " " + ntpTime() + \
" IN IP4 sphinx.via.ecp.fr\r\n" + \
"s=" + self.sessionName + "\r\n" + \
"u=" + self.uri + "\r\n" + \
"t=0 0\r\n" + \
"c=IN IP4 " + self.address + "/1\r\n" + \
"m=video 1234 RTP/MP2T 33\r\n" + \
"a=control:" + self.uri + "\r\n"
return msg
class RtspServerHandler(SocketServer.StreamRequestHandler):
"Request handler of the server socket"
version = "RTSP/1.0"
ok = "200 OK"
badRequest = "400 Bad Request"
uriNotFound = "404 Not found"
sessionNotFound = "454 Session Not Found"
invalidHeader = "456 Header Field Not Valid for Resource"
internalError = "500 Internal Server Error"
notImplemented = "501 Not Implemented"
def error(self, message, cseq):
self.wfile.write(self.version + " " + message + "\r\n" + \
"Cseq: " + cseq + "\r\n" + \
"\r\n")
def parseHeader(self, header):
"Split a RTCP header into a mapping of parameters"
list = map(string.strip, re.split('[; \n]*', header, re.S))
result = {}
for item in list:
m = re.match('([^=]*)(?:=(.*))?', item)
if m is None:
return None
result[m.group(1)] = m.group(2)
return result
def optionsMethod(self):
"Handle an OPTION request"
response = "Public: OPTIONS, DESCRIBE, SETUP, PLAY, PAUSE, PING, TEARDOWN\r\n" + \
"\r\n"
return response
def pingMethod(self, msg):
"Handle an PING request"
cseq = msg.getheader('cseq')
id = msg.getheader('Session')
if id is None:
self.error(self.badRequest, cseq)
return
response = "Session: " + id + "\r\n" + \
"\r\n"
return response
def describeMethod(self, msg, uri):
"Handle a DESCRIBE request"
cseq = msg.getheader('cseq')
# Find the URI in the playlist
media = cfg.playlist.getMedia(uri)
if media is None:
self.error(self.uriNotFound, cseq)
return None
message = SdpMessage(media['name'], media['addr'], uri)
description = message.getMessage()
size = `len(description)`
response = "Content-Type: application/sdp\r\n" + \
"Content-Length: " + size + "\r\n" + \
"\r\n" + description
return response
def setupMethod(self, msg, uri):
"Handle a SETUP request"
cseq = msg.getheader('cseq')
# Find the URI in the playlist
media = cfg.playlist.getMedia(uri)
if media is None:
self.error(self.uriNotFound, cseq)
return None
transportHeader = msg.getheader('transport')
if transportHeader is None:
self.error(self.badRequest, cseq)
return None
transport = self.parseHeader(transportHeader)
# Check the multicast/unicast fields in the headers
if transport.has_key('multicast'):
type = "multicast"
elif transport.has_key('unicast'):
type = "unicast"
else:
self.error(self.invalidHeader, cseq)
return None
# Check the destination field in the headers
dest= None
if transport.has_key('destination'):
dest = transport['destination']
if dest is None:
dest = media['addr'] # default destination address
id = cfg.sessionList.newSession(uri, dest)
if id is None:
self.error(self.internalError, cseq)
return None
response = "Session: " + id + "\r\n" + \
"Transport: RTP/MP2T/UDP;" + type + ";destination=" + dest + "\r\n" + \
"\r\n"
return response
def playMethod(self, msg, uri):
"Handle a PLAY request"
cseq = msg.getheader('cseq')
# Find the URI in the playlist
media = cfg.playlist.getMedia(uri)
if media is None:
self.error(self.uriNotFound, cseq)
return None
id = msg.getheader('Session')
session = cfg.sessionList.getSession(id)
if session is None:
self.error(self.sessionNotFound, cseq)
return None
if session.play() < 0:
self.error(self.internalError, cseq)
return None
response = "Session: " + id + "\r\n" + \
"\r\n"
return response
def pauseMethod(self, msg, uri):
"Handle a PAUSE request"
cseq = msg.getheader('cseq')
# Find the URI in the playlist
media = cfg.playlist.getMedia(uri)
if media is None:
self.error(self.uriNotFound, cseq)
return None
id = msg.getheader('Session')
session = cfg.sessionList.getSession(id)
if session is None:
self.error(self.sessionNotFound, cseq)
return None
if session.pause() < 0:
self.error(self.internalError, cseq)
return None
response = "Session: " + id + "\r\n" + \
"\r\n"
return response
def teardownMethod(self, msg, uri):
"Handle a TEARDOWN request"
cseq = msg.getheader('cseq')
# Find the URI in the playlist
media = cfg.playlist.getMedia(uri)
if media is None:
self.error(self.uriNotFound, cseq)
return None
id = msg.getheader('Session')
session = cfg.sessionList.getSession(id)
if session is None:
self.error(self.sessionNotFound, cseq)
return None
if session.stop() < 0:
self.error(self.internalError, cseq)
return None
if cfg.sessionList.delSession(id) < 0:
self.error(self.internalError, cseq)
return None
response = "\r\n"
return response
def parseRequest(self):
"Parse a RSTP request"
requestLine = self.rfile.readline()
m = re.match("(?P<method>[A-Z]+) (?P<uri>(\*|(?:(?P<protocol>rtsp|rtspu)://" + \
"(?P<host>[^:/]*)(:(?P<port>\d*))?(?P<path>.*)))) " + \
"RTSP/(?P<major>\d)\.(?P<minor>\d)", requestLine)
if m is None:
self.error(self.badRequest, "0")
return
uri = m.group('uri')
# Get the message headers
msg = mimetools.Message(self.rfile, "0")
cseq = msg.getheader('CSeq')
if cseq is None:
self.error(self.badRequest, "0")
return
method = m.group('method')
if method == 'OPTIONS':
response = self.optionsMethod()
elif method == 'DESCRIBE':
response = self.describeMethod(msg, uri)
elif method == 'SETUP':
response = self.setupMethod(msg, uri)
elif method == 'PLAY':
response = self.playMethod(msg, uri)
elif method == 'PAUSE':
response = self.pauseMethod(msg, uri)
elif method == 'PING':
response = self.pingMethod(msg)
elif method == 'TEARDOWN':
response = self.teardownMethod(msg, uri)
else:
self.error(self.notImplemented, cseq)
return
# Send the response
if response is None:
return
else:
self.wfile.write(self.version + " " + self.ok + "\r\n" + \
"CSeq: " + cseq + "\r\n" + \
response)
def handle(self):
"Handle an incoming request"
while 1:
try:
self.parseRequest()
except IOError:
return
#!/usr/bin/python -O
#
# VideoLAN RTSP Server
#
# Author: Cyril Deguet <asmax@via.ecp.fr>
import cfg, random, time
from streamer import VlcError, VlcStreamer
class Session:
"RTSP Session"
def __init__(self, id, uri, dest):
self.id = id
self.uri = uri
self.dest = dest
self.state = 'ready'
media = cfg.playlist.getMedia(self.uri)
self.fileName = media['file']
address = "rtp/ts://" + dest
self.streamer = VlcStreamer(self.fileName, address)
def play(self):
"Play this session"
if self.state == 'playing':
print "Session " + self.id + " (" + self.fileName + "): already playing"
return 0
self.state = 'playing'
print "Session " + self.id + " (" + self.fileName + "): play"
try:
self.streamer.play()
except VlcError:
print "Streamer: play failed"
return -1
return 0
def pause(self):
"Pause this session"
print "Session " + self.id + " (" + self.fileName + "): pause"
self.state = 'ready'
try:
self.streamer.pause()
except VlcError:
print "Streamer: pause failed"
return -1
return 0
def stop(self):
"Stop this session"
print "Session " + self.id + " (" + self.fileName + "): stop"
try:
self.streamer.stop()
except VlcError:
print "Streamer: stop failed"
return -1
return 0
class SessionList:
"Manages RTSP sessions"
list = {}
chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
def __init__(self):
self.rand = random.Random(time.time())
def newSessionId(self):
"Build a random session id"
id = ""
for x in range(12):
id += self.chars[self.rand.randrange(0, len(self.chars), 1)]
return id
def newSession(self, uri, dest):
"Create a new RTSP session"
id = self.newSessionId()
while self.list.has_key(id):
id = self.newSessionId()
try:
session = Session(id, uri, dest)
except VlcError:
print "Streamer: creation failed"
return None
self.list[id] = session
print "New session: " + id
return id
def getSession(self, id):
"Get a session from its session id"
if self.list.has_key(id):
return self.list[id]
else:
return None
def delSession(self, id):
"Delete a session"
if self.list.has_key(id):
del self.list[id]
return 0
else:
return -1
#!/usr/bin/python -O
#
# VideoLAN RTSP Server
#
# Author: Cyril Deguet <asmax@via.ecp.fr>
import cfg, vlc
class VlcError(Exception):
"Exception class for libvlc calls"
pass
class VlcStreamer:
"Manage a streamer with libvlc"
def __init__(self, file, address):
"Create the streamer"
self.file = file
self.address = address
self.id = vlc.create()
if self.id < 0:
raise VlcError
if vlc.init(self.id, self.file, self.address) < 0:
raise VlcError
def play(self):
"Play the stream"
if vlc.play(self.id) < 0:
raise VlcError
def stop(self):
"Stop the stream"
if vlc.stop(self.id) < 0:
raise VlcError
def pause(self):
"Pause the stream"
if vlc.pause(self.id) < 0:
raise VlcError
#!/usr/bin/python -O
#
# VideoLAN RTSP Server
#
# Author: Cyril Deguet <asmax@via.ecp.fr>
import cfg, SocketServer, string, sys
from playlist import PlayList
from rtsp import RtspServerHandler
from session import SessionList
PORT = 1554
if len(sys.argv) == 1:
print "usage: vlrs <playlist>\n"
sys.exit()
cfg.playlist = PlayList()
cfg.playlist.readConfig(sys.argv[1])
cfg.sessionList = SessionList()
rtspServer = SocketServer.TCPServer(('', PORT), RtspServerHandler)
try:
rtspServer.serve_forever()
except KeyboardInterrupt:
rtspServer.server_close()