2014-09-02 02:45:48 +08:00
|
|
|
/*
|
|
|
|
Copyright (C) 2013 by Maxim Biro <nurupo.contributions@gmail.com>
|
2018-04-13 04:02:28 +08:00
|
|
|
Copyright © 2014-2018 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
|
|
|
|
|
|
|
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
|
|
|
*/
|
|
|
|
|
2015-06-26 19:00:16 +08:00
|
|
|
#include "coreav.h"
|
2017-10-28 20:17:14 +08:00
|
|
|
#include "core.h"
|
2016-12-19 10:26:26 +08:00
|
|
|
#include "src/audio/audio.h"
|
2017-07-30 01:39:14 +08:00
|
|
|
#include "src/model/friend.h"
|
|
|
|
#include "src/model/group.h"
|
2016-12-19 10:26:26 +08:00
|
|
|
#include "src/persistence/settings.h"
|
|
|
|
#include "src/video/corevideosource.h"
|
2017-02-26 19:52:45 +08:00
|
|
|
#include "src/video/videoframe.h"
|
|
|
|
#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
|
|
|
|
|
|
|
/**
|
2016-08-01 16:20:56 +08:00
|
|
|
* @brief Maps friend IDs to ToxFriendCall.
|
2017-10-27 06:49:36 +08:00
|
|
|
* @note Need to use STL container here, because Qt containers need a copy constructor.
|
2016-08-01 16:20:56 +08:00
|
|
|
*/
|
2019-01-28 04:47:28 +08:00
|
|
|
std::map<uint32_t, CoreAV::ToxFriendCallPtr> CoreAV::calls;
|
2016-07-27 06:21:22 +08:00
|
|
|
|
|
|
|
/**
|
2016-08-01 16:20:56 +08:00
|
|
|
* @brief Maps group IDs to ToxGroupCalls.
|
2017-10-27 06:49:36 +08:00
|
|
|
* @note Need to use STL container here, because Qt containers need a copy constructor.
|
2016-08-01 16:20:56 +08:00
|
|
|
*/
|
2019-01-28 04:47:28 +08:00
|
|
|
std::map<int, CoreAV::ToxGroupCallPtr> CoreAV::groupCalls;
|
2014-08-28 21:46:11 +08:00
|
|
|
|
2018-07-14 20:30:34 +08:00
|
|
|
CoreAV::CoreAV(std::unique_ptr<ToxAV, ToxAVDeleter> toxav)
|
|
|
|
: toxav{std::move(toxav)}
|
|
|
|
, coreavThread{new QThread{this}}
|
2017-02-26 19:52:45 +08:00
|
|
|
, iterateTimer{new QTimer{this}}
|
|
|
|
, threadSwitchLock{false}
|
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);
|
2015-10-08 03:53:22 +08:00
|
|
|
|
2018-07-14 20:30:34 +08:00
|
|
|
coreavThread->start();
|
|
|
|
}
|
2015-09-28 01:59:26 +08:00
|
|
|
|
2018-07-14 20:30:34 +08:00
|
|
|
void CoreAV::connectCallbacks(ToxAV& toxav) {
|
|
|
|
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
|
|
|
|
*/
|
|
|
|
CoreAV::CoreAVPtr CoreAV::makeCoreAV(Tox* core)
|
|
|
|
{
|
|
|
|
TOXAV_ERR_NEW err;
|
|
|
|
std::unique_ptr<ToxAV, ToxAVDeleter> toxav{toxav_new(core, &err)};
|
|
|
|
switch (err) {
|
|
|
|
case TOXAV_ERR_NEW_OK:
|
|
|
|
break;
|
|
|
|
case TOXAV_ERR_NEW_MALLOC:
|
|
|
|
qCritical() << "Failed to allocate ressources for ToxAV";
|
|
|
|
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);
|
|
|
|
|
|
|
|
return CoreAVPtr{new CoreAV{std::move(toxav)}};
|
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
|
|
|
{
|
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
|
|
|
}
|
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()
|
|
|
|
{
|
|
|
|
// Timers can only be touched from their own thread
|
|
|
|
if (QThread::currentThread() != coreavThread.get())
|
|
|
|
return (void)QMetaObject::invokeMethod(this, "start", Qt::BlockingQueuedConnection);
|
|
|
|
iterateTimer->start();
|
|
|
|
}
|
|
|
|
|
2015-09-28 01:59:26 +08:00
|
|
|
void CoreAV::process()
|
|
|
|
{
|
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
|
|
|
}
|
|
|
|
|
2016-07-27 06:21:22 +08:00
|
|
|
/**
|
2016-11-09 14:20:47 +08:00
|
|
|
* @brief Check, if any calls are currently active.
|
|
|
|
* @return true if any calls are currently active, false otherwise
|
|
|
|
* @note A call about to start is not yet active.
|
2016-08-01 16:20:56 +08:00
|
|
|
*/
|
2016-11-09 14:20:47 +08:00
|
|
|
bool CoreAV::anyActiveCalls() const
|
2014-08-28 21:46:11 +08:00
|
|
|
{
|
2017-10-27 06:49:36 +08:00
|
|
|
return !calls.empty();
|
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
|
|
|
|
{
|
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
|
|
|
|
{
|
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
|
|
|
{
|
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
|
|
|
|
{
|
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
|
|
|
|
{
|
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
|
|
|
{
|
2017-02-26 19:52:45 +08:00
|
|
|
if (QThread::currentThread() != coreavThread.get()) {
|
|
|
|
if (threadSwitchLock.test_and_set(std::memory_order_acquire)) {
|
2015-10-24 20:10:28 +08:00
|
|
|
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,
|
2017-10-28 20:17:14 +08:00
|
|
|
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-09-28 01:59: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());
|
2015-09-28 01:59:26 +08:00
|
|
|
TOXAV_ERR_ANSWER err;
|
2017-09-13 04:29:02 +08:00
|
|
|
|
|
|
|
const uint32_t videoBitrate = video ? VIDEO_DEFAULT_BITRATE : 0;
|
2018-07-14 20:30:34 +08:00
|
|
|
if (toxav_answer(toxav.get(), friendNum, Settings::getInstance().getAudioBitrate(), 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
|
|
|
{
|
2017-02-26 19:52:45 +08:00
|
|
|
if (QThread::currentThread() != coreavThread.get()) {
|
|
|
|
if (threadSwitchLock.test_and_set(std::memory_order_acquire)) {
|
2015-10-24 20:10:28 +08:00
|
|
|
qDebug() << "CoreAV::startCall: Backed off of thread-switch lock";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2016-06-14 04:41:07 +08:00
|
|
|
bool ret;
|
|
|
|
QMetaObject::invokeMethod(this, "startCall", Qt::BlockingQueuedConnection,
|
2017-02-26 19:52:45 +08:00
|
|
|
Q_RETURN_ARG(bool, ret), Q_ARG(uint32_t, friendNum),
|
2016-06-14 04:41:07 +08:00
|
|
|
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);
|
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;
|
2018-07-14 20:30:34 +08:00
|
|
|
if (!toxav_call(toxav.get(), friendNum, Settings::getInstance().getAudioBitrate(), videoBitrate, nullptr))
|
2015-10-08 06:52:05 +08:00
|
|
|
return false;
|
|
|
|
|
2019-01-28 04:47:28 +08:00
|
|
|
ToxFriendCallPtr call = ToxFriendCallPtr(new ToxFriendCall(friendNum, video, *this));
|
|
|
|
assert(call != nullptr);
|
|
|
|
auto ret = calls.emplace(friendNum, std::move(call));
|
|
|
|
ret.first->second->startTimeout(friendNum);
|
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
|
|
|
{
|
2017-02-26 19:52:45 +08:00
|
|
|
if (QThread::currentThread() != coreavThread.get()) {
|
|
|
|
if (threadSwitchLock.test_and_set(std::memory_order_acquire)) {
|
2015-10-24 20:10:28 +08:00
|
|
|
qDebug() << "CoreAV::cancelCall: Backed off of thread-switch lock";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2015-10-08 06:52:05 +08:00
|
|
|
bool ret;
|
2017-02-26 19:52:45 +08:00
|
|
|
QMetaObject::invokeMethod(this, "cancelCall", 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
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)
|
|
|
|
{
|
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
|
2017-02-26 19:52:45 +08:00
|
|
|
if (QThread::currentThread() != coreavThread.get()) {
|
2015-10-24 20:10:28 +08:00
|
|
|
QMetaObject::invokeMethod(this, "timeoutCall", Qt::QueuedConnection,
|
2017-02-26 19:52:45 +08:00
|
|
|
Q_ARG(uint32_t, friendNum));
|
2015-10-24 07:53:10 +08:00
|
|
|
return;
|
|
|
|
}
|
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
|
|
|
{
|
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
|
|
|
|
TOXAV_ERR_SEND_FRAME err;
|
|
|
|
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
|
|
|
{
|
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;
|
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
|
|
|
|
TOXAV_ERR_SEND_FRAME err;
|
|
|
|
int retries = 0;
|
|
|
|
do {
|
2018-07-14 20:30:34 +08:00
|
|
|
if (!toxav_video_send_frame(toxav.get(), callId, frame.width, frame.height, frame.y, frame.u,
|
2017-02-26 19:52:45 +08:00
|
|
|
frame.v, &err)) {
|
|
|
|
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
|
|
|
{
|
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
|
|
|
{
|
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)
|
|
|
|
{
|
|
|
|
Q_UNUSED(tox);
|
|
|
|
|
|
|
|
Core* c = static_cast<Core*>(core);
|
2018-05-13 17:20:57 +08:00
|
|
|
const ToxPk peerPk = c->getGroupPeerPk(group, peer);
|
|
|
|
const Settings& s = Settings::getInstance();
|
|
|
|
// don't play the audio if it comes from a muted peer
|
|
|
|
if (s.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-02-21 05:12:14 +08:00
|
|
|
CoreAV* cav = c->getAv();
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
Audio& audio = Audio::getInstance();
|
2019-01-24 01:23:54 +08:00
|
|
|
if(!call.havePeer(peerPk)) {
|
|
|
|
call.addPeer(peerPk);
|
2018-02-21 05:12:14 +08:00
|
|
|
}
|
|
|
|
|
2019-01-24 01:23:54 +08:00
|
|
|
audio.playAudioBuffer(call.getAlSource(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
|
|
|
|
*/
|
2019-01-24 01:23:54 +08:00
|
|
|
void CoreAV::invalidateGroupCallPeerSource(int group, ToxPk peerPk)
|
2017-02-26 19:52:45 +08:00
|
|
|
{
|
2018-03-16 13:32:54 +08:00
|
|
|
auto it = groupCalls.find(group);
|
|
|
|
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
|
|
|
{
|
2018-03-16 13:32:54 +08:00
|
|
|
auto it = calls.find(friendNum);
|
|
|
|
if (it == calls.end()) {
|
2017-02-26 19:52:45 +08:00
|
|
|
qWarning() << "CoreAV::getVideoSourceFromCall: No such call, did it die before we finished "
|
|
|
|
"answering?";
|
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
|
|
|
|
*/
|
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
|
|
|
|
2019-01-28 04:47:28 +08:00
|
|
|
ToxGroupCallPtr groupcall = ToxGroupCallPtr(new ToxGroupCall{groupId, *this});
|
|
|
|
assert(groupcall != nullptr);
|
|
|
|
auto ret = groupCalls.emplace(groupId, 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
|
|
|
{
|
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-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
|
|
|
{
|
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
|
|
|
{
|
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
|
|
|
{
|
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
|
|
|
{
|
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
|
|
|
{
|
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
|
|
|
{
|
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 Forces to regenerate each call's audio sources.
|
|
|
|
*/
|
2015-12-10 13:48:28 +08:00
|
|
|
void CoreAV::invalidateCallSources()
|
2015-06-26 19:00:16 +08:00
|
|
|
{
|
2017-10-27 06:49:36 +08:00
|
|
|
for (auto& kv : groupCalls) {
|
2019-01-28 04:47:28 +08:00
|
|
|
kv.second->clearPeers();
|
2015-06-26 19:00:16 +08:00
|
|
|
}
|
|
|
|
|
2017-10-27 06:49:36 +08:00
|
|
|
for (auto& kv : calls) {
|
2017-10-28 01:35:25 +08:00
|
|
|
// TODO: this is wrong, "0" is a valid source id
|
2019-01-28 04:47:28 +08:00
|
|
|
kv.second->setAlSource(0);
|
2015-06-26 19:00:16 +08:00
|
|
|
}
|
|
|
|
}
|
2015-09-28 01:59:26 +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()
|
|
|
|
{
|
|
|
|
// 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
|
|
|
|
2017-02-26 19:52:45 +08:00
|
|
|
// Run this slow callback asynchronously on the AV thread to avoid deadlocks with what our
|
|
|
|
// caller (toxcore) holds
|
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
|
|
|
// 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...
|
2017-02-26 19:52:45 +08:00
|
|
|
if (QThread::currentThread() != self->coreavThread.get()) {
|
|
|
|
QtConcurrent::run([=]() {
|
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
|
|
|
// 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,
|
2016-12-19 05:42:25 +08:00
|
|
|
Q_ARG(ToxAV*, toxav), Q_ARG(uint32_t, friendNum),
|
|
|
|
Q_ARG(bool, audio), Q_ARG(bool, video), Q_ARG(void*, vSelf));
|
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
|
|
|
}
|
|
|
|
|
2019-01-28 04:47:28 +08:00
|
|
|
ToxFriendCallPtr call = ToxFriendCallPtr(new ToxFriendCall{friendNum, video, *self});
|
|
|
|
assert(call != nullptr);
|
|
|
|
|
|
|
|
auto it = self->calls.emplace(friendNum, std::move(call));
|
2018-03-16 13:32:54 +08:00
|
|
|
if (it.second == false) {
|
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,
|
2017-02-26 19:52:45 +08:00
|
|
|
/// 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-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));
|
2018-03-16 13:32:54 +08:00
|
|
|
// note: changed to self->
|
2015-09-28 05:16:40 +08:00
|
|
|
|
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
|
|
|
}
|
|
|
|
|
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
|
|
|
{
|
2016-12-19 05:42:25 +08:00
|
|
|
CoreAV* self = static_cast<CoreAV*>(vSelf);
|
2015-09-28 01:59:26 +08:00
|
|
|
|
2017-02-26 19:52:45 +08:00
|
|
|
// Run this slow callback asynchronously on the AV thread to avoid deadlocks with what our
|
|
|
|
// caller (toxcore) holds
|
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
|
|
|
// 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...
|
2017-02-26 19:52:45 +08:00
|
|
|
if (QThread::currentThread() != self->coreavThread.get()) {
|
|
|
|
QtConcurrent::run([=]() {
|
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
|
|
|
// 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,
|
2017-02-26 19:52:45 +08:00
|
|
|
Q_ARG(ToxAV*, toxav), Q_ARG(uint32_t, friendNum),
|
|
|
|
Q_ARG(uint32_t, state), Q_ARG(void*, vSelf));
|
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
|
|
|
}
|
|
|
|
|
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);
|
2015-10-25 06:17:22 +08:00
|
|
|
self->threadSwitchLock.clear(std::memory_order_release);
|
2015-10-08 06:52:05 +08:00
|
|
|
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!";
|
2017-10-27 06:49:36 +08:00
|
|
|
calls.erase(friendNum);
|
2018-03-16 13:32:54 +08:00
|
|
|
// why not self->
|
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";
|
2017-10-27 06:49:36 +08:00
|
|
|
calls.erase(friendNum);
|
2018-03-16 13:32:54 +08:00
|
|
|
// why not self->
|
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) {
|
2015-10-24 07:53:10 +08:00
|
|
|
call.stopTimeout();
|
2017-11-02 10:58:23 +08:00
|
|
|
call.setActive(true);
|
2017-10-28 01:35:25 +08:00
|
|
|
emit self->avStart(friendNum, call.getVideoEnabled());
|
|
|
|
} 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();
|
|
|
|
}
|
|
|
|
} 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-10-24 10:26:29 +08:00
|
|
|
}
|
2015-09-28 01:59:26 +08:00
|
|
|
|
2017-10-28 01:35:25 +08:00
|
|
|
call.setState(static_cast<TOXAV_FRIEND_CALL_STATE>(state));
|
2015-09-28 01:59:26 +08:00
|
|
|
}
|
2015-10-25 06:17:22 +08:00
|
|
|
self->threadSwitchLock.clear(std::memory_order_release);
|
2015-09-28 01:59:26 +08:00
|
|
|
}
|
|
|
|
|
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);
|
2015-10-08 06:52:05 +08:00
|
|
|
|
|
|
|
// Run this slow path callback asynchronously on the AV thread to avoid deadlocks
|
2017-02-26 19:52:45 +08:00
|
|
|
if (QThread::currentThread() != self->coreavThread.get()) {
|
2015-10-12 22:29:46 +08:00
|
|
|
return (void)QMetaObject::invokeMethod(self, "bitrateCallback", Qt::QueuedConnection,
|
2017-02-26 19:52:45 +08:00
|
|
|
Q_ARG(ToxAV*, toxav), Q_ARG(uint32_t, friendNum),
|
|
|
|
Q_ARG(uint32_t, arate), Q_ARG(uint32_t, vrate),
|
|
|
|
Q_ARG(void*, vSelf));
|
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
|
|
|
}
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
// Run this slow path callback asynchronously on the AV thread to avoid deadlocks
|
|
|
|
if (QThread::currentThread() != self->coreavThread.get()) {
|
|
|
|
return (void)QMetaObject::invokeMethod(self, "audioBitrateCallback", Qt::QueuedConnection,
|
|
|
|
Q_ARG(ToxAV*, toxav), Q_ARG(uint32_t, friendNum),
|
|
|
|
Q_ARG(uint32_t, rate), Q_ARG(void*, vSelf));
|
|
|
|
}
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
|
|
|
void CoreAV::videoBitrateCallback(ToxAV* toxav, uint32_t friendNum, uint32_t rate, void* vSelf)
|
|
|
|
{
|
|
|
|
CoreAV* self = static_cast<CoreAV*>(vSelf);
|
|
|
|
|
|
|
|
// Run this slow path callback asynchronously on the AV thread to avoid deadlocks
|
|
|
|
if (QThread::currentThread() != self->coreavThread.get()) {
|
|
|
|
return (void)QMetaObject::invokeMethod(self, "videoBitrateCallback", Qt::QueuedConnection,
|
|
|
|
Q_ARG(ToxAV*, toxav), Q_ARG(uint32_t, friendNum),
|
|
|
|
Q_ARG(uint32_t, rate), Q_ARG(void*, vSelf));
|
|
|
|
}
|
|
|
|
|
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);
|
2018-03-16 13:32:54 +08:00
|
|
|
auto it = calls.find(friendNum);
|
|
|
|
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
|
|
|
|
2015-12-10 13:48:28 +08:00
|
|
|
Audio& audio = Audio::getInstance();
|
2017-10-28 01:35:25 +08:00
|
|
|
if (!call.getAlSource()) {
|
|
|
|
quint32 sourceId;
|
|
|
|
audio.subscribeOutput(sourceId);
|
|
|
|
call.setAlSource(sourceId);
|
|
|
|
}
|
2015-09-28 05:16:40 +08:00
|
|
|
|
2017-10-28 01:35:25 +08:00
|
|
|
audio.playAudioBuffer(call.getAlSource(), 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,
|
|
|
|
int32_t ystride, int32_t ustride, int32_t vstride, void*)
|
2015-09-28 01:59:26 +08:00
|
|
|
{
|
2018-03-16 13:32:54 +08:00
|
|
|
auto it = calls.find(friendNum);
|
|
|
|
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
|
|
|
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
|
|
|
}
|