1 /*
2  * Copyright (C) 2009  Barracuda Networks, Inc.
3  *
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.
8  *
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.
13  *
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/>.
16  *
17  */
18 
19 #include "avcall.h"
20 
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <QCoreApplication>
24 #include <QLibrary>
25 #include <QDir>
26 #include <QtCrypto>
27 #include "xmpp_jid.h"
28 #include "jinglertp.h"
29 #include "../psimedia/psimedia.h"
30 #include "applicationinfo.h"
31 #include "psiaccount.h"
32 #include "psioptions.h"
33 
34 #define USE_THREAD
35 
36 class Configuration
37 {
38 public:
39 	bool liveInput;
40 	QString audioOutDeviceId, audioInDeviceId, videoInDeviceId;
41 	QString file;
42 	bool loopFile;
43 	PsiMedia::AudioParams audioParams;
44 	PsiMedia::VideoParams videoParams;
45 
46 	int basePort;
47 	QString extHost;
48 
Configuration()49 	Configuration() :
50 		liveInput(false),
51 		loopFile(false),
52 		basePort(-1)
53 	{
54 	}
55 };
56 
57 // get default settings
getDefaultConfiguration()58 static Configuration getDefaultConfiguration()
59 {
60 	Configuration config;
61 	config.liveInput = true;
62 	config.loopFile = true;
63 	return config;
64 }
65 
66 static Configuration *g_config = 0;
67 
ensureConfig()68 static void ensureConfig()
69 {
70 	if(!g_config)
71 	{
72 		g_config = new Configuration;
73 		*g_config = getDefaultConfiguration();
74 	}
75 }
76 
77 #ifdef GSTPROVIDER_STATIC
Q_IMPORT_PLUGIN(gstprovider)78 Q_IMPORT_PLUGIN(gstprovider)
79 #endif
80 
81 #ifndef GSTPROVIDER_STATIC
82 static QString findPlugin(const QString &relpath, const QString &basename)
83 {
84 	QDir dir(QCoreApplication::applicationDirPath());
85 	if(!dir.cd(relpath))
86 		return QString();
87 	foreach(const QString &fileName, dir.entryList())
88 	{
89 		if(fileName.contains(basename))
90 		{
91 			QString filePath = dir.filePath(fileName);
92 			if(QLibrary::isLibrary(filePath))
93 				return filePath;
94 		}
95 	}
96 	return QString();
97 }
98 #endif
99 
100 static bool g_loaded = false;
101 
ensureLoaded()102 static void ensureLoaded()
103 {
104 	if(!g_loaded)
105 	{
106 #ifndef GSTPROVIDER_STATIC
107 		QString pluginFile;
108 		QString resourcePath;
109 
110 		pluginFile = qgetenv("PSI_MEDIA_PLUGIN");
111 		if(pluginFile.isEmpty())
112 		{
113 #if defined(Q_OS_WIN)
114 			pluginFile = findPlugin(".", "gstprovider" DEBUG_POSTFIX);
115 			resourcePath = QCoreApplication::applicationDirPath() + "/gstreamer-0.10";
116 #elif defined(Q_OS_MAC)
117 			pluginFile = findPlugin("../Plugins", "gstprovider" DEBUG_POSTFIX);
118 			resourcePath = QCoreApplication::applicationDirPath() + "/../Frameworks/gstreamer-0.10";
119 #else
120 			pluginFile = findPlugin(ApplicationInfo::libDir() + "/plugins", "gstprovider" DEBUG_POSTFIX);
121 #endif
122 		}
123 
124 		PsiMedia::PluginResult r = PsiMedia::loadPlugin(pluginFile, resourcePath);
125 		if(r == PsiMedia::PluginSuccess)
126 			g_loaded = true;
127 #else
128 		g_loaded = true;
129 #endif
130 		if(g_loaded)
131 			ensureConfig();
132 	}
133 }
134 
payloadInfoToPayloadType(const PsiMedia::PayloadInfo & pi)135 static JingleRtpPayloadType payloadInfoToPayloadType(const PsiMedia::PayloadInfo &pi)
136 {
137 	JingleRtpPayloadType out;
138 	out.id = pi.id();
139 	out.name = pi.name();
140 	out.clockrate = pi.clockrate();
141 	out.channels = pi.channels();
142 	out.ptime = pi.ptime();
143 	out.maxptime = pi.maxptime();
144 	foreach(const PsiMedia::PayloadInfo::Parameter &pip, pi.parameters())
145 	{
146 		JingleRtpPayloadType::Parameter ptp;
147 		ptp.name = pip.name;
148 		ptp.value = pip.value;
149 		out.parameters += ptp;
150 	}
151 	return out;
152 }
153 
payloadTypeToPayloadInfo(const JingleRtpPayloadType & pt)154 static PsiMedia::PayloadInfo payloadTypeToPayloadInfo(const JingleRtpPayloadType &pt)
155 {
156 	PsiMedia::PayloadInfo out;
157 	out.setId(pt.id);
158 	out.setName(pt.name);
159 	out.setClockrate(pt.clockrate);
160 	out.setChannels(pt.channels);
161 	out.setPtime(pt.ptime);
162 	out.setMaxptime(pt.maxptime);
163 	QList<PsiMedia::PayloadInfo::Parameter> list;
164 	foreach(const JingleRtpPayloadType::Parameter &ptp, pt.parameters)
165 	{
166 		PsiMedia::PayloadInfo::Parameter pip;
167 		pip.name = ptp.name;
168 		pip.value = ptp.value;
169 		list += pip;
170 	}
171 	out.setParameters(list);
172 	return out;
173 }
174 
175 class AvTransmit : public QObject
176 {
177 	Q_OBJECT
178 
179 public:
180 	PsiMedia::RtpChannel *audio, *video;
181 	JingleRtpChannel *transport;
182 
AvTransmit(PsiMedia::RtpChannel * _audio,PsiMedia::RtpChannel * _video,JingleRtpChannel * _transport,QObject * parent=0)183 	AvTransmit(PsiMedia::RtpChannel *_audio, PsiMedia::RtpChannel *_video, JingleRtpChannel *_transport, QObject *parent = 0) :
184 		QObject(parent),
185 		audio(_audio),
186 		video(_video),
187 		transport(_transport)
188 	{
189 		if(audio)
190 		{
191 			audio->setParent(this);
192 			connect(audio, SIGNAL(readyRead()), SLOT(audio_readyRead()));
193 		}
194 
195 		if(video)
196 		{
197 			video->setParent(this);
198 			connect(video, SIGNAL(readyRead()), SLOT(video_readyRead()));
199 		}
200 
201 		transport->setParent(this);
202 		connect(transport, SIGNAL(readyRead()), SLOT(transport_readyRead()));
203 		connect(transport, SIGNAL(packetsWritten(int)), SLOT(transport_packetsWritten(int)));
204 	}
205 
~AvTransmit()206 	~AvTransmit()
207 	{
208 		if(audio)
209 			audio->setParent(0);
210 		if(video)
211 			video->setParent(0);
212 		transport->setParent(0);
213 	}
214 
215 private slots:
audio_readyRead()216 	void audio_readyRead()
217 	{
218 		while(audio->packetsAvailable() > 0)
219 		{
220 			PsiMedia::RtpPacket packet = audio->read();
221 
222 			JingleRtp::RtpPacket jpacket;
223 			jpacket.type = JingleRtp::Audio;
224 			jpacket.portOffset = packet.portOffset();
225 			jpacket.value = packet.rawValue();
226 
227 			transport->write(jpacket);
228 		}
229 	}
230 
video_readyRead()231 	void video_readyRead()
232 	{
233 		while(video->packetsAvailable() > 0)
234 		{
235 			PsiMedia::RtpPacket packet = video->read();
236 
237 			JingleRtp::RtpPacket jpacket;
238 			jpacket.type = JingleRtp::Video;
239 			jpacket.portOffset = packet.portOffset();
240 			jpacket.value = packet.rawValue();
241 
242 			transport->write(jpacket);
243 		}
244 	}
245 
transport_readyRead()246 	void transport_readyRead()
247 	{
248 		while(transport->packetsAvailable())
249 		{
250 			JingleRtp::RtpPacket jpacket = transport->read();
251 
252 			if(jpacket.type == JingleRtp::Audio)
253 				audio->write(PsiMedia::RtpPacket(jpacket.value, jpacket.portOffset));
254 			else if(jpacket.type == JingleRtp::Video)
255 				video->write(PsiMedia::RtpPacket(jpacket.value, jpacket.portOffset));
256 		}
257 	}
258 
transport_packetsWritten(int count)259 	void transport_packetsWritten(int count)
260 	{
261 		Q_UNUSED(count);
262 
263 		// nothing
264 	}
265 };
266 
267 class AvTransmitHandler : public QObject
268 {
269 	Q_OBJECT
270 
271 public:
272 	AvTransmit *avTransmit;
273 	QThread *previousThread;
274 
AvTransmitHandler(QObject * parent=0)275 	AvTransmitHandler(QObject *parent = 0) :
276 		QObject(parent),
277 		avTransmit(0)
278 	{
279 	}
280 
~AvTransmitHandler()281 	~AvTransmitHandler()
282 	{
283 		if(avTransmit)
284 			releaseAvTransmit();
285 	}
286 
287 	// NOTE: the handler never touches these variables except here
288 	//   and on destruction, so it's safe to call this function from
289 	//   another thread if you know what you're doing.
setAvTransmit(AvTransmit * _avTransmit)290 	void setAvTransmit(AvTransmit *_avTransmit)
291 	{
292 		avTransmit = _avTransmit;
293 		previousThread = avTransmit->thread();
294 		avTransmit->moveToThread(thread());
295 	}
296 
releaseAvTransmit()297 	void releaseAvTransmit()
298 	{
299 		Q_ASSERT(avTransmit);
300 		avTransmit->moveToThread(previousThread);
301 		avTransmit = 0;
302 	}
303 };
304 
305 class AvTransmitThread : public QCA::SyncThread
306 {
307 	Q_OBJECT
308 
309 public:
310 	AvTransmitHandler *handler;
311 
AvTransmitThread(QObject * parent=0)312 	AvTransmitThread(QObject *parent = 0) :
313 		QCA::SyncThread(parent),
314 		handler(0)
315 	{
316 	}
317 
~AvTransmitThread()318 	~AvTransmitThread()
319 	{
320 		stop();
321 	}
322 
323 protected:
atStart()324 	virtual void atStart()
325 	{
326 		handler = new AvTransmitHandler;
327 	}
328 
atEnd()329 	virtual void atEnd()
330 	{
331 		delete handler;
332 	}
333 };
334 
335 //----------------------------------------------------------------------------
336 // AvCall
337 //----------------------------------------------------------------------------
338 class AvCallManagerPrivate : public QObject
339 {
340 	Q_OBJECT
341 
342 public:
343 	AvCallManager *q;
344 	PsiAccount *pa;
345 	JingleRtpManager *rtpManager;
346 	QList<AvCall*> sessions;
347 	QList<AvCall*> pending;
348 
349 	AvCallManagerPrivate(PsiAccount *_pa, AvCallManager *_q);
350 	~AvCallManagerPrivate();
351 
352 	void unlink(AvCall *call);
353 
354 private slots:
355 	void rtp_incomingReady();
356 };
357 
358 class AvCallPrivate : public QObject
359 {
360 	Q_OBJECT
361 
362 public:
363 	AvCall *q;
364 	AvCallManagerPrivate *manager;
365 	bool incoming;
366 	JingleRtp *sess;
367 	PsiMedia::RtpSession rtp;
368 	XMPP::Jid peer;
369 	AvCall::Mode mode;
370 	int bitrate;
371 	bool allowVideo;
372 	QString errorString;
373 	bool transmitAudio;
374 	bool transmitVideo;
375 	bool transmitting;
376 	AvTransmit *avTransmit;
377 	AvTransmitThread *avTransmitThread;
378 
AvCallPrivate(AvCall * _q)379 	AvCallPrivate(AvCall *_q) :
380 		QObject(_q),
381 		q(_q),
382 		manager(0),
383 		sess(0),
384 		transmitAudio(false),
385 		transmitVideo(false),
386 		transmitting(false),
387 		avTransmit(0),
388 		avTransmitThread(0)
389 	{
390 		allowVideo = AvCallManager::isVideoSupported();
391 
392 		connect(&rtp, SIGNAL(started()), SLOT(rtp_started()));
393 		connect(&rtp, SIGNAL(preferencesUpdated()), SLOT(rtp_preferencesUpdated()));
394 		connect(&rtp, SIGNAL(stopped()), SLOT(rtp_stopped()));
395 		connect(&rtp, SIGNAL(error()), SLOT(rtp_error()));
396 	}
397 
~AvCallPrivate()398 	~AvCallPrivate()
399 	{
400 		rtp.disconnect(this);
401 		cleanup();
402 		unlink();
403 	}
404 
unlink()405 	void unlink()
406 	{
407 		if(manager)
408 		{
409 			// note that the object remains active, just
410 			//   dissociated from the manager
411 			manager->unlink(q);
412 			manager = 0;
413 		}
414 	}
415 
startOutgoing()416 	void startOutgoing()
417 	{
418 		if(!manager)
419 			return;
420 
421 		manager->rtpManager->setBasePort(g_config->basePort);
422 		manager->rtpManager->setExternalAddress(g_config->extHost);
423 
424 		start_rtp();
425 	}
426 
initIncoming()427 	bool initIncoming()
428 	{
429 		setup_sess();
430 
431 		// JingleRtp guarantees there will be at least one of audio or video
432 		bool offeredAudio = false;
433 		bool offeredVideo = false;
434 		if(!sess->remoteAudioPayloadTypes().isEmpty())
435 			offeredAudio = true;
436 		if(allowVideo && !sess->remoteVideoPayloadTypes().isEmpty())
437 			offeredVideo = true;
438 
439 		if(offeredAudio && offeredVideo)
440 			mode = AvCall::Both;
441 		else if(offeredAudio)
442 			mode = AvCall::Audio;
443 		else if(offeredVideo)
444 			mode = AvCall::Video;
445 		else
446 		{
447 			// this could happen if only video is offered but
448 			//   we don't allow it
449 			return false;
450 		}
451 
452 		return true;
453 	}
454 
accept()455 	void accept()
456 	{
457 		if(!manager)
458 			return;
459 
460 		manager->rtpManager->setBasePort(g_config->basePort);
461 		manager->rtpManager->setExternalAddress(g_config->extHost);
462 
463 		// kick off the acceptance negotiation while simultaneously
464 		//   initializing the rtp engine.  note that session-accept
465 		//   won't actually get sent to the peer until we call
466 		//   localMediaUpdated()
467 		int types;
468 		if(mode == AvCall::Both)
469 			types = JingleRtp::Audio | JingleRtp::Video;
470 		else if(mode == AvCall::Audio)
471 			types = JingleRtp::Audio;
472 		else // Video
473 			types = JingleRtp::Video;
474 
475 		sess->accept(types);
476 		start_rtp();
477 	}
478 
reject()479 	void reject()
480 	{
481 		if(sess)
482 			sess->reject();
483 		cleanup();
484 	}
485 
486 private:
rtpSessionErrorToString(PsiMedia::RtpSession::Error e)487 	static QString rtpSessionErrorToString(PsiMedia::RtpSession::Error e)
488 	{
489 		QString str;
490 		switch(e)
491 		{
492 			case PsiMedia::RtpSession::ErrorSystem:
493 				str = tr("System error"); break;
494 			case PsiMedia::RtpSession::ErrorCodec:
495 				str = tr("Codec error"); break;
496 			default: // generic
497 				str = tr("Generic error"); break;
498 		}
499 		return str;
500 	}
501 
cleanup()502 	void cleanup()
503 	{
504 		// if we had a thread, this will move the object back
505 		delete avTransmitThread;
506 		avTransmitThread = 0;
507 
508 		delete avTransmit;
509 		avTransmit = 0;
510 
511 		rtp.reset();
512 
513 		delete sess;
514 		sess = 0;
515 	}
516 
start_rtp()517 	void start_rtp()
518 	{
519 		Configuration &config = *g_config;
520 
521 		transmitAudio = false;
522 		transmitVideo = false;
523 
524 		if(config.liveInput)
525 		{
526 			if(config.audioInDeviceId.isEmpty() && config.videoInDeviceId.isEmpty())
527 			{
528 				errorString = tr("Cannot call without selecting a device.  Do you have a microphone?  Check the Psi options.");
529 				cleanup();
530 				emit q->error();
531 				return;
532 			}
533 
534 			if((mode == AvCall::Audio || mode == AvCall::Both) && !config.audioInDeviceId.isEmpty())
535 			{
536 				rtp.setAudioInputDevice(config.audioInDeviceId);
537 				transmitAudio = true;
538 			}
539 			else
540 				rtp.setAudioInputDevice(QString());
541 
542 			if((mode == AvCall::Video || mode == AvCall::Both) && !config.videoInDeviceId.isEmpty() && allowVideo)
543 			{
544 				rtp.setVideoInputDevice(config.videoInDeviceId);
545 				transmitVideo = true;
546 			}
547 			else
548 				rtp.setVideoInputDevice(QString());
549 		}
550 		else // non-live (file) input
551 		{
552 			rtp.setFileInput(config.file);
553 			rtp.setFileLoopEnabled(config.loopFile);
554 
555 			// we just assume the file has both audio and video.
556 			//   if it doesn't, no big deal, it'll still work.
557 			//   update: after starting, we can correct these
558 			//   variables.
559 			transmitAudio = true;
560 			transmitVideo = true;
561 		}
562 
563 		if(!config.audioOutDeviceId.isEmpty())
564 			rtp.setAudioOutputDevice(config.audioOutDeviceId);
565 
566 		// media types are flagged by params, even if empty
567 		QList<PsiMedia::AudioParams> audioParamsList;
568 		if(transmitAudio)
569 			audioParamsList += PsiMedia::AudioParams();
570 		rtp.setLocalAudioPreferences(audioParamsList);
571 
572 		QList<PsiMedia::VideoParams> videoParamsList;
573 		if(transmitVideo)
574 			videoParamsList += PsiMedia::VideoParams();
575 		rtp.setLocalVideoPreferences(videoParamsList);
576 
577 		// for incoming sessions, we have the remote media info at
578 		//   the start, so use it
579 		if(incoming)
580 			setup_remote_media();
581 
582 		if(bitrate != -1)
583 			rtp.setMaximumSendingBitrate(bitrate);
584 
585 		transmitting = false;
586 		rtp.start();
587 	}
588 
setup_sess()589 	void setup_sess()
590 	{
591 		connect(sess, SIGNAL(rejected()), SLOT(sess_rejected()));
592 		connect(sess, SIGNAL(error()), SLOT(sess_error()));
593 		connect(sess, SIGNAL(activated()), SLOT(sess_activated()));
594 		connect(sess, SIGNAL(remoteMediaUpdated()), SLOT(sess_remoteMediaUpdated()));
595 	}
596 
setup_remote_media()597 	void setup_remote_media()
598 	{
599 		if(transmitAudio)
600 		{
601 			QList<JingleRtpPayloadType> payloadTypes = sess->remoteAudioPayloadTypes();
602 			QList<PsiMedia::PayloadInfo> list;
603 			foreach(const JingleRtpPayloadType &pt, payloadTypes)
604 				list += payloadTypeToPayloadInfo(pt);
605 			rtp.setRemoteAudioPreferences(list);
606 		}
607 
608 		if(transmitVideo)
609 		{
610 			QList<JingleRtpPayloadType> payloadTypes = sess->remoteVideoPayloadTypes();
611 			QList<PsiMedia::PayloadInfo> list;
612 			foreach(const JingleRtpPayloadType &pt, payloadTypes)
613 				list += payloadTypeToPayloadInfo(pt);
614 			rtp.setRemoteVideoPreferences(list);
615 		}
616 
617 		// FIXME: if the remote side doesn't support a media type,
618 		//   then we need to downgrade locally
619 	}
620 
621 private slots:
rtp_started()622 	void rtp_started()
623 	{
624 		if(!manager)
625 			return;
626 
627 		printf("rtp_started\n");
628 
629 		if(!incoming)
630 		{
631 			sess = manager->rtpManager->createOutgoing();
632 			setup_sess();
633 		}
634 
635 		if(transmitAudio && !rtp.localAudioPayloadInfo().isEmpty())
636 		{
637 			QList<JingleRtpPayloadType> pis;
638 			foreach(PsiMedia::PayloadInfo pi, rtp.localAudioPayloadInfo())
639 			{
640 				JingleRtpPayloadType pt = payloadInfoToPayloadType(pi);
641 				pis << pt;
642 			}
643 			sess->setLocalAudioPayloadTypes(pis);
644 		}
645 		else
646 			transmitAudio = false;
647 
648 		if(transmitVideo && !rtp.localVideoPayloadInfo().isEmpty())
649 		{
650 			QList<JingleRtpPayloadType> pis;
651 			foreach(PsiMedia::PayloadInfo pi, rtp.localVideoPayloadInfo())
652 			{
653 				JingleRtpPayloadType pt = payloadInfoToPayloadType(pi);
654 				pis << pt;
655 			}
656 			sess->setLocalVideoPayloadTypes(pis);
657 		}
658 		else
659 			transmitVideo = false;
660 
661 		if(transmitAudio && transmitVideo)
662 			mode = AvCall::Both;
663 		else if(transmitAudio && !transmitVideo)
664 			mode = AvCall::Audio;
665 		else if(transmitVideo && !transmitAudio)
666 			mode = AvCall::Video;
667 		else
668 		{
669 			// can't happen?
670 			Q_ASSERT(0);
671 		}
672 
673 		if(!incoming)
674 			sess->connectToJid(peer);
675 		else
676 			sess->localMediaUpdate();
677 	}
678 
rtp_preferencesUpdated()679 	void rtp_preferencesUpdated()
680 	{
681 		// nothing?
682 	}
683 
rtp_stopped()684 	void rtp_stopped()
685 	{
686 		// nothing for now, until we do async shutdown
687 	}
688 
rtp_error()689 	void rtp_error()
690 	{
691 		errorString = tr("An error occurred while trying to send:\n%1.").arg(rtpSessionErrorToString(rtp.errorCode()));
692 		reject();
693 		emit q->error();
694 	}
695 
sess_rejected()696 	void sess_rejected()
697 	{
698 		errorString = tr("Call was rejected or terminated.");
699 		cleanup();
700 		emit q->error();
701 	}
702 
sess_error()703 	void sess_error()
704 	{
705 		JingleRtp::Error e = sess->errorCode();
706 		if(e == JingleRtp::ErrorTimeout)
707 		{
708 			errorString = tr("Call negotiation timed out.");
709 			cleanup();
710 		}
711 		else if(e == JingleRtp::ErrorICE)
712 		{
713 			errorString = tr("Unable to establish peer-to-peer connection.");
714 			reject();
715 		}
716 		else
717 		{
718 			errorString = tr("Call negotiation failed.");
719 			cleanup();
720 		}
721 
722 		emit q->error();
723 	}
724 
sess_activated()725 	void sess_activated()
726 	{
727 		PsiMedia::RtpChannel *audio = 0;
728 		PsiMedia::RtpChannel *video = 0;
729 
730 		if(transmitAudio)
731 			audio = rtp.audioRtpChannel();
732 		if(transmitVideo)
733 			video = rtp.videoRtpChannel();
734 
735 		avTransmit = new AvTransmit(audio, video, sess->rtpChannel());
736 #ifdef USE_THREAD
737 		avTransmitThread = new AvTransmitThread(this);
738 		avTransmitThread->start();
739 		avTransmitThread->handler->setAvTransmit(avTransmit);
740 #endif
741 
742 		if(transmitAudio)
743 			rtp.transmitAudio();
744 		if(transmitVideo)
745 			rtp.transmitVideo();
746 
747 		transmitting = true;
748 		emit q->activated();
749 	}
750 
sess_remoteMediaUpdated()751 	void sess_remoteMediaUpdated()
752 	{
753 		setup_remote_media();
754 		rtp.updatePreferences();
755 	}
756 };
757 
AvCall()758 AvCall::AvCall()
759 {
760 	d = new AvCallPrivate(this);
761 }
762 
AvCall(const AvCall & from)763 AvCall::AvCall(const AvCall &from) :
764 	QObject(0)
765 {
766 	Q_UNUSED(from);
767 	fprintf(stderr, "AvCall copy not supported\n");
768 	abort();
769 }
770 
~AvCall()771 AvCall::~AvCall()
772 {
773 	delete d;
774 }
775 
jid() const776 XMPP::Jid AvCall::jid() const
777 {
778 	if(d->sess)
779 		return d->sess->jid();
780 	else
781 		return XMPP::Jid();
782 }
783 
mode() const784 AvCall::Mode AvCall::mode() const
785 {
786 	return d->mode;
787 }
788 
connectToJid(const XMPP::Jid & jid,Mode mode,int kbps)789 void AvCall::connectToJid(const XMPP::Jid &jid, Mode mode, int kbps)
790 {
791 	d->peer = jid;
792 	d->mode = mode;
793 	d->bitrate = kbps;
794 	d->startOutgoing();
795 }
796 
accept(Mode mode,int kbps)797 void AvCall::accept(Mode mode, int kbps)
798 {
799 	d->mode = mode;
800 	d->bitrate = kbps;
801 	d->accept();
802 }
803 
reject()804 void AvCall::reject()
805 {
806 	d->reject();
807 }
808 
setIncomingVideo(PsiMedia::VideoWidget * widget)809 void AvCall::setIncomingVideo(PsiMedia::VideoWidget *widget)
810 {
811 	d->rtp.setVideoOutputWidget(widget);
812 }
813 
errorString() const814 QString AvCall::errorString() const
815 {
816 	return d->errorString;
817 }
818 
unlink()819 void AvCall::unlink()
820 {
821 	d->unlink();
822 }
823 
824 //----------------------------------------------------------------------------
825 // AvCallManager
826 //----------------------------------------------------------------------------
AvCallManagerPrivate(PsiAccount * _pa,AvCallManager * _q)827 AvCallManagerPrivate::AvCallManagerPrivate(PsiAccount *_pa, AvCallManager *_q) :
828 	QObject(_q),
829 	q(_q),
830 	pa(_pa)
831 {
832 	rtpManager = new JingleRtpManager(pa->client());
833 	connect(rtpManager, SIGNAL(incomingReady()), SLOT(rtp_incomingReady()));
834 }
835 
~AvCallManagerPrivate()836 AvCallManagerPrivate::~AvCallManagerPrivate()
837 {
838 	delete rtpManager;
839 }
840 
unlink(AvCall * call)841 void AvCallManagerPrivate::unlink(AvCall *call)
842 {
843 	sessions.removeAll(call);
844 }
845 
rtp_incomingReady()846 void AvCallManagerPrivate::rtp_incomingReady()
847 {
848 	AvCall *call = new AvCall;
849 	call->d->manager = this;
850 	call->d->incoming = true;
851 	call->d->sess = rtpManager->takeIncoming();
852 	sessions += call;
853 	if(!call->d->initIncoming())
854 	{
855 		call->d->sess->reject();
856 		delete call->d->sess;
857 		call->d->sess = 0;
858 		delete call;
859 		return;
860 	}
861 
862 	pending += call;
863 	emit q->incomingReady();
864 }
865 
AvCallManager(PsiAccount * pa)866 AvCallManager::AvCallManager(PsiAccount *pa) :
867 	QObject(0)
868 {
869 	d = new AvCallManagerPrivate(pa, this);
870 }
871 
~AvCallManager()872 AvCallManager::~AvCallManager()
873 {
874 	delete d;
875 }
876 
createOutgoing()877 AvCall *AvCallManager::createOutgoing()
878 {
879 	AvCall *call = new AvCall;
880 	call->d->manager = d;
881 	call->d->incoming = false;
882 	return call;
883 }
884 
takeIncoming()885 AvCall *AvCallManager::takeIncoming()
886 {
887 	return d->pending.takeFirst();
888 }
889 
config()890 void AvCallManager::config()
891 {
892 	// TODO: remove this function?
893 }
894 
isSupported()895 bool AvCallManager::isSupported()
896 {
897 	ensureLoaded();
898 	if(!QCA::isSupported("hmac(sha1)"))
899 	{
900 		printf("hmac support missing for voice calls, install qca-ossl\n");
901 		return false;
902 	}
903 	return PsiMedia::isSupported();
904 }
905 
isVideoSupported()906 bool AvCallManager::isVideoSupported()
907 {
908 	if(!isSupported())
909 		return false;
910 
911 	if(PsiOptions::instance()->getOption("options.media.video-support").toBool())
912 		return true;
913 
914 	if(!QString::fromLatin1(qgetenv("PSI_ENABLE_VIDEO")).isEmpty())
915 		return true;
916 	else
917 		return false;
918 }
919 
setSelfAddress(const QHostAddress & addr)920 void AvCallManager::setSelfAddress(const QHostAddress &addr)
921 {
922 	d->rtpManager->setSelfAddress(addr);
923 }
924 
setStunBindService(const QString & host,int port)925 void AvCallManager::setStunBindService(const QString &host, int port)
926 {
927 	d->rtpManager->setStunBindService(host, port);
928 }
929 
setStunRelayUdpService(const QString & host,int port,const QString & user,const QString & pass)930 void AvCallManager::setStunRelayUdpService(const QString &host, int port, const QString &user, const QString &pass)
931 {
932 	d->rtpManager->setStunRelayUdpService(host, port, user, pass);
933 }
934 
setStunRelayTcpService(const QString & host,int port,const XMPP::AdvancedConnector::Proxy & proxy,const QString & user,const QString & pass)935 void AvCallManager::setStunRelayTcpService(const QString &host, int port, const XMPP::AdvancedConnector::Proxy &proxy, const QString &user, const QString &pass)
936 {
937 	d->rtpManager->setStunRelayTcpService(host, port, proxy, user, pass);
938 }
939 
setBasePort(int port)940 void AvCallManager::setBasePort(int port)
941 {
942 	if(port == 0)
943 		port = -1;
944 	g_config->basePort = port;
945 }
946 
setExternalAddress(const QString & host)947 void AvCallManager::setExternalAddress(const QString &host)
948 {
949 	g_config->extHost = host;
950 }
951 
setAudioOutDevice(const QString & id)952 void AvCallManager::setAudioOutDevice(const QString &id)
953 {
954 	g_config->audioOutDeviceId = id;
955 }
956 
setAudioInDevice(const QString & id)957 void AvCallManager::setAudioInDevice(const QString &id)
958 {
959 	g_config->audioInDeviceId = id;
960 }
961 
setVideoInDevice(const QString & id)962 void AvCallManager::setVideoInDevice(const QString &id)
963 {
964 	g_config->videoInDeviceId = id;
965 }
966 
967 #include "avcall.moc"
968