2014-09-02 02:45:48 +08:00
|
|
|
/*
|
2019-06-24 22:01:18 +08:00
|
|
|
Copyright © 2013 by Maxim Biro <nurupo.contributions@gmail.com>
|
|
|
|
Copyright © 2014-2019 by The qTox Project Contributors
|
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
|
|
|
|
2019-06-24 22:01:18 +08:00
|
|
|
qTox is libre software: you can redistribute it and/or modify
|
2014-09-02 02:45:48 +08:00
|
|
|
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
|
|
|
*/
|
|
|
|
|
2015-06-26 19:00:16 +08:00
|
|
|
#include "coreav.h"
|
2020-08-28 06:49:54 +08:00
|
|
|
#include "audio/iaudiosettings.h"
|
2017-10-28 20:17:14 +08:00
|
|
|
#include "core.h"
|
2017-07-30 01:39:14 +08:00
|
|
|
#include "src/model/friend.h"
|
|
|
|
#include "src/model/group.h"
|
2020-08-28 06:49:54 +08:00
|
|
|
#include "src/persistence/igroupsettings.h"
|
2016-12-19 10:26:26 +08:00
|
|
|
#include "src/video/corevideosource.h"
|
2017-02-26 19:52:45 +08:00
|
|
|
#include "src/video/videoframe.h"
|
2021-06-06 16:29:44 +08:00
|
|
|
#include "util/compatiblerecursivemutex.h"
|
2017-02-26 19:52:45 +08:00
|
|
|
#include <QCoreApplication>
|
|
|
|
#include <QDebug>
|
2015-10-08 03:53:22 +08:00
|
|
|
#include <QThread>
|
|
|
|
#include <QTimer>
|
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>
|
2017-02-26 19:52:45 +08:00
|
|
|
#include <cassert>
|
2015-09-28 01:59:26 +08:00
|
|
|
|
2016-07-27 06:21:22 +08:00
|
|
|
/**
|
2016-08-01 16:20:56 +08:00
|
|
|
* @fn void CoreAV::avInvite(uint32_t friendId, bool video)
|
|
|
|
* @brief Sent when a friend calls us.
|
|
|
|
* @param friendId Id of friend in call list.
|
|
|
|
* @param video False if chat is audio only, true audio and video.
|
|
|
|
*
|
|
|
|
* @fn void CoreAV::avStart(uint32_t friendId, bool video)
|
|
|
|
* @brief Sent when a call we initiated has started.
|
|
|
|
* @param friendId Id of friend in call list.
|
|
|
|
* @param video False if chat is audio only, true audio and video.
|
|
|
|
*
|
|
|
|
* @fn void CoreAV::avEnd(uint32_t friendId)
|
|
|
|
* @brief Sent when a call was ended by the peer.
|
|
|
|
* @param friendId Id of friend in call list.
|
|
|
|
*
|
|
|
|
* @var CoreAV::VIDEO_DEFAULT_BITRATE
|
|
|
|
* @brief Picked at random by fair dice roll.
|
|
|
|
*/
|
2016-07-27 06:21:22 +08:00
|
|
|
|
|
|
|
/**
|
2016-08-01 16:20:56 +08:00
|
|
|
* @var std::atomic_flag CoreAV::threadSwitchLock
|
2017-02-26 19:52:45 +08:00
|
|
|
* @brief This flag is to be acquired before switching in a blocking way between the UI and CoreAV
|
|
|
|
* thread.
|
2016-08-01 16:20:56 +08:00
|
|
|
*
|
2017-02-26 19:52:45 +08:00
|
|
|
* The CoreAV thread must have priority for the flag, other threads should back off or release it
|
|
|
|
* quickly.
|
2016-08-01 16:20:56 +08:00
|
|
|
* CoreAV needs to interface with three threads, the toxcore/Core thread that fires non-payload
|
|
|
|
* toxav callbacks, the toxav/CoreAV thread that fires AV payload callbacks and manages
|
|
|
|
* most of CoreAV's members, and the UI thread, which calls our [start/answer/cancel]Call functions
|
|
|
|
* and which we call via signals.
|
|
|
|
* When the UI calls us, we switch from the UI thread to the CoreAV thread to do the processing,
|
|
|
|
* when toxcore fires a non-payload av callback, we do the processing in the CoreAV thread and then
|
2017-02-26 19:52:45 +08:00
|
|
|
* switch to the UI thread to send it a signal. Both switches block both threads, so this would
|
|
|
|
* deadlock.
|
2016-08-01 16:20:56 +08:00
|
|
|
*/
|
2016-07-27 06:21:22 +08:00
|
|
|
|
2021-06-06 16:29:44 +08:00
|
|
|
CoreAV::CoreAV(std::unique_ptr<ToxAV, ToxAVDeleter> toxav, CompatibleRecursiveMutex& toxCoreLock,
|
2020-08-28 06:49:54 +08:00
|
|
|
IAudioSettings& _audioSettings, IGroupSettings& _groupSettings)
|
2019-05-19 01:08:58 +08:00
|
|
|
: audio{nullptr}
|
|
|
|
, toxav{std::move(toxav)}
|
2018-07-14 20:30:34 +08:00
|
|
|
, coreavThread{new QThread{this}}
|
2017-02-26 19:52:45 +08:00
|
|
|
, iterateTimer{new QTimer{this}}
|
2019-11-17 06:16:51 +08:00
|
|
|
, coreLock{toxCoreLock}
|
2020-08-28 06:49:54 +08:00
|
|
|
, audioSettings{_audioSettings}
|
|
|
|
, groupSettings{_groupSettings}
|
2014-08-28 21:46:11 +08:00
|
|
|
{
|
2018-10-07 19:43:39 +08:00
|
|
|
assert(coreavThread);
|
|
|
|
assert(iterateTimer);
|
|
|
|
|
2015-10-08 03:53:22 +08:00
|
|
|
coreavThread->setObjectName("qTox CoreAV");
|
|
|
|
moveToThread(coreavThread.get());
|
|
|
|
|
2018-07-14 20:30:34 +08:00
|
|
|
connectCallbacks(*this->toxav);
|
|
|
|
|
2015-10-08 03:53:22 +08:00
|
|
|
iterateTimer->setSingleShot(true);
|
2018-10-07 19:43:39 +08:00
|
|
|
|
|
|
|
connect(iterateTimer, &QTimer::timeout, this, &CoreAV::process);
|
|
|
|
connect(coreavThread.get(), &QThread::finished, iterateTimer, &QTimer::stop);
|
2019-11-14 22:24:20 +08:00
|
|
|
connect(coreavThread.get(), &QThread::started, this, &CoreAV::process);
|
2018-07-14 20:30:34 +08:00
|
|
|
}
|
2015-09-28 01:59:26 +08:00
|
|
|
|
2019-05-19 01:08:58 +08:00
|
|
|
void CoreAV::connectCallbacks(ToxAV& toxav)
|
|
|
|
{
|
2018-07-14 20:30:34 +08:00
|
|
|
toxav_callback_call(&toxav, CoreAV::callCallback, this);
|
|
|
|
toxav_callback_call_state(&toxav, CoreAV::stateCallback, this);
|
|
|
|
toxav_callback_audio_bit_rate(&toxav, CoreAV::audioBitrateCallback, this);
|
|
|
|
toxav_callback_video_bit_rate(&toxav, CoreAV::videoBitrateCallback, this);
|
|
|
|
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
|
|
|
|
2018-07-14 20:30:34 +08:00
|
|
|
/**
|
|
|
|
* @brief Factory method for CoreAV
|
|
|
|
* @param core pointer to the Tox instance
|
|
|
|
* @return CoreAV instance on success, {} on failure
|
|
|
|
*/
|
2021-06-06 16:29:44 +08:00
|
|
|
CoreAV::CoreAVPtr CoreAV::makeCoreAV(Tox* core, CompatibleRecursiveMutex& toxCoreLock,
|
2020-08-28 06:49:54 +08:00
|
|
|
IAudioSettings& audioSettings, IGroupSettings& groupSettings)
|
2018-07-14 20:30:34 +08:00
|
|
|
{
|
2020-03-23 16:11:09 +08:00
|
|
|
Toxav_Err_New err;
|
2018-07-14 20:30:34 +08:00
|
|
|
std::unique_ptr<ToxAV, ToxAVDeleter> toxav{toxav_new(core, &err)};
|
|
|
|
switch (err) {
|
|
|
|
case TOXAV_ERR_NEW_OK:
|
|
|
|
break;
|
|
|
|
case TOXAV_ERR_NEW_MALLOC:
|
2019-09-14 13:48:26 +08:00
|
|
|
qCritical() << "Failed to allocate resources for ToxAV";
|
2018-07-14 20:30:34 +08:00
|
|
|
return {};
|
|
|
|
case TOXAV_ERR_NEW_MULTIPLE:
|
|
|
|
qCritical() << "Attempted to create multiple ToxAV instances";
|
|
|
|
return {};
|
|
|
|
case TOXAV_ERR_NEW_NULL:
|
|
|
|
qCritical() << "Unexpected NULL parameter";
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
assert(toxav != nullptr);
|
|
|
|
|
2020-08-28 06:49:54 +08:00
|
|
|
return CoreAVPtr{new CoreAV{std::move(toxav), toxCoreLock, audioSettings, groupSettings}};
|
2014-08-28 21:46:11 +08:00
|
|
|
}
|
|
|
|
|
2019-05-19 01:08:58 +08:00
|
|
|
/**
|
|
|
|
* @brief Set the audio backend
|
|
|
|
* @param audio The audio backend to use
|
|
|
|
* @note This must be called before starting CoreAV and audio must outlive CoreAV
|
|
|
|
*/
|
|
|
|
void CoreAV::setAudio(IAudioControl& newAudio)
|
|
|
|
{
|
|
|
|
audio.exchange(&newAudio);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief Get the audio backend used
|
|
|
|
* @return Pointer to the audio backend
|
|
|
|
* @note This is needed only for the case CoreAV needs to restart and the restarting class doesn't
|
|
|
|
* have access to the audio backend and wants to keep it the same.
|
|
|
|
*/
|
|
|
|
IAudioControl* CoreAV::getAudio()
|
|
|
|
{
|
|
|
|
return audio;
|
|
|
|
}
|
|
|
|
|
2015-09-28 01:59:26 +08:00
|
|
|
CoreAV::~CoreAV()
|
2015-01-25 11:57:20 +08:00
|
|
|
{
|
2020-01-21 06:19:08 +08:00
|
|
|
/* Gracefully leave calls and group calls to avoid deadlocks in destructor */
|
2017-10-28 01:35:25 +08:00
|
|
|
for (const auto& call : calls) {
|
2017-10-27 06:49:36 +08:00
|
|
|
cancelCall(call.first);
|
2017-10-28 01:35:25 +08:00
|
|
|
}
|
2020-01-21 06:19:08 +08:00
|
|
|
for (const auto& call : groupCalls) {
|
|
|
|
leaveGroupCall(call.first);
|
|
|
|
}
|
|
|
|
|
|
|
|
assert(calls.empty());
|
|
|
|
assert(groupCalls.empty());
|
2018-07-26 03:17:00 +08:00
|
|
|
|
2015-10-25 20:53:04 +08:00
|
|
|
coreavThread->exit(0);
|
2018-07-26 03:17:00 +08:00
|
|
|
coreavThread->wait();
|
2015-09-28 01:59:26 +08:00
|
|
|
}
|
2015-06-26 19:00:16 +08:00
|
|
|
|
2016-07-27 06:21:22 +08:00
|
|
|
/**
|
2016-08-01 16:20:56 +08:00
|
|
|
* @brief Starts the CoreAV main loop that calls toxav's main loop
|
|
|
|
*/
|
2015-10-08 03:53:22 +08:00
|
|
|
void CoreAV::start()
|
|
|
|
{
|
2019-11-14 22:24:20 +08:00
|
|
|
coreavThread->start();
|
2015-10-08 03:53:22 +08:00
|
|
|
}
|
|
|
|
|
2015-09-28 01:59:26 +08:00
|
|
|
void CoreAV::process()
|
|
|
|
{
|
2019-11-14 22:47:33 +08:00
|
|
|
assert(QThread::currentThread() == coreavThread.get());
|
2018-07-14 20:30:34 +08:00
|
|
|
toxav_iterate(toxav.get());
|
|
|
|
iterateTimer->start(toxav_iteration_interval(toxav.get()));
|
2014-08-28 21:46:11 +08:00
|
|
|
}
|
|
|
|
|
2017-01-10 05:29:06 +08:00
|
|
|
/**
|
|
|
|
* @brief Checks the call status for a Tox friend.
|
|
|
|
* @param f the friend to check
|
|
|
|
* @return true, if call is started for the friend, false otherwise
|
|
|
|
*/
|
|
|
|
bool CoreAV::isCallStarted(const Friend* f) const
|
|
|
|
{
|
2019-12-03 04:26:49 +08:00
|
|
|
QReadLocker locker{&callsLock};
|
2017-10-27 06:49:36 +08:00
|
|
|
return f && (calls.find(f->getId()) != calls.end());
|
2017-01-10 05:29:06 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief Checks the call status for a Tox group.
|
|
|
|
* @param g the group to check
|
|
|
|
* @return true, if call is started for the group, false otherwise
|
|
|
|
*/
|
|
|
|
bool CoreAV::isCallStarted(const Group* g) const
|
|
|
|
{
|
2019-12-03 04:26:49 +08:00
|
|
|
QReadLocker locker{&callsLock};
|
2017-10-27 06:49:36 +08:00
|
|
|
return g && (groupCalls.find(g->getId()) != groupCalls.end());
|
2017-01-10 05:29:06 +08:00
|
|
|
}
|
|
|
|
|
2016-11-09 14:20:47 +08:00
|
|
|
/**
|
|
|
|
* @brief Checks the call status for a Tox friend.
|
|
|
|
* @param f the friend to check
|
|
|
|
* @return true, if call is active for the friend, false otherwise
|
|
|
|
*/
|
|
|
|
bool CoreAV::isCallActive(const Friend* f) const
|
2014-08-28 20:30:38 +08:00
|
|
|
{
|
2019-12-03 04:26:49 +08:00
|
|
|
QReadLocker locker{&callsLock};
|
2018-03-16 13:32:54 +08:00
|
|
|
auto it = calls.find(f->getId());
|
|
|
|
if (it == calls.end()) {
|
|
|
|
return false;
|
|
|
|
}
|
2019-01-28 04:47:28 +08:00
|
|
|
return isCallStarted(f) && it->second->isActive();
|
2016-11-09 14:20:47 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief Checks the call status for a Tox group.
|
|
|
|
* @param g the group to check
|
|
|
|
* @return true, if the call is active for the group, false otherwise
|
|
|
|
*/
|
|
|
|
bool CoreAV::isCallActive(const Group* g) const
|
|
|
|
{
|
2019-12-03 04:26:49 +08:00
|
|
|
QReadLocker locker{&callsLock};
|
2018-03-16 13:32:54 +08:00
|
|
|
auto it = groupCalls.find(g->getId());
|
|
|
|
if (it == groupCalls.end()) {
|
|
|
|
return false;
|
|
|
|
}
|
2019-01-28 04:47:28 +08:00
|
|
|
return isCallStarted(g) && it->second->isActive();
|
2016-11-09 14:20:47 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
bool CoreAV::isCallVideoEnabled(const Friend* f) const
|
|
|
|
{
|
2019-12-03 04:26:49 +08:00
|
|
|
QReadLocker locker{&callsLock};
|
2018-03-16 13:32:54 +08:00
|
|
|
auto it = calls.find(f->getId());
|
2019-01-28 04:47:28 +08:00
|
|
|
return isCallStarted(f) && it->second->getVideoEnabled();
|
2015-10-08 06:52:05 +08:00
|
|
|
}
|
|
|
|
|
2017-09-13 04:29:02 +08:00
|
|
|
bool CoreAV::answerCall(uint32_t friendNum, bool video)
|
2015-10-08 06:52:05 +08:00
|
|
|
{
|
2019-12-03 04:26:49 +08:00
|
|
|
QWriteLocker locker{&callsLock};
|
2019-11-17 06:16:51 +08:00
|
|
|
QMutexLocker coreLocker{&coreLock};
|
2019-11-14 22:47:33 +08:00
|
|
|
|
2019-09-14 13:48:26 +08:00
|
|
|
qDebug() << QString("Answering call %1").arg(friendNum);
|
2018-03-16 13:32:54 +08:00
|
|
|
auto it = calls.find(friendNum);
|
|
|
|
assert(it != calls.end());
|
2020-03-23 16:11:09 +08:00
|
|
|
Toxav_Err_Answer err;
|
2017-09-13 04:29:02 +08:00
|
|
|
|
|
|
|
const uint32_t videoBitrate = video ? VIDEO_DEFAULT_BITRATE : 0;
|
2020-08-28 06:49:54 +08:00
|
|
|
if (toxav_answer(toxav.get(), friendNum, audioSettings.getAudioBitrate(),
|
2019-05-19 01:08:58 +08:00
|
|
|
videoBitrate, &err)) {
|
2019-01-28 04:47:28 +08:00
|
|
|
it->second->setActive(true);
|
2015-10-08 06:52:05 +08:00
|
|
|
return true;
|
2017-02-26 19:52:45 +08:00
|
|
|
} else {
|
|
|
|
qWarning() << "Failed to answer call with error" << err;
|
2018-07-14 20:30:34 +08:00
|
|
|
toxav_call_control(toxav.get(), friendNum, TOXAV_CALL_CONTROL_CANCEL, nullptr);
|
2018-03-16 13:32:54 +08:00
|
|
|
calls.erase(it);
|
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
|
|
|
{
|
2019-12-03 04:26:49 +08:00
|
|
|
QWriteLocker locker{&callsLock};
|
2019-11-17 06:16:51 +08:00
|
|
|
QMutexLocker coreLocker{&coreLock};
|
2019-11-14 22:47:33 +08:00
|
|
|
|
2015-10-08 06:52:05 +08:00
|
|
|
qDebug() << QString("Starting call with %1").arg(friendNum);
|
2018-03-16 13:32:54 +08:00
|
|
|
auto it = calls.find(friendNum);
|
|
|
|
if (it != calls.end()) {
|
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;
|
2020-08-28 06:49:54 +08:00
|
|
|
if (!toxav_call(toxav.get(), friendNum, audioSettings.getAudioBitrate(), videoBitrate,
|
2019-05-19 01:08:58 +08:00
|
|
|
nullptr))
|
2015-10-08 06:52:05 +08:00
|
|
|
return false;
|
|
|
|
|
2019-05-19 01:08:58 +08:00
|
|
|
// Audio backend must be set before making a call
|
|
|
|
assert(audio != nullptr);
|
|
|
|
ToxFriendCallPtr call = ToxFriendCallPtr(new ToxFriendCall(friendNum, video, *this, *audio));
|
2020-03-16 19:40:17 +08:00
|
|
|
// Call object must be owned by this thread or there will be locking problems with Audio
|
|
|
|
call->moveToThread(this->thread());
|
2019-01-28 04:47:28 +08:00
|
|
|
assert(call != nullptr);
|
2019-11-17 06:16:51 +08:00
|
|
|
calls.emplace(friendNum, std::move(call));
|
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
|
|
|
{
|
2019-12-03 04:26:49 +08:00
|
|
|
QWriteLocker locker{&callsLock};
|
2019-11-17 06:16:51 +08:00
|
|
|
QMutexLocker coreLocker{&coreLock};
|
2019-11-14 22:47:33 +08:00
|
|
|
|
2015-10-08 06:52:05 +08:00
|
|
|
qDebug() << QString("Cancelling call with %1").arg(friendNum);
|
2018-07-14 20:30:34 +08:00
|
|
|
if (!toxav_call_control(toxav.get(), friendNum, TOXAV_CALL_CONTROL_CANCEL, nullptr)) {
|
2015-10-08 06:52:05 +08:00
|
|
|
qWarning() << QString("Failed to cancel call with %1").arg(friendNum);
|
|
|
|
return false;
|
2015-10-08 02:11:19 +08:00
|
|
|
}
|
2016-11-09 14:20:47 +08:00
|
|
|
|
2017-10-27 06:49:36 +08:00
|
|
|
calls.erase(friendNum);
|
2019-12-03 04:26:49 +08:00
|
|
|
locker.unlock();
|
|
|
|
|
2016-11-09 14:20:47 +08:00
|
|
|
emit avEnd(friendNum);
|
2015-10-08 06:52:05 +08:00
|
|
|
return true;
|
2014-08-28 20:30:38 +08:00
|
|
|
}
|
|
|
|
|
2015-10-24 07:53:10 +08:00
|
|
|
void CoreAV::timeoutCall(uint32_t friendNum)
|
|
|
|
{
|
2019-12-03 04:26:49 +08:00
|
|
|
QWriteLocker locker{&callsLock};
|
2015-10-24 20:10:28 +08:00
|
|
|
|
2017-02-26 19:52:45 +08:00
|
|
|
if (!cancelCall(friendNum)) {
|
2015-10-24 07:53:10 +08:00
|
|
|
qWarning() << QString("Failed to timeout call with %1").arg(friendNum);
|
|
|
|
return;
|
|
|
|
}
|
2017-02-26 19:52:45 +08:00
|
|
|
qDebug() << "Call with friend" << friendNum << "timed out";
|
2015-10-24 07:53:10 +08:00
|
|
|
}
|
|
|
|
|
2016-07-27 06:21:22 +08:00
|
|
|
/**
|
2016-08-01 16:20:56 +08:00
|
|
|
* @brief Send audio frame to a friend
|
|
|
|
* @param callId Id of friend in call list.
|
|
|
|
* @param pcm An array of audio samples (Pulse-code modulation).
|
|
|
|
* @param samples Number of samples in this frame.
|
|
|
|
* @param chans Number of audio channels.
|
|
|
|
* @param rate Audio sampling rate used in this frame.
|
|
|
|
* @return False only on error, but not if there's nothing to send.
|
|
|
|
*/
|
2017-02-26 19:52:45 +08:00
|
|
|
bool CoreAV::sendCallAudio(uint32_t callId, const int16_t* pcm, size_t samples, uint8_t chans,
|
2018-03-16 13:32:54 +08:00
|
|
|
uint32_t rate) const
|
2014-08-28 20:30:38 +08:00
|
|
|
{
|
2019-12-03 04:26:49 +08:00
|
|
|
QReadLocker locker{&callsLock};
|
2019-11-14 21:48:34 +08:00
|
|
|
|
2018-03-16 13:32:54 +08:00
|
|
|
auto it = calls.find(callId);
|
|
|
|
if (it == calls.end()) {
|
2015-10-05 08:36:50 +08:00
|
|
|
return false;
|
2017-10-28 01:35:25 +08:00
|
|
|
}
|
2014-08-28 23:13:26 +08:00
|
|
|
|
2019-01-28 04:47:28 +08:00
|
|
|
ToxFriendCall const& call = *it->second;
|
2015-09-28 05:16:40 +08:00
|
|
|
|
2017-11-02 10:58:23 +08:00
|
|
|
if (call.getMuteMic() || !call.isActive()
|
2017-10-28 20:17:14 +08:00
|
|
|
|| !(call.getState() & TOXAV_FRIEND_CALL_STATE_ACCEPTING_A)) {
|
2015-10-05 08:36:50 +08:00
|
|
|
return true;
|
2014-08-28 23:46:45 +08:00
|
|
|
}
|
|
|
|
|
2016-01-22 03:17:18 +08:00
|
|
|
// TOXAV_ERR_SEND_FRAME_SYNC means toxav failed to lock, retry 5 times in this case
|
2020-03-23 16:11:09 +08:00
|
|
|
Toxav_Err_Send_Frame err;
|
2016-01-22 03:17:18 +08:00
|
|
|
int retries = 0;
|
|
|
|
do {
|
2018-07-14 20:30:34 +08:00
|
|
|
if (!toxav_audio_send_frame(toxav.get(), callId, pcm, samples, chans, rate, &err)) {
|
2017-02-26 19:52:45 +08:00
|
|
|
if (err == TOXAV_ERR_SEND_FRAME_SYNC) {
|
2016-10-30 06:48:03 +08:00
|
|
|
++retries;
|
2016-01-22 03:17:18 +08:00
|
|
|
QThread::usleep(500);
|
2017-02-26 19:52:45 +08:00
|
|
|
} else {
|
|
|
|
qDebug() << "toxav_audio_send_frame error: " << err;
|
2015-12-05 10:22:00 +08:00
|
|
|
}
|
2016-01-22 03:17:18 +08:00
|
|
|
}
|
|
|
|
} while (err == TOXAV_ERR_SEND_FRAME_SYNC && retries < 5);
|
2017-10-28 01:35:25 +08:00
|
|
|
if (err == TOXAV_ERR_SEND_FRAME_SYNC) {
|
2016-01-22 03:17:18 +08:00
|
|
|
qDebug() << "toxav_audio_send_frame error: Lock busy, dropping frame";
|
2017-10-28 01:35:25 +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
|
|
|
}
|
|
|
|
|
2016-06-14 04:41:07 +08:00
|
|
|
void CoreAV::sendCallVideo(uint32_t callId, std::shared_ptr<VideoFrame> vframe)
|
2014-08-28 20:30:38 +08:00
|
|
|
{
|
2020-01-28 06:44:19 +08:00
|
|
|
QWriteLocker locker{&callsLock};
|
2019-11-14 21:48:34 +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
|
2018-03-16 13:32:54 +08:00
|
|
|
auto it = calls.find(callId);
|
|
|
|
if (it == calls.end()) {
|
2015-09-28 07:04:39 +08:00
|
|
|
return;
|
2017-10-28 01:35:25 +08:00
|
|
|
}
|
2015-09-28 07:04:39 +08:00
|
|
|
|
2019-01-28 04:47:28 +08:00
|
|
|
ToxFriendCall& call = *it->second;
|
2015-09-28 07:04:39 +08:00
|
|
|
|
2017-11-02 10:58:23 +08:00
|
|
|
if (!call.getVideoEnabled() || !call.isActive()
|
2017-10-28 20:17:14 +08:00
|
|
|
|| !(call.getState() & TOXAV_FRIEND_CALL_STATE_ACCEPTING_V)) {
|
2014-08-28 20:30:38 +08:00
|
|
|
return;
|
2017-10-28 01:35:25 +08:00
|
|
|
}
|
2014-08-28 20:30:38 +08:00
|
|
|
|
2017-11-02 10:58:23 +08:00
|
|
|
if (call.getNullVideoBitrate()) {
|
2017-02-26 19:52:45 +08:00
|
|
|
qDebug() << "Restarting video stream to friend" << callId;
|
2019-11-17 06:16:51 +08:00
|
|
|
QMutexLocker coreLocker{&coreLock};
|
2018-07-14 20:30:34 +08:00
|
|
|
toxav_video_set_bit_rate(toxav.get(), callId, VIDEO_DEFAULT_BITRATE, nullptr);
|
2017-11-02 10:58:23 +08:00
|
|
|
call.setNullVideoBitrate(false);
|
2015-10-24 10:26:29 +08:00
|
|
|
}
|
|
|
|
|
2016-08-05 08:43:15 +08:00
|
|
|
ToxYUVFrame frame = vframe->toToxYUVFrame();
|
2016-04-24 08:51:54 +08:00
|
|
|
|
2017-02-26 19:52:45 +08:00
|
|
|
if (!frame) {
|
2016-04-24 08:51:54 +08:00
|
|
|
return;
|
|
|
|
}
|
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
|
2020-03-23 16:11:09 +08:00
|
|
|
Toxav_Err_Send_Frame err;
|
2015-12-04 19:53:58 +08:00
|
|
|
int retries = 0;
|
|
|
|
do {
|
2019-05-19 01:08:58 +08:00
|
|
|
if (!toxav_video_send_frame(toxav.get(), callId, frame.width, frame.height, frame.y,
|
|
|
|
frame.u, frame.v, &err)) {
|
2017-02-26 19:52:45 +08:00
|
|
|
if (err == TOXAV_ERR_SEND_FRAME_SYNC) {
|
2016-10-30 06:48:03 +08:00
|
|
|
++retries;
|
2015-12-04 19:53:58 +08:00
|
|
|
QThread::usleep(500);
|
2017-02-26 19:52:45 +08:00
|
|
|
} else {
|
|
|
|
qDebug() << "toxav_video_send_frame error: " << err;
|
2015-12-04 19:53:58 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} while (err == TOXAV_ERR_SEND_FRAME_SYNC && retries < 5);
|
2017-10-28 01:35:25 +08:00
|
|
|
if (err == TOXAV_ERR_SEND_FRAME_SYNC) {
|
2015-12-04 19:53:58 +08:00
|
|
|
qDebug() << "toxav_video_send_frame error: Lock busy, dropping frame";
|
2017-10-28 01:35:25 +08:00
|
|
|
}
|
2014-08-28 20:30:38 +08:00
|
|
|
}
|
|
|
|
|
2016-11-09 14:20:47 +08:00
|
|
|
/**
|
|
|
|
* @brief Toggles the mute state of the call's input (microphone).
|
|
|
|
* @param f The friend assigned to the call
|
|
|
|
*/
|
|
|
|
void CoreAV::toggleMuteCallInput(const Friend* f)
|
2014-08-28 20:30:38 +08:00
|
|
|
{
|
2020-01-28 06:44:19 +08:00
|
|
|
QWriteLocker locker{&callsLock};
|
2019-11-14 21:48:34 +08:00
|
|
|
|
2018-03-16 13:32:54 +08:00
|
|
|
auto it = calls.find(f->getId());
|
|
|
|
if (f && (it != calls.end())) {
|
2019-01-28 04:47:28 +08:00
|
|
|
ToxCall& call = *it->second;
|
2017-10-28 01:35:25 +08:00
|
|
|
call.setMuteMic(!call.getMuteMic());
|
2016-11-09 14:20:47 +08:00
|
|
|
}
|
2014-08-28 20:30:38 +08:00
|
|
|
}
|
|
|
|
|
2016-11-09 14:20:47 +08:00
|
|
|
/**
|
|
|
|
* @brief Toggles the mute state of the call's output (speaker).
|
|
|
|
* @param f The friend assigned to the call
|
|
|
|
*/
|
|
|
|
void CoreAV::toggleMuteCallOutput(const Friend* f)
|
2014-10-28 20:50:30 +08:00
|
|
|
{
|
2020-01-28 06:44:19 +08:00
|
|
|
QWriteLocker locker{&callsLock};
|
2019-11-14 21:48:34 +08:00
|
|
|
|
2018-03-16 13:32:54 +08:00
|
|
|
auto it = calls.find(f->getId());
|
|
|
|
if (f && (it != calls.end())) {
|
2019-01-28 04:47:28 +08:00
|
|
|
ToxCall& call = *it->second;
|
2017-11-02 10:58:23 +08:00
|
|
|
call.setMuteVol(!call.getMuteVol());
|
2016-11-09 14:20:47 +08:00
|
|
|
}
|
2014-10-28 20:50:30 +08:00
|
|
|
}
|
|
|
|
|
2016-04-24 03:11:49 +08:00
|
|
|
/**
|
2016-08-01 16:20:56 +08:00
|
|
|
* @brief Called from Tox API when group call receives audio data.
|
|
|
|
*
|
|
|
|
* @param[in] tox the Tox object
|
|
|
|
* @param[in] group the group number
|
|
|
|
* @param[in] peer the peer number
|
|
|
|
* @param[in] data the audio data to playback
|
|
|
|
* @param[in] samples the audio samples
|
|
|
|
* @param[in] channels the audio channels
|
|
|
|
* @param[in] sample_rate the audio sample rate
|
|
|
|
* @param[in] core the qTox Core class
|
|
|
|
*/
|
2018-02-21 05:12:14 +08:00
|
|
|
void CoreAV::groupCallCallback(void* tox, uint32_t group, uint32_t peer, const int16_t* data,
|
|
|
|
unsigned samples, uint8_t channels, uint32_t sample_rate, void* core)
|
|
|
|
{
|
2020-01-21 06:19:08 +08:00
|
|
|
/*
|
|
|
|
* Currently group call audio decoding is handled in the Tox thread by c-toxcore,
|
|
|
|
* so we can be sure that this function is always called from the Core thread.
|
|
|
|
* To change this, an API change in c-toxcore is needed and this function probably must be
|
|
|
|
* changed.
|
|
|
|
* See https://github.com/TokTok/c-toxcore/issues/1364 for details.
|
|
|
|
*/
|
|
|
|
|
2020-04-23 07:49:23 +08:00
|
|
|
Q_UNUSED(tox)
|
2018-02-21 05:12:14 +08:00
|
|
|
Core* c = static_cast<Core*>(core);
|
2019-11-14 21:48:34 +08:00
|
|
|
CoreAV* cav = c->getAv();
|
|
|
|
|
2019-12-03 04:26:49 +08:00
|
|
|
QReadLocker locker{&cav->callsLock};
|
2019-11-14 21:48:34 +08:00
|
|
|
|
2018-05-13 17:20:57 +08:00
|
|
|
const ToxPk peerPk = c->getGroupPeerPk(group, peer);
|
|
|
|
// don't play the audio if it comes from a muted peer
|
2020-08-28 06:49:54 +08:00
|
|
|
if (cav->groupSettings.getBlackList().contains(peerPk.toString())) {
|
2018-06-30 18:10:46 +08:00
|
|
|
return;
|
2018-05-13 17:20:57 +08:00
|
|
|
}
|
|
|
|
|
2019-01-29 18:43:49 +08:00
|
|
|
emit c->groupPeerAudioPlaying(group, peerPk);
|
2019-01-24 00:22:14 +08:00
|
|
|
|
2018-03-16 13:32:54 +08:00
|
|
|
auto it = cav->groupCalls.find(group);
|
|
|
|
if (it == cav->groupCalls.end()) {
|
2018-02-21 05:12:14 +08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-01-28 04:47:28 +08:00
|
|
|
ToxGroupCall& call = *it->second;
|
2018-02-21 05:12:14 +08:00
|
|
|
|
|
|
|
if (call.getMuteVol() || !call.isActive()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-04-26 03:30:02 +08:00
|
|
|
call.playAudioBuffer(peerPk, data, samples, channels, sample_rate);
|
2018-02-21 05:12:14 +08:00
|
|
|
}
|
2016-04-24 03:11:49 +08:00
|
|
|
|
2017-02-06 07:34:41 +08:00
|
|
|
/**
|
|
|
|
* @brief Called from core to make sure the source for that peer is invalidated when they leave.
|
|
|
|
* @param group Group Index
|
|
|
|
* @param peer Peer Index
|
|
|
|
*/
|
2020-05-25 04:13:35 +08:00
|
|
|
void CoreAV::invalidateGroupCallPeerSource(const Group& group, ToxPk peerPk)
|
2017-02-26 19:52:45 +08:00
|
|
|
{
|
2020-01-28 06:44:19 +08:00
|
|
|
QWriteLocker locker{&callsLock};
|
2019-11-14 21:48:34 +08:00
|
|
|
|
2020-05-25 04:13:35 +08:00
|
|
|
auto it = groupCalls.find(group.getId());
|
2018-03-16 13:32:54 +08:00
|
|
|
if (it == groupCalls.end()) {
|
|
|
|
return;
|
|
|
|
}
|
2019-01-28 04:47:28 +08:00
|
|
|
it->second->removePeer(peerPk);
|
2018-02-21 05:12:14 +08:00
|
|
|
}
|
|
|
|
|
2016-07-27 06:21:22 +08:00
|
|
|
/**
|
2016-08-01 16:20:56 +08:00
|
|
|
* @brief Get a call's video source.
|
|
|
|
* @param friendNum Id of friend in call list.
|
|
|
|
* @return Video surface to show
|
|
|
|
*/
|
2018-03-16 13:32:54 +08:00
|
|
|
VideoSource* CoreAV::getVideoSourceFromCall(int friendNum) const
|
2014-10-15 20:46:01 +08:00
|
|
|
{
|
2019-12-03 04:26:49 +08:00
|
|
|
QReadLocker locker{&callsLock};
|
2019-11-14 21:48:34 +08:00
|
|
|
|
2018-03-16 13:32:54 +08:00
|
|
|
auto it = calls.find(friendNum);
|
|
|
|
if (it == calls.end()) {
|
2019-09-14 13:48:26 +08:00
|
|
|
qWarning() << "CoreAV::getVideoSourceFromCall: No such call, possibly cancelled";
|
2015-10-25 03:41:19 +08:00
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2019-01-28 04:47:28 +08:00
|
|
|
return it->second->getVideoSource();
|
2014-10-15 20:46:01 +08:00
|
|
|
}
|
|
|
|
|
2016-07-27 06:21:22 +08:00
|
|
|
/**
|
2016-08-01 16:20:56 +08:00
|
|
|
* @brief Starts a call in an existing AV groupchat.
|
|
|
|
* @note Call from the GUI thread.
|
|
|
|
* @param groupId Id of group to join
|
|
|
|
*/
|
2020-02-12 12:45:56 +08:00
|
|
|
void CoreAV::joinGroupCall(const Group& group)
|
2014-11-13 19:18:04 +08:00
|
|
|
{
|
2019-12-03 04:26:49 +08:00
|
|
|
QWriteLocker locker{&callsLock};
|
2019-11-14 21:48:34 +08:00
|
|
|
|
2020-02-12 12:45:56 +08:00
|
|
|
qDebug() << QString("Joining group call %1").arg(group.getId());
|
2015-10-05 08:36:50 +08:00
|
|
|
|
2019-05-19 01:08:58 +08:00
|
|
|
// Audio backend must be set before starting a call
|
|
|
|
assert(audio != nullptr);
|
2020-03-16 19:40:17 +08:00
|
|
|
|
2020-02-12 12:45:56 +08:00
|
|
|
ToxGroupCallPtr groupcall = ToxGroupCallPtr(new ToxGroupCall{group, *this, *audio});
|
2020-03-16 19:40:17 +08:00
|
|
|
// Call Objects must be owned by CoreAV or there will be locking problems with Audio
|
|
|
|
groupcall->moveToThread(this->thread());
|
2019-01-28 04:47:28 +08:00
|
|
|
assert(groupcall != nullptr);
|
2020-02-12 12:45:56 +08:00
|
|
|
auto ret = groupCalls.emplace(group.getId(), std::move(groupcall));
|
2018-03-16 13:32:54 +08:00
|
|
|
if (ret.second == false) {
|
|
|
|
qWarning() << "This group call already exists, not joining!";
|
|
|
|
return;
|
|
|
|
}
|
2019-01-28 04:47:28 +08:00
|
|
|
ret.first->second->setActive(true);
|
2014-11-13 19:18:04 +08:00
|
|
|
}
|
|
|
|
|
2016-07-27 06:21:22 +08:00
|
|
|
/**
|
2016-08-01 16:20:56 +08:00
|
|
|
* @brief Will not leave the group, just stop the call.
|
|
|
|
* @note Call from the GUI thread.
|
|
|
|
* @param groupId Id of group to leave
|
|
|
|
*/
|
2015-06-26 19:00:16 +08:00
|
|
|
void CoreAV::leaveGroupCall(int groupId)
|
2014-11-13 19:18:04 +08:00
|
|
|
{
|
2019-12-03 04:26:49 +08:00
|
|
|
QWriteLocker locker{&callsLock};
|
2019-11-14 21:48:34 +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
|
|
|
|
2017-10-27 06:49:36 +08:00
|
|
|
groupCalls.erase(groupId);
|
2014-11-13 19:18:04 +08:00
|
|
|
}
|
|
|
|
|
2017-02-26 19:52:45 +08:00
|
|
|
bool CoreAV::sendGroupCallAudio(int groupId, const int16_t* pcm, size_t samples, uint8_t chans,
|
2018-03-16 13:32:54 +08:00
|
|
|
uint32_t rate) const
|
2014-11-13 19:18:04 +08:00
|
|
|
{
|
2019-12-03 04:26:49 +08:00
|
|
|
QReadLocker locker{&callsLock};
|
2019-11-14 21:48:34 +08:00
|
|
|
|
2019-01-28 04:47:28 +08:00
|
|
|
std::map<int, ToxGroupCallPtr>::const_iterator it = groupCalls.find(groupId);
|
2018-03-16 13:32:54 +08:00
|
|
|
if (it == groupCalls.end()) {
|
2015-10-05 08:36:50 +08:00
|
|
|
return false;
|
2017-10-27 06:49:36 +08:00
|
|
|
}
|
2014-11-13 19:18:04 +08:00
|
|
|
|
2019-01-28 04:47:28 +08:00
|
|
|
if (!it->second->isActive() || it->second->getMuteMic()) {
|
2015-10-05 08:36:50 +08:00
|
|
|
return true;
|
2017-10-28 01:35:25 +08:00
|
|
|
}
|
2014-11-13 19:18:04 +08:00
|
|
|
|
2018-07-14 20:30:34 +08:00
|
|
|
if (toxav_group_send_audio(toxav_get_tox(toxav.get()), groupId, pcm, samples, chans, rate) != 0)
|
2016-01-22 03:17:18 +08:00
|
|
|
qDebug() << "toxav_group_send_audio error";
|
2015-10-05 08:36:50 +08:00
|
|
|
|
|
|
|
return true;
|
2014-11-11 23:05:01 +08:00
|
|
|
}
|
2014-11-13 20:11:23 +08:00
|
|
|
|
2016-11-09 14:20:47 +08:00
|
|
|
/**
|
|
|
|
* @brief Mutes or unmutes the group call's input (microphone).
|
|
|
|
* @param g The group
|
|
|
|
* @param mute True to mute, false to unmute
|
|
|
|
*/
|
|
|
|
void CoreAV::muteCallInput(const Group* g, bool mute)
|
2014-11-13 20:11:23 +08:00
|
|
|
{
|
2020-01-28 06:44:19 +08:00
|
|
|
QWriteLocker locker{&callsLock};
|
2019-11-14 21:48:34 +08:00
|
|
|
|
2018-03-16 13:32:54 +08:00
|
|
|
auto it = groupCalls.find(g->getId());
|
|
|
|
if (g && (it != groupCalls.end())) {
|
2019-01-28 04:47:28 +08:00
|
|
|
it->second->setMuteMic(mute);
|
2017-10-28 01:35:25 +08:00
|
|
|
}
|
2014-11-13 20:11:23 +08:00
|
|
|
}
|
|
|
|
|
2016-11-09 14:20:47 +08:00
|
|
|
/**
|
|
|
|
* @brief Mutes or unmutes the group call's output (speaker).
|
|
|
|
* @param g The group
|
|
|
|
* @param mute True to mute, false to unmute
|
|
|
|
*/
|
|
|
|
void CoreAV::muteCallOutput(const Group* g, bool mute)
|
2014-11-13 20:11:23 +08:00
|
|
|
{
|
2020-01-28 06:44:19 +08:00
|
|
|
QWriteLocker locker{&callsLock};
|
2019-11-14 21:48:34 +08:00
|
|
|
|
2018-03-16 13:32:54 +08:00
|
|
|
auto it = groupCalls.find(g->getId());
|
|
|
|
if (g && (it != groupCalls.end())) {
|
2019-01-28 04:47:28 +08:00
|
|
|
it->second->setMuteVol(mute);
|
2017-10-28 01:35:25 +08:00
|
|
|
}
|
2014-11-13 20:11:23 +08:00
|
|
|
}
|
|
|
|
|
2016-11-09 14:20:47 +08:00
|
|
|
/**
|
|
|
|
* @brief Returns the group calls input (microphone) state.
|
|
|
|
* @param groupId The group id to check
|
|
|
|
* @return true when muted, false otherwise
|
|
|
|
*/
|
|
|
|
bool CoreAV::isGroupCallInputMuted(const Group* g) const
|
2014-11-13 20:11:23 +08:00
|
|
|
{
|
2019-12-03 04:26:49 +08:00
|
|
|
QReadLocker locker{&callsLock};
|
2019-11-14 21:48:34 +08:00
|
|
|
|
2017-10-28 01:35:25 +08:00
|
|
|
if (!g) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
const uint32_t groupId = g->getId();
|
2018-03-16 13:32:54 +08:00
|
|
|
auto it = groupCalls.find(groupId);
|
2019-01-28 04:47:28 +08:00
|
|
|
return (it != groupCalls.end()) && it->second->getMuteMic();
|
2014-11-13 20:11:23 +08:00
|
|
|
}
|
|
|
|
|
2016-11-09 14:20:47 +08:00
|
|
|
/**
|
|
|
|
* @brief Returns the group calls output (speaker) state.
|
|
|
|
* @param groupId The group id to check
|
|
|
|
* @return true when muted, false otherwise
|
|
|
|
*/
|
|
|
|
bool CoreAV::isGroupCallOutputMuted(const Group* g) const
|
2014-11-13 20:11:23 +08:00
|
|
|
{
|
2019-12-03 04:26:49 +08:00
|
|
|
QReadLocker locker{&callsLock};
|
2019-11-14 21:48:34 +08:00
|
|
|
|
2017-10-28 01:35:25 +08:00
|
|
|
if (!g) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
const uint32_t groupId = g->getId();
|
2018-03-16 13:32:54 +08:00
|
|
|
auto it = groupCalls.find(groupId);
|
2019-01-28 04:47:28 +08:00
|
|
|
return (it != groupCalls.end()) && it->second->getMuteVol();
|
2014-11-13 20:11:23 +08:00
|
|
|
}
|
2014-11-14 02:20:06 +08:00
|
|
|
|
2016-11-09 14:20:47 +08:00
|
|
|
/**
|
|
|
|
* @brief Returns the calls input (microphone) mute state.
|
|
|
|
* @param f The friend to check
|
|
|
|
* @return true when muted, false otherwise
|
|
|
|
*/
|
|
|
|
bool CoreAV::isCallInputMuted(const Friend* f) const
|
2014-11-14 02:20:06 +08:00
|
|
|
{
|
2019-12-03 04:26:49 +08:00
|
|
|
QReadLocker locker{&callsLock};
|
2019-11-14 21:48:34 +08:00
|
|
|
|
2017-10-28 01:35:25 +08:00
|
|
|
if (!f) {
|
2017-10-28 20:17:14 +08:00
|
|
|
return false;
|
2017-10-28 01:35:25 +08:00
|
|
|
}
|
|
|
|
const uint32_t friendId = f->getId();
|
2018-03-16 13:32:54 +08:00
|
|
|
auto it = calls.find(friendId);
|
2019-01-28 04:47:28 +08:00
|
|
|
return (it != calls.end()) && it->second->getMuteMic();
|
2014-11-14 02:20:06 +08:00
|
|
|
}
|
2015-06-26 19:00:16 +08:00
|
|
|
|
2016-07-27 06:21:22 +08:00
|
|
|
/**
|
2016-11-09 14:20:47 +08:00
|
|
|
* @brief Returns the calls output (speaker) mute state.
|
|
|
|
* @param friendId The friend to check
|
|
|
|
* @return true when muted, false otherwise
|
2016-08-01 16:20:56 +08:00
|
|
|
*/
|
2016-11-09 14:20:47 +08:00
|
|
|
bool CoreAV::isCallOutputMuted(const Friend* f) const
|
2015-06-26 19:00:16 +08:00
|
|
|
{
|
2019-12-03 04:26:49 +08:00
|
|
|
QReadLocker locker{&callsLock};
|
2019-11-14 21:48:34 +08:00
|
|
|
|
2017-10-28 01:35:25 +08:00
|
|
|
if (!f) {
|
2017-10-28 20:17:14 +08:00
|
|
|
return false;
|
2017-10-28 01:35:25 +08:00
|
|
|
}
|
|
|
|
const uint32_t friendId = f->getId();
|
2018-03-16 13:32:54 +08:00
|
|
|
auto it = calls.find(friendId);
|
2019-01-28 04:47:28 +08:00
|
|
|
return (it != calls.end()) && it->second->getMuteVol();
|
2015-06-26 19:00:16 +08:00
|
|
|
}
|
|
|
|
|
2016-07-27 06:21:22 +08:00
|
|
|
/**
|
2016-08-01 16:20:56 +08:00
|
|
|
* @brief Signal to all peers that we're not sending video anymore.
|
|
|
|
* @note The next frame sent cancels this.
|
|
|
|
*/
|
2015-10-24 10:26:29 +08:00
|
|
|
void CoreAV::sendNoVideo()
|
|
|
|
{
|
2020-01-28 06:44:19 +08:00
|
|
|
QWriteLocker locker{&callsLock};
|
2019-11-14 21:48:34 +08:00
|
|
|
|
2015-10-24 10:26:29 +08:00
|
|
|
// We don't change the audio bitrate, but we signal that we're not sending video anymore
|
|
|
|
qDebug() << "CoreAV: Signaling end of video sending";
|
2017-10-27 06:49:36 +08:00
|
|
|
for (auto& kv : calls) {
|
2019-01-28 04:47:28 +08:00
|
|
|
ToxFriendCall& call = *kv.second;
|
2018-07-14 20:30:34 +08:00
|
|
|
toxav_video_set_bit_rate(toxav.get(), kv.first, 0, nullptr);
|
2017-10-28 01:35:25 +08:00
|
|
|
call.setNullVideoBitrate(true);
|
2015-10-24 10:26:29 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-19 05:42:25 +08:00
|
|
|
void CoreAV::callCallback(ToxAV* toxav, uint32_t friendNum, bool audio, bool video, void* vSelf)
|
2015-09-28 01:59:26 +08:00
|
|
|
{
|
2016-12-19 05:42:25 +08:00
|
|
|
CoreAV* self = static_cast<CoreAV*>(vSelf);
|
2015-10-08 06:52:05 +08:00
|
|
|
|
2019-12-03 04:26:49 +08:00
|
|
|
QWriteLocker locker{&self->callsLock};
|
2015-10-08 06:52:05 +08:00
|
|
|
|
2019-05-19 01:08:58 +08:00
|
|
|
// Audio backend must be set before receiving a call
|
|
|
|
assert(self->audio != nullptr);
|
|
|
|
ToxFriendCallPtr call = ToxFriendCallPtr(new ToxFriendCall{friendNum, video, *self, *self->audio});
|
2020-03-16 19:40:17 +08:00
|
|
|
// Call object must be owned by CoreAV thread or there will be locking problems with Audio
|
|
|
|
call->moveToThread(self->thread());
|
2019-01-28 04:47:28 +08:00
|
|
|
assert(call != nullptr);
|
|
|
|
|
|
|
|
auto it = self->calls.emplace(friendNum, std::move(call));
|
2018-03-16 13:32:54 +08:00
|
|
|
if (it.second == false) {
|
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-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;
|
2019-01-28 04:47:28 +08:00
|
|
|
it.first->second->setState(static_cast<TOXAV_FRIEND_CALL_STATE>(state));
|
2015-09-28 05:16:40 +08:00
|
|
|
|
2020-01-30 05:21:42 +08:00
|
|
|
// Must explicitely unlock, because a deadlock can happen via ChatForm/Audio
|
2019-12-03 04:26:49 +08:00
|
|
|
locker.unlock();
|
|
|
|
|
2020-01-30 05:21:42 +08:00
|
|
|
emit self->avInvite(friendNum, video);
|
2015-09-28 01:59:26 +08:00
|
|
|
}
|
|
|
|
|
2016-12-19 05:42:25 +08:00
|
|
|
void CoreAV::stateCallback(ToxAV* toxav, uint32_t friendNum, uint32_t state, void* vSelf)
|
2015-09-28 01:59:26 +08:00
|
|
|
{
|
2020-04-23 07:49:23 +08:00
|
|
|
Q_UNUSED(toxav)
|
2016-12-19 05:42:25 +08:00
|
|
|
CoreAV* self = static_cast<CoreAV*>(vSelf);
|
2015-09-28 01:59:26 +08:00
|
|
|
|
2020-01-30 05:21:42 +08:00
|
|
|
// we must unlock this lock before emitting any signals
|
2019-12-03 04:26:49 +08:00
|
|
|
QWriteLocker locker{&self->callsLock};
|
2015-10-08 06:52:05 +08:00
|
|
|
|
2018-03-16 13:32:54 +08:00
|
|
|
auto it = self->calls.find(friendNum);
|
|
|
|
if (it == self->calls.end()) {
|
2015-10-08 06:52:05 +08:00
|
|
|
qWarning() << QString("stateCallback called, but call %1 is already dead").arg(friendNum);
|
|
|
|
return;
|
|
|
|
}
|
2016-07-07 18:10:53 +08:00
|
|
|
|
2019-01-28 04:47:28 +08:00
|
|
|
ToxFriendCall& call = *it->second;
|
2015-09-28 01:59:26 +08:00
|
|
|
|
2017-02-26 19:52:45 +08:00
|
|
|
if (state & TOXAV_FRIEND_CALL_STATE_ERROR) {
|
|
|
|
qWarning() << "Call with friend" << friendNum << "died of unnatural causes!";
|
2019-11-11 10:56:30 +08:00
|
|
|
self->calls.erase(friendNum);
|
2019-12-03 04:26:49 +08:00
|
|
|
locker.unlock();
|
2017-07-01 21:17:43 +08:00
|
|
|
emit self->avEnd(friendNum, true);
|
2017-02-26 19:52:45 +08:00
|
|
|
} else if (state & TOXAV_FRIEND_CALL_STATE_FINISHED) {
|
|
|
|
qDebug() << "Call with friend" << friendNum << "finished quietly";
|
2019-11-11 10:56:30 +08:00
|
|
|
self->calls.erase(friendNum);
|
2019-12-03 04:26:49 +08:00
|
|
|
locker.unlock();
|
2015-09-28 01:59:26 +08:00
|
|
|
emit self->avEnd(friendNum);
|
2017-02-26 19:52:45 +08:00
|
|
|
} else {
|
2015-10-05 08:36:50 +08:00
|
|
|
// If our state was null, we started the call and were still ringing
|
2017-10-28 01:35:25 +08:00
|
|
|
if (!call.getState() && state) {
|
2017-11-02 10:58:23 +08:00
|
|
|
call.setActive(true);
|
2020-01-30 05:21:42 +08:00
|
|
|
bool videoEnabled = call.getVideoEnabled();
|
|
|
|
call.setState(static_cast<TOXAV_FRIEND_CALL_STATE>(state));
|
2019-12-03 04:26:49 +08:00
|
|
|
locker.unlock();
|
2020-01-30 05:21:42 +08:00
|
|
|
emit self->avStart(friendNum, videoEnabled);
|
2017-10-28 01:35:25 +08:00
|
|
|
} else if ((call.getState() & TOXAV_FRIEND_CALL_STATE_SENDING_V)
|
2017-02-26 19:52:45 +08:00
|
|
|
&& !(state & TOXAV_FRIEND_CALL_STATE_SENDING_V)) {
|
|
|
|
qDebug() << "Friend" << friendNum << "stopped sending video";
|
2017-10-28 01:35:25 +08:00
|
|
|
if (call.getVideoSource()) {
|
|
|
|
call.getVideoSource()->stopSource();
|
|
|
|
}
|
2020-01-30 05:21:42 +08:00
|
|
|
|
|
|
|
call.setState(static_cast<TOXAV_FRIEND_CALL_STATE>(state));
|
2017-10-28 01:35:25 +08:00
|
|
|
} else if (!(call.getState() & TOXAV_FRIEND_CALL_STATE_SENDING_V)
|
2017-02-26 19:52:45 +08:00
|
|
|
&& (state & TOXAV_FRIEND_CALL_STATE_SENDING_V)) {
|
|
|
|
// Workaround toxav sometimes firing callbacks for "send last frame" -> "stop sending
|
|
|
|
// video"
|
2015-10-24 10:26:29 +08:00
|
|
|
// out of orders (even though they were sent in order by the other end).
|
2017-02-26 19:52:45 +08:00
|
|
|
// We simply stop the videoSource from emitting anything while the other end says it's
|
|
|
|
// not sending
|
2017-10-28 01:35:25 +08:00
|
|
|
if (call.getVideoSource()) {
|
|
|
|
call.getVideoSource()->restartSource();
|
|
|
|
}
|
2015-09-28 01:59:26 +08:00
|
|
|
|
2020-01-30 05:21:42 +08:00
|
|
|
call.setState(static_cast<TOXAV_FRIEND_CALL_STATE>(state));
|
|
|
|
}
|
2015-09-28 01:59:26 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-14 21:48:34 +08:00
|
|
|
// This is only a dummy implementation for now
|
2017-02-26 19:52:45 +08:00
|
|
|
void CoreAV::bitrateCallback(ToxAV* toxav, uint32_t friendNum, uint32_t arate, uint32_t vrate,
|
|
|
|
void* vSelf)
|
2015-09-28 01:59:26 +08:00
|
|
|
{
|
2016-12-19 05:42:25 +08:00
|
|
|
CoreAV* self = static_cast<CoreAV*>(vSelf);
|
2020-04-23 07:49:23 +08:00
|
|
|
Q_UNUSED(self)
|
|
|
|
Q_UNUSED(toxav)
|
2015-10-08 06:52:05 +08:00
|
|
|
|
2017-02-26 19:52:45 +08:00
|
|
|
qDebug() << "Recommended bitrate with" << friendNum << " is now " << arate << "/" << vrate
|
|
|
|
<< ", ignoring it";
|
2015-09-28 01:59:26 +08:00
|
|
|
}
|
|
|
|
|
2019-11-14 21:48:34 +08:00
|
|
|
// This is only a dummy implementation for now
|
2018-01-31 14:31:28 +08:00
|
|
|
void CoreAV::audioBitrateCallback(ToxAV* toxav, uint32_t friendNum, uint32_t rate, void* vSelf)
|
|
|
|
{
|
|
|
|
CoreAV* self = static_cast<CoreAV*>(vSelf);
|
2020-04-23 07:49:23 +08:00
|
|
|
Q_UNUSED(self)
|
|
|
|
Q_UNUSED(toxav)
|
2018-01-31 14:31:28 +08:00
|
|
|
|
2018-06-30 18:10:46 +08:00
|
|
|
qDebug() << "Recommended audio bitrate with" << friendNum << " is now " << rate << ", ignoring it";
|
2018-01-31 14:31:28 +08:00
|
|
|
}
|
|
|
|
|
2019-11-14 21:48:34 +08:00
|
|
|
// This is only a dummy implementation for now
|
2018-01-31 14:31:28 +08:00
|
|
|
void CoreAV::videoBitrateCallback(ToxAV* toxav, uint32_t friendNum, uint32_t rate, void* vSelf)
|
|
|
|
{
|
|
|
|
CoreAV* self = static_cast<CoreAV*>(vSelf);
|
2020-04-23 07:49:23 +08:00
|
|
|
Q_UNUSED(self)
|
|
|
|
Q_UNUSED(toxav)
|
2018-01-31 14:31:28 +08:00
|
|
|
|
2018-06-30 18:10:46 +08:00
|
|
|
qDebug() << "Recommended video bitrate with" << friendNum << " is now " << rate << ", ignoring it";
|
2018-01-31 14:31:28 +08:00
|
|
|
}
|
|
|
|
|
2017-02-26 19:52:45 +08:00
|
|
|
void CoreAV::audioFrameCallback(ToxAV*, uint32_t friendNum, const int16_t* pcm, size_t sampleCount,
|
|
|
|
uint8_t channels, uint32_t samplingRate, void* vSelf)
|
2015-09-28 01:59:26 +08:00
|
|
|
{
|
2016-12-19 05:42:25 +08:00
|
|
|
CoreAV* self = static_cast<CoreAV*>(vSelf);
|
2019-11-14 22:47:33 +08:00
|
|
|
// This callback should come from the CoreAV thread
|
|
|
|
assert(QThread::currentThread() == self->coreavThread.get());
|
2019-12-03 04:26:49 +08:00
|
|
|
QReadLocker locker{&self->callsLock};
|
2019-11-14 21:48:34 +08:00
|
|
|
|
2019-11-11 10:56:30 +08:00
|
|
|
auto it = self->calls.find(friendNum);
|
2018-03-16 13:32:54 +08:00
|
|
|
if (it == self->calls.end()) {
|
2015-09-28 05:16:40 +08:00
|
|
|
return;
|
2017-10-28 01:35:25 +08:00
|
|
|
}
|
2015-09-28 05:16:40 +08:00
|
|
|
|
2019-01-28 04:47:28 +08:00
|
|
|
ToxFriendCall& call = *it->second;
|
2015-09-28 05:16:40 +08:00
|
|
|
|
2017-10-28 01:35:25 +08:00
|
|
|
if (call.getMuteVol()) {
|
2015-09-28 05:16:40 +08:00
|
|
|
return;
|
2017-10-28 01:35:25 +08:00
|
|
|
}
|
2015-09-28 05:16:40 +08:00
|
|
|
|
2019-04-26 03:30:02 +08:00
|
|
|
call.playAudioBuffer(pcm, sampleCount, channels, samplingRate);
|
2015-09-28 01:59:26 +08:00
|
|
|
}
|
|
|
|
|
2017-02-26 19:52:45 +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,
|
2019-11-11 10:56:30 +08:00
|
|
|
int32_t ystride, int32_t ustride, int32_t vstride, void* vSelf)
|
2015-09-28 01:59:26 +08:00
|
|
|
{
|
2019-11-11 10:56:30 +08:00
|
|
|
auto self = static_cast<CoreAV*>(vSelf);
|
2019-11-14 22:47:33 +08:00
|
|
|
// This callback should come from the CoreAV thread
|
|
|
|
assert(QThread::currentThread() == self->coreavThread.get());
|
2019-12-03 04:26:49 +08:00
|
|
|
QReadLocker locker{&self->callsLock};
|
2019-11-11 10:56:30 +08:00
|
|
|
|
|
|
|
auto it = self->calls.find(friendNum);
|
|
|
|
if (it == self->calls.end()) {
|
2015-09-28 07:04:39 +08:00
|
|
|
return;
|
2017-10-28 01:35:25 +08:00
|
|
|
}
|
2015-09-28 07:04:39 +08:00
|
|
|
|
2019-01-28 04:47:28 +08:00
|
|
|
CoreVideoSource* videoSource = it->second->getVideoSource();
|
2017-10-28 01:35:25 +08:00
|
|
|
if (!videoSource) {
|
2015-11-06 19:41:15 +08:00
|
|
|
return;
|
2017-10-28 01:35:25 +08:00
|
|
|
}
|
2015-11-06 19:41:15 +08:00
|
|
|
|
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;
|
|
|
|
|
2017-10-28 01:35:25 +08:00
|
|
|
videoSource->pushFrame(&frame);
|
2015-09-28 01:59:26 +08:00
|
|
|
}
|