1
# -*- coding: utf-8 -*-
3
# papyon - a python client library for Msn
5
# Copyright (C) 2009 Collabora Ltd.
7
# This program is free software; you can redistribute it and/or modify
8
# it under the terms of the GNU General Public License as published by
9
# the Free Software Foundation; either version 2 of the License, or
10
# (at your option) any later version.
12
# This program is distributed in the hope that it will be useful,
13
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU General Public License for more details.
17
# You should have received a copy of the GNU General Public License
18
# along with this program; if not, write to the Free Software
19
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21
from papyon.media import *
22
from papyon.event.media import *
32
logger = logging.getLogger("papylib.conference")
34
codecs_definitions = {
36
(114, "x-msrta", farsight.MEDIA_TYPE_AUDIO, 16000),
37
(111, "SIREN", farsight.MEDIA_TYPE_AUDIO, 16000),
38
(112, "G7221", farsight.MEDIA_TYPE_AUDIO, 16000),
39
(115, "x-msrta", farsight.MEDIA_TYPE_AUDIO, 8000),
40
(116, "SIREN", farsight.MEDIA_TYPE_AUDIO, 8000),
41
(4, "G723", farsight.MEDIA_TYPE_AUDIO, 8000),
42
(8, "PCMA", farsight.MEDIA_TYPE_AUDIO, 8000),
43
(0, "PCMU", farsight.MEDIA_TYPE_AUDIO, 8000),
44
(97, "RED", farsight.MEDIA_TYPE_AUDIO, 8000),
45
(101, "telephone-event", farsight.MEDIA_TYPE_AUDIO, 8000)
48
(121, "x-rtvc1", farsight.MEDIA_TYPE_VIDEO, 90000),
49
(34, "H263", farsight.MEDIA_TYPE_VIDEO, 90000)
55
farsight.CANDIDATE_TYPE_HOST : "host",
56
farsight.CANDIDATE_TYPE_SRFLX : "srflx",
57
farsight.CANDIDATE_TYPE_PRFLX : "prflx",
58
farsight.CANDIDATE_TYPE_RELAY : "relay"
62
farsight.NETWORK_PROTOCOL_TCP : "TCP",
63
farsight.NETWORK_PROTOCOL_UDP : "UDP"
67
farsight.MEDIA_TYPE_AUDIO : "audio",
68
farsight.MEDIA_TYPE_VIDEO : "video"
72
"audio" : farsight.MEDIA_TYPE_AUDIO,
73
"video" : farsight.MEDIA_TYPE_VIDEO
77
class Conference(gobject.GObject):
80
gobject.GObject.__init__(self)
82
def set_source(self, source):
86
class MediaSessionHandler(MediaSessionEventInterface):
88
def __init__(self, session, surface_buddy=None, surface_self=None):
89
MediaSessionEventInterface.__init__(self, session)
90
self.surface_buddy = surface_buddy
91
self.surface_self = surface_self
93
self._conference = None
96
for stream in session.streams:
97
self.on_stream_added(stream)
100
self._pipeline = gst.Pipeline()
101
bus = self._pipeline.get_bus()
102
bus.add_signal_watch()
103
bus.connect("message", self.on_bus_message)
104
if self._session.type is MediaSessionType.WEBCAM_RECV:
105
name = "fsmsncamrecvconference"
106
elif self._session.type is MediaSessionType.WEBCAM_SEND:
107
name = "fsmsncamsendconference"
109
name = "fsrtpconference"
110
self._conference = gst.element_factory_make(name)
111
self._participant = self._conference.new_participant("")
112
self._pipeline.add(self._conference)
113
self._pipeline.set_state(gst.STATE_PLAYING)
114
#FIXME Create FsElementAddedNotifier
116
def on_stream_added(self, stream):
117
logger.debug("Stream \"%s\" added" % stream.name)
118
handler = MediaStreamHandler(stream, self.surface_buddy, self.surface_self)
119
handler.setup(self._conference, self._pipeline, self._participant,
121
self._handlers.append(handler)
122
if self._session.type is MediaSessionType.WEBCAM_RECV or\
123
self._session.type is MediaSessionType.WEBCAM_SEND:
124
stream.set_local_codecs([])
126
def on_bus_message(self, bus, msg):
128
if msg.type == gst.MESSAGE_ELEMENT:
130
if s.has_name("prepare-xwindow-id"):
131
# Sets buddy's xid in our nice window.
132
msg.src.set_xwindow_id(self.surface_buddy)
133
if s.has_name("farsight-error"):
134
logger.error("Farsight error : %s" % s['error-msg'])
135
if s.has_name("farsight-codecs-changed"):
136
logger.debug("Farsight codecs changed")
138
ready = s["session"].get_property("codecs-ready")
140
codecs = s["session"].get_property("codecs")
141
name = media_names[s["session"].get_property("media-type")]
142
stream = self._session.get_stream(name)
143
stream.set_local_codecs(convert_fs_codecs(codecs))
144
if s.has_name("farsight-new-local-candidate"):
145
logger.debug("New local candidate")
147
name = media_names[s["stream"].get_property("session").get_property("media-type")]
148
candidate = convert_fs_candidate(s["candidate"])
149
stream = self._session.get_stream(name)
150
stream.new_local_candidate(candidate)
151
if s.has_name("farsight-local-candidates-prepared"):
152
logger.debug("Local candidates are prepared")
154
type = s["stream"].get_property("session").get_property("media-type")
155
name = media_names[type]
156
stream = self._session.get_stream(name)
157
stream.local_candidates_prepared()
158
if s.has_name("farsight-new-active-candidate-pair"):
159
logger.debug("New active candidate pair")
161
type = s["stream"].get_property("session").get_property("media-type")
162
name = media_names[type]
163
stream = self._session.get_stream(name)
164
local = s["local-candidate"]
165
remote = s["remote-candidate"]
166
local = convert_fs_candidate(local)
167
remote = convert_fs_candidate(remote)
168
stream.new_active_candidate_pair(local, remote)
172
class MediaStreamHandler(MediaStreamEventInterface):
174
def __init__(self, stream, surface_buddy=None, surface_self=None):
175
MediaStreamEventInterface.__init__(self, stream)
177
self.surface_buddy = surface_buddy
178
self.surface_self = surface_self
180
def setup(self, conference, pipeline, participant, type):
182
for r in self._stream.relays:
183
relay = gst.Structure("relay")
184
relay.set_value("username", r.username)
185
relay.set_value("password", r.password)
186
relay.set_value("ip", r.ip)
187
relay.set_value("port", r.port, "uint")
190
if type in (MediaSessionType.SIP, MediaSessionType.TUNNELED_SIP):
191
if type is MediaSessionType.TUNNELED_SIP:
192
compatibility_mode = 3
194
compatibility_mode = 2
195
params = {"stun-ip" : "64.14.48.28", "stun-port" : 3478,
196
"compatibility-mode" : compatibility_mode,
197
"controlling-mode": self._stream.created_locally,
198
"relay-info": relays}
201
media_type = media_types[self._stream.name]
202
self.fssession = conference.new_session(media_type)
203
self.fssession.set_codec_preferences(build_codecs(self._stream.name))
204
self.fsstream = self.fssession.new_stream(participant,
205
self._stream.direction, "nice", params)
206
self.fsstream.connect("src-pad-added", self.on_src_pad_added, pipeline)
207
source = make_source(self._stream.name)
210
def on_src_message(bus, msg):
211
if msg.structure is None:
213
if msg.structure.get_name() == "prepare-xwindow-id":
214
msg.src.set_xwindow_id(self.surface_self)
217
#bus = pipeline.get_bus()
218
#bus.add_signal_watch()
219
#bus.enable_sync_message_emission()
220
#bus.connect("sync-message::element", on_src_message)
222
source.get_pad("src").link(self.fssession.get_property("sink-pad"))
223
pipeline.set_state(gst.STATE_PLAYING)
225
def on_stream_closed(self):
228
def on_remote_candidates_received(self, candidates):
229
candidates = filter(lambda x: x.transport == "UDP", candidates)
230
candidates = convert_media_candidates(candidates)
231
self.fsstream.set_remote_candidates(candidates)
233
def on_remote_codecs_received(self, codecs):
234
codecs = convert_media_codecs(codecs, self._stream.name)
235
self.fsstream.set_remote_codecs(codecs)
237
def on_src_pad_added(self, stream, pad, codec, pipeline):
238
sink = make_sink(self._stream.name)
240
sink.set_state(gst.STATE_PLAYING)
241
pad.link(sink.get_pad("sink"))
244
# Farsight utility functions
246
def create_notifier(pipeline, filename):
247
notifier = farsight.ElementAddedNotifier()
248
notifier.add(pipeline)
249
notifier.set_properties_from_file(filename)
252
def convert_fs_candidate(fscandidate):
253
candidate = MediaCandidate()
254
candidate.ip = fscandidate.ip
255
candidate.port = fscandidate.port
256
candidate.foundation = fscandidate.foundation
257
candidate.component_id = fscandidate.component_id
258
candidate.transport = protos[fscandidate.proto]
259
candidate.priority = int(fscandidate.priority)
260
candidate.username = fscandidate.username
261
candidate.password = fscandidate.password
262
candidate.type = types[fscandidate.type]
263
candidate.base_ip = fscandidate.base_ip
264
candidate.base_port = fscandidate.base_port
267
def convert_media_candidates(candidates):
269
for candidate in candidates:
270
proto = farsight.NETWORK_PROTOCOL_TCP
271
if candidate.transport == "UDP":
272
proto = farsight.NETWORK_PROTOCOL_UDP
274
for k,v in types.iteritems():
275
if v == candidate.type:
277
fscandidate = farsight.Candidate()
278
fscandidate.foundation = candidate.foundation
279
fscandidate.ip = candidate.ip
280
fscandidate.port = candidate.port
281
fscandidate.component_id = candidate.component_id
282
fscandidate.proto = proto
283
fscandidate.type = type
284
fscandidate.username = candidate.username
285
fscandidate.password = candidate.password
286
fscandidate.priority = int(candidate.priority)
287
fscandidates.append(fscandidate)
290
def build_codecs(type):
292
for args in codecs_definitions[type]:
293
codec = farsight.Codec(*args)
297
def convert_fs_codecs(fscodecs):
299
for fscodec in fscodecs:
301
codec.payload = fscodec.id
302
codec.encoding = fscodec.encoding_name
303
codec.clockrate = fscodec.clock_rate
304
codec.params = dict(fscodec.optional_params)
308
def convert_media_codecs(codecs, name):
310
media_type = media_types[name]
312
fscodec = farsight.Codec(
317
fscodec.optional_params = codec.params.items()
318
fscodecs.append(fscodec)
322
# GStreamer utility functions
324
def make_source(media_name):
325
func = globals()["make_%s_source" % media_name]
328
def make_sink(media_name):
329
func = globals()["make_%s_sink" % media_name]
332
# TODO: FIXME: Make this work, and then make a nice gui for configuration.
333
def make_audio_source(name="pulsesrc"): #was: "audiotestsrc"
334
element = gst.element_factory_make(name)
335
#element.set_property("is-live", True)
338
def make_audio_sink(async=False):
339
return gst.element_factory_make("autoaudiosink")
341
# TODO: FIXME: Make this work, and then make a nice gui for configuration.
342
def make_video_source(name="v4l2src"):
343
"Make a bin with a video source in it, defaulting to first webcamera "
344
bin = gst.Bin("videosrc")
345
src = gst.element_factory_make(name, name)
347
filter = gst.element_factory_make("capsfilter")
348
filter.set_property("caps", gst.Caps("video/x-raw-yuv , width=[300,1000] , height=[200,500], framerate=[20/1,100/1]"))
351
videoscale = gst.element_factory_make("videoscale")
353
filter.link(videoscale)
354
bin.add_pad(gst.GhostPad("src", videoscale.get_pad("src")))
357
def make_video_sink(async=False):
358
"Make a bin with a video sink in it, that will be displayed on xid."
359
bin = gst.Bin("videosink")
360
sink = gst.element_factory_make("autovideosink", "videosink")
361
#sink.set_property("sync", async)
363
colorspace = gst.element_factory_make("ffmpegcolorspace")
365
videoscale = gst.element_factory_make("videoscale")
367
videoscale.link(colorspace)
368
colorspace.link(sink)
369
bin.add_pad(gst.GhostPad("sink", videoscale.get_pad("sink")))