2014-09-02 02:45:48 +08:00
|
|
|
/*
|
|
|
|
Copyright (C) 2013 by Maxim Biro <nurupo.contributions@gmail.com>
|
2015-06-06 09:40:08 +08:00
|
|
|
Copyright © 2014-2015 by The qTox Project
|
2014-09-02 02:45:48 +08:00
|
|
|
|
2015-06-06 09:40:08 +08:00
|
|
|
This file is part of qTox, a Qt-based graphical interface for Tox.
|
2014-09-02 02:45:48 +08:00
|
|
|
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
|
|
(at your option) any later version.
|
2015-06-06 09:40:08 +08:00
|
|
|
|
|
|
|
qTox is distributed in the hope that it will be useful,
|
2014-09-02 02:45:48 +08:00
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
2015-06-06 09:40:08 +08:00
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
2014-09-02 02:45:48 +08:00
|
|
|
|
2015-06-06 09:40:08 +08:00
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with qTox. If not, see <http://www.gnu.org/licenses/>.
|
2014-09-02 02:45:48 +08:00
|
|
|
*/
|
|
|
|
|
2014-08-28 20:30:38 +08:00
|
|
|
#include "core.h"
|
2015-06-26 19:00:16 +08:00
|
|
|
#include "coreav.h"
|
2015-06-06 07:44:47 +08:00
|
|
|
#include "src/audio/audio.h"
|
|
|
|
#include "src/persistence/settings.h"
|
2015-10-05 08:36:50 +08:00
|
|
|
#include "src/video/videoframe.h"
|
|
|
|
#include "src/video/corevideosource.h"
|
2015-10-08 03:53:22 +08:00
|
|
|
#include <cassert>
|
|
|
|
#include <QThread>
|
|
|
|
#include <QTimer>
|
2014-09-11 21:44:34 +08:00
|
|
|
#include <QDebug>
|
2015-10-25 20:53:04 +08:00
|
|
|
#include <QCoreApplication>
|
Fix theoritical A/V deadlock
Could only be hit by pausing at a key point in a debugger until the call timed-out.
Having one thread going up the call stack and acquiring locks (toxcore callbacks), while another thread goes down taking locks in the other order (CoreAV calling toxav functions) creates some pretty freezy situations.
The deadlock was caused by the GUI thread calling the CoreAV thread, acquiring the CoreAV callback, then right before calling a toxav function, not schedule the thread until the call times out. At this point the toxcore thread fires its state callback to tell us the call is over, locking internal toxcore/toxav mutexes, it reaches our callback function which tries to switch to the CoreAV thread to clean up the call data structures, but has to wait since the CoreAV thread holds its own lock. At this point if we resume the CoreAV thread, it'll be busy calling into a toxav function, which tries to acquire internal toxav locks, those locks are held by the toxcore callback so we deadlock.
Our solution is that when getting a toxcore callback, we immediately switch to a temporary thread, allowing toxcore to release the locks it held, and that temporary thread tries to switch to do work on call data structures. Meanwhile if the CoreAV thread needs internal toxcore locks, it can get them.
2015-11-08 07:32:32 +08:00
|
|
|
#include <QtConcurrent/QtConcurrentRun>
|
2015-09-28 01:59:26 +08:00
|
|
|
|
2014-12-14 16:50:18 +08:00
|
|
|
#ifdef QTOX_FILTER_AUDIO
|
2015-10-05 08:36:50 +08:00
|
|
|
#include "src/audio/audiofilterer.h"
|
2015-09-28 03:34:14 +08:00
|
|
|
#endif
|
|
|
|
|
2015-10-05 08:36:50 +08:00
|
|
|
IndexedList<ToxFriendCall> CoreAV::calls;
|
|
|
|
IndexedList<ToxGroupCall> CoreAV::groupCalls;
|
2014-08-28 21:46:11 +08:00
|
|
|
|
2015-10-05 08:36:50 +08:00
|
|
|
using namespace std;
|
2014-08-28 21:46:11 +08:00
|
|
|
|
2015-09-28 01:59:26 +08:00
|
|
|
CoreAV::CoreAV(Tox *tox)
|
2015-10-24 20:10:28 +08:00
|
|
|
: coreavThread{new QThread}, iterateTimer{new QTimer{this}},
|
|
|
|
threadSwitchLock{false}
|
2014-08-28 21:46:11 +08:00
|
|
|
{
|
2015-10-08 03:53:22 +08:00
|
|
|
coreavThread->setObjectName("qTox CoreAV");
|
|
|
|
moveToThread(coreavThread.get());
|
|
|
|
|
|
|
|
iterateTimer->setSingleShot(true);
|
|
|
|
connect(iterateTimer.get(), &QTimer::timeout, this, &CoreAV::process);
|
|
|
|
|
2015-09-28 01:59:26 +08:00
|
|
|
toxav = toxav_new(tox, nullptr);
|
|
|
|
|
|
|
|
toxav_callback_call(toxav, CoreAV::callCallback, this);
|
|
|
|
toxav_callback_call_state(toxav, CoreAV::stateCallback, this);
|
2015-10-12 22:29:46 +08:00
|
|
|
toxav_callback_bit_rate_status(toxav, CoreAV::bitrateCallback, this);
|
2015-09-28 01:59:26 +08:00
|
|
|
toxav_callback_audio_receive_frame(toxav, CoreAV::audioFrameCallback, this);
|
|
|
|
toxav_callback_video_receive_frame(toxav, CoreAV::videoFrameCallback, this);
|
2015-10-08 03:53:22 +08:00
|
|
|
|
|
|
|
coreavThread->start();
|
2014-08-28 21:46:11 +08:00
|
|
|
}
|
|
|
|
|
2015-09-28 01:59:26 +08:00
|
|
|
CoreAV::~CoreAV()
|
2015-01-25 11:57:20 +08:00
|
|
|
{
|
2015-10-05 08:36:50 +08:00
|
|
|
for (const ToxFriendCall& call : calls)
|
|
|
|
cancelCall(call.callId);
|
2015-10-24 02:05:30 +08:00
|
|
|
killTimerFromThread();
|
2015-09-28 01:59:26 +08:00
|
|
|
toxav_kill(toxav);
|
2015-10-25 20:53:04 +08:00
|
|
|
coreavThread->exit(0);
|
|
|
|
while (coreavThread->isRunning())
|
|
|
|
{
|
|
|
|
qApp->processEvents();
|
|
|
|
coreavThread->wait(100);
|
|
|
|
}
|
2015-01-25 11:57:20 +08:00
|
|
|
}
|
|
|
|
|
2015-09-28 01:59:26 +08:00
|
|
|
const ToxAV *CoreAV::getToxAv() const
|
2014-08-28 21:46:11 +08:00
|
|
|
{
|
2015-09-28 01:59:26 +08:00
|
|
|
return toxav;
|
|
|
|
}
|
2015-06-26 19:00:16 +08:00
|
|
|
|
2015-10-08 03:53:22 +08:00
|
|
|
void CoreAV::start()
|
|
|
|
{
|
|
|
|
// Timers can only be touched from their own thread
|
|
|
|
if (QThread::currentThread() != coreavThread.get())
|
|
|
|
return (void)QMetaObject::invokeMethod(this, "start", Qt::BlockingQueuedConnection);
|
|
|
|
iterateTimer->start();
|
|
|
|
}
|
|
|
|
|
|
|
|
void CoreAV::stop()
|
|
|
|
{
|
|
|
|
// Timers can only be touched from their own thread
|
|
|
|
if (QThread::currentThread() != coreavThread.get())
|
|
|
|
return (void)QMetaObject::invokeMethod(this, "stop", Qt::BlockingQueuedConnection);
|
|
|
|
iterateTimer->stop();
|
|
|
|
}
|
|
|
|
|
2015-10-24 02:05:30 +08:00
|
|
|
void CoreAV::killTimerFromThread()
|
|
|
|
{
|
|
|
|
// Timers can only be touched from their own thread
|
|
|
|
if (QThread::currentThread() != coreavThread.get())
|
|
|
|
return (void)QMetaObject::invokeMethod(this, "killTimerFromThread", Qt::BlockingQueuedConnection);
|
|
|
|
iterateTimer.release();
|
|
|
|
}
|
|
|
|
|
2015-09-28 01:59:26 +08:00
|
|
|
void CoreAV::process()
|
|
|
|
{
|
|
|
|
toxav_iterate(toxav);
|
2015-10-08 03:53:22 +08:00
|
|
|
iterateTimer->start(toxav_iteration_interval(toxav));
|
2014-08-28 21:46:11 +08:00
|
|
|
}
|
|
|
|
|
2015-09-28 01:59:26 +08:00
|
|
|
bool CoreAV::anyActiveCalls()
|
2014-08-28 21:46:11 +08:00
|
|
|
{
|
2015-09-28 01:59:26 +08:00
|
|
|
return !calls.isEmpty();
|
2014-08-28 21:46:11 +08:00
|
|
|
}
|
|
|
|
|
2015-10-08 06:52:05 +08:00
|
|
|
bool CoreAV::isCallVideoEnabled(uint32_t friendNum)
|
2014-08-28 20:30:38 +08:00
|
|
|
{
|
2015-10-08 06:52:05 +08:00
|
|
|
assert(calls.contains(friendNum));
|
|
|
|
return calls[friendNum].videoEnabled;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CoreAV::answerCall(uint32_t friendNum)
|
|
|
|
{
|
|
|
|
if (QThread::currentThread() != coreavThread.get())
|
|
|
|
{
|
2015-10-24 20:10:28 +08:00
|
|
|
if (threadSwitchLock.test_and_set(std::memory_order_acquire))
|
|
|
|
{
|
|
|
|
qDebug() << "CoreAV::answerCall: Backed off of thread-switch lock";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2015-10-08 06:52:05 +08:00
|
|
|
bool ret;
|
|
|
|
QMetaObject::invokeMethod(this, "answerCall", Qt::BlockingQueuedConnection,
|
|
|
|
Q_RETURN_ARG(bool, ret), Q_ARG(uint32_t, friendNum));
|
2015-10-24 20:10:28 +08:00
|
|
|
|
|
|
|
threadSwitchLock.clear(std::memory_order_release);
|
2015-10-08 06:52:05 +08:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2015-09-28 01:59:26 +08:00
|
|
|
qDebug() << QString("answering call %1").arg(friendNum);
|
|
|
|
assert(calls.contains(friendNum));
|
|
|
|
TOXAV_ERR_ANSWER err;
|
|
|
|
if (toxav_answer(toxav, friendNum, AUDIO_DEFAULT_BITRATE, VIDEO_DEFAULT_BITRATE, &err))
|
|
|
|
{
|
2015-10-05 08:36:50 +08:00
|
|
|
calls[friendNum].inactive = false;
|
2015-10-08 06:52:05 +08:00
|
|
|
return true;
|
2015-09-28 01:59:26 +08:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
qWarning() << "Failed to answer call with error"<<err;
|
2015-10-08 02:11:19 +08:00
|
|
|
toxav_call_control(toxav, friendNum, TOXAV_CALL_CONTROL_CANCEL, nullptr);
|
2015-09-28 01:59:26 +08:00
|
|
|
calls.remove(friendNum);
|
2015-10-08 06:52:05 +08:00
|
|
|
return false;
|
2015-09-28 01:59:26 +08:00
|
|
|
}
|
|
|
|
}
|
2015-10-11 14:38:44 +08:00
|
|
|
|
2015-10-08 06:52:05 +08:00
|
|
|
bool CoreAV::startCall(uint32_t friendNum, bool video)
|
2015-09-28 01:59:26 +08:00
|
|
|
{
|
2015-10-08 06:52:05 +08:00
|
|
|
if (QThread::currentThread() != coreavThread.get())
|
2015-10-08 00:56:19 +08:00
|
|
|
{
|
2015-10-24 20:10:28 +08:00
|
|
|
if (threadSwitchLock.test_and_set(std::memory_order_acquire))
|
|
|
|
{
|
|
|
|
qDebug() << "CoreAV::startCall: Backed off of thread-switch lock";
|
|
|
|
return false;
|
|
|
|
}
|
2015-10-08 06:52:05 +08:00
|
|
|
bool ret;
|
2015-10-24 20:10:28 +08:00
|
|
|
|
2015-10-08 06:52:05 +08:00
|
|
|
(void)QMetaObject::invokeMethod(this, "startCall", Qt::BlockingQueuedConnection,
|
|
|
|
Q_RETURN_ARG(bool, ret), Q_ARG(uint32_t, friendNum), Q_ARG(bool, video));
|
2015-10-24 20:10:28 +08:00
|
|
|
|
|
|
|
threadSwitchLock.clear(std::memory_order_release);
|
2015-10-08 06:52:05 +08:00
|
|
|
return ret;
|
2015-10-08 00:56:19 +08:00
|
|
|
}
|
|
|
|
|
2015-10-08 06:52:05 +08:00
|
|
|
qDebug() << QString("Starting call with %1").arg(friendNum);
|
|
|
|
if(calls.contains(friendNum))
|
2015-05-14 10:46:28 +08:00
|
|
|
{
|
2015-10-08 06:52:05 +08:00
|
|
|
qWarning() << QString("Can't start call with %1, we're already in this call!").arg(friendNum);
|
|
|
|
return false;
|
2015-05-14 10:46:28 +08:00
|
|
|
}
|
2015-04-24 19:01:50 +08:00
|
|
|
|
2015-10-08 06:52:05 +08:00
|
|
|
uint32_t videoBitrate = video ? VIDEO_DEFAULT_BITRATE : 0;
|
|
|
|
if (!toxav_call(toxav, friendNum, AUDIO_DEFAULT_BITRATE, videoBitrate, nullptr))
|
|
|
|
return false;
|
|
|
|
|
2015-10-24 07:53:10 +08:00
|
|
|
auto call = calls.insert({friendNum, video, *this});
|
|
|
|
call->startTimeout();
|
2015-10-08 06:52:05 +08:00
|
|
|
return true;
|
2015-09-28 01:59:26 +08:00
|
|
|
}
|
|
|
|
|
2015-10-08 06:52:05 +08:00
|
|
|
bool CoreAV::cancelCall(uint32_t friendNum)
|
2015-09-28 01:59:26 +08:00
|
|
|
{
|
2015-10-08 06:52:05 +08:00
|
|
|
if (QThread::currentThread() != coreavThread.get())
|
2015-10-08 02:11:19 +08:00
|
|
|
{
|
2015-10-24 20:10:28 +08:00
|
|
|
if (threadSwitchLock.test_and_set(std::memory_order_acquire))
|
|
|
|
{
|
|
|
|
qDebug() << "CoreAV::cancelCall: Backed off of thread-switch lock";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2015-10-08 06:52:05 +08:00
|
|
|
bool ret;
|
2015-10-24 20:10:28 +08:00
|
|
|
QMetaObject::invokeMethod(this, "cancelCall", Qt::BlockingQueuedConnection,
|
2015-10-08 06:52:05 +08:00
|
|
|
Q_RETURN_ARG(bool, ret), Q_ARG(uint32_t, friendNum));
|
2015-10-24 20:10:28 +08:00
|
|
|
|
|
|
|
threadSwitchLock.clear(std::memory_order_release);
|
2015-10-08 06:52:05 +08:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
qDebug() << QString("Cancelling call with %1").arg(friendNum);
|
|
|
|
if (!toxav_call_control(toxav, friendNum, TOXAV_CALL_CONTROL_CANCEL, nullptr))
|
|
|
|
{
|
|
|
|
qWarning() << QString("Failed to cancel call with %1").arg(friendNum);
|
|
|
|
return false;
|
2015-10-08 02:11:19 +08:00
|
|
|
}
|
2015-10-08 06:52:05 +08:00
|
|
|
calls.remove(friendNum);
|
|
|
|
return true;
|
2014-08-28 20:30:38 +08:00
|
|
|
}
|
|
|
|
|
2015-10-24 07:53:10 +08:00
|
|
|
void CoreAV::timeoutCall(uint32_t friendNum)
|
|
|
|
{
|
2015-10-24 20:10:28 +08:00
|
|
|
// Non-blocking switch to the CoreAV thread, we really don't want to be coming
|
|
|
|
// blocking-queued from the UI thread while we emit blocking-queued to it
|
2015-10-24 07:53:10 +08:00
|
|
|
if (QThread::currentThread() != coreavThread.get())
|
|
|
|
{
|
2015-10-24 20:10:28 +08:00
|
|
|
QMetaObject::invokeMethod(this, "timeoutCall", Qt::QueuedConnection,
|
|
|
|
Q_ARG(uint32_t, friendNum));
|
2015-10-24 07:53:10 +08:00
|
|
|
return;
|
|
|
|
}
|
2015-10-24 20:10:28 +08:00
|
|
|
|
2015-10-24 07:53:10 +08:00
|
|
|
if (!cancelCall(friendNum))
|
|
|
|
{
|
|
|
|
qWarning() << QString("Failed to timeout call with %1").arg(friendNum);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
qDebug() << "Call with friend"<<friendNum<<"timed out";
|
|
|
|
emit avEnd(friendNum);
|
|
|
|
}
|
|
|
|
|
2015-10-05 08:36:50 +08:00
|
|
|
bool CoreAV::sendCallAudio(uint32_t callId)
|
2014-08-28 20:30:38 +08:00
|
|
|
{
|
2015-09-28 01:59:26 +08:00
|
|
|
if (!calls.contains(callId))
|
2015-10-05 08:36:50 +08:00
|
|
|
return false;
|
2014-08-28 23:13:26 +08:00
|
|
|
|
2015-10-05 08:36:50 +08:00
|
|
|
ToxFriendCall& call = calls[callId];
|
2015-09-28 05:16:40 +08:00
|
|
|
|
2015-10-05 08:36:50 +08:00
|
|
|
if (call.muteMic || call.inactive
|
2015-09-28 05:16:40 +08:00
|
|
|
|| !(call.state & TOXAV_FRIEND_CALL_STATE_ACCEPTING_A)
|
|
|
|
|| !Audio::getInstance().isInputReady())
|
2014-08-28 23:46:45 +08:00
|
|
|
{
|
2015-10-05 08:36:50 +08:00
|
|
|
return true;
|
2014-08-28 23:46:45 +08:00
|
|
|
}
|
|
|
|
|
2015-09-28 05:16:40 +08:00
|
|
|
int16_t buf[AUDIO_FRAME_SAMPLE_COUNT * AUDIO_CHANNELS] = {0};
|
|
|
|
if (Audio::getInstance().tryCaptureSamples(buf, AUDIO_FRAME_SAMPLE_COUNT))
|
2014-08-28 23:13:26 +08:00
|
|
|
{
|
2015-02-18 13:20:31 +08:00
|
|
|
#ifdef QTOX_FILTER_AUDIO
|
2015-03-12 10:13:18 +08:00
|
|
|
if (Settings::getInstance().getFilterAudio())
|
2015-02-18 14:25:25 +08:00
|
|
|
{
|
2015-09-28 05:16:40 +08:00
|
|
|
if (!call.filterer)
|
2015-03-12 10:13:18 +08:00
|
|
|
{
|
2015-09-28 05:16:40 +08:00
|
|
|
call.filterer = new AudioFilterer();
|
|
|
|
call.filterer->startFilter(AUDIO_SAMPLE_RATE);
|
2015-03-12 10:13:18 +08:00
|
|
|
}
|
2015-02-18 14:25:25 +08:00
|
|
|
|
2015-12-08 11:52:19 +08:00
|
|
|
#ifdef ALC_LOOPBACK_CAPTURE_SAMPLES
|
|
|
|
// compatibility with older versions of OpenAL
|
|
|
|
Audio::getInstance().getEchoesToFilter(call.filterer, AUDIO_FRAME_SAMPLE_COUNT * AUDIO_CHANNELS);
|
|
|
|
#endif
|
2015-12-05 01:20:47 +08:00
|
|
|
call.filterer->filterAudio(buf, AUDIO_FRAME_SAMPLE_COUNT * AUDIO_CHANNELS);
|
2015-02-18 14:25:25 +08:00
|
|
|
}
|
2015-09-28 05:16:40 +08:00
|
|
|
else if (call.filterer)
|
2015-03-12 10:13:18 +08:00
|
|
|
{
|
2015-09-28 05:16:40 +08:00
|
|
|
delete call.filterer;
|
|
|
|
call.filterer = nullptr;
|
2015-03-12 10:13:18 +08:00
|
|
|
}
|
2015-02-18 13:20:31 +08:00
|
|
|
#endif
|
|
|
|
|
2015-12-05 10:22:00 +08:00
|
|
|
// TOXAV_ERR_SEND_FRAME_SYNC means toxav failed to lock, retry 5 times in this case
|
2015-12-05 09:35:11 +08:00
|
|
|
TOXAV_ERR_SEND_FRAME err;
|
2015-12-05 10:22:00 +08:00
|
|
|
int retries = 0;
|
|
|
|
do {
|
|
|
|
if (!toxav_audio_send_frame(toxav, callId, buf, AUDIO_FRAME_SAMPLE_COUNT,
|
|
|
|
AUDIO_CHANNELS, AUDIO_SAMPLE_RATE, &err))
|
|
|
|
{
|
|
|
|
if (err == TOXAV_ERR_SEND_FRAME_SYNC)
|
|
|
|
{
|
|
|
|
retries++;
|
|
|
|
QThread::usleep(500);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
qDebug() << "toxav_audio_send_frame error: "<<err;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} while (err == TOXAV_ERR_SEND_FRAME_SYNC && retries < 5);
|
|
|
|
if (err == TOXAV_ERR_SEND_FRAME_SYNC)
|
|
|
|
qDebug() << "toxav_audio_send_frame error: Lock busy, dropping frame";
|
2014-08-28 23:13:26 +08:00
|
|
|
}
|
2015-09-28 05:16:40 +08:00
|
|
|
|
2015-10-05 08:36:50 +08:00
|
|
|
return true;
|
2014-08-28 20:30:38 +08:00
|
|
|
}
|
|
|
|
|
2015-10-05 08:36:50 +08:00
|
|
|
void CoreAV::sendCallVideo(uint32_t callId, shared_ptr<VideoFrame> vframe)
|
2014-08-28 20:30:38 +08:00
|
|
|
{
|
2015-10-08 03:53:22 +08:00
|
|
|
// We might be running in the FFmpeg thread and holding the CameraSource lock
|
|
|
|
// So be careful not to deadlock with anything while toxav locks in toxav_video_send_frame
|
2015-09-28 07:04:39 +08:00
|
|
|
if (!calls.contains(callId))
|
|
|
|
return;
|
|
|
|
|
2015-10-05 08:36:50 +08:00
|
|
|
ToxFriendCall& call = calls[callId];
|
2015-09-28 07:04:39 +08:00
|
|
|
|
2015-10-05 08:36:50 +08:00
|
|
|
if (!call.videoEnabled || call.inactive
|
2015-09-28 07:04:39 +08:00
|
|
|
|| !(call.state & TOXAV_FRIEND_CALL_STATE_ACCEPTING_V))
|
2014-08-28 20:30:38 +08:00
|
|
|
return;
|
|
|
|
|
2015-10-24 10:26:29 +08:00
|
|
|
if (call.nullVideoBitrate)
|
|
|
|
{
|
|
|
|
qDebug() << "Restarting video stream to friend"<<callId;
|
|
|
|
toxav_bit_rate_set(toxav, call.callId, -1, VIDEO_DEFAULT_BITRATE, nullptr);
|
|
|
|
call.nullVideoBitrate = false;
|
|
|
|
}
|
|
|
|
|
2015-05-14 10:46:28 +08:00
|
|
|
// This frame shares vframe's buffers, we don't call vpx_img_free but just delete it
|
|
|
|
vpx_image* frame = vframe->toVpxImage();
|
|
|
|
if (frame->fmt == VPX_IMG_FMT_NONE)
|
2014-08-28 20:30:38 +08:00
|
|
|
{
|
2015-05-14 10:46:28 +08:00
|
|
|
qWarning() << "Invalid frame";
|
|
|
|
delete frame;
|
|
|
|
return;
|
2014-08-28 20:30:38 +08:00
|
|
|
}
|
2015-05-14 10:46:28 +08:00
|
|
|
|
2015-12-04 19:53:58 +08:00
|
|
|
// TOXAV_ERR_SEND_FRAME_SYNC means toxav failed to lock, retry 5 times in this case
|
|
|
|
// We don't want to be dropping iframes because of some lock held by toxav_iterate
|
|
|
|
TOXAV_ERR_SEND_FRAME err;
|
|
|
|
int retries = 0;
|
|
|
|
do {
|
|
|
|
if (!toxav_video_send_frame(toxav, callId, frame->d_w, frame->d_h,
|
|
|
|
frame->planes[0], frame->planes[1], frame->planes[2], &err))
|
|
|
|
{
|
|
|
|
if (err == TOXAV_ERR_SEND_FRAME_SYNC)
|
|
|
|
{
|
|
|
|
retries++;
|
|
|
|
QThread::usleep(500);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
qDebug() << "toxav_video_send_frame error: "<<err;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} while (err == TOXAV_ERR_SEND_FRAME_SYNC && retries < 5);
|
|
|
|
if (err == TOXAV_ERR_SEND_FRAME_SYNC)
|
|
|
|
qDebug() << "toxav_video_send_frame error: Lock busy, dropping frame";
|
2015-05-14 10:46:28 +08:00
|
|
|
|
|
|
|
delete frame;
|
2014-08-28 20:30:38 +08:00
|
|
|
}
|
|
|
|
|
2015-09-28 01:59:26 +08:00
|
|
|
void CoreAV::micMuteToggle(uint32_t callId)
|
2014-08-28 20:30:38 +08:00
|
|
|
{
|
2015-09-28 01:59:26 +08:00
|
|
|
if (calls.contains(callId))
|
2014-09-22 04:33:07 +08:00
|
|
|
calls[callId].muteMic = !calls[callId].muteMic;
|
2014-08-28 20:30:38 +08:00
|
|
|
}
|
|
|
|
|
2015-09-28 01:59:26 +08:00
|
|
|
void CoreAV::volMuteToggle(uint32_t callId)
|
2014-10-28 20:50:30 +08:00
|
|
|
{
|
2015-09-28 01:59:26 +08:00
|
|
|
if (calls.contains(callId))
|
2014-10-28 20:50:30 +08:00
|
|
|
calls[callId].muteVol = !calls[callId].muteVol;
|
|
|
|
}
|
|
|
|
|
2015-09-28 01:59:26 +08:00
|
|
|
VideoSource *CoreAV::getVideoSourceFromCall(int friendNum)
|
2014-10-15 20:46:01 +08:00
|
|
|
{
|
2015-10-25 03:41:19 +08:00
|
|
|
if (!calls.contains(friendNum))
|
|
|
|
{
|
|
|
|
qWarning() << "CoreAV::getVideoSourceFromCall: No such call, did it die before we finished answering?";
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2015-09-28 01:59:26 +08:00
|
|
|
return calls[friendNum].videoSource;
|
2014-10-15 20:46:01 +08:00
|
|
|
}
|
|
|
|
|
2015-06-26 19:00:16 +08:00
|
|
|
void CoreAV::joinGroupCall(int groupId)
|
2014-11-13 19:18:04 +08:00
|
|
|
{
|
2015-05-11 20:54:03 +08:00
|
|
|
qDebug() << QString("Joining group call %1").arg(groupId);
|
2015-10-05 08:36:50 +08:00
|
|
|
|
|
|
|
auto call = groupCalls.insert({groupId, *this});
|
|
|
|
call->inactive = false;
|
2014-11-13 19:18:04 +08:00
|
|
|
}
|
|
|
|
|
2015-06-26 19:00:16 +08:00
|
|
|
void CoreAV::leaveGroupCall(int groupId)
|
2014-11-13 19:18:04 +08:00
|
|
|
{
|
2015-05-11 20:54:03 +08:00
|
|
|
qDebug() << QString("Leaving group call %1").arg(groupId);
|
2015-10-05 08:36:50 +08:00
|
|
|
|
|
|
|
groupCalls.remove(groupId);
|
2014-11-13 19:18:04 +08:00
|
|
|
}
|
|
|
|
|
2015-10-05 08:36:50 +08:00
|
|
|
bool CoreAV::sendGroupCallAudio(int groupId)
|
2014-11-13 19:18:04 +08:00
|
|
|
{
|
2015-10-05 08:36:50 +08:00
|
|
|
if (!groupCalls.contains(groupId))
|
|
|
|
return false;
|
2014-11-13 19:18:04 +08:00
|
|
|
|
2015-10-05 08:36:50 +08:00
|
|
|
ToxGroupCall& call = groupCalls[groupId];
|
2014-11-13 19:18:04 +08:00
|
|
|
|
2015-10-05 08:36:50 +08:00
|
|
|
if (call.inactive || call.muteMic || !Audio::getInstance().isInputReady())
|
|
|
|
return true;
|
2014-11-13 19:18:04 +08:00
|
|
|
|
2015-10-05 08:36:50 +08:00
|
|
|
int16_t buf[AUDIO_FRAME_SAMPLE_COUNT * AUDIO_CHANNELS] = {0};
|
|
|
|
if (Audio::getInstance().tryCaptureSamples(buf, AUDIO_FRAME_SAMPLE_COUNT))
|
2014-11-13 19:18:04 +08:00
|
|
|
{
|
2015-10-05 08:36:50 +08:00
|
|
|
#ifdef QTOX_FILTER_AUDIO
|
|
|
|
if (Settings::getInstance().getFilterAudio())
|
2014-11-13 19:18:04 +08:00
|
|
|
{
|
2015-10-05 08:36:50 +08:00
|
|
|
if (!call.filterer)
|
|
|
|
{
|
|
|
|
call.filterer = new AudioFilterer();
|
|
|
|
call.filterer->startFilter(AUDIO_SAMPLE_RATE);
|
|
|
|
}
|
|
|
|
|
2015-12-08 11:52:19 +08:00
|
|
|
#ifdef ALC_LOOPBACK_CAPTURE_SAMPLES
|
|
|
|
Audio::getInstance().getEchoesToFilter(call.filterer, AUDIO_FRAME_SAMPLE_COUNT);
|
|
|
|
#endif
|
2015-10-05 08:36:50 +08:00
|
|
|
}
|
|
|
|
else if (call.filterer)
|
|
|
|
{
|
|
|
|
delete call.filterer;
|
|
|
|
call.filterer = nullptr;
|
2014-11-13 19:18:04 +08:00
|
|
|
}
|
2015-06-26 19:00:16 +08:00
|
|
|
#endif
|
2015-10-05 08:36:50 +08:00
|
|
|
|
|
|
|
if (toxav_group_send_audio(toxav_get_tox(toxav), groupId, buf, AUDIO_FRAME_SAMPLE_COUNT,
|
|
|
|
AUDIO_CHANNELS, AUDIO_SAMPLE_RATE) != 0)
|
|
|
|
qDebug() << "toxav_group_send_audio error";
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
2014-11-11 23:05:01 +08:00
|
|
|
}
|
2014-11-13 20:11:23 +08:00
|
|
|
|
2015-06-26 19:00:16 +08:00
|
|
|
void CoreAV::disableGroupCallMic(int groupId)
|
2014-11-13 20:11:23 +08:00
|
|
|
{
|
|
|
|
groupCalls[groupId].muteMic = true;
|
|
|
|
}
|
|
|
|
|
2015-06-26 19:00:16 +08:00
|
|
|
void CoreAV::disableGroupCallVol(int groupId)
|
2014-11-13 20:11:23 +08:00
|
|
|
{
|
|
|
|
groupCalls[groupId].muteVol = true;
|
|
|
|
}
|
|
|
|
|
2015-06-26 19:00:16 +08:00
|
|
|
void CoreAV::enableGroupCallMic(int groupId)
|
2014-11-13 20:11:23 +08:00
|
|
|
{
|
|
|
|
groupCalls[groupId].muteMic = false;
|
|
|
|
}
|
|
|
|
|
2015-06-26 19:00:16 +08:00
|
|
|
void CoreAV::enableGroupCallVol(int groupId)
|
2014-11-13 20:11:23 +08:00
|
|
|
{
|
|
|
|
groupCalls[groupId].muteVol = false;
|
|
|
|
}
|
2014-11-14 02:20:06 +08:00
|
|
|
|
2015-09-28 01:59:26 +08:00
|
|
|
bool CoreAV::isGroupCallMicEnabled(int groupId) const
|
2014-11-14 02:20:06 +08:00
|
|
|
{
|
|
|
|
return !groupCalls[groupId].muteMic;
|
|
|
|
}
|
|
|
|
|
2015-09-28 01:59:26 +08:00
|
|
|
bool CoreAV::isGroupCallVolEnabled(int groupId) const
|
2014-11-14 02:20:06 +08:00
|
|
|
{
|
|
|
|
return !groupCalls[groupId].muteVol;
|
|
|
|
}
|
2015-06-26 19:00:16 +08:00
|
|
|
|
2015-09-28 01:59:26 +08:00
|
|
|
bool CoreAV::isGroupAvEnabled(int groupId) const
|
2015-06-26 19:00:16 +08:00
|
|
|
{
|
|
|
|
return tox_group_get_type(Core::getInstance()->tox, groupId) == TOX_GROUPCHAT_TYPE_AV;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CoreAV::resetCallSources()
|
|
|
|
{
|
|
|
|
for (ToxGroupCall& call : groupCalls)
|
|
|
|
{
|
2015-10-05 08:36:50 +08:00
|
|
|
if (call.alSource)
|
|
|
|
{
|
2015-12-11 07:16:21 +08:00
|
|
|
Audio::getInstance().deleteSource(call.alSource);
|
|
|
|
Audio::getInstance().createSource(&call.alSource);
|
2015-10-05 08:36:50 +08:00
|
|
|
}
|
2015-06-26 19:00:16 +08:00
|
|
|
}
|
|
|
|
|
2015-10-05 08:36:50 +08:00
|
|
|
for (ToxFriendCall& call : calls)
|
2015-06-26 19:00:16 +08:00
|
|
|
{
|
2015-09-28 01:59:26 +08:00
|
|
|
if (call.alSource)
|
2015-06-26 19:00:16 +08:00
|
|
|
{
|
2015-12-11 07:16:21 +08:00
|
|
|
Audio::getInstance().deleteSource(call.alSource);
|
|
|
|
Audio::getInstance().createSource(&call.alSource);
|
2015-06-26 19:00:16 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-09-28 01:59:26 +08:00
|
|
|
|
2015-10-24 10:26:29 +08:00
|
|
|
void CoreAV::sendNoVideo()
|
|
|
|
{
|
|
|
|
// We don't change the audio bitrate, but we signal that we're not sending video anymore
|
|
|
|
qDebug() << "CoreAV: Signaling end of video sending";
|
|
|
|
for (ToxFriendCall& call : calls)
|
|
|
|
{
|
|
|
|
toxav_bit_rate_set(toxav, call.callId, -1, 0, nullptr);
|
|
|
|
call.nullVideoBitrate = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-10-08 00:56:19 +08:00
|
|
|
void CoreAV::callCallback(ToxAV* toxav, uint32_t friendNum, bool audio, bool video, void *_self)
|
2015-09-28 01:59:26 +08:00
|
|
|
{
|
|
|
|
CoreAV* self = static_cast<CoreAV*>(_self);
|
2015-10-08 06:52:05 +08:00
|
|
|
|
Fix theoritical A/V deadlock
Could only be hit by pausing at a key point in a debugger until the call timed-out.
Having one thread going up the call stack and acquiring locks (toxcore callbacks), while another thread goes down taking locks in the other order (CoreAV calling toxav functions) creates some pretty freezy situations.
The deadlock was caused by the GUI thread calling the CoreAV thread, acquiring the CoreAV callback, then right before calling a toxav function, not schedule the thread until the call times out. At this point the toxcore thread fires its state callback to tell us the call is over, locking internal toxcore/toxav mutexes, it reaches our callback function which tries to switch to the CoreAV thread to clean up the call data structures, but has to wait since the CoreAV thread holds its own lock. At this point if we resume the CoreAV thread, it'll be busy calling into a toxav function, which tries to acquire internal toxav locks, those locks are held by the toxcore callback so we deadlock.
Our solution is that when getting a toxcore callback, we immediately switch to a temporary thread, allowing toxcore to release the locks it held, and that temporary thread tries to switch to do work on call data structures. Meanwhile if the CoreAV thread needs internal toxcore locks, it can get them.
2015-11-08 07:32:32 +08:00
|
|
|
// Run this slow callback asynchronously on the AV thread to avoid deadlocks with what our caller (toxcore) holds
|
|
|
|
// Also run the code to switch to the CoreAV thread in yet another thread, in case CoreAV
|
|
|
|
// has threadSwitchLock and wants a toxcore lock that our call stack is holding...
|
2015-10-08 06:52:05 +08:00
|
|
|
if (QThread::currentThread() != self->coreavThread.get())
|
|
|
|
{
|
Fix theoritical A/V deadlock
Could only be hit by pausing at a key point in a debugger until the call timed-out.
Having one thread going up the call stack and acquiring locks (toxcore callbacks), while another thread goes down taking locks in the other order (CoreAV calling toxav functions) creates some pretty freezy situations.
The deadlock was caused by the GUI thread calling the CoreAV thread, acquiring the CoreAV callback, then right before calling a toxav function, not schedule the thread until the call times out. At this point the toxcore thread fires its state callback to tell us the call is over, locking internal toxcore/toxav mutexes, it reaches our callback function which tries to switch to the CoreAV thread to clean up the call data structures, but has to wait since the CoreAV thread holds its own lock. At this point if we resume the CoreAV thread, it'll be busy calling into a toxav function, which tries to acquire internal toxav locks, those locks are held by the toxcore callback so we deadlock.
Our solution is that when getting a toxcore callback, we immediately switch to a temporary thread, allowing toxcore to release the locks it held, and that temporary thread tries to switch to do work on call data structures. Meanwhile if the CoreAV thread needs internal toxcore locks, it can get them.
2015-11-08 07:32:32 +08:00
|
|
|
QtConcurrent::run([=](){
|
|
|
|
// We assume the original caller doesn't come from the CoreAV thread here
|
|
|
|
while (self->threadSwitchLock.test_and_set(std::memory_order_acquire))
|
|
|
|
QThread::yieldCurrentThread(); // Shouldn't spin for long, we have priority
|
|
|
|
|
|
|
|
QMetaObject::invokeMethod(self, "callCallback", Qt::QueuedConnection,
|
|
|
|
Q_ARG(ToxAV*, toxav), Q_ARG(uint32_t, friendNum),
|
|
|
|
Q_ARG(bool, audio), Q_ARG(bool, video), Q_ARG(void*, _self));
|
|
|
|
});
|
|
|
|
return;
|
2015-10-08 06:52:05 +08:00
|
|
|
}
|
|
|
|
|
2015-10-08 00:56:19 +08:00
|
|
|
if (self->calls.contains(friendNum))
|
|
|
|
{
|
Fix theoritical A/V deadlock
Could only be hit by pausing at a key point in a debugger until the call timed-out.
Having one thread going up the call stack and acquiring locks (toxcore callbacks), while another thread goes down taking locks in the other order (CoreAV calling toxav functions) creates some pretty freezy situations.
The deadlock was caused by the GUI thread calling the CoreAV thread, acquiring the CoreAV callback, then right before calling a toxav function, not schedule the thread until the call times out. At this point the toxcore thread fires its state callback to tell us the call is over, locking internal toxcore/toxav mutexes, it reaches our callback function which tries to switch to the CoreAV thread to clean up the call data structures, but has to wait since the CoreAV thread holds its own lock. At this point if we resume the CoreAV thread, it'll be busy calling into a toxav function, which tries to acquire internal toxav locks, those locks are held by the toxcore callback so we deadlock.
Our solution is that when getting a toxcore callback, we immediately switch to a temporary thread, allowing toxcore to release the locks it held, and that temporary thread tries to switch to do work on call data structures. Meanwhile if the CoreAV thread needs internal toxcore locks, it can get them.
2015-11-08 07:32:32 +08:00
|
|
|
/// Hanging up from a callback is supposed to be UB,
|
2015-10-10 07:24:04 +08:00
|
|
|
/// but since currently the toxav callbacks are fired from the toxcore thread,
|
|
|
|
/// we'll always reach this point through a non-blocking queud connection, so not in the callback.
|
2015-10-08 00:56:19 +08:00
|
|
|
qWarning() << QString("Rejecting call invite from %1, we're already in that call!").arg(friendNum);
|
|
|
|
toxav_call_control(toxav, friendNum, TOXAV_CALL_CONTROL_CANCEL, nullptr);
|
|
|
|
return;
|
|
|
|
}
|
2015-10-08 06:52:05 +08:00
|
|
|
qDebug() << QString("Received call invite from %1").arg(friendNum);
|
2015-10-08 00:56:19 +08:00
|
|
|
const auto& callIt = self->calls.insert({friendNum, video, *self});
|
2015-09-28 05:16:40 +08:00
|
|
|
|
|
|
|
// We don't get a state callback when answering, so fill the state ourselves in advance
|
|
|
|
int state = 0;
|
|
|
|
if (audio)
|
|
|
|
state |= TOXAV_FRIEND_CALL_STATE_SENDING_A | TOXAV_FRIEND_CALL_STATE_ACCEPTING_A;
|
|
|
|
if (video)
|
|
|
|
state |= TOXAV_FRIEND_CALL_STATE_SENDING_V | TOXAV_FRIEND_CALL_STATE_ACCEPTING_V;
|
|
|
|
callIt->state = static_cast<TOXAV_FRIEND_CALL_STATE>(state);
|
|
|
|
|
2015-09-28 01:59:26 +08:00
|
|
|
emit reinterpret_cast<CoreAV*>(self)->avInvite(friendNum, video);
|
2015-10-24 20:10:28 +08:00
|
|
|
self->threadSwitchLock.clear(std::memory_order_release);
|
2015-09-28 01:59:26 +08:00
|
|
|
}
|
|
|
|
|
2015-10-08 06:52:05 +08:00
|
|
|
void CoreAV::stateCallback(ToxAV* toxav, uint32_t friendNum, uint32_t state, void *_self)
|
2015-09-28 01:59:26 +08:00
|
|
|
{
|
|
|
|
CoreAV* self = static_cast<CoreAV*>(_self);
|
|
|
|
|
Fix theoritical A/V deadlock
Could only be hit by pausing at a key point in a debugger until the call timed-out.
Having one thread going up the call stack and acquiring locks (toxcore callbacks), while another thread goes down taking locks in the other order (CoreAV calling toxav functions) creates some pretty freezy situations.
The deadlock was caused by the GUI thread calling the CoreAV thread, acquiring the CoreAV callback, then right before calling a toxav function, not schedule the thread until the call times out. At this point the toxcore thread fires its state callback to tell us the call is over, locking internal toxcore/toxav mutexes, it reaches our callback function which tries to switch to the CoreAV thread to clean up the call data structures, but has to wait since the CoreAV thread holds its own lock. At this point if we resume the CoreAV thread, it'll be busy calling into a toxav function, which tries to acquire internal toxav locks, those locks are held by the toxcore callback so we deadlock.
Our solution is that when getting a toxcore callback, we immediately switch to a temporary thread, allowing toxcore to release the locks it held, and that temporary thread tries to switch to do work on call data structures. Meanwhile if the CoreAV thread needs internal toxcore locks, it can get them.
2015-11-08 07:32:32 +08:00
|
|
|
// Run this slow callback asynchronously on the AV thread to avoid deadlocks with what our caller (toxcore) holds
|
|
|
|
// Also run the code to switch to the CoreAV thread in yet another thread, in case CoreAV
|
|
|
|
// has threadSwitchLock and wants a toxcore lock that our call stack is holding...
|
2015-10-08 06:52:05 +08:00
|
|
|
if (QThread::currentThread() != self->coreavThread.get())
|
|
|
|
{
|
Fix theoritical A/V deadlock
Could only be hit by pausing at a key point in a debugger until the call timed-out.
Having one thread going up the call stack and acquiring locks (toxcore callbacks), while another thread goes down taking locks in the other order (CoreAV calling toxav functions) creates some pretty freezy situations.
The deadlock was caused by the GUI thread calling the CoreAV thread, acquiring the CoreAV callback, then right before calling a toxav function, not schedule the thread until the call times out. At this point the toxcore thread fires its state callback to tell us the call is over, locking internal toxcore/toxav mutexes, it reaches our callback function which tries to switch to the CoreAV thread to clean up the call data structures, but has to wait since the CoreAV thread holds its own lock. At this point if we resume the CoreAV thread, it'll be busy calling into a toxav function, which tries to acquire internal toxav locks, those locks are held by the toxcore callback so we deadlock.
Our solution is that when getting a toxcore callback, we immediately switch to a temporary thread, allowing toxcore to release the locks it held, and that temporary thread tries to switch to do work on call data structures. Meanwhile if the CoreAV thread needs internal toxcore locks, it can get them.
2015-11-08 07:32:32 +08:00
|
|
|
QtConcurrent::run([=](){
|
|
|
|
// We assume the original caller doesn't come from the CoreAV thread here
|
|
|
|
while (self->threadSwitchLock.test_and_set(std::memory_order_acquire))
|
|
|
|
QThread::yieldCurrentThread(); // Shouldn't spin for long, we have priority
|
|
|
|
|
|
|
|
QMetaObject::invokeMethod(self, "stateCallback", Qt::QueuedConnection,
|
2015-10-08 06:52:05 +08:00
|
|
|
Q_ARG(ToxAV*, toxav), Q_ARG(uint32_t, friendNum),
|
|
|
|
Q_ARG(uint32_t, state), Q_ARG(void*, _self));
|
Fix theoritical A/V deadlock
Could only be hit by pausing at a key point in a debugger until the call timed-out.
Having one thread going up the call stack and acquiring locks (toxcore callbacks), while another thread goes down taking locks in the other order (CoreAV calling toxav functions) creates some pretty freezy situations.
The deadlock was caused by the GUI thread calling the CoreAV thread, acquiring the CoreAV callback, then right before calling a toxav function, not schedule the thread until the call times out. At this point the toxcore thread fires its state callback to tell us the call is over, locking internal toxcore/toxav mutexes, it reaches our callback function which tries to switch to the CoreAV thread to clean up the call data structures, but has to wait since the CoreAV thread holds its own lock. At this point if we resume the CoreAV thread, it'll be busy calling into a toxav function, which tries to acquire internal toxav locks, those locks are held by the toxcore callback so we deadlock.
Our solution is that when getting a toxcore callback, we immediately switch to a temporary thread, allowing toxcore to release the locks it held, and that temporary thread tries to switch to do work on call data structures. Meanwhile if the CoreAV thread needs internal toxcore locks, it can get them.
2015-11-08 07:32:32 +08:00
|
|
|
});
|
|
|
|
return;
|
2015-10-08 06:52:05 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if(!self->calls.contains(friendNum))
|
|
|
|
{
|
|
|
|
qWarning() << QString("stateCallback called, but call %1 is already dead").arg(friendNum);
|
2015-10-25 06:17:22 +08:00
|
|
|
self->threadSwitchLock.clear(std::memory_order_release);
|
2015-10-08 06:52:05 +08:00
|
|
|
return;
|
|
|
|
}
|
2015-10-05 08:36:50 +08:00
|
|
|
ToxFriendCall& call = self->calls[friendNum];
|
2015-09-28 01:59:26 +08:00
|
|
|
|
|
|
|
if (state & TOXAV_FRIEND_CALL_STATE_ERROR)
|
|
|
|
{
|
2015-10-08 03:53:22 +08:00
|
|
|
qWarning() << "Call with friend"<<friendNum<<"died of unnatural causes!";
|
2015-09-28 01:59:26 +08:00
|
|
|
calls.remove(friendNum);
|
2015-10-08 06:52:05 +08:00
|
|
|
emit self->avEnd(friendNum);
|
2015-09-28 01:59:26 +08:00
|
|
|
}
|
|
|
|
else if (state & TOXAV_FRIEND_CALL_STATE_FINISHED)
|
|
|
|
{
|
2015-10-08 06:52:05 +08:00
|
|
|
qDebug() << "Call with friend"<<friendNum<<"finished quietly";
|
2015-09-28 01:59:26 +08:00
|
|
|
calls.remove(friendNum);
|
|
|
|
emit self->avEnd(friendNum);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2015-10-05 08:36:50 +08:00
|
|
|
// If our state was null, we started the call and were still ringing
|
2015-09-28 01:59:26 +08:00
|
|
|
if (!call.state && state)
|
2015-09-28 05:16:40 +08:00
|
|
|
{
|
2015-10-24 07:53:10 +08:00
|
|
|
call.stopTimeout();
|
2015-10-05 08:36:50 +08:00
|
|
|
call.inactive = false;
|
2015-09-28 01:59:26 +08:00
|
|
|
emit self->avStart(friendNum, call.videoEnabled);
|
2015-09-28 05:16:40 +08:00
|
|
|
}
|
2015-10-24 10:26:29 +08:00
|
|
|
else if ((call.state & TOXAV_FRIEND_CALL_STATE_SENDING_V)
|
|
|
|
&& !(state & TOXAV_FRIEND_CALL_STATE_SENDING_V))
|
|
|
|
{
|
|
|
|
qDebug() << "Friend"<<friendNum<<"stopped sending video";
|
2015-11-06 19:41:15 +08:00
|
|
|
if (call.videoSource)
|
|
|
|
call.videoSource->stopSource();
|
2015-10-24 10:26:29 +08:00
|
|
|
}
|
|
|
|
else if (!(call.state & TOXAV_FRIEND_CALL_STATE_SENDING_V)
|
|
|
|
&& (state & TOXAV_FRIEND_CALL_STATE_SENDING_V))
|
|
|
|
{
|
|
|
|
// Workaround toxav sometimes firing callbacks for "send last frame" -> "stop sending video"
|
|
|
|
// out of orders (even though they were sent in order by the other end).
|
|
|
|
// We simply stop the videoSource from emitting anything while the other end says it's not sending
|
2015-11-06 19:41:15 +08:00
|
|
|
if (call.videoSource)
|
|
|
|
call.videoSource->restartSource();
|
2015-10-24 10:26:29 +08:00
|
|
|
}
|
2015-09-28 01:59:26 +08:00
|
|
|
|
|
|
|
call.state = static_cast<TOXAV_FRIEND_CALL_STATE>(state);
|
|
|
|
}
|
2015-10-25 06:17:22 +08:00
|
|
|
self->threadSwitchLock.clear(std::memory_order_release);
|
2015-09-28 01:59:26 +08:00
|
|
|
}
|
|
|
|
|
2015-10-12 22:29:46 +08:00
|
|
|
void CoreAV::bitrateCallback(ToxAV* toxav, uint32_t friendNum, uint32_t arate, uint32_t vrate, void *_self)
|
2015-09-28 01:59:26 +08:00
|
|
|
{
|
2015-10-08 06:52:05 +08:00
|
|
|
CoreAV* self = static_cast<CoreAV*>(_self);
|
|
|
|
|
|
|
|
// Run this slow path callback asynchronously on the AV thread to avoid deadlocks
|
|
|
|
if (QThread::currentThread() != self->coreavThread.get())
|
|
|
|
{
|
2015-10-12 22:29:46 +08:00
|
|
|
return (void)QMetaObject::invokeMethod(self, "bitrateCallback", Qt::QueuedConnection,
|
2015-10-08 06:52:05 +08:00
|
|
|
Q_ARG(ToxAV*, toxav), Q_ARG(uint32_t, friendNum),
|
2015-10-12 22:29:46 +08:00
|
|
|
Q_ARG(uint32_t, arate), Q_ARG(uint32_t, vrate), Q_ARG(void*, _self));
|
2015-10-08 06:52:05 +08:00
|
|
|
}
|
|
|
|
|
2015-11-15 06:51:30 +08:00
|
|
|
qDebug() << "Recommended bitrate with"<<friendNum<<" is now "<<arate<<"/"<<vrate<<", ignoring it";
|
2015-09-28 01:59:26 +08:00
|
|
|
}
|
|
|
|
|
2015-09-29 02:43:17 +08:00
|
|
|
void CoreAV::audioFrameCallback(ToxAV *, uint32_t friendNum, const int16_t *pcm,
|
2015-09-28 05:16:40 +08:00
|
|
|
size_t sampleCount, uint8_t channels, uint32_t samplingRate, void *_self)
|
2015-09-28 01:59:26 +08:00
|
|
|
{
|
2015-09-28 05:16:40 +08:00
|
|
|
CoreAV* self = static_cast<CoreAV*>(_self);
|
|
|
|
if (!self->calls.contains(friendNum))
|
|
|
|
return;
|
|
|
|
|
|
|
|
ToxCall& call = self->calls[friendNum];
|
|
|
|
|
|
|
|
if (call.muteVol)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (!call.alSource)
|
2015-12-11 07:16:21 +08:00
|
|
|
Audio::getInstance().createSource(&call.alSource);
|
2015-09-28 05:16:40 +08:00
|
|
|
|
2015-12-11 07:16:21 +08:00
|
|
|
Audio::getInstance().playAudioBuffer(call.alSource, pcm, sampleCount, channels, samplingRate);
|
2015-09-28 01:59:26 +08:00
|
|
|
}
|
|
|
|
|
2015-09-29 02:43:17 +08:00
|
|
|
void CoreAV::videoFrameCallback(ToxAV *, uint32_t friendNum, uint16_t w, uint16_t h,
|
|
|
|
const uint8_t *y, const uint8_t *u, const uint8_t *v,
|
|
|
|
int32_t ystride, int32_t ustride, int32_t vstride, void *)
|
2015-09-28 01:59:26 +08:00
|
|
|
{
|
2015-09-28 07:04:39 +08:00
|
|
|
if (!calls.contains(friendNum))
|
|
|
|
return;
|
|
|
|
|
2015-11-06 19:41:15 +08:00
|
|
|
ToxFriendCall& call = calls[friendNum];
|
|
|
|
if (!call.videoSource)
|
|
|
|
return;
|
|
|
|
|
2015-09-28 07:04:39 +08:00
|
|
|
vpx_image frame;
|
|
|
|
frame.d_h = h;
|
|
|
|
frame.d_w = w;
|
|
|
|
frame.planes[0] = const_cast<uint8_t*>(y);
|
|
|
|
frame.planes[1] = const_cast<uint8_t*>(u);
|
|
|
|
frame.planes[2] = const_cast<uint8_t*>(v);
|
|
|
|
frame.stride[0] = ystride;
|
|
|
|
frame.stride[1] = ustride;
|
|
|
|
frame.stride[2] = vstride;
|
|
|
|
|
|
|
|
call.videoSource->pushFrame(&frame);
|
2015-09-28 01:59:26 +08:00
|
|
|
}
|