2009-11-15 20:47:06 +01:00
|
|
|
|
##
|
|
|
|
|
## Copyright (C) 2006 Gajim Team
|
|
|
|
|
##
|
2013-08-16 09:33:15 +02:00
|
|
|
|
## Gajim is free software; you can redistribute it and/or modify
|
2009-11-15 20:47:06 +01:00
|
|
|
|
## it under the terms of the GNU General Public License as published
|
2013-08-16 09:33:15 +02:00
|
|
|
|
## by the Free Software Foundation; version 3 only.
|
2009-11-15 20:47:06 +01:00
|
|
|
|
##
|
2013-08-16 09:33:15 +02:00
|
|
|
|
## Gajim is distributed in the hope that it will be useful,
|
2009-11-15 20:47:06 +01:00
|
|
|
|
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
2013-08-16 09:33:15 +02:00
|
|
|
|
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
2009-11-15 20:47:06 +01:00
|
|
|
|
## GNU General Public License for more details.
|
|
|
|
|
##
|
2013-08-16 09:33:15 +02:00
|
|
|
|
## You should have received a copy of the GNU General Public License
|
|
|
|
|
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
2009-11-15 20:47:06 +01:00
|
|
|
|
|
2009-11-26 12:58:12 +01:00
|
|
|
|
"""
|
|
|
|
|
Handles Jingle RTP sessions (XEP 0167)
|
|
|
|
|
"""
|
2009-11-15 20:47:06 +01:00
|
|
|
|
|
2017-02-08 03:49:33 +01:00
|
|
|
|
import logging
|
2009-12-10 22:02:03 +01:00
|
|
|
|
import socket
|
2012-12-09 21:37:51 +01:00
|
|
|
|
import nbxmpp
|
2014-03-25 21:26:57 +01:00
|
|
|
|
import gi
|
2016-12-22 17:47:53 +01:00
|
|
|
|
from gi.repository import Farstream
|
|
|
|
|
|
2014-03-25 21:26:57 +01:00
|
|
|
|
gi.require_version('Gst', '1.0')
|
|
|
|
|
from gi.repository import Gst
|
|
|
|
|
from gi.repository import GLib
|
2009-11-15 20:47:06 +01:00
|
|
|
|
|
2017-08-13 13:18:56 +02:00
|
|
|
|
from gajim.common import app
|
2009-11-29 15:39:26 +01:00
|
|
|
|
|
2017-06-13 23:58:06 +02:00
|
|
|
|
from gajim.common.jingle_transport import JingleTransportICEUDP
|
|
|
|
|
from gajim.common.jingle_content import contents, JingleContent, JingleContentSetupException
|
|
|
|
|
from gajim.common.connection_handlers_events import InformationEvent
|
|
|
|
|
from gajim.common.jingle_session import FailedApplication
|
2009-11-15 20:47:06 +01:00
|
|
|
|
|
2016-12-22 17:47:53 +01:00
|
|
|
|
from collections import deque
|
2009-11-15 20:47:06 +01:00
|
|
|
|
|
2010-03-14 22:47:54 +01:00
|
|
|
|
log = logging.getLogger('gajim.c.jingle_rtp')
|
|
|
|
|
|
|
|
|
|
|
2009-11-15 20:47:06 +01:00
|
|
|
|
class JingleRTPContent(JingleContent):
|
2010-02-08 15:08:40 +01:00
|
|
|
|
def __init__(self, session, media, transport=None):
|
|
|
|
|
if transport is None:
|
2010-08-26 13:31:29 +02:00
|
|
|
|
transport = JingleTransportICEUDP(None)
|
2010-02-08 15:08:40 +01:00
|
|
|
|
JingleContent.__init__(self, session, transport)
|
|
|
|
|
self.media = media
|
|
|
|
|
self._dtmf_running = False
|
2017-02-08 03:49:33 +01:00
|
|
|
|
self.farstream_media = {
|
|
|
|
|
'audio': Farstream.MediaType.AUDIO,
|
2014-03-25 21:26:57 +01:00
|
|
|
|
'video': Farstream.MediaType.VIDEO}[media]
|
2010-02-08 15:08:40 +01:00
|
|
|
|
|
2010-05-03 21:59:46 +02:00
|
|
|
|
self.pipeline = None
|
|
|
|
|
self.src_bin = None
|
|
|
|
|
self.stream_failed_once = False
|
|
|
|
|
|
2010-02-08 15:08:40 +01:00
|
|
|
|
self.candidates_ready = False # True when local candidates are prepared
|
|
|
|
|
|
2017-02-08 03:49:33 +01:00
|
|
|
|
# TODO
|
|
|
|
|
self.conference = None
|
|
|
|
|
self.funnel = None
|
|
|
|
|
self.p2psession = None
|
|
|
|
|
self.p2pstream = None
|
|
|
|
|
|
2010-02-08 15:08:40 +01:00
|
|
|
|
self.callbacks['session-initiate'] += [self.__on_remote_codecs]
|
|
|
|
|
self.callbacks['content-add'] += [self.__on_remote_codecs]
|
|
|
|
|
self.callbacks['description-info'] += [self.__on_remote_codecs]
|
2010-08-26 16:56:08 +02:00
|
|
|
|
self.callbacks['content-accept'] += [self.__on_remote_codecs]
|
|
|
|
|
self.callbacks['session-accept'] += [self.__on_remote_codecs]
|
2010-02-08 15:08:40 +01:00
|
|
|
|
self.callbacks['session-terminate'] += [self.__stop]
|
|
|
|
|
self.callbacks['session-terminate-sent'] += [self.__stop]
|
|
|
|
|
|
2012-04-01 19:49:52 +02:00
|
|
|
|
def setup_stream(self, on_src_pad_added):
|
2010-02-08 15:08:40 +01:00
|
|
|
|
# pipeline and bus
|
2014-03-25 21:26:57 +01:00
|
|
|
|
self.pipeline = Gst.Pipeline()
|
2010-02-08 15:08:40 +01:00
|
|
|
|
bus = self.pipeline.get_bus()
|
|
|
|
|
bus.add_signal_watch()
|
|
|
|
|
bus.connect('message', self._on_gst_message)
|
|
|
|
|
|
|
|
|
|
# conference
|
2014-03-25 21:26:57 +01:00
|
|
|
|
self.conference = Gst.ElementFactory.make('fsrtpconference', None)
|
2010-02-08 15:08:40 +01:00
|
|
|
|
self.pipeline.add(self.conference)
|
|
|
|
|
self.funnel = None
|
|
|
|
|
|
2012-04-01 19:49:52 +02:00
|
|
|
|
self.p2psession = self.conference.new_session(self.farstream_media)
|
2010-02-08 15:08:40 +01:00
|
|
|
|
|
2012-04-01 19:49:52 +02:00
|
|
|
|
participant = self.conference.new_participant()
|
2010-02-08 15:08:40 +01:00
|
|
|
|
# FIXME: Consider a workaround, here...
|
|
|
|
|
# pidgin and telepathy-gabble don't follow the XEP, and it won't work
|
|
|
|
|
# due to bad controlling-mode
|
2014-03-25 21:26:57 +01:00
|
|
|
|
|
2010-02-08 15:08:40 +01:00
|
|
|
|
params = {'controlling-mode': self.session.weinitiate, 'debug': False}
|
2017-08-13 13:18:56 +02:00
|
|
|
|
if app.config.get('use_stun_server'):
|
|
|
|
|
stun_server = app.config.get('stun_server')
|
2010-02-08 15:08:40 +01:00
|
|
|
|
if not stun_server and self.session.connection._stun_servers:
|
|
|
|
|
stun_server = self.session.connection._stun_servers[0]['host']
|
|
|
|
|
if stun_server:
|
|
|
|
|
try:
|
|
|
|
|
ip = socket.getaddrinfo(stun_server, 0, socket.AF_UNSPEC,
|
2017-02-08 03:49:33 +01:00
|
|
|
|
socket.SOCK_STREAM)[0][4][0]
|
2013-01-01 23:18:36 +01:00
|
|
|
|
except socket.gaierror as e:
|
2017-02-08 03:49:33 +01:00
|
|
|
|
log.warning('Lookup of stun ip failed: %s', str(e))
|
2010-02-08 15:08:40 +01:00
|
|
|
|
else:
|
|
|
|
|
params['stun-ip'] = ip
|
|
|
|
|
|
|
|
|
|
self.p2pstream = self.p2psession.new_stream(participant,
|
2017-02-08 03:49:33 +01:00
|
|
|
|
Farstream.StreamDirection.BOTH)
|
2012-04-01 19:49:52 +02:00
|
|
|
|
self.p2pstream.connect('src-pad-added', on_src_pad_added)
|
2014-03-25 21:26:57 +01:00
|
|
|
|
self.p2pstream.set_transmitter_ht('nice', params)
|
2010-02-08 15:08:40 +01:00
|
|
|
|
|
|
|
|
|
def is_ready(self):
|
2017-02-08 03:49:33 +01:00
|
|
|
|
return JingleContent.is_ready(self) and self.candidates_ready
|
2010-02-08 15:08:40 +01:00
|
|
|
|
|
|
|
|
|
def make_bin_from_config(self, config_key, pipeline, text):
|
2017-08-13 13:18:56 +02:00
|
|
|
|
pipeline = pipeline % app.config.get(config_key)
|
2010-02-08 15:08:40 +01:00
|
|
|
|
try:
|
2017-02-08 03:49:33 +01:00
|
|
|
|
gst_bin = Gst.parse_bin_from_description(pipeline, True)
|
|
|
|
|
return gst_bin
|
2014-03-25 21:26:57 +01:00
|
|
|
|
except GLib.GError as e:
|
2017-08-13 13:18:56 +02:00
|
|
|
|
app.nec.push_incoming_event(
|
2017-02-08 03:49:33 +01:00
|
|
|
|
InformationEvent(
|
|
|
|
|
None, conn=self.session.connection, level='error',
|
|
|
|
|
pri_txt=_('%s configuration error') % text.capitalize(),
|
|
|
|
|
sec_txt=_('Couldn’t setup %s. Check your configuration.\n\n'
|
|
|
|
|
'Pipeline was:\n%s\n\nError was:\n%s') % (text, pipeline, str(e))))
|
2010-02-08 15:08:40 +01:00
|
|
|
|
raise JingleContentSetupException
|
|
|
|
|
|
|
|
|
|
def add_remote_candidates(self, candidates):
|
|
|
|
|
JingleContent.add_remote_candidates(self, candidates)
|
|
|
|
|
# FIXME: connectivity should not be etablished yet
|
|
|
|
|
# Instead, it should be etablished after session-accept!
|
|
|
|
|
if self.sent:
|
2012-04-01 19:49:52 +02:00
|
|
|
|
self.p2pstream.add_remote_candidates(candidates)
|
2010-02-08 15:08:40 +01:00
|
|
|
|
|
|
|
|
|
def batch_dtmf(self, events):
|
|
|
|
|
"""
|
|
|
|
|
Send several DTMF tones
|
|
|
|
|
"""
|
|
|
|
|
if self._dtmf_running:
|
2010-03-23 08:31:52 +01:00
|
|
|
|
raise Exception("There is a DTMF batch already running")
|
2010-03-23 10:45:58 +01:00
|
|
|
|
events = deque(events)
|
2010-02-08 15:08:40 +01:00
|
|
|
|
self._dtmf_running = True
|
2010-03-23 10:45:58 +01:00
|
|
|
|
self._start_dtmf(events.popleft())
|
2013-07-28 20:50:30 +02:00
|
|
|
|
GLib.timeout_add(500, self._next_dtmf, events)
|
2010-02-08 15:08:40 +01:00
|
|
|
|
|
|
|
|
|
def _next_dtmf(self, events):
|
|
|
|
|
self._stop_dtmf()
|
|
|
|
|
if events:
|
2010-03-23 10:45:58 +01:00
|
|
|
|
self._start_dtmf(events.popleft())
|
2013-07-28 20:50:30 +02:00
|
|
|
|
GLib.timeout_add(500, self._next_dtmf, events)
|
2010-02-08 15:08:40 +01:00
|
|
|
|
else:
|
|
|
|
|
self._dtmf_running = False
|
|
|
|
|
|
|
|
|
|
def _start_dtmf(self, event):
|
|
|
|
|
if event in ('*', '#'):
|
2014-03-25 21:26:57 +01:00
|
|
|
|
event = {'*': Farstream.DTMFEvent.STAR,
|
2017-02-08 03:49:33 +01:00
|
|
|
|
'#': Farstream.DTMFEvent.POUND}[event]
|
2010-02-08 15:08:40 +01:00
|
|
|
|
else:
|
|
|
|
|
event = int(event)
|
2012-04-01 19:49:52 +02:00
|
|
|
|
self.p2psession.start_telephony_event(event, 2)
|
2010-02-08 15:08:40 +01:00
|
|
|
|
|
|
|
|
|
def _stop_dtmf(self):
|
2012-04-01 19:49:52 +02:00
|
|
|
|
self.p2psession.stop_telephony_event()
|
2010-02-08 15:08:40 +01:00
|
|
|
|
|
|
|
|
|
def _fill_content(self, content):
|
2012-12-09 21:37:51 +01:00
|
|
|
|
content.addChild(nbxmpp.NS_JINGLE_RTP + ' description',
|
2017-02-08 03:49:33 +01:00
|
|
|
|
attrs={'media': self.media},
|
|
|
|
|
payload=list(self.iter_codecs()))
|
2010-02-08 15:08:40 +01:00
|
|
|
|
|
|
|
|
|
def _setup_funnel(self):
|
2014-03-25 21:26:57 +01:00
|
|
|
|
self.funnel = Gst.ElementFactory.make('funnel', None)
|
2010-02-08 15:08:40 +01:00
|
|
|
|
self.pipeline.add(self.funnel)
|
|
|
|
|
self.funnel.link(self.sink)
|
2014-03-25 21:26:57 +01:00
|
|
|
|
self.sink.set_state(Gst.State.PLAYING)
|
|
|
|
|
self.funnel.set_state(Gst.State.PLAYING)
|
2010-02-08 15:08:40 +01:00
|
|
|
|
|
|
|
|
|
def _on_src_pad_added(self, stream, pad, codec):
|
|
|
|
|
if not self.funnel:
|
|
|
|
|
self._setup_funnel()
|
2014-03-25 21:26:57 +01:00
|
|
|
|
pad.link(self.funnel.get_request_pad('sink_%u'))
|
2010-02-08 15:08:40 +01:00
|
|
|
|
|
|
|
|
|
def _on_gst_message(self, bus, message):
|
2014-03-25 21:26:57 +01:00
|
|
|
|
if message.type == Gst.MessageType.ELEMENT:
|
|
|
|
|
name = message.get_structure().get_name()
|
2017-02-08 03:49:33 +01:00
|
|
|
|
log.debug('gst element message: %s: %s', name, message)
|
2012-04-01 19:49:52 +02:00
|
|
|
|
if name == 'farstream-new-active-candidate-pair':
|
2010-02-08 15:08:40 +01:00
|
|
|
|
pass
|
2012-04-01 19:49:52 +02:00
|
|
|
|
elif name == 'farstream-recv-codecs-changed':
|
2010-02-08 15:08:40 +01:00
|
|
|
|
pass
|
2012-04-01 19:49:52 +02:00
|
|
|
|
elif name == 'farstream-codecs-changed':
|
2014-03-25 21:26:57 +01:00
|
|
|
|
if self.sent and self.p2psession.props.codecs_without_config:
|
2010-05-04 10:31:05 +02:00
|
|
|
|
self.send_description_info()
|
2012-12-30 16:20:57 +01:00
|
|
|
|
if self.transport.remote_candidates:
|
|
|
|
|
# those lines MUST be done after we get info on our
|
|
|
|
|
# codecs
|
|
|
|
|
self.p2pstream.add_remote_candidates(
|
|
|
|
|
self.transport.remote_candidates)
|
|
|
|
|
self.transport.remote_candidates = []
|
|
|
|
|
self.p2pstream.set_property('direction',
|
2017-02-08 03:49:33 +01:00
|
|
|
|
Farstream.StreamDirection.BOTH)
|
2012-12-30 16:20:57 +01:00
|
|
|
|
|
2012-04-01 19:49:52 +02:00
|
|
|
|
elif name == 'farstream-local-candidates-prepared':
|
2010-02-08 15:08:40 +01:00
|
|
|
|
self.candidates_ready = True
|
|
|
|
|
if self.is_ready():
|
|
|
|
|
self.session.on_session_state_changed(self)
|
2012-04-01 19:49:52 +02:00
|
|
|
|
elif name == 'farstream-new-local-candidate':
|
2015-08-21 22:04:55 +02:00
|
|
|
|
candidate = self.p2pstream.parse_new_local_candidate(message)[1]
|
2010-02-08 15:08:40 +01:00
|
|
|
|
self.transport.candidates.append(candidate)
|
2010-05-03 15:27:56 +02:00
|
|
|
|
if self.sent:
|
2010-02-08 15:08:40 +01:00
|
|
|
|
# FIXME: Is this case even possible?
|
|
|
|
|
self.send_candidate(candidate)
|
2012-04-01 19:49:52 +02:00
|
|
|
|
elif name == 'farstream-component-state-changed':
|
2014-03-25 21:26:57 +01:00
|
|
|
|
state = message.get_structure().get_value('state')
|
|
|
|
|
if state == Farstream.StreamState.FAILED:
|
2012-12-09 21:37:51 +01:00
|
|
|
|
reason = nbxmpp.Node('reason')
|
2010-02-08 15:08:40 +01:00
|
|
|
|
reason.setTag('failed-transport')
|
2010-05-04 10:04:53 +02:00
|
|
|
|
self.session.remove_content(self.creator, self.name, reason)
|
2012-04-01 19:49:52 +02:00
|
|
|
|
elif name == 'farstream-error':
|
2017-02-08 03:49:33 +01:00
|
|
|
|
log.error('Farstream error #%d!\nMessage: %s',
|
|
|
|
|
message.get_structure().get_value('error-no'),
|
|
|
|
|
message.get_structure().get_value('error-msg'))
|
2014-03-25 21:26:57 +01:00
|
|
|
|
elif message.type == Gst.MessageType.ERROR:
|
2010-05-03 21:59:46 +02:00
|
|
|
|
# TODO: Fix it to fallback to videotestsrc anytime an error occur,
|
|
|
|
|
# or raise an error, Jingle way
|
|
|
|
|
# or maybe one-sided stream?
|
|
|
|
|
if not self.stream_failed_once:
|
2017-08-13 13:18:56 +02:00
|
|
|
|
app.nec.push_incoming_event(
|
2017-02-08 03:49:33 +01:00
|
|
|
|
InformationEvent(
|
|
|
|
|
None, conn=self.session.connection, level='error',
|
|
|
|
|
pri_txt=_('GStreamer error'),
|
|
|
|
|
sec_txt=_('Error: %s\nDebug: %s' %
|
|
|
|
|
(message.get_structure().get_value('gerror'),
|
|
|
|
|
message.get_structure().get_value('debug')))))
|
2010-05-03 21:59:46 +02:00
|
|
|
|
|
|
|
|
|
sink_pad = self.p2psession.get_property('sink-pad')
|
|
|
|
|
|
|
|
|
|
# Remove old source
|
2014-03-25 21:26:57 +01:00
|
|
|
|
self.src_bin.get_static_pad('src').unlink(sink_pad)
|
|
|
|
|
self.src_bin.set_state(Gst.State.NULL)
|
2010-05-03 21:59:46 +02:00
|
|
|
|
self.pipeline.remove(self.src_bin)
|
|
|
|
|
|
|
|
|
|
if not self.stream_failed_once:
|
|
|
|
|
# Add fallback source
|
|
|
|
|
self.src_bin = self.get_fallback_src()
|
|
|
|
|
self.pipeline.add(self.src_bin)
|
2015-12-23 22:40:53 +01:00
|
|
|
|
self.src_bin.link(sink_pad)
|
2010-05-03 21:59:46 +02:00
|
|
|
|
self.stream_failed_once = True
|
|
|
|
|
else:
|
2012-12-09 21:37:51 +01:00
|
|
|
|
reason = nbxmpp.Node('reason')
|
2010-05-03 21:59:46 +02:00
|
|
|
|
reason.setTag('failed-application')
|
2010-05-04 10:04:53 +02:00
|
|
|
|
self.session.remove_content(self.creator, self.name, reason)
|
2010-05-03 21:59:46 +02:00
|
|
|
|
|
|
|
|
|
# Start playing again
|
2014-03-25 21:26:57 +01:00
|
|
|
|
self.pipeline.set_state(Gst.State.PLAYING)
|
2010-05-03 21:59:46 +02:00
|
|
|
|
|
2017-02-08 03:49:33 +01:00
|
|
|
|
@staticmethod
|
|
|
|
|
def get_fallback_src():
|
2014-03-25 21:26:57 +01:00
|
|
|
|
return Gst.ElementFactory.make('fakesrc', None)
|
2010-02-08 15:08:40 +01:00
|
|
|
|
|
2010-08-26 16:56:08 +02:00
|
|
|
|
def on_negotiated(self):
|
2010-02-08 15:08:40 +01:00
|
|
|
|
if self.accepted:
|
2012-12-30 16:20:57 +01:00
|
|
|
|
if self.p2psession.get_property('codecs'):
|
|
|
|
|
# those lines MUST be done after we get info on our codecs
|
|
|
|
|
if self.transport.remote_candidates:
|
|
|
|
|
self.p2pstream.add_remote_candidates(
|
|
|
|
|
self.transport.remote_candidates)
|
|
|
|
|
self.transport.remote_candidates = []
|
2014-03-25 21:26:57 +01:00
|
|
|
|
# TODO: Farstream.StreamDirection.BOTH only if senders='both'
|
|
|
|
|
# self.p2pstream.set_property('direction',
|
|
|
|
|
# Farstream.StreamDirection.BOTH)
|
2010-08-26 16:56:08 +02:00
|
|
|
|
JingleContent.on_negotiated(self)
|
2010-02-08 15:08:40 +01:00
|
|
|
|
|
|
|
|
|
def __on_remote_codecs(self, stanza, content, error, action):
|
|
|
|
|
"""
|
|
|
|
|
Get peer codecs from what we get from peer
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
codecs = []
|
|
|
|
|
for codec in content.getTag('description').iterTags('payload-type'):
|
2010-09-23 13:05:33 +02:00
|
|
|
|
if not codec['id'] or not codec['name'] or not codec['clockrate']:
|
|
|
|
|
# ignore invalid payload-types
|
|
|
|
|
continue
|
2014-03-25 21:26:57 +01:00
|
|
|
|
c = Farstream.Codec.new(int(codec['id']), codec['name'],
|
2017-02-08 03:49:33 +01:00
|
|
|
|
self.farstream_media, int(codec['clockrate']))
|
2010-02-08 15:08:40 +01:00
|
|
|
|
if 'channels' in codec:
|
|
|
|
|
c.channels = int(codec['channels'])
|
|
|
|
|
else:
|
|
|
|
|
c.channels = 1
|
2014-03-25 21:26:57 +01:00
|
|
|
|
for p in codec.iterTags('parameter'):
|
|
|
|
|
c.add_optional_parameter(p['name'], str(p['value']))
|
2010-02-08 15:08:40 +01:00
|
|
|
|
codecs.append(c)
|
|
|
|
|
|
|
|
|
|
if codecs:
|
2016-01-03 22:26:15 +01:00
|
|
|
|
try:
|
|
|
|
|
self.p2pstream.set_remote_codecs(codecs)
|
|
|
|
|
except GLib.Error:
|
|
|
|
|
raise FailedApplication
|
2010-02-08 15:08:40 +01:00
|
|
|
|
|
|
|
|
|
def iter_codecs(self):
|
2014-03-25 21:26:57 +01:00
|
|
|
|
codecs = self.p2psession.props.codecs_without_config
|
2010-02-08 15:08:40 +01:00
|
|
|
|
for codec in codecs:
|
2017-02-08 03:49:33 +01:00
|
|
|
|
attrs = {
|
|
|
|
|
'name': codec.encoding_name,
|
2014-03-25 21:26:57 +01:00
|
|
|
|
'id': codec.id,
|
2017-02-08 03:49:33 +01:00
|
|
|
|
'channels': codec.channels
|
|
|
|
|
}
|
2010-02-08 15:08:40 +01:00
|
|
|
|
if codec.clock_rate:
|
|
|
|
|
attrs['clockrate'] = codec.clock_rate
|
|
|
|
|
if codec.optional_params:
|
2017-02-08 03:49:33 +01:00
|
|
|
|
payload = [nbxmpp.Node('parameter',
|
|
|
|
|
{'name': p.name, 'value': p.value})
|
|
|
|
|
for p in codec.optional_params]
|
2010-02-08 15:08:40 +01:00
|
|
|
|
else:
|
2016-01-03 20:44:03 +01:00
|
|
|
|
payload = []
|
2012-12-09 21:37:51 +01:00
|
|
|
|
yield nbxmpp.Node('payload-type', attrs, payload)
|
2010-02-08 15:08:40 +01:00
|
|
|
|
|
|
|
|
|
def __stop(self, *things):
|
2014-03-25 21:26:57 +01:00
|
|
|
|
self.pipeline.set_state(Gst.State.NULL)
|
2010-02-08 15:08:40 +01:00
|
|
|
|
|
|
|
|
|
def __del__(self):
|
|
|
|
|
self.__stop()
|
|
|
|
|
|
|
|
|
|
def destroy(self):
|
|
|
|
|
JingleContent.destroy(self)
|
|
|
|
|
self.p2pstream.disconnect_by_func(self._on_src_pad_added)
|
|
|
|
|
self.pipeline.get_bus().disconnect_by_func(self._on_gst_message)
|
2009-11-15 20:47:06 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class JingleAudio(JingleRTPContent):
|
2010-02-08 15:08:40 +01:00
|
|
|
|
"""
|
|
|
|
|
Jingle VoIP sessions consist of audio content transported over an ICE UDP
|
|
|
|
|
protocol
|
|
|
|
|
"""
|
2009-11-26 12:58:12 +01:00
|
|
|
|
|
2010-02-08 15:08:40 +01:00
|
|
|
|
def __init__(self, session, transport=None):
|
|
|
|
|
JingleRTPContent.__init__(self, session, 'audio', transport)
|
|
|
|
|
self.setup_stream()
|
2009-11-15 20:47:06 +01:00
|
|
|
|
|
2010-02-08 15:08:40 +01:00
|
|
|
|
def set_mic_volume(self, vol):
|
|
|
|
|
"""
|
|
|
|
|
vol must be between 0 ans 1
|
|
|
|
|
"""
|
|
|
|
|
self.mic_volume.set_property('volume', vol)
|
2009-12-02 21:37:21 +01:00
|
|
|
|
|
2010-02-08 15:08:40 +01:00
|
|
|
|
def set_out_volume(self, vol):
|
|
|
|
|
"""
|
|
|
|
|
vol must be between 0 ans 1
|
|
|
|
|
"""
|
|
|
|
|
self.out_volume.set_property('volume', vol)
|
2010-01-10 16:31:00 +01:00
|
|
|
|
|
2010-02-08 15:08:40 +01:00
|
|
|
|
def setup_stream(self):
|
2012-04-01 19:49:52 +02:00
|
|
|
|
JingleRTPContent.setup_stream(self, self._on_src_pad_added)
|
2009-11-15 20:47:06 +01:00
|
|
|
|
|
2010-02-08 15:08:40 +01:00
|
|
|
|
# Configure SPEEX
|
|
|
|
|
# Workaround for psi (not needed since rev
|
|
|
|
|
# 147aedcea39b43402fe64c533d1866a25449888a):
|
|
|
|
|
# place 16kHz before 8kHz, as buggy psi versions will take in
|
|
|
|
|
# account only the first codec
|
2009-11-15 20:47:06 +01:00
|
|
|
|
|
2017-02-08 03:49:33 +01:00
|
|
|
|
codecs = [
|
|
|
|
|
Farstream.Codec.new(Farstream.CODEC_ID_ANY, 'SPEEX',
|
|
|
|
|
Farstream.MediaType.AUDIO, 16000),
|
2014-03-25 21:26:57 +01:00
|
|
|
|
Farstream.Codec.new(Farstream.CODEC_ID_ANY, 'SPEEX',
|
2017-02-08 03:49:33 +01:00
|
|
|
|
Farstream.MediaType.AUDIO, 8000)]
|
2010-02-08 15:08:40 +01:00
|
|
|
|
self.p2psession.set_codec_preferences(codecs)
|
2009-11-15 20:47:06 +01:00
|
|
|
|
|
2010-02-08 15:08:40 +01:00
|
|
|
|
# the local parts
|
|
|
|
|
# TODO: Add queues?
|
2010-05-03 21:59:46 +02:00
|
|
|
|
self.src_bin = self.make_bin_from_config('audio_input_device',
|
2017-02-08 03:49:33 +01:00
|
|
|
|
'%s ! audioconvert',
|
|
|
|
|
_("audio input"))
|
2009-11-29 15:39:26 +01:00
|
|
|
|
|
2010-02-08 15:08:40 +01:00
|
|
|
|
self.sink = self.make_bin_from_config('audio_output_device',
|
2017-02-08 03:49:33 +01:00
|
|
|
|
'audioconvert ! volume name=gajim_out_vol ! %s',
|
|
|
|
|
_("audio output"))
|
2009-11-29 15:39:26 +01:00
|
|
|
|
|
2010-05-03 21:59:46 +02:00
|
|
|
|
self.mic_volume = self.src_bin.get_by_name('gajim_vol')
|
2010-02-08 15:08:40 +01:00
|
|
|
|
self.out_volume = self.sink.get_by_name('gajim_out_vol')
|
2009-11-15 20:47:06 +01:00
|
|
|
|
|
2010-02-08 15:08:40 +01:00
|
|
|
|
# link gst elements
|
2014-03-25 21:26:57 +01:00
|
|
|
|
self.pipeline.add(self.sink)
|
|
|
|
|
self.pipeline.add(self.src_bin)
|
2009-11-15 20:47:06 +01:00
|
|
|
|
|
2014-03-25 21:26:57 +01:00
|
|
|
|
self.src_bin.get_static_pad('src').link(self.p2psession.get_property(
|
|
|
|
|
'sink-pad'))
|
2009-11-15 20:47:06 +01:00
|
|
|
|
|
2012-04-01 19:49:52 +02:00
|
|
|
|
# The following is needed for farstream to process ICE requests:
|
2014-03-25 21:26:57 +01:00
|
|
|
|
self.pipeline.set_state(Gst.State.PLAYING)
|
2009-11-15 20:47:06 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class JingleVideo(JingleRTPContent):
|
2015-08-21 22:04:55 +02:00
|
|
|
|
def __init__(self, session, transport=None, in_xid=0, out_xid=0):
|
2010-02-08 15:08:40 +01:00
|
|
|
|
JingleRTPContent.__init__(self, session, 'video', transport)
|
2015-08-21 22:04:55 +02:00
|
|
|
|
self.in_xid = in_xid
|
|
|
|
|
self.out_xid = out_xid
|
|
|
|
|
self.out_xid_set = False
|
2010-02-08 15:08:40 +01:00
|
|
|
|
self.setup_stream()
|
2009-11-15 20:47:06 +01:00
|
|
|
|
|
2010-02-08 15:08:40 +01:00
|
|
|
|
def setup_stream(self):
|
|
|
|
|
# TODO: Everything is not working properly:
|
|
|
|
|
# sometimes, one window won't show up,
|
|
|
|
|
# sometimes it'll freeze...
|
2012-04-01 19:49:52 +02:00
|
|
|
|
JingleRTPContent.setup_stream(self, self._on_src_pad_added)
|
2015-08-21 22:04:55 +02:00
|
|
|
|
bus = self.pipeline.get_bus()
|
2015-12-23 22:40:53 +01:00
|
|
|
|
bus.enable_sync_message_emission()
|
2015-08-21 22:04:55 +02:00
|
|
|
|
bus.connect('sync-message::element', self._on_sync_message)
|
2009-11-29 15:39:26 +01:00
|
|
|
|
|
2010-02-08 15:08:40 +01:00
|
|
|
|
# the local parts
|
2017-08-13 13:18:56 +02:00
|
|
|
|
if app.config.get('video_framerate'):
|
2014-03-25 21:26:57 +01:00
|
|
|
|
framerate = 'videorate ! video/x-raw,framerate=%s ! ' % \
|
2017-08-13 13:18:56 +02:00
|
|
|
|
app.config.get('video_framerate')
|
2010-06-19 15:44:10 +02:00
|
|
|
|
else:
|
|
|
|
|
framerate = ''
|
|
|
|
|
try:
|
2017-08-13 13:18:56 +02:00
|
|
|
|
w, h = app.config.get('video_size').split('x')
|
2010-06-19 15:44:10 +02:00
|
|
|
|
except:
|
|
|
|
|
w = h = None
|
|
|
|
|
if w and h:
|
2014-03-25 21:26:57 +01:00
|
|
|
|
video_size = 'video/x-raw,width=%s,height=%s ! ' % (w, h)
|
2010-06-19 15:44:10 +02:00
|
|
|
|
else:
|
|
|
|
|
video_size = ''
|
2017-08-13 13:18:56 +02:00
|
|
|
|
if app.config.get('video_see_self'):
|
2015-08-21 22:04:55 +02:00
|
|
|
|
tee = '! tee name=t ! queue ! videoscale ! ' + \
|
|
|
|
|
'video/x-raw,width=160,height=120 ! videoconvert ! ' + \
|
2017-08-13 13:18:56 +02:00
|
|
|
|
'%s t. ! queue ' % app.config.get(
|
2015-08-21 22:04:55 +02:00
|
|
|
|
'video_output_device')
|
|
|
|
|
else:
|
|
|
|
|
tee = ''
|
|
|
|
|
|
2010-05-03 21:59:46 +02:00
|
|
|
|
self.src_bin = self.make_bin_from_config('video_input_device',
|
2017-02-08 03:49:33 +01:00
|
|
|
|
'%%s %s! %svideoscale ! %svideoconvert' %
|
|
|
|
|
(tee, framerate, video_size),
|
|
|
|
|
_("video input"))
|
2009-11-15 20:47:06 +01:00
|
|
|
|
|
2015-12-23 22:40:53 +01:00
|
|
|
|
self.pipeline.add(self.src_bin)
|
2015-08-21 22:04:55 +02:00
|
|
|
|
self.pipeline.set_state(Gst.State.PLAYING)
|
2009-11-15 20:47:06 +01:00
|
|
|
|
|
2010-02-08 15:08:40 +01:00
|
|
|
|
self.sink = self.make_bin_from_config('video_output_device',
|
2017-02-08 03:49:33 +01:00
|
|
|
|
'videoscale ! videoconvert ! %s',
|
|
|
|
|
_("video output"))
|
2015-12-23 22:40:53 +01:00
|
|
|
|
|
2010-02-08 15:08:40 +01:00
|
|
|
|
self.pipeline.add(self.sink)
|
2009-11-15 20:47:06 +01:00
|
|
|
|
|
2014-03-25 21:26:57 +01:00
|
|
|
|
self.src_bin.get_static_pad('src').link(self.p2psession.get_property(
|
2011-04-06 21:56:42 +02:00
|
|
|
|
'sink-pad'))
|
2009-11-15 20:47:06 +01:00
|
|
|
|
|
2012-04-01 19:49:52 +02:00
|
|
|
|
# The following is needed for farstream to process ICE requests:
|
2014-03-25 21:26:57 +01:00
|
|
|
|
self.pipeline.set_state(Gst.State.PLAYING)
|
2009-11-15 20:47:06 +01:00
|
|
|
|
|
2015-08-21 22:04:55 +02:00
|
|
|
|
def _on_sync_message(self, bus, message):
|
2015-12-23 22:40:53 +01:00
|
|
|
|
if message.get_structure() is None:
|
2015-08-21 22:04:55 +02:00
|
|
|
|
return False
|
2015-12-23 22:40:53 +01:00
|
|
|
|
if message.get_structure().get_name() == 'prepare-window-handle':
|
2015-08-21 22:04:55 +02:00
|
|
|
|
message.src.set_property('force-aspect-ratio', True)
|
|
|
|
|
imagesink = message.src
|
2017-08-13 13:18:56 +02:00
|
|
|
|
if app.config.get('video_see_self') and not self.out_xid_set:
|
2015-12-23 22:40:53 +01:00
|
|
|
|
imagesink.set_window_handle(self.out_xid)
|
2015-08-21 22:04:55 +02:00
|
|
|
|
self.out_xid_set = True
|
|
|
|
|
else:
|
2015-12-23 22:40:53 +01:00
|
|
|
|
imagesink.set_window_handle(self.in_xid)
|
2015-08-21 22:04:55 +02:00
|
|
|
|
|
2010-05-03 21:59:46 +02:00
|
|
|
|
def get_fallback_src(self):
|
|
|
|
|
# TODO: Use avatar?
|
2014-03-25 21:26:57 +01:00
|
|
|
|
pipeline = 'videotestsrc is-live=true ! video/x-raw,framerate=10/1 ! videoconvert'
|
|
|
|
|
return Gst.parse_bin_from_description(pipeline, True)
|
2009-11-15 20:47:06 +01:00
|
|
|
|
|
2015-08-21 22:04:55 +02:00
|
|
|
|
def destroy(self):
|
|
|
|
|
JingleRTPContent.destroy(self)
|
|
|
|
|
self.pipeline.get_bus().disconnect_by_func(self._on_sync_message)
|
|
|
|
|
|
2009-11-15 20:47:06 +01:00
|
|
|
|
def get_content(desc):
|
2010-02-08 15:08:40 +01:00
|
|
|
|
if desc['media'] == 'audio':
|
|
|
|
|
return JingleAudio
|
|
|
|
|
elif desc['media'] == 'video':
|
|
|
|
|
return JingleVideo
|
2009-11-15 20:47:06 +01:00
|
|
|
|
|
2012-12-09 21:37:51 +01:00
|
|
|
|
contents[nbxmpp.NS_JINGLE_RTP] = get_content
|