2
* Copyright (C) 2009 Barracuda Networks, Inc.
4
* This program is free software: you can redistribute it and/or modify
5
* it under the terms of the GNU General Public License as published by
6
* the Free Software Foundation, either version 2 of the License, or
7
* (at your option) any later version.
9
* This program is distributed in the hope that it will be useful,
10
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
* GNU General Public License for more details.
14
* You should have received a copy of the GNU General Public License
15
* along with this program. If not, see <http://www.gnu.org/licenses/>.
23
#include <QCoreApplication>
28
#include "jinglertp.h"
29
#include "../psimedia/psimedia.h"
30
#include "applicationinfo.h"
31
#include "psiaccount.h"
39
QString audioOutDeviceId, audioInDeviceId, videoInDeviceId;
42
PsiMedia::AudioParams audioParams;
43
PsiMedia::VideoParams videoParams;
56
// get default settings
57
static Configuration getDefaultConfiguration()
60
config.liveInput = true;
61
config.loopFile = true;
65
static Configuration *g_config = 0;
67
static void ensureConfig()
71
g_config = new Configuration;
72
*g_config = getDefaultConfiguration();
76
#ifdef GSTPROVIDER_STATIC
77
Q_IMPORT_PLUGIN(gstprovider)
80
#ifndef GSTPROVIDER_STATIC
81
static QString findPlugin(const QString &relpath, const QString &basename)
83
QDir dir(QCoreApplication::applicationDirPath());
86
foreach(const QString &fileName, dir.entryList())
88
if(fileName.contains(basename))
90
QString filePath = dir.filePath(fileName);
91
if(QLibrary::isLibrary(filePath))
99
static bool g_loaded = false;
101
static void ensureLoaded()
105
#ifndef GSTPROVIDER_STATIC
107
QString resourcePath;
109
pluginFile = qgetenv("PSI_MEDIA_PLUGIN");
110
if(pluginFile.isEmpty())
112
#if defined(Q_OS_WIN)
113
pluginFile = findPlugin(".", "gstprovider");
114
resourcePath = QCoreApplication::applicationDirPath() + "/gstreamer-0.10";
115
#elif defined(Q_OS_MAC)
116
pluginFile = findPlugin("../Plugins", "gstprovider");
117
resourcePath = QCoreApplication::applicationDirPath() + "/../Frameworks/gstreamer-0.10";
119
pluginFile = findPlugin(ApplicationInfo::libDir() + "/plugins", "gstprovider");
123
PsiMedia::PluginResult r = PsiMedia::loadPlugin(pluginFile, resourcePath);
124
if(r == PsiMedia::PluginSuccess)
134
static JingleRtpPayloadType payloadInfoToPayloadType(const PsiMedia::PayloadInfo &pi)
136
JingleRtpPayloadType out;
138
out.name = pi.name();
139
out.clockrate = pi.clockrate();
140
out.channels = pi.channels();
141
out.ptime = pi.ptime();
142
out.maxptime = pi.maxptime();
143
foreach(const PsiMedia::PayloadInfo::Parameter &pip, pi.parameters())
145
JingleRtpPayloadType::Parameter ptp;
147
ptp.value = pip.value;
148
out.parameters += ptp;
153
static PsiMedia::PayloadInfo payloadTypeToPayloadInfo(const JingleRtpPayloadType &pt)
155
PsiMedia::PayloadInfo out;
157
out.setName(pt.name);
158
out.setClockrate(pt.clockrate);
159
out.setChannels(pt.channels);
160
out.setPtime(pt.ptime);
161
out.setMaxptime(pt.maxptime);
162
QList<PsiMedia::PayloadInfo::Parameter> list;
163
foreach(const JingleRtpPayloadType::Parameter &ptp, pt.parameters)
165
PsiMedia::PayloadInfo::Parameter pip;
167
pip.value = ptp.value;
170
out.setParameters(list);
174
class AvTransmit : public QObject
179
PsiMedia::RtpChannel *audio, *video;
180
JingleRtpChannel *transport;
182
AvTransmit(PsiMedia::RtpChannel *_audio, PsiMedia::RtpChannel *_video, JingleRtpChannel *_transport, QObject *parent = 0) :
186
transport(_transport)
190
audio->setParent(this);
191
connect(audio, SIGNAL(readyRead()), SLOT(audio_readyRead()));
196
video->setParent(this);
197
connect(video, SIGNAL(readyRead()), SLOT(video_readyRead()));
200
transport->setParent(this);
201
connect(transport, SIGNAL(readyRead()), SLOT(transport_readyRead()));
202
connect(transport, SIGNAL(packetsWritten(int)), SLOT(transport_packetsWritten(int)));
211
transport->setParent(0);
215
void audio_readyRead()
217
while(audio->packetsAvailable() > 0)
219
PsiMedia::RtpPacket packet = audio->read();
221
JingleRtp::RtpPacket jpacket;
222
jpacket.type = JingleRtp::Audio;
223
jpacket.portOffset = packet.portOffset();
224
jpacket.value = packet.rawValue();
226
transport->write(jpacket);
230
void video_readyRead()
232
while(video->packetsAvailable() > 0)
234
PsiMedia::RtpPacket packet = video->read();
236
JingleRtp::RtpPacket jpacket;
237
jpacket.type = JingleRtp::Video;
238
jpacket.portOffset = packet.portOffset();
239
jpacket.value = packet.rawValue();
241
transport->write(jpacket);
245
void transport_readyRead()
247
while(transport->packetsAvailable())
249
JingleRtp::RtpPacket jpacket = transport->read();
251
if(jpacket.type == JingleRtp::Audio)
252
audio->write(PsiMedia::RtpPacket(jpacket.value, jpacket.portOffset));
253
else if(jpacket.type == JingleRtp::Video)
254
video->write(PsiMedia::RtpPacket(jpacket.value, jpacket.portOffset));
258
void transport_packetsWritten(int count)
266
class AvTransmitHandler : public QObject
271
AvTransmit *avTransmit;
272
QThread *previousThread;
274
AvTransmitHandler(QObject *parent = 0) :
286
// NOTE: the handler never touches these variables except here
287
// and on destruction, so it's safe to call this function from
288
// another thread if you know what you're doing.
289
void setAvTransmit(AvTransmit *_avTransmit)
291
avTransmit = _avTransmit;
292
previousThread = avTransmit->thread();
293
avTransmit->moveToThread(thread());
296
void releaseAvTransmit()
298
Q_ASSERT(avTransmit);
299
avTransmit->moveToThread(previousThread);
304
class AvTransmitThread : public QCA::SyncThread
309
AvTransmitHandler *handler;
311
AvTransmitThread(QObject *parent = 0) :
312
QCA::SyncThread(parent),
323
virtual void atStart()
325
handler = new AvTransmitHandler;
334
//----------------------------------------------------------------------------
336
//----------------------------------------------------------------------------
337
class AvCallManagerPrivate : public QObject
344
JingleRtpManager *rtpManager;
345
QList<AvCall*> sessions;
346
QList<AvCall*> pending;
348
AvCallManagerPrivate(PsiAccount *_pa, AvCallManager *_q);
349
~AvCallManagerPrivate();
351
void unlink(AvCall *call);
354
void rtp_incomingReady();
357
class AvCallPrivate : public QObject
363
AvCallManagerPrivate *manager;
366
PsiMedia::RtpSession rtp;
375
AvTransmit *avTransmit;
376
AvTransmitThread *avTransmitThread;
378
AvCallPrivate(AvCall *_q) :
383
transmitAudio(false),
384
transmitVideo(false),
389
allowVideo = AvCallManager::isVideoSupported();
391
connect(&rtp, SIGNAL(started()), SLOT(rtp_started()));
392
connect(&rtp, SIGNAL(preferencesUpdated()), SLOT(rtp_preferencesUpdated()));
393
connect(&rtp, SIGNAL(stopped()), SLOT(rtp_stopped()));
394
connect(&rtp, SIGNAL(error()), SLOT(rtp_error()));
399
rtp.disconnect(this);
408
// note that the object remains active, just
409
// dissociated from the manager
420
manager->rtpManager->setBasePort(g_config->basePort);
421
manager->rtpManager->setExternalAddress(g_config->extHost);
430
// JingleRtp guarantees there will be at least one of audio or video
431
bool offeredAudio = false;
432
bool offeredVideo = false;
433
if(!sess->remoteAudioPayloadTypes().isEmpty())
435
if(allowVideo && !sess->remoteVideoPayloadTypes().isEmpty())
438
if(offeredAudio && offeredVideo)
440
else if(offeredAudio)
441
mode = AvCall::Audio;
442
else if(offeredVideo)
443
mode = AvCall::Video;
446
// this could happen if only video is offered but
459
manager->rtpManager->setBasePort(g_config->basePort);
460
manager->rtpManager->setExternalAddress(g_config->extHost);
462
// kick off the acceptance negotiation while simultaneously
463
// initializing the rtp engine. note that session-accept
464
// won't actually get sent to the peer until we call
465
// localMediaUpdated()
467
if(mode == AvCall::Both)
468
types = JingleRtp::Audio | JingleRtp::Video;
469
else if(mode == AvCall::Audio)
470
types = JingleRtp::Audio;
472
types = JingleRtp::Video;
486
static QString rtpSessionErrorToString(PsiMedia::RtpSession::Error e)
491
case PsiMedia::RtpSession::ErrorSystem:
492
str = tr("System error"); break;
493
case PsiMedia::RtpSession::ErrorCodec:
494
str = tr("Codec error"); break;
496
str = tr("Generic error"); break;
503
// if we had a thread, this will move the object back
504
delete avTransmitThread;
505
avTransmitThread = 0;
518
Configuration &config = *g_config;
520
transmitAudio = false;
521
transmitVideo = false;
525
if(config.audioInDeviceId.isEmpty() && config.videoInDeviceId.isEmpty())
527
errorString = tr("Cannot call without selecting a device. Do you have a microphone? Check the Psi options.");
533
if((mode == AvCall::Audio || mode == AvCall::Both) && !config.audioInDeviceId.isEmpty())
535
rtp.setAudioInputDevice(config.audioInDeviceId);
536
transmitAudio = true;
539
rtp.setAudioInputDevice(QString());
541
if((mode == AvCall::Video || mode == AvCall::Both) && !config.videoInDeviceId.isEmpty() && allowVideo)
543
rtp.setVideoInputDevice(config.videoInDeviceId);
544
transmitVideo = true;
547
rtp.setVideoInputDevice(QString());
549
else // non-live (file) input
551
rtp.setFileInput(config.file);
552
rtp.setFileLoopEnabled(config.loopFile);
554
// we just assume the file has both audio and video.
555
// if it doesn't, no big deal, it'll still work.
556
// update: after starting, we can correct these
558
transmitAudio = true;
559
transmitVideo = true;
562
if(!config.audioOutDeviceId.isEmpty())
563
rtp.setAudioOutputDevice(config.audioOutDeviceId);
565
// media types are flagged by params, even if empty
566
QList<PsiMedia::AudioParams> audioParamsList;
568
audioParamsList += PsiMedia::AudioParams();
569
rtp.setLocalAudioPreferences(audioParamsList);
571
QList<PsiMedia::VideoParams> videoParamsList;
573
videoParamsList += PsiMedia::VideoParams();
574
rtp.setLocalVideoPreferences(videoParamsList);
576
// for incoming sessions, we have the remote media info at
577
// the start, so use it
579
setup_remote_media();
582
rtp.setMaximumSendingBitrate(bitrate);
584
transmitting = false;
590
connect(sess, SIGNAL(rejected()), SLOT(sess_rejected()));
591
connect(sess, SIGNAL(error()), SLOT(sess_error()));
592
connect(sess, SIGNAL(activated()), SLOT(sess_activated()));
593
connect(sess, SIGNAL(remoteMediaUpdated()), SLOT(sess_remoteMediaUpdated()));
596
void setup_remote_media()
600
QList<JingleRtpPayloadType> payloadTypes = sess->remoteAudioPayloadTypes();
601
QList<PsiMedia::PayloadInfo> list;
602
if(!payloadTypes.isEmpty())
603
list += payloadTypeToPayloadInfo(payloadTypes.first());
604
rtp.setRemoteAudioPreferences(list);
609
QList<JingleRtpPayloadType> payloadTypes = sess->remoteVideoPayloadTypes();
610
QList<PsiMedia::PayloadInfo> list;
611
if(!payloadTypes.isEmpty())
612
list += payloadTypeToPayloadInfo(payloadTypes.first());
613
rtp.setRemoteVideoPreferences(list);
616
// FIXME: if the remote side doesn't support a media type,
617
// then we need to downgrade locally
626
printf("rtp_started\n");
628
PsiMedia::PayloadInfo audio, *pAudio;
629
PsiMedia::PayloadInfo video, *pVideo;
635
// confirm transmitting of audio is actually possible,
636
// in the case that a file is used as input
637
if(rtp.canTransmitAudio())
639
audio = rtp.localAudioPayloadInfo().first();
643
transmitAudio = false;
648
if(rtp.canTransmitVideo())
650
video = rtp.localVideoPayloadInfo().first();
654
transmitVideo = false;
657
if(transmitAudio && transmitVideo)
659
else if(transmitAudio && !transmitVideo)
660
mode = AvCall::Audio;
661
else if(transmitVideo && !transmitAudio)
662
mode = AvCall::Video;
671
sess = manager->rtpManager->createOutgoing();
677
JingleRtpPayloadType pt = payloadInfoToPayloadType(*pAudio);
678
sess->setLocalAudioPayloadTypes(QList<JingleRtpPayloadType>() << pt);
683
JingleRtpPayloadType pt = payloadInfoToPayloadType(*pVideo);
684
sess->setLocalVideoPayloadTypes(QList<JingleRtpPayloadType>() << pt);
688
sess->connectToJid(peer);
690
sess->localMediaUpdate();
693
void rtp_preferencesUpdated()
700
// nothing for now, until we do async shutdown
705
errorString = tr("An error occurred while trying to send:\n%1.").arg(rtpSessionErrorToString(rtp.errorCode()));
712
errorString = tr("Call was rejected or terminated.");
719
JingleRtp::Error e = sess->errorCode();
720
if(e == JingleRtp::ErrorTimeout)
722
errorString = tr("Call negotiation timed out.");
725
else if(e == JingleRtp::ErrorICE)
727
errorString = tr("Unable to establish peer-to-peer connection.");
732
errorString = tr("Call negotiation failed.");
739
void sess_activated()
741
PsiMedia::RtpChannel *audio = 0;
742
PsiMedia::RtpChannel *video = 0;
745
audio = rtp.audioRtpChannel();
747
video = rtp.videoRtpChannel();
749
avTransmit = new AvTransmit(audio, video, sess->rtpChannel());
751
avTransmitThread = new AvTransmitThread(this);
752
avTransmitThread->start();
753
avTransmitThread->handler->setAvTransmit(avTransmit);
765
void sess_remoteMediaUpdated()
767
setup_remote_media();
768
rtp.updatePreferences();
774
d = new AvCallPrivate(this);
777
AvCall::AvCall(const AvCall &from) :
781
fprintf(stderr, "AvCall copy not supported\n");
790
XMPP::Jid AvCall::jid() const
793
return d->sess->jid();
798
AvCall::Mode AvCall::mode() const
803
void AvCall::connectToJid(const XMPP::Jid &jid, Mode mode, int kbps)
811
void AvCall::accept(Mode mode, int kbps)
818
void AvCall::reject()
823
void AvCall::setIncomingVideo(PsiMedia::VideoWidget *widget)
825
d->rtp.setVideoOutputWidget(widget);
828
QString AvCall::errorString() const
830
return d->errorString;
833
void AvCall::unlink()
838
//----------------------------------------------------------------------------
840
//----------------------------------------------------------------------------
841
AvCallManagerPrivate::AvCallManagerPrivate(PsiAccount *_pa, AvCallManager *_q) :
846
rtpManager = new JingleRtpManager(pa->client());
847
connect(rtpManager, SIGNAL(incomingReady()), SLOT(rtp_incomingReady()));
850
AvCallManagerPrivate::~AvCallManagerPrivate()
855
void AvCallManagerPrivate::unlink(AvCall *call)
857
sessions.removeAll(call);
860
void AvCallManagerPrivate::rtp_incomingReady()
862
AvCall *call = new AvCall;
863
call->d->manager = this;
864
call->d->incoming = true;
865
call->d->sess = rtpManager->takeIncoming();
867
if(!call->d->initIncoming())
869
call->d->sess->reject();
870
delete call->d->sess;
877
emit q->incomingReady();
880
AvCallManager::AvCallManager(PsiAccount *pa) :
883
d = new AvCallManagerPrivate(pa, this);
886
AvCallManager::~AvCallManager()
891
AvCall *AvCallManager::createOutgoing()
893
AvCall *call = new AvCall;
894
call->d->manager = d;
895
call->d->incoming = false;
899
AvCall *AvCallManager::takeIncoming()
901
return d->pending.takeFirst();
904
void AvCallManager::config()
906
// TODO: remove this function?
909
bool AvCallManager::isSupported()
912
if(!QCA::isSupported("hmac(sha1)"))
914
printf("hmac support missing for voice calls, install qca-ossl\n");
917
return PsiMedia::isSupported();
920
bool AvCallManager::isVideoSupported()
925
if(!QString::fromLatin1(qgetenv("PSI_ENABLE_VIDEO")).isEmpty())
931
void AvCallManager::setSelfAddress(const QHostAddress &addr)
933
d->rtpManager->setSelfAddress(addr);
936
void AvCallManager::setStunHost(const QString &host, int port)
938
d->rtpManager->setStunHost(host, port);
941
void AvCallManager::setBasePort(int port)
945
g_config->basePort = port;
948
void AvCallManager::setExternalAddress(const QString &host)
950
g_config->extHost = host;
953
void AvCallManager::setAudioOutDevice(const QString &id)
955
g_config->audioOutDeviceId = id;
958
void AvCallManager::setAudioInDevice(const QString &id)
960
g_config->audioInDeviceId = id;
963
void AvCallManager::setVideoInDevice(const QString &id)
965
g_config->videoInDeviceId = id;
968
#include "avcall.moc"