1
0
mirror of https://github.com/qTox/qTox.git synced 2024-03-22 14:00:36 +08:00

Merge branch 'newav_final_for_realsies'

Implements the new toxav API.
This commit is contained in:
tux3 2015-11-06 01:52:40 +01:00
commit 5b036cca0f
No known key found for this signature in database
GPG Key ID: 7E086DD661263264
32 changed files with 1321 additions and 1258 deletions

View File

@ -501,7 +501,8 @@ SOURCES += \
src/widget/tool/movablewidget.cpp \
src/widget/tool/micfeedbackwidget.cpp \
src/widget/tool/removefrienddialog.cpp \
src/video/groupnetcamview.cpp
src/video/groupnetcamview.cpp \
src/core/toxcall.cpp
HEADERS += \
src/audio/audio.h \
@ -552,4 +553,6 @@ HEADERS += \
src/widget/tool/removefrienddialog.h \
src/widget/tool/movablewidget.h \
src/video/genericnetcamview.h \
src/video/groupnetcamview.h
src/video/groupnetcamview.h \
src/core/indexedlist.h \
src/core/toxcall.h

View File

@ -29,10 +29,13 @@
#include "audio.h"
#include "src/core/core.h"
#include "src/persistence/settings.h"
#include "src/core/coreav.h"
#include <QDebug>
#include <QTimer>
#include <QThread>
#include <QMutexLocker>
#include <QFile>
#include <cassert>
@ -110,18 +113,13 @@ void Audio::setOutputVolume(qreal volume)
outputVolume = volume;
alSourcef(alMainSource, AL_GAIN, outputVolume);
for (const ToxGroupCall& call : Core::groupCalls)
for (const ToxGroupCall& call : CoreAV::groupCalls)
{
if (!call.active)
continue;
for (ALuint source : call.alSources)
alSourcef(source, AL_GAIN, outputVolume);
alSourcef(call.alSource, AL_GAIN, outputVolume);
}
for (const ToxCall& call : Core::calls)
for (const ToxFriendCall& call : CoreAV::calls)
{
if (!call.active)
continue;
alSourcef(call.alSource, AL_GAIN, outputVolume);
}
}
@ -193,10 +191,11 @@ void Audio::openInput(const QString& inDevDescr)
}
alInDev = nullptr;
int stereoFlag = av_DefaultSettings.audio_channels==1 ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16;
const uint32_t sampleRate = av_DefaultSettings.audio_sample_rate;
const uint16_t frameDuration = av_DefaultSettings.audio_frame_duration;
const uint32_t chnls = av_DefaultSettings.audio_channels;
/// TODO: Try to actually detect if our audio source is stereo
int stereoFlag = AUDIO_CHANNELS == 1 ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16;
const uint32_t sampleRate = AUDIO_SAMPLE_RATE;
const uint16_t frameDuration = AUDIO_FRAME_DURATION;
const uint32_t chnls = AUDIO_CHANNELS;
const ALCsizei bufSize = (frameDuration * sampleRate * 4) / 1000 * chnls;
if (inDevDescr.isEmpty())
alInDev = alcCaptureOpenDevice(nullptr, sampleRate, stereoFlag, bufSize);
@ -211,7 +210,7 @@ void Audio::openInput(const QString& inDevDescr)
Core* core = Core::getInstance();
if (core)
core->resetCallSources(); // Force to regen each group call's sources
core->getAv()->resetCallSources(); // Force to regen each group call's sources
// Restart the capture if necessary
if (alInDev)
@ -269,7 +268,7 @@ bool Audio::openOutput(const QString &outDevDescr)
Core* core = Core::getInstance();
if (core)
core->resetCallSources(); // Force to regen each group call's sources
core->getAv()->resetCallSources(); // Force to regen each group call's sources
return true;
}
@ -366,12 +365,22 @@ void Audio::playMono16Sound(const QByteArray& data)
alDeleteBuffers(1, &buffer);
}
/**
Play a 44100Hz mono 16bit PCM sound from a file
*/
void Audio::playMono16Sound(const char *path)
{
QFile sndFile(path);
sndFile.open(QIODevice::ReadOnly);
playMono16Sound(sndFile.readAll());
}
/**
@brief May be called from any thread, will always queue a call to playGroupAudio.
The first and last argument are ignored, but allow direct compatibility with toxcore.
*/
void Audio::playGroupAudioQueued(Tox*,int group, int peer, const int16_t* data,
void Audio::playGroupAudioQueued(void*,int group, int peer, const int16_t* data,
unsigned samples, uint8_t channels, unsigned sample_rate, void* core)
{
QMetaObject::invokeMethod(instance, "playGroupAudio", Qt::BlockingQueuedConnection,
@ -391,15 +400,18 @@ void Audio::playGroupAudio(int group, int peer, const int16_t* data,
assert(QThread::currentThread() == audioThread);
QMutexLocker lock(&audioOutLock);
ToxGroupCall& call = Core::groupCalls[group];
if (!call.active || call.muteVol)
if (!CoreAV::groupCalls.contains(group))
return;
if (!call.alSources.contains(peer))
ToxGroupCall& call = CoreAV::groupCalls[group];
if (call.inactive || call.muteVol)
return;
if (!call.alSource)
{
alGenSources(1, &call.alSources[peer]);
alSourcef(call.alSources[peer], AL_GAIN, outputVolume);
alGenSources(1, &call.alSource);
alSourcef(call.alSource, AL_GAIN, outputVolume);
}
qreal volume = 0.;
@ -409,14 +421,14 @@ void Audio::playGroupAudio(int group, int peer, const int16_t* data,
emit groupAudioPlayed(group, peer, volume / bufsize);
playAudioBuffer(call.alSources[peer], data, samples, channels, sample_rate);
playAudioBuffer(call.alSource, data, samples, channels, sample_rate);
}
void Audio::playAudioBuffer(ALuint alSource, const int16_t *data, int samples, unsigned channels, int sampleRate)
{
assert(channels == 1 || channels == 2);
QMutexLocker lock(&audioOutLock);
QMutexLocker lock(&getInstance().audioOutLock);
ALuint bufid;
ALint processed = 0, queued = 16;
@ -447,7 +459,7 @@ void Audio::playAudioBuffer(ALuint alSource, const int16_t *data, int samples, u
ALint state;
alGetSourcei(alSource, AL_SOURCE_STATE, &state);
alSourcef(alSource, AL_GAIN, outputVolume);
alSourcef(alSource, AL_GAIN, getInstance().outputVolume);
if (state != AL_PLAYING)
alSourcePlay(alSource);
}
@ -464,40 +476,63 @@ bool Audio::isInputReady()
/**
Returns true if the output device is open
*/
bool Audio::isOutputClosed()
bool Audio::isOutputReady()
{
QMutexLocker locker(&audioOutLock);
return alOutDev;
}
void Audio::createSource(ALuint* source)
{
alGenSources(1, source);
alSourcef(*source, AL_GAIN, getInstance().outputVolume);
}
void Audio::deleteSource(ALuint* source)
{
if (alIsSource(*source))
alDeleteSources(1, source);
else
qWarning() << "Trying to delete invalid audio source"<<*source;
}
void Audio::startLoop()
{
alSourcei(alMainSource, AL_LOOPING, AL_TRUE);
}
void Audio::stopLoop()
{
alSourcei(alMainSource, AL_LOOPING, AL_FALSE);
alSourceStop(alMainSource);
}
/**
Does nothing and return false on failure
*/
bool Audio::tryCaptureSamples(uint8_t* buf, int framesize)
bool Audio::tryCaptureSamples(int16_t* buf, int samples)
{
QMutexLocker lock(&audioInLock);
ALint samples=0;
alcGetIntegerv(Audio::alInDev, ALC_CAPTURE_SAMPLES, sizeof(samples), &samples);
if (samples < framesize)
ALint curSamples=0;
alcGetIntegerv(Audio::alInDev, ALC_CAPTURE_SAMPLES, sizeof(curSamples), &curSamples);
if (curSamples < samples)
return false;
memset(buf, 0, framesize * 2 * av_DefaultSettings.audio_channels); // Avoid uninitialized values (Valgrind)
alcCaptureSamples(Audio::alInDev, buf, framesize);
alcCaptureSamples(Audio::alInDev, buf, samples);
if (inputVolume != 1)
{
int16_t* bufReal = reinterpret_cast<int16_t*>(buf);
for (int i = 0; i < framesize; ++i)
for (size_t i = 0; i < samples * AUDIO_CHANNELS; ++i)
{
int sample = bufReal[i] * pow(inputVolume, 2);
int sample = buf[i] * pow(inputVolume, 2);
if (sample < std::numeric_limits<int16_t>::min())
sample = std::numeric_limits<int16_t>::min();
else if (sample > std::numeric_limits<int16_t>::max())
sample = std::numeric_limits<int16_t>::max();
bufReal[i] = sample;
buf[i] = sample;
}
}

View File

@ -22,6 +22,7 @@
#define AUDIO_H
#include <QObject>
#include <QHash>
#include <QMutexLocker>
#include <atomic>
#include <cmath>
@ -34,10 +35,20 @@
#include <AL/alc.h>
#endif
class QString;
class QByteArray;
class QTimer;
class QThread;
class QMutex;
struct Tox;
class AudioFilterer;
// Public default audio settings
static constexpr uint32_t AUDIO_SAMPLE_RATE = 48000; ///< The next best Opus would take is 24k
static constexpr uint32_t AUDIO_FRAME_DURATION = 20; ///< In milliseconds
static constexpr uint32_t AUDIO_FRAME_SAMPLE_COUNT = AUDIO_FRAME_DURATION * AUDIO_SAMPLE_RATE/1000;
static constexpr uint32_t AUDIO_CHANNELS = 2; ///< Ideally, we'd auto-detect, but that's a sane default
class Audio : public QObject
{
Q_OBJECT
@ -59,12 +70,20 @@ public:
bool openOutput(const QString& outDevDescr);
bool isInputReady();
bool isOutputClosed();
bool isOutputReady();
static void createSource(ALuint* source);
static void deleteSource(ALuint* source);
void startLoop();
void stopLoop();
void playMono16Sound(const QByteArray& data);
bool tryCaptureSamples(uint8_t* buf, int framesize);
void playMono16Sound(const char* path);
bool tryCaptureSamples(int16_t *buf, int samples);
static void playGroupAudioQueued(Tox*, int group, int peer, const int16_t* data,
static void playAudioBuffer(ALuint alSource, const int16_t *data, int samples, unsigned channels, int sampleRate);
static void playGroupAudioQueued(void *, int group, int peer, const int16_t* data,
unsigned samples, uint8_t channels, unsigned sample_rate, void*);
#ifdef QTOX_FILTER_AUDIO
@ -85,8 +104,6 @@ private:
Audio();
~Audio();
void playAudioBuffer(ALuint alSource, const int16_t *data, int samples, unsigned channels, int sampleRate);
private:
static Audio* instance;

View File

@ -22,6 +22,7 @@
#include "src/nexus.h"
#include "src/core/cdata.h"
#include "src/core/cstring.h"
#include "src/core/coreav.h"
#include "src/persistence/settings.h"
#include "src/widget/gui.h"
#include "src/persistence/historykeeper.h"
@ -33,6 +34,7 @@
#include "src/video/camerasource.h"
#include <tox/tox.h>
#include <tox/toxav.h>
#include <ctime>
#include <cassert>
@ -54,40 +56,31 @@
const QString Core::CONFIG_FILE_NAME = "data";
const QString Core::TOX_EXT = ".tox";
QHash<int, ToxGroupCall> Core::groupCalls;
QThread* Core::coreThread{nullptr};
#define MAX_GROUP_MESSAGE_LEN 1024
Core::Core(QThread *CoreThread, Profile& profile) :
tox(nullptr), toxav(nullptr), profile(profile), ready{false}
tox(nullptr), av(nullptr), profile(profile), ready{false}
{
coreThread = CoreThread;
Audio::getInstance();
videobuf = nullptr;
toxTimer = new QTimer(this);
toxTimer->setSingleShot(true);
connect(toxTimer, &QTimer::timeout, this, &Core::process);
connect(&Settings::getInstance(), &Settings::dhtServerListChanged, this, &Core::process);
for (int i=0; i<TOXAV_MAX_CALLS;i++)
{
calls[i].active = false;
calls[i].alSource = 0;
calls[i].sendAudioTimer = new QTimer();
calls[i].sendAudioTimer->moveToThread(coreThread);
}
}
void Core::deadifyTox()
{
if (toxav)
if (av)
{
toxav_kill(toxav);
toxav = nullptr;
delete av;
av = nullptr;
}
if (tox)
{
@ -113,18 +106,8 @@ Core::~Core()
coreThread->wait(500);
}
for (ToxCall call : calls)
{
if (!call.active)
continue;
hangupCall(call.callId);
}
deadifyTox();
delete[] videobuf;
videobuf=nullptr;
Audio& audio = Audio::getInstance();
audio.closeInput();
audio.closeOutput();
@ -135,6 +118,11 @@ Core* Core::getInstance()
return Nexus::getCore();
}
CoreAV *Core::getAv()
{
return av;
}
void Core::makeTox(QByteArray savedata)
{
// IPv6 needed for LAN discovery, but can crash some weird routers. On by default, can be disabled in options.
@ -233,8 +221,8 @@ void Core::makeTox(QByteArray savedata)
return;
}
toxav = toxav_new(tox, TOXAV_MAX_CALLS);
if (toxav == nullptr)
av = new CoreAV(tox);
if (av->getToxAv() == nullptr)
{
qCritical() << "Toxav core failed to start";
emit failedToStart();
@ -310,20 +298,6 @@ void Core::start()
tox_callback_file_recv_chunk(tox, CoreFile::onFileRecvChunkCallback, this);
tox_callback_file_recv_control(tox, CoreFile::onFileControlCallback, this);
toxav_register_callstate_callback(toxav, onAvInvite, av_OnInvite, this);
toxav_register_callstate_callback(toxav, onAvStart, av_OnStart, this);
toxav_register_callstate_callback(toxav, onAvCancel, av_OnCancel, this);
toxav_register_callstate_callback(toxav, onAvReject, av_OnReject, this);
toxav_register_callstate_callback(toxav, onAvEnd, av_OnEnd, this);
toxav_register_callstate_callback(toxav, onAvRinging, av_OnRinging, this);
toxav_register_callstate_callback(toxav, onAvMediaChange, av_OnPeerCSChange, this);
toxav_register_callstate_callback(toxav, onAvMediaChange, av_OnSelfCSChange, this);
toxav_register_callstate_callback(toxav, onAvRequestTimeout, av_OnRequestTimeout, this);
toxav_register_callstate_callback(toxav, onAvPeerTimeout, av_OnPeerTimeout, this);
toxav_register_audio_callback(toxav, playCallAudio, this);
toxav_register_video_callback(toxav, playCallVideo, this);
HistoryKeeper::getInstance()->importAvatarToDatabase(getSelfId().toString().left(64));
QPixmap pic = Settings::getInstance().getSavedAvatar(getSelfId().toString());
if (!pic.isNull() && !pic.size().isEmpty())
@ -356,6 +330,7 @@ void Core::start()
GUI::setEnabled(true);
process(); // starts its own timer
av->start();
}
/* Using the now commented out statements in checkConnection(), I watched how
@ -370,11 +345,13 @@ void Core::start()
void Core::process()
{
if (!isReady())
{
av->stop();
return;
}
static int tolerance = CORE_DISCONNECT_TOLERANCE;
tox_iterate(tox);
toxav_do(toxav);
#ifdef DEBUG
//we want to see the debug messages immediately
@ -391,8 +368,7 @@ void Core::process()
tolerance = 3*CORE_DISCONNECT_TOLERANCE;
}
unsigned sleeptime = qMin(tox_iteration_interval(tox), toxav_do_interval(toxav));
sleeptime = qMin(sleeptime, CoreFile::corefileIterationInterval());
unsigned sleeptime = qMin(tox_iteration_interval(tox), CoreFile::corefileIterationInterval());
toxTimer->start(sleeptime);
}
@ -753,9 +729,7 @@ void Core::removeGroup(int groupId, bool fake)
return;
tox_del_groupchat(tox, groupId);
if (groupCalls[groupId].active)
leaveGroupCall(groupId);
av->leaveGroupCall(groupId);
}
QString Core::getUsername() const
@ -1078,11 +1052,6 @@ void Core::createGroup(uint8_t type)
}
}
bool Core::isGroupAvEnabled(int groupId)
{
return tox_group_get_type(tox, groupId) == TOX_GROUPCHAT_TYPE_AV;
}
bool Core::isFriendOnline(uint32_t friendId) const
{
return tox_friend_get_connection_status(tox, friendId, nullptr) != TOX_CONNECTION_NONE;
@ -1225,7 +1194,7 @@ QString Core::getPeerName(const ToxId& id) const
bool Core::isReady()
{
return toxav && tox && ready;
return av && av->getToxAv() && tox && ready;
}
void Core::setNospam(uint32_t nospam)
@ -1235,31 +1204,10 @@ void Core::setNospam(uint32_t nospam)
tox_self_set_nospam(tox, nospam);
}
void Core::resetCallSources()
{
for (ToxGroupCall& call : groupCalls)
{
for (ALuint source : call.alSources)
alDeleteSources(1, &source);
call.alSources.clear();
}
for (ToxCall& call : calls)
{
if (call.active && call.alSource)
{
ALuint tmp = call.alSource;
call.alSource = 0;
alDeleteSources(1, &tmp);
alGenSources(1, &call.alSource);
}
}
}
void Core::killTimers(bool onlyStop)
{
assert(QThread::currentThread() == coreThread);
av->stop();
toxTimer->stop();
if (!onlyStop)
{

View File

@ -29,7 +29,6 @@
#include <tox/toxencryptsave.h>
#include "corestructs.h"
#include "coreav.h"
#include "coredefines.h"
#include "toxid.h"
@ -38,11 +37,9 @@ template <typename T> class QList;
class QTimer;
class QString;
class CString;
class VideoSource;
class VideoFrame;
#ifdef QTOX_FILTER_AUDIO
class AudioFilterer;
#endif
struct ToxAV;
class CoreAV;
struct vpx_image;
class Core : public QObject
{
@ -50,6 +47,7 @@ class Core : public QObject
public:
explicit Core(QThread* coreThread, Profile& profile);
static Core* getInstance(); ///< Returns the global widget's Core instance
CoreAV* getAv();
~Core();
static const QString TOX_EXT;
@ -88,13 +86,8 @@ public:
static QByteArray decryptData(const QByteArray& data, const TOX_PASS_KEY &encryptionKey);
static QByteArray decryptData(const QByteArray& data); ///< Uses the default profile's key
VideoSource* getVideoSourceFromCall(int callNumber); ///< Get a call's video source
static bool anyActiveCalls(); ///< true is any calls are currently active (note: a call about to start is not yet active)
bool isReady(); ///< Most of the API shouldn't be used until Core is ready, call start() first
void resetCallSources(); ///< Forces to regenerate each call's audio sources
public slots:
void start(); ///< Initializes the core, must be called before anything else
void reset(); ///< Reinitialized the core. Must be called from the Core thread, with the GUI thread ready to process events.
@ -132,27 +125,8 @@ public slots:
void pauseResumeFileSend(uint32_t friendId, uint32_t fileNum);
void pauseResumeFileRecv(uint32_t friendId, uint32_t fileNum);
void answerCall(int callId);
void rejectCall(int callId);
void hangupCall(int callId);
void startCall(uint32_t friendId, bool video=false);
void cancelCall(int callId, uint32_t friendId);
void micMuteToggle(int callId);
void volMuteToggle(int callId);
void setNospam(uint32_t nospam);
bool isGroupAvEnabled(int groupId); ///< True for AV groups, false for text-only groups
static void joinGroupCall(int groupId); ///< Starts a call in an existing AV groupchat. Call from the GUI thread.
static void leaveGroupCall(int groupId); ///< Will not leave the group, just stop the call. Call from the GUI thread.
static void disableGroupCallMic(int groupId);
static void disableGroupCallVol(int groupId);
static void enableGroupCallMic(int groupId);
static void enableGroupCallVol(int groupId);
static bool isGroupCallMicEnabled(int groupId);
static bool isGroupCallVolEnabled(int groupId);
signals:
void connected();
@ -218,21 +192,6 @@ signals:
void fileSendFailed(uint32_t friendId, const QString& fname);
void avInvite(uint32_t friendId, int callIndex, bool video);
void avStart(uint32_t friendId, int callIndex, bool video);
void avCancel(uint32_t friendId, int callIndex);
void avEnd(uint32_t friendId, int callIndex);
void avRinging(uint32_t friendId, int callIndex, bool video);
void avStarting(uint32_t friendId, int callIndex, bool video);
void avEnding(uint32_t friendId, int callIndex);
void avRequestTimeout(uint32_t friendId, int callIndex);
void avPeerTimeout(uint32_t friendId, int callIndex);
void avMediaChange(uint32_t friendId, int callIndex, bool videoEnabled);
void avCallFailed(uint32_t friendId);
void avRejected(uint32_t friendId, int callIndex);
void videoFrameReceived(vpx_image* frame);
private:
static void onFriendRequest(Tox* tox, const uint8_t* cUserId, const uint8_t* cMessage,
size_t cMessageSize, void* core);
@ -256,28 +215,6 @@ private:
const uint8_t* title, uint8_t len, void* _core);
static void onReadReceiptCallback(Tox *tox, uint32_t friendId, uint32_t receipt, void *core);
static void onAvInvite(void* toxav, int32_t call_index, void* core);
static void onAvStart(void* toxav, int32_t call_index, void* core);
static void onAvCancel(void* toxav, int32_t call_index, void* core);
static void onAvReject(void* toxav, int32_t call_index, void* core);
static void onAvEnd(void* toxav, int32_t call_index, void* core);
static void onAvRinging(void* toxav, int32_t call_index, void* core);
static void onAvRequestTimeout(void* toxav, int32_t call_index, void* core);
static void onAvPeerTimeout(void* toxav, int32_t call_index, void* core);
static void onAvMediaChange(void *toxav, int32_t call_index, void* core);
static void sendGroupCallAudio(int groupId, ToxAv* toxav);
static void prepareCall(uint32_t friendId, int callId, ToxAv *toxav, bool videoEnabled);
static void cleanupCall(int callId);
static void playCallAudio(void *toxav, int32_t callId, const int16_t *data,
uint16_t samples, void *user_data); // Callback
static void sendCallAudio(int callId, ToxAv* toxav);
static void playAudioBuffer(ALuint alSource, const int16_t *data, int samples,
unsigned channels, int sampleRate);
static void playCallVideo(void *toxav, int32_t callId, const vpx_image_t* img, void *user_data);
static void sendCallVideo(int callId, ToxAv* toxav, std::shared_ptr<VideoFrame> frame);
bool checkConnection();
void checkEncryptedHistory();
@ -293,24 +230,17 @@ private slots:
private:
Tox* tox;
ToxAv* toxav;
CoreAV* av;
QTimer *toxTimer;
Profile& profile;
static ToxCall calls[TOXAV_MAX_CALLS];
#ifdef QTOX_FILTER_AUDIO
static AudioFilterer * filterer[TOXAV_MAX_CALLS];
#endif
static QHash<int, ToxGroupCall> groupCalls; // Maps group IDs to ToxGroupCalls
QMutex messageSendMutex;
bool ready;
static const int videobufsize;
static uint8_t* videobuf;
static QThread *coreThread;
friend class Audio; ///< Audio can access our calls directly to reduce latency
friend class CoreFile; ///< CoreFile can access tox* and emit our signals
friend class CoreAV; ///< CoreAV accesses our toxav* for now
};
#endif // CORE_HPP

File diff suppressed because it is too large Load Diff

View File

@ -21,7 +21,9 @@
#ifndef COREAV_H
#define COREAV_H
#include <QHash>
#include <QObject>
#include <memory>
#include <atomic>
#include <tox/toxav.h>
#if defined(__APPLE__) && defined(__MACH__)
@ -32,33 +34,108 @@
#include <AL/alc.h>
#endif
#include "src/core/toxcall.h"
#ifdef QTOX_FILTER_AUDIO
class AudioFilterer;
#endif
class QTimer;
class QThread;
class CoreVideoSource;
class CameraSource;
class VideoSource;
class VideoFrame;
class CoreAV;
struct vpx_image;
struct ToxCall
class CoreAV : public QObject
{
ToxAvCSettings codecSettings;
QTimer *sendAudioTimer;
int32_t callId;
uint32_t friendId;
bool videoEnabled;
bool active;
bool muteMic;
bool muteVol;
ALuint alSource;
CoreVideoSource* videoSource;
};
Q_OBJECT
struct ToxGroupCall
{
ToxAvCSettings codecSettings;
QTimer *sendAudioTimer;
int groupId;
bool active = false;
bool muteMic;
bool muteVol;
QHash<int, ALuint> alSources;
public:
CoreAV(Tox* tox);
~CoreAV();
const ToxAV* getToxAv() const;
bool anyActiveCalls(); ///< true is any calls are currently active (note: a call about to start is not yet active)
bool isCallVideoEnabled(uint32_t friendNum);
bool sendCallAudio(uint32_t friendNum); ///< Returns false only on error, but not if there's nothing to send
void sendCallVideo(uint32_t friendNum, std::shared_ptr<VideoFrame> frame);
bool sendGroupCallAudio(int groupNum);
VideoSource* getVideoSourceFromCall(int callNumber); ///< Get a call's video source
void resetCallSources(); ///< Forces to regenerate each call's audio sources
void sendNoVideo(); ///< Signal to all peers that we're not sending video anymore. The next frame sent cancels this.
void joinGroupCall(int groupNum); ///< Starts a call in an existing AV groupchat. Call from the GUI thread.
void leaveGroupCall(int groupNum); ///< Will not leave the group, just stop the call. Call from the GUI thread.
void disableGroupCallMic(int groupNum);
void disableGroupCallVol(int groupNum);
void enableGroupCallMic(int groupNum);
void enableGroupCallVol(int groupNum);
bool isGroupCallMicEnabled(int groupNum) const;
bool isGroupCallVolEnabled(int groupNum) const;
bool isGroupAvEnabled(int groupNum) const; ///< True for AV groups, false for text-only groups
void micMuteToggle(uint32_t friendNum);
void volMuteToggle(uint32_t friendNum);
public slots:
bool startCall(uint32_t friendNum, bool video=false);
bool answerCall(uint32_t friendNum);
bool cancelCall(uint32_t friendNum);
void timeoutCall(uint32_t friendNum);
/// Starts the CoreAV main loop that calls toxav's main loop
void start();
/// Stops the main loop
void stop();
signals:
void avInvite(uint32_t friendId, bool video); ///< Sent when a friend calls us
void avStart(uint32_t friendId, bool video); ///< Sent when a call we initiated has started
void avEnd(uint32_t friendId); ///< Sent when a call was ended by the peer
private slots:
static void callCallback(ToxAV *toxAV, uint32_t friendNum, bool audio, bool video, void* self);
static void stateCallback(ToxAV *, uint32_t friendNum, uint32_t state, void* self);
static void bitrateCallback(ToxAV *toxAV, uint32_t friendNum, uint32_t arate, uint32_t vrate, void* self);
void killTimerFromThread(); ///< Calls itself blocking queued on the coreav thread
private:
void process();
static void audioFrameCallback(ToxAV *toxAV, uint32_t friendNum, const int16_t *pcm, size_t sampleCount,
uint8_t channels, uint32_t samplingRate, void* self);
static void videoFrameCallback(ToxAV *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* self);
private:
static constexpr uint32_t AUDIO_DEFAULT_BITRATE = 64; ///< In kb/s. More than enough for Opus.
static constexpr uint32_t VIDEO_DEFAULT_BITRATE = 3000; ///< Picked at random by fair dice roll.
private:
ToxAV* toxav;
std::unique_ptr<QThread> coreavThread;
std::unique_ptr<QTimer> iterateTimer;
static IndexedList<ToxFriendCall> calls;
static IndexedList<ToxGroupCall> groupCalls; // Maps group IDs to ToxGroupCalls
/**
* This flag is to be acquired before switching in a blocking way between the UI and CoreAV thread.
* The CoreAV thread must have priority for the flag, other threads should back off or release it quickly.
*
* 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
* switch to the UI thread to send it a signal. Both switches block both threads, so this would deadlock.
*/
std::atomic_flag threadSwitchLock;
friend class Audio;
};
#endif // COREAV_H

View File

@ -21,7 +21,6 @@
#ifndef COREDEFINES_H
#define COREDEFINES_H
#define TOXAV_MAX_CALLS 16
#define TOXAV_RINGING_TIME 45
// TODO: Put that in the settings

45
src/core/indexedlist.h Normal file
View File

@ -0,0 +1,45 @@
#ifndef INDEXEDLIST_H
#define INDEXEDLIST_H
#include <vector>
#include <algorithm>
/// More or less a hashmap, indexed by the noexcept move-only value type's operator int
/// Really nice to store our ToxCall objects.
template <typename T>
class IndexedList
{
public:
explicit IndexedList() = default;
// Qt
inline bool isEmpty() { return v.empty(); }
bool contains(int i) { return std::find_if(begin(), end(), [i](T& t){return (int)t == i;}) != end(); }
void remove(int i) { v.erase(std::remove_if(begin(), end(), [i](T& t){return (int)t == i;}), end()); }
T& operator[](int i)
{
iterator it = std::find_if(begin(), end(), [i](T& t){return (int)t == i;});
if (it == end())
it = insert({});
return *it;
}
// STL
using iterator = typename std::vector<T>::iterator;
using const_iterator = typename std::vector<T>::const_iterator;
inline iterator begin() { return v.begin(); }
inline const_iterator begin() const { return v.begin(); }
inline const_iterator cbegin() const { return v.cbegin(); }
inline iterator end() { return v.end(); }
inline const_iterator end() const { return v.end(); }
inline const_iterator cend() const { return v.cend(); }
inline iterator erase(iterator pos) { return v.erase(pos); }
inline iterator erase(iterator first, iterator last) { return v.erase(first, last); }
inline iterator insert(T&& value) { v.push_back(std::move(value)); return --v.end(); }
private:
std::vector<T> v;
};
#endif // INDEXEDLIST_H

217
src/core/toxcall.cpp Normal file
View File

@ -0,0 +1,217 @@
#include "src/audio/audio.h"
#include "src/core/toxcall.h"
#include "src/core/coreav.h"
#include "src/persistence/settings.h"
#include "src/video/camerasource.h"
#include "src/video/corevideosource.h"
#include <QTimer>
#include <QtConcurrent/QtConcurrent>
#ifdef QTOX_FILTER_AUDIO
#include "src/audio/audiofilterer.h"
#endif
using namespace std;
ToxCall::ToxCall(uint32_t CallId)
: sendAudioTimer{new QTimer}, callId{CallId},
inactive{true}, muteMic{false}, muteVol{false}, alSource{0}
{
sendAudioTimer->setInterval(5);
sendAudioTimer->setSingleShot(true);
Audio::getInstance().subscribeInput();
#ifdef QTOX_FILTER_AUDIO
if (Settings::getInstance().getFilterAudio())
{
filterer = new AudioFilterer();
filterer->startFilter(AUDIO_SAMPLE_RATE);
}
else
{
filterer = nullptr;
}
#endif
}
ToxCall::ToxCall(ToxCall&& other) noexcept
: sendAudioTimer{other.sendAudioTimer}, callId{other.callId},
inactive{other.inactive}, muteMic{other.muteMic}, muteVol{other.muteVol},
alSource{other.alSource}
{
other.sendAudioTimer = nullptr;
other.callId = numeric_limits<decltype(callId)>::max();
other.alSource = 0;
#ifdef QTOX_FILTER_AUDIO
filterer = other.filterer;
other.filterer = nullptr;
#endif
}
ToxCall::~ToxCall()
{
if (sendAudioTimer)
{
QObject::disconnect(sendAudioTimer, nullptr, nullptr, nullptr);
sendAudioTimer->stop();
Audio::getInstance().unsubscribeInput();
}
if (alSource)
Audio::deleteSource(&alSource);
#ifdef QTOX_FILTER_AUDIO
if (filterer)
delete filterer;
#endif
}
const ToxCall& ToxCall::operator=(ToxCall&& other) noexcept
{
sendAudioTimer = other.sendAudioTimer;
other.sendAudioTimer = nullptr;
callId = other.callId;
other.callId = numeric_limits<decltype(callId)>::max();
inactive = other.inactive;
muteMic = other.muteMic;
muteVol = other.muteVol;
alSource = other.alSource;
other.alSource = 0;
#ifdef QTOX_FILTER_AUDIO
filterer = other.filterer;
other.filterer = nullptr;
#endif
return *this;
}
void ToxFriendCall::startTimeout()
{
if (!timeoutTimer)
{
timeoutTimer = new QTimer();
// We might move, so we need copies of members. CoreAV won't move while we're alive
CoreAV* avCopy = av;
auto callIdCopy = callId;
QObject::connect(timeoutTimer, &QTimer::timeout, [avCopy, callIdCopy](){
avCopy->timeoutCall(callIdCopy);
});
}
if (!timeoutTimer->isActive())
timeoutTimer->start(CALL_TIMEOUT);
}
void ToxFriendCall::stopTimeout()
{
if (!timeoutTimer)
return;
timeoutTimer->stop();
delete timeoutTimer;
timeoutTimer = nullptr;
}
ToxFriendCall::ToxFriendCall(uint32_t FriendNum, bool VideoEnabled, CoreAV& av)
: ToxCall(FriendNum),
videoEnabled{VideoEnabled}, nullVideoBitrate{false}, videoSource{nullptr},
state{static_cast<TOXAV_FRIEND_CALL_STATE>(0)},
av{&av}, timeoutTimer{nullptr}
{
auto audioTimerCopy = sendAudioTimer; // this might change after a move, but not sendAudioTimer
QObject::connect(sendAudioTimer, &QTimer::timeout, [FriendNum,&av,audioTimerCopy]()
{
// If sendCallAudio returns false, there was a serious error and we might as well stop the timer
if (av.sendCallAudio(FriendNum))
audioTimerCopy->start();
});
sendAudioTimer->start();
if (videoEnabled)
{
videoSource = new CoreVideoSource;
CameraSource& source = CameraSource::getInstance();
if (!source.isOpen())
source.open();
source.subscribe();
QObject::connect(&source, &VideoSource::frameAvailable,
[FriendNum,&av](shared_ptr<VideoFrame> frame){av.sendCallVideo(FriendNum,frame);});
}
}
ToxFriendCall::ToxFriendCall(ToxFriendCall&& other) noexcept
: ToxCall(move(other)),
videoEnabled{other.videoEnabled}, nullVideoBitrate{other.nullVideoBitrate},
videoSource{other.videoSource}, state{other.state},
av{other.av}, timeoutTimer{other.timeoutTimer}
{
other.videoEnabled = false;
other.videoSource = nullptr;
other.timeoutTimer = nullptr;
}
ToxFriendCall::~ToxFriendCall()
{
if (timeoutTimer)
delete timeoutTimer;
if (videoEnabled)
{
// This destructor could be running in a toxav callback while holding toxav locks.
// If the CameraSource thread calls toxav *_send_frame, we might deadlock the toxav and CameraSource locks,
// so we unsuscribe asynchronously, it's fine if the webcam takes a couple milliseconds more to poweroff.
QtConcurrent::run([](){CameraSource::getInstance().unsubscribe();});
if (videoSource)
{
videoSource->setDeleteOnClose(true);
videoSource = nullptr;
}
}
}
const ToxFriendCall& ToxFriendCall::operator=(ToxFriendCall&& other) noexcept
{
ToxCall::operator =(move(other));
videoEnabled = other.videoEnabled;
other.videoEnabled = false;
videoSource = other.videoSource;
other.videoSource = nullptr;
state = other.state;
timeoutTimer = other.timeoutTimer;
other.timeoutTimer = nullptr;
av = other.av;
nullVideoBitrate = other.nullVideoBitrate;
return *this;
}
ToxGroupCall::ToxGroupCall(int GroupNum, CoreAV &av)
: ToxCall(static_cast<decltype(callId)>(GroupNum))
{
static_assert(numeric_limits<decltype(callId)>::max() >= numeric_limits<decltype(GroupNum)>::max(),
"The callId must be able to represent any group number, change its type if needed");
auto audioTimerCopy = sendAudioTimer; // this might change after a move, but not sendAudioTimer
QObject::connect(sendAudioTimer, &QTimer::timeout, [GroupNum,&av,audioTimerCopy]()
{
// If sendGroupCallAudio returns false, there was a serious error and we might as well stop the timer
if (av.sendGroupCallAudio(GroupNum))
audioTimerCopy->start();
});
sendAudioTimer->start();
}
ToxGroupCall::ToxGroupCall(ToxGroupCall&& other) noexcept
: ToxCall(move(other))
{
}
const ToxGroupCall &ToxGroupCall::operator=(ToxGroupCall &&other) noexcept
{
ToxCall::operator =(move(other));
return *this;
}

88
src/core/toxcall.h Normal file
View File

@ -0,0 +1,88 @@
#ifndef TOXCALL_H
#define TOXCALL_H
#include <cstdint>
#include "src/core/indexedlist.h"
#if defined(__APPLE__) && defined(__MACH__)
#include <OpenAL/al.h>
#include <OpenAL/alc.h>
#else
#include <AL/al.h>
#include <AL/alc.h>
#endif
#include <tox/toxav.h>
class QTimer;
class AudioFilterer;
class CoreVideoSource;
class CoreAV;
struct ToxCall
{
protected:
ToxCall() = default;
ToxCall(uint32_t CallId);
~ToxCall();
public:
ToxCall(const ToxCall& other) = delete;
ToxCall(ToxCall&& other) noexcept;
inline operator int() {return callId;}
const ToxCall& operator=(const ToxCall& other) = delete;
const ToxCall& operator=(ToxCall&& other) noexcept;
protected:
QTimer* sendAudioTimer;
public:
uint32_t callId; ///< Could be a friendNum or groupNum, must uniquely identify the call
bool inactive; ///< True while we're not participating. (stopped group call, ringing but hasn't started yet, ...)
bool muteMic;
bool muteVol;
ALuint alSource;
#ifdef QTOX_FILTER_AUDIO
AudioFilterer* filterer;
#endif
};
struct ToxFriendCall : public ToxCall
{
ToxFriendCall() = default;
ToxFriendCall(uint32_t FriendNum, bool VideoEnabled, CoreAV& av);
ToxFriendCall(ToxFriendCall&& other) noexcept;
~ToxFriendCall();
const ToxFriendCall& operator=(ToxFriendCall&& other) noexcept;
bool videoEnabled; ///< True if our user asked for a video call, sending and recving
bool nullVideoBitrate; ///< True if our video bitrate is zero, i.e. if the device is closed
CoreVideoSource* videoSource;
TOXAV_FRIEND_CALL_STATE state; ///< State of the peer (not ours!)
void startTimeout();
void stopTimeout();
protected:
CoreAV* av;
QTimer* timeoutTimer;
private:
static constexpr int CALL_TIMEOUT = 45000;
};
struct ToxGroupCall : public ToxCall
{
ToxGroupCall() = default;
ToxGroupCall(int GroupNum, CoreAV& av);
ToxGroupCall(ToxGroupCall&& other) noexcept;
const ToxGroupCall& operator=(ToxGroupCall&& other) noexcept;
// If you add something here, don't forget to override the ctors and move operators!
};
#endif // TOXCALL_H

View File

@ -43,7 +43,8 @@
#endif
#ifdef LOG_TO_FILE
static QTextStream* logFile {nullptr};
static std::unique_ptr<QTextStream> logFileStream {nullptr};
static std::unique_ptr<QFile> logFileFile {nullptr};
static QMutex mutex;
#endif
@ -80,12 +81,12 @@ void logMessageHandler(QtMsgType type, const QMessageLogContext& ctxt, const QSt
out << LogMsg;
#ifdef LOG_TO_FILE
if (!logFile)
if (!logFileStream)
return;
QMutexLocker locker(&mutex);
*logFile << LogMsg;
logFile->flush();
*logFileStream << LogMsg;
logFileStream->flush();
#endif
}
@ -121,18 +122,17 @@ int main(int argc, char *argv[])
sodium_init(); // For the auto-updater
#ifdef LOG_TO_FILE
logFile = new QTextStream;
QFile logfile(Settings::getInstance().getSettingsDirPath()+"qtox.log");
if (logfile.open(QIODevice::Append))
logFileStream.reset(new QTextStream);
logFileFile.reset(new QFile(Settings::getInstance().getSettingsDirPath()+"qtox.log"));
if (logFileFile->open(QIODevice::Append))
{
logFile->setDevice(&logfile);
*logFile << QDateTime::currentDateTime().toString("\nyyyy-MM-dd HH:mm:ss' file logger starting\n'");
logFileStream->setDevice(logFileFile.get());
*logFileStream << QDateTime::currentDateTime().toString("\nyyyy-MM-dd HH:mm:ss' file logger starting\n'");
}
else
{
qWarning() << "Couldn't open log file!\n";
delete logFile;
logFile = nullptr;
logFileStream.release();
}
#endif
@ -260,8 +260,7 @@ int main(int argc, char *argv[])
int errorcode = a.exec();
#ifdef LOG_TO_FILE
delete logFile;
logFile = nullptr;
logFileStream.release();
#endif
Nexus::destroyInstance();

View File

@ -21,6 +21,7 @@
#include "nexus.h"
#include "src/persistence/profile.h"
#include "src/core/core.h"
#include "src/core/coreav.h"
#include "persistence/settings.h"
#include "video/camerasource.h"
#include "widget/gui.h"
@ -32,6 +33,7 @@
#include <QFile>
#include <QApplication>
#include <cassert>
#include <vpx/vpx_image.h>
#ifdef Q_OS_ANDROID
#include <src/widget/androidgui.h>
@ -47,6 +49,8 @@
#include <QSignalMapper>
#endif
Q_DECLARE_OPAQUE_POINTER(ToxAV*)
static Nexus* nexus{nullptr};
Nexus::Nexus(QObject *parent) :
@ -88,6 +92,7 @@ void Nexus::start()
qRegisterMetaType<int64_t>("int64_t");
qRegisterMetaType<QPixmap>("QPixmap");
qRegisterMetaType<Profile*>("Profile*");
qRegisterMetaType<ToxAV*>("ToxAV*");
qRegisterMetaType<ToxFile>("ToxFile");
qRegisterMetaType<ToxFile::FileDirection>("ToxFile::FileDirection");
qRegisterMetaType<std::shared_ptr<VideoFrame>>("std::shared_ptr<VideoFrame>");
@ -226,7 +231,6 @@ void Nexus::showMainGUI()
connect(core, &Core::groupTitleChanged, widget, &Widget::onGroupTitleChanged);
connect(core, &Core::groupPeerAudioPlaying, widget, &Widget::onGroupPeerAudioPlaying);
connect(core, &Core::emptyGroupCreated, widget, &Widget::onEmptyGroupCreated);
connect(core, &Core::avInvite, widget, &Widget::playRingtone);
connect(core, &Core::friendTypingChanged, widget, &Widget::onFriendTypingChanged);
connect(core, &Core::messageSentResult, widget, &Widget::onMessageSendResult);

View File

@ -118,8 +118,9 @@ CameraDevice* CameraDevice::open(QString devName, VideoMode mode)
{
screen = QApplication::desktop()->screenGeometry().size();
// Workaround https://trac.ffmpeg.org/ticket/4574 by choping 1 px bottom and right
screen.setWidth(screen.width()-1);
screen.setHeight(screen.height()-1);
// Actually, let's chop two pixels, toxav hates odd resolutions (off by one stride)
screen.setWidth(screen.width()-2);
screen.setHeight(screen.height()-2);
}
av_dict_set(&options, "video_size", QString("%1x%2").arg(screen.width()).arg(screen.height()).toStdString().c_str(), 0);
if (mode.FPS)

View File

@ -37,8 +37,7 @@ CameraSource* CameraSource::instance{nullptr};
CameraSource::CameraSource()
: deviceName{"none"}, device{nullptr}, mode(VideoMode{0,0,0}),
cctx{nullptr}, cctxOrig{nullptr}, videoStreamIndex{-1},
biglock{false}, freelistLock{false},
isOpen{false}, subscriptions{0}
_isOpen{false}, streamBlocker{false}, subscriptions{0}
{
subscriptions = 0;
av_register_all();
@ -73,15 +72,12 @@ void CameraSource::open(const QString deviceName)
void CameraSource::open(const QString DeviceName, VideoMode Mode)
{
{
bool expected = false;
while (!biglock.compare_exchange_weak(expected, true))
expected = false;
}
streamBlocker = true;
QMutexLocker l{&biglock};
if (DeviceName == deviceName && Mode == mode)
{
biglock = false;
streamBlocker = false;
return;
}
@ -90,12 +86,12 @@ void CameraSource::open(const QString DeviceName, VideoMode Mode)
deviceName = DeviceName;
mode = Mode;
isOpen = (deviceName != "none");
_isOpen = (deviceName != "none");
if (subscriptions && isOpen)
if (subscriptions && _isOpen)
openDevice();
biglock = false;
streamBlocker = false;
}
void CameraSource::close()
@ -103,20 +99,17 @@ void CameraSource::close()
open("none");
}
bool CameraSource::isOpen()
{
return _isOpen;
}
CameraSource::~CameraSource()
{
// Fast lock, in case our stream thread is running
{
bool expected = false;
while (!biglock.compare_exchange_weak(expected, true))
expected = false;
}
QMutexLocker l{&biglock};
if (!isOpen)
{
biglock = false;
if (!_isOpen)
return;
}
// Free all remaining VideoFrame
// Locking must be done precisely this way to avoid races
@ -133,13 +126,16 @@ CameraSource::~CameraSource()
if (cctxOrig)
avcodec_close(cctxOrig);
for(int i = 0; i < subscriptions; i++)
device->close();
if (device)
{
for(int i = 0; i < subscriptions; i++)
device->close();
device = nullptr;
}
device = nullptr;
// Memfence so the stream thread sees a nullptr device
std::atomic_thread_fence(std::memory_order_release);
biglock=false;
l.unlock();
// Synchronize with our stream thread
while (streamFuture.isRunning())
@ -148,24 +144,17 @@ CameraSource::~CameraSource()
bool CameraSource::subscribe()
{
// Fast lock
{
bool expected = false;
while (!biglock.compare_exchange_weak(expected, true))
expected = false;
}
QMutexLocker l{&biglock};
if (!isOpen)
if (!_isOpen)
{
++subscriptions;
biglock = false;
return true;
}
if (openDevice())
{
++subscriptions;
biglock = false;
return true;
}
else
@ -176,7 +165,6 @@ bool CameraSource::subscribe()
videoStreamIndex = -1;
// Memfence so the stream thread sees a nullptr device
std::atomic_thread_fence(std::memory_order_release);
biglock = false;
return false;
}
@ -184,31 +172,24 @@ bool CameraSource::subscribe()
void CameraSource::unsubscribe()
{
// Fast lock
{
bool expected = false;
while (!biglock.compare_exchange_weak(expected, true))
expected = false;
}
QMutexLocker l{&biglock};
if (!isOpen)
if (!_isOpen)
{
subscriptions--;
biglock = false;
--subscriptions;
return;
}
if (!device)
{
qWarning() << "Unsubscribing with zero subscriber";
biglock = false;
return;
}
if (subscriptions - 1 == 0)
{
closeDevice();
biglock = false;
l.unlock();
// Synchronize with our stream thread
while (streamFuture.isRunning())
@ -217,7 +198,6 @@ void CameraSource::unsubscribe()
else
{
device->close();
biglock = false;
}
subscriptions--;
@ -225,6 +205,8 @@ void CameraSource::unsubscribe()
bool CameraSource::openDevice()
{
qDebug() << "Opening device "<<deviceName;
if (device)
{
device->open();
@ -296,6 +278,8 @@ bool CameraSource::openDevice()
void CameraSource::closeDevice()
{
qDebug() << "Closing device "<<deviceName;
// Free all remaining VideoFrame
// Locking must be done precisely this way to avoid races
for (int i = 0; i < freelist.size(); i++)
@ -341,18 +325,13 @@ void CameraSource::stream()
if (!frameFinished)
return;
// Broadcast a new VideoFrame, it takes ownership of the AVFrame
{
bool expected = false;
while (!freelistLock.compare_exchange_weak(expected, true))
expected = false;
}
freelistLock.lock();
int freeFreelistSlot = getFreelistSlotLockless();
auto frameFreeCb = std::bind(&CameraSource::freelistCallback, this, freeFreelistSlot);
std::shared_ptr<VideoFrame> vframe = std::make_shared<VideoFrame>(frame, frameFreeCb);
freelist.append(vframe);
freelistLock = false;
freelistLock.unlock();
emit frameAvailable(vframe);
}
@ -361,39 +340,30 @@ void CameraSource::stream()
};
forever {
// Fast lock
{
bool expected = false;
while (!biglock.compare_exchange_weak(expected, true))
expected = false;
}
biglock.lock();
// When a thread makes device null, it releases it, so we acquire here
std::atomic_thread_fence(std::memory_order_acquire);
if (!device)
{
biglock = false;
biglock.unlock();
return;
}
streamLoop();
// Give a chance to other functions to pick up the lock if needed
biglock = false;
biglock.unlock();
while (streamBlocker)
QThread::yieldCurrentThread();
QThread::yieldCurrentThread();
}
}
void CameraSource::freelistCallback(int freelistIndex)
{
// Fast spinlock
{
bool expected = false;
while (!freelistLock.compare_exchange_weak(expected, true))
expected = false;
}
QMutexLocker l{&freelistLock};
freelist[freelistIndex].reset();
freelistLock = false;
}
int CameraSource::getFreelistSlotLockless()

View File

@ -55,6 +55,7 @@ public:
void open(const QString deviceName);
void open(const QString deviceName, VideoMode mode);
void close(); ///< Equivalent to opening the source with the video device "none". Stops streaming.
bool isOpen();
// VideoSource interface
virtual bool subscribe() override;
@ -90,8 +91,9 @@ private:
VideoMode mode; ///< What mode we tried to open the device in, all zeros means default mode
AVCodecContext* cctx, *cctxOrig; ///< Codec context of the camera's selected video stream
int videoStreamIndex; ///< A camera can have multiple streams, this is the one we're decoding
std::atomic_bool biglock, freelistLock; ///< True when locked. Faster than mutexes for video decoding.
std::atomic_bool isOpen;
QMutex biglock, freelistLock; ///< True when locked. Faster than mutexes for video decoding.
std::atomic_bool _isOpen;
std::atomic_bool streamBlocker; ///< Holds the streaming thread still when true
std::atomic_int subscriptions; ///< Remember how many times we subscribed for RAII
static CameraSource* instance;

View File

@ -26,18 +26,16 @@ extern "C" {
CoreVideoSource::CoreVideoSource()
: subscribers{0}, deleteOnClose{false},
biglock{false}
stopped{false}
{
}
void CoreVideoSource::pushFrame(const vpx_image_t* vpxframe)
{
// Fast lock
{
bool expected = false;
while (!biglock.compare_exchange_weak(expected, true))
expected = false;
}
if (stopped)
return;
QMutexLocker locker(&biglock);
std::shared_ptr<VideoFrame> vframe;
AVFrame* avframe;
@ -46,11 +44,11 @@ void CoreVideoSource::pushFrame(const vpx_image_t* vpxframe)
int dstStride, srcStride, minStride;
if (subscribers <= 0)
goto end;
return;
avframe = av_frame_alloc();
if (!avframe)
goto end;
return;
avframe->width = width;
avframe->height = height;
avframe->format = AV_PIX_FMT_YUV420P;
@ -59,7 +57,7 @@ void CoreVideoSource::pushFrame(const vpx_image_t* vpxframe)
if (!buf)
{
av_frame_free(&avframe);
goto end;
return;
}
avframe->opaque = buf;
@ -77,52 +75,46 @@ void CoreVideoSource::pushFrame(const vpx_image_t* vpxframe)
vframe = std::make_shared<VideoFrame>(avframe);
emit frameAvailable(vframe);
end:
biglock = false;
}
bool CoreVideoSource::subscribe()
{
// Fast lock
{
bool expected = false;
while (!biglock.compare_exchange_weak(expected, true))
expected = false;
}
QMutexLocker locker(&biglock);
++subscribers;
biglock = false;
return true;
}
void CoreVideoSource::unsubscribe()
{
// Fast lock
{
bool expected = false;
while (!biglock.compare_exchange_weak(expected, true))
expected = false;
}
biglock.lock();
if (--subscribers == 0)
{
if (deleteOnClose)
{
biglock = false;
biglock.unlock();
// DANGEROUS: No member access after this point, that's why we manually unlock
delete this;
return;
}
}
biglock = false;
biglock.unlock();
}
void CoreVideoSource::setDeleteOnClose(bool newstate)
{
// Fast lock
{
bool expected = false;
while (!biglock.compare_exchange_weak(expected, true))
expected = false;
}
QMutexLocker locker(&biglock);
deleteOnClose = newstate;
biglock = false;
}
void CoreVideoSource::stopSource()
{
QMutexLocker locker(&biglock);
stopped = true;
emit sourceStopped();
}
void CoreVideoSource::restartSource()
{
QMutexLocker locker(&biglock);
stopped = false;
}

View File

@ -24,6 +24,7 @@
#include <vpx/vpx_image.h>
#include <atomic>
#include "videosource.h"
#include <QMutex>
/// A VideoSource that emits frames received by Core
class CoreVideoSource : public VideoSource
@ -35,8 +36,8 @@ public:
virtual void unsubscribe() override;
private:
// Only Core should create a CoreVideoSource since
// only Core can push images to it
// Only CoreAV should create a CoreVideoSource since
// only CoreAV can push images to it
CoreVideoSource();
/// Makes a copy of the vpx_image_t and emits it as a new VideoFrame
@ -44,12 +45,19 @@ private:
/// If true, self-delete after the last suscriber is gone
void setDeleteOnClose(bool newstate);
/// Stopping the source will block any pushFrame calls from doing anything
/// See the callers in CoreAV for the rationale
void stopSource();
void restartSource();
private:
std::atomic_int subscribers; ///< Number of suscribers
std::atomic_bool deleteOnClose; ///< If true, self-delete after the last suscriber is gone
std::atomic_bool biglock; ///< Fast lock
QMutex biglock;
std::atomic_bool stopped;
friend class Core;
friend class CoreAV;
friend struct ToxFriendCall;
};
#endif // COREVIDEOSOURCE_H

View File

@ -86,7 +86,6 @@ public:
protected:
void resizeEvent(QResizeEvent* event) final override
{
qDebug() << "Resize!";
updateSize();
QWidget::resizeEvent(event);
}
@ -94,13 +93,11 @@ protected:
private slots:
void updateSize()
{
qDebug() << videoSurface->isExpanding();
if (videoSurface->isExpanding())
{
int width = videoSurface->height() * videoSurface->getRatio();
videoSurface->setMinimumWidth(width);
videoSurface->setMaximumWidth(width);
qDebug() << videoSurface->minimumWidth();
}
}

View File

@ -33,6 +33,7 @@ class VideoSource : public QObject
Q_OBJECT
public:
virtual ~VideoSource() = default;
/// If subscribe sucessfully opens the source, it will start emitting frameAvailable signals
virtual bool subscribe() = 0;
/// Stop emitting frameAvailable signals, and free associated resources if necessary
@ -40,6 +41,9 @@ public:
signals:
void frameAvailable(std::shared_ptr<VideoFrame> frame);
/// Emitted when the source is stopped for an indefinite amount of time,
/// but might restart sending frames again later
void sourceStopped();
};
#endif // VIDEOSOURCE_H

View File

@ -102,6 +102,7 @@ void VideoSurface::subscribe()
{
source->subscribe();
connect(source, &VideoSource::frameAvailable, this, &VideoSurface::onNewFrameAvailable);
connect(source, &VideoSource::sourceStopped, this, &VideoSurface::onSourceStopped);
}
}
@ -124,6 +125,7 @@ void VideoSurface::unsubscribe()
source->unsubscribe();
disconnect(source, &VideoSource::frameAvailable, this, &VideoSurface::onNewFrameAvailable);
disconnect(source, &VideoSource::sourceStopped, this, &VideoSurface::onSourceStopped);
}
void VideoSurface::onNewFrameAvailable(std::shared_ptr<VideoFrame> newFrame)
@ -148,6 +150,13 @@ void VideoSurface::onNewFrameAvailable(std::shared_ptr<VideoFrame> newFrame)
update();
}
void VideoSurface::onSourceStopped()
{
// If the source's stream is on hold, just revert back to the avatar view
lastFrame.reset();
update();
}
void VideoSurface::paintEvent(QPaintEvent*)
{
lock();
@ -157,6 +166,8 @@ void VideoSurface::paintEvent(QPaintEvent*)
if (lastFrame)
{
QImage frame = lastFrame->toQImage(rect().size());
if (frame.isNull())
lastFrame.reset();
painter.drawImage(boundingRect, frame, frame.rect(), Qt::NoFormatConversion);
}
else

View File

@ -55,6 +55,7 @@ protected:
private slots:
void onNewFrameAvailable(std::shared_ptr<VideoFrame> newFrame);
void onSourceStopped();
private:
void recalulateBounds();

View File

@ -34,7 +34,9 @@
#include <QSplitter>
#include <cassert>
#include "chatform.h"
#include "src/audio/audio.h"
#include "src/core/core.h"
#include "src/core/coreav.h"
#include "src/friend.h"
#include "src/persistence/historykeeper.h"
#include "src/widget/style.h"
@ -62,8 +64,11 @@
ChatForm::ChatForm(Friend* chatFriend)
: f(chatFriend)
, callId{0}, isTyping{false}
, isTyping{false}
{
Core* core = Core::getInstance();
coreav = core->getAv();
nameLabel->setText(f->getDisplayedName());
avatar->setPixmap(QPixmap(":/img/contact_dark.svg"), Qt::transparent);
@ -95,7 +100,7 @@ ChatForm::ChatForm(Friend* chatFriend)
loadHistoryAction = menu.addAction(QString(), this, SLOT(onLoadHistory()));
connect(Core::getInstance(), &Core::fileSendStarted, this, &ChatForm::startFileSend);
connect(core, &Core::fileSendStarted, this, &ChatForm::startFileSend);
connect(sendButton, &QPushButton::clicked, this, &ChatForm::onSendTriggered);
connect(fileButton, &QPushButton::clicked, this, &ChatForm::onAttachClicked);
connect(screenshotButton, &QPushButton::clicked, this, &ChatForm::onScreenshotClicked);
@ -103,7 +108,7 @@ ChatForm::ChatForm(Friend* chatFriend)
connect(videoButton, &QPushButton::clicked, this, &ChatForm::onVideoCallTriggered);
connect(msgEdit, &ChatTextEdit::enterPressed, this, &ChatForm::onSendTriggered);
connect(msgEdit, &ChatTextEdit::textChanged, this, &ChatForm::onTextEditChanged);
connect(Core::getInstance(), &Core::fileSendFailed, this, &ChatForm::onFileSendFailed);
connect(core, &Core::fileSendFailed, this, &ChatForm::onFileSendFailed);
connect(this, &ChatForm::chatAreaCleared, getOfflineMsgEngine(), &OfflineMsgEngine::removeAllReciepts);
connect(&typingTimer, &QTimer::timeout, this, [=]{
Core::getInstance()->sendTyping(f->getFriendID(), false);
@ -250,76 +255,63 @@ void ChatForm::onFileRecvRequest(ToxFile file)
Widget::getInstance()->updateFriendActivity(f);
}
void ChatForm::onAvInvite(uint32_t FriendId, int CallId, bool video)
void ChatForm::onAvInvite(uint32_t FriendId, bool video)
{
if (FriendId != f->getFriendID())
return;
qDebug() << "onAvInvite, callId: " << CallId;
qDebug() << "onAvInvite";
callId = CallId;
callButton->disconnect();
videoButton->disconnect();
disableCallButtons();
if (video)
{
callConfirm = new CallConfirmWidget(videoButton, *f);
if (f->getFriendWidget()->chatFormIsSet(false))
callConfirm->show();
connect(callConfirm, &CallConfirmWidget::accepted, this, &ChatForm::onAnswerCallTriggered);
connect(callConfirm, &CallConfirmWidget::rejected, this, &ChatForm::onRejectCallTriggered);
callButton->setObjectName("grey");
callButton->setToolTip("");
videoButton->setObjectName("yellow");
videoButton->setToolTip(tr("Accept video call"));
videoButton->style()->polish(videoButton);
connect(videoButton, &QPushButton::clicked, this, &ChatForm::onAnswerCallTriggered);
}
else
{
callConfirm = new CallConfirmWidget(callButton, *f);
if (f->getFriendWidget()->chatFormIsSet(false))
callConfirm->show();
connect(callConfirm, &CallConfirmWidget::accepted, this, &ChatForm::onAnswerCallTriggered);
connect(callConfirm, &CallConfirmWidget::rejected, this, &ChatForm::onRejectCallTriggered);
callButton->setObjectName("yellow");
callButton->setToolTip(tr("Accept audio call"));
videoButton->setObjectName("grey");
videoButton->setToolTip("");
callButton->style()->polish(callButton);
connect(callButton, &QPushButton::clicked, this, &ChatForm::onAnswerCallTriggered);
}
callButton->style()->polish(callButton);
videoButton->style()->polish(videoButton);
if (f->getFriendWidget()->chatFormIsSet(false))
callConfirm->show();
connect(callConfirm, &CallConfirmWidget::accepted, this, &ChatForm::onAnswerCallTriggered);
connect(callConfirm, &CallConfirmWidget::rejected, this, &ChatForm::onRejectCallTriggered);
insertChatMessage(ChatMessage::createChatInfoMessage(tr("%1 calling").arg(f->getDisplayedName()),
ChatMessage::INFO,
QDateTime::currentDateTime()));
Widget::getInstance()->newFriendMessageAlert(FriendId);
Widget::getInstance()->newFriendMessageAlert(FriendId, false);
Audio& audio = Audio::getInstance();
audio.startLoop();
audio.playMono16Sound(":audio/ToxicIncomingCall.pcm");
}
void ChatForm::onAvStart(uint32_t FriendId, int CallId, bool video)
void ChatForm::onAvStart(uint32_t FriendId, bool video)
{
if (FriendId != f->getFriendID())
return;
qDebug() << "onAvStart, callId: " << CallId;
qDebug() << "onAvStart";
audioInputFlag = true;
audioOutputFlag = true;
callId = CallId;
callButton->disconnect();
videoButton->disconnect();
disableCallButtons();
if (video)
{
callButton->setObjectName("grey");
callButton->setToolTip("");
videoButton->setObjectName("red");
videoButton->setToolTip(tr("End video call"));
videoButton->style()->polish(videoButton);
connect(videoButton, SIGNAL(clicked()),
this, SLOT(onHangupCallTriggered()));
@ -329,14 +321,11 @@ void ChatForm::onAvStart(uint32_t FriendId, int CallId, bool video)
{
callButton->setObjectName("red");
callButton->setToolTip(tr("End audio call"));
videoButton->setObjectName("grey");
videoButton->setToolTip("");
callButton->style()->polish(callButton);
connect(callButton, SIGNAL(clicked()),
this, SLOT(onHangupCallTriggered()));
hideNetcam();
}
callButton->style()->polish(callButton);
videoButton->style()->polish(videoButton);
micButton->setObjectName("green");
micButton->style()->polish(micButton);
@ -353,198 +342,53 @@ void ChatForm::onAvStart(uint32_t FriendId, int CallId, bool video)
startCounter();
}
void ChatForm::onAvCancel(uint32_t FriendId, int CallId)
void ChatForm::onAvEnd(uint32_t FriendId)
{
if (FriendId != f->getFriendID())
return;
qDebug() << "onAvCancel, callId: " << CallId;
qDebug() << "onAvEnd";
delete callConfirm;
callConfirm = nullptr;
Audio::getInstance().stopLoop();
enableCallButtons();
stopCounter();
hideNetcam();
addSystemInfoMessage(tr("%1 stopped calling").arg(f->getDisplayedName()),
ChatMessage::INFO,
QDateTime::currentDateTime());
}
void ChatForm::onAvRinging(uint32_t FriendId, int CallId, bool video)
void ChatForm::showOutgoingCall(bool video)
{
if (FriendId != f->getFriendID())
return;
audioInputFlag = true;
audioOutputFlag = true;
qDebug() << "onAvRinging, callId: " << CallId;
callId = CallId;
callButton->disconnect();
videoButton->disconnect();
disableCallButtons();
if (video)
{
callButton->setObjectName("grey");
callButton->style()->polish(callButton);
callButton->setToolTip("");
videoButton->setObjectName("yellow");
videoButton->style()->polish(videoButton);
videoButton->setToolTip(tr("Cancel video call"));
connect(videoButton, SIGNAL(clicked()),
this, SLOT(onCancelCallTriggered()));
connect(videoButton, &QPushButton::clicked,
this, &ChatForm::onCancelCallTriggered);
}
else
{
callButton->setObjectName("yellow");
callButton->style()->polish(callButton);
callButton->setToolTip(tr("Cancel audio call"));
videoButton->setObjectName("grey");
videoButton->style()->polish(videoButton);
videoButton->setToolTip("");
connect(callButton, SIGNAL(clicked()),
this, SLOT(onCancelCallTriggered()));
connect(callButton, &QPushButton::clicked,
this, &ChatForm::onCancelCallTriggered);
}
addSystemInfoMessage(tr("Calling to %1").arg(f->getDisplayedName()),
addSystemInfoMessage(tr("Calling %1").arg(f->getDisplayedName()),
ChatMessage::INFO,
QDateTime::currentDateTime());
Widget::getInstance()->updateFriendActivity(f);
}
void ChatForm::onAvStarting(uint32_t FriendId, int CallId, bool video)
{
if (FriendId != f->getFriendID())
return;
qDebug() << "onAvStarting, callId:" << CallId;
callId = CallId;
callButton->disconnect();
videoButton->disconnect();
if (video)
{
callButton->setObjectName("grey");
callButton->style()->polish(callButton);
callButton->setToolTip("");
videoButton->setObjectName("red");
videoButton->style()->polish(videoButton);
videoButton->setToolTip(tr("End video call"));
connect(videoButton, SIGNAL(clicked()), this, SLOT(onHangupCallTriggered()));
showNetcam();
}
else
{
callButton->setObjectName("red");
callButton->style()->polish(callButton);
callButton->setToolTip(tr("End audio call"));
videoButton->setObjectName("grey");
videoButton->style()->polish(videoButton);
videoButton->setToolTip("");
connect(callButton, SIGNAL(clicked()), this, SLOT(onHangupCallTriggered()));
}
startCounter();
}
void ChatForm::onAvEnding(uint32_t FriendId, int CallId)
{
if (FriendId != f->getFriendID())
return;
qDebug() << "onAvEnding, callId: " << CallId;
delete callConfirm;
callConfirm = nullptr;
enableCallButtons();
stopCounter();
CameraSource::getInstance().unsubscribe();
hideNetcam();
}
void ChatForm::onAvEnd(uint32_t FriendId, int CallId)
{
if (FriendId != f->getFriendID())
return;
qDebug() << "onAvEnd, callId: " << CallId;
delete callConfirm;
callConfirm = nullptr;
enableCallButtons();
stopCounter();
hideNetcam();
}
void ChatForm::onAvRequestTimeout(uint32_t FriendId, int CallId)
{
if (FriendId != f->getFriendID())
return;
qDebug() << "onAvRequestTimeout, callId: " << CallId;
delete callConfirm;
callConfirm = nullptr;
enableCallButtons();
stopCounter();
hideNetcam();
}
void ChatForm::onAvPeerTimeout(uint32_t FriendId, int)
{
if (FriendId != f->getFriendID())
return;
qDebug() << "onAvPeerTimeout";
delete callConfirm;
callConfirm = nullptr;
enableCallButtons();
stopCounter();
hideNetcam();
}
void ChatForm::onAvRejected(uint32_t FriendId, int)
{
if (FriendId != f->getFriendID())
return;
qDebug() << "onAvRejected";
delete callConfirm;
callConfirm = nullptr;
enableCallButtons();
insertChatMessage(ChatMessage::createChatInfoMessage(tr("Call rejected"),
ChatMessage::INFO,
QDateTime::currentDateTime()));
hideNetcam();
}
void ChatForm::onAvMediaChange(uint32_t FriendId, int CallId, bool video)
{
if (FriendId != f->getFriendID() || CallId != callId)
return;
qDebug() << "onAvMediaChange, callId: " << CallId;
if (video)
showNetcam();
else
hideNetcam();
}
void ChatForm::onAnswerCallTriggered()
{
qDebug() << "onAnswerCallTriggered";
@ -555,9 +399,19 @@ void ChatForm::onAnswerCallTriggered()
callConfirm = nullptr;
}
audioInputFlag = true;
audioOutputFlag = true;
emit answerCall(callId);
Audio::getInstance().stopLoop();
disableCallButtons();
if (!coreav->answerCall(f->getFriendID()))
{
enableCallButtons();
stopCounter();
hideNetcam();
return;
}
onAvStart(f->getFriendID(), coreav->isCallVideoEnabled(f->getFriendID()));
}
void ChatForm::onHangupCallTriggered()
@ -570,9 +424,11 @@ void ChatForm::onHangupCallTriggered()
audioInputFlag = false;
audioOutputFlag = false;
emit hangupCall(callId);
coreav->cancelCall(f->getFriendID());
stopCounter();
enableCallButtons();
hideNetcam();
}
void ChatForm::onRejectCallTriggered()
@ -585,56 +441,48 @@ void ChatForm::onRejectCallTriggered()
callConfirm = nullptr;
}
Audio::getInstance().stopLoop();
audioInputFlag = false;
audioOutputFlag = false;
emit rejectCall(callId);
coreav->cancelCall(f->getFriendID());
enableCallButtons();
stopCounter();
}
void ChatForm::onCallTriggered()
{
qDebug() << "onCallTriggered";
audioInputFlag = true;
audioOutputFlag = true;
callButton->disconnect();
videoButton->disconnect();
emit startCall(f->getFriendID(), false);
disableCallButtons();
if (coreav->startCall(f->getFriendID(), false))
showOutgoingCall(false);
else
enableCallButtons();
}
void ChatForm::onVideoCallTriggered()
{
qDebug() << "onVideoCallTriggered";
audioInputFlag = true;
audioOutputFlag = true;
callButton->disconnect();
videoButton->disconnect();
emit startCall(f->getFriendID(), true);
}
void ChatForm::onAvCallFailed(uint32_t FriendId)
{
if (FriendId != f->getFriendID())
return;
qDebug() << "onAvCallFailed";
delete callConfirm;
callConfirm = nullptr;
enableCallButtons();
disableCallButtons();
if (coreav->startCall(f->getFriendID(), true))
showOutgoingCall(true);
else
enableCallButtons();
}
void ChatForm::onCancelCallTriggered()
{
qDebug() << "onCancelCallTriggered";
enableCallButtons();
if (!coreav->cancelCall(f->getFriendID()))
qWarning() << "Failed to cancel a call! Assuming we're not in call";
enableCallButtons();
stopCounter();
hideNetcam();
emit cancelCall(callId, f->getFriendID());
}
void ChatForm::enableCallButtons()
@ -644,6 +492,31 @@ void ChatForm::enableCallButtons()
audioInputFlag = false;
audioOutputFlag = false;
disableCallButtons();
if (disableCallButtonsTimer == nullptr)
{
disableCallButtonsTimer = new QTimer();
connect(disableCallButtonsTimer, SIGNAL(timeout()),
this, SLOT(onEnableCallButtons()));
disableCallButtonsTimer->start(1500); // 1.5sec
qDebug() << "timer started!!";
}
}
void ChatForm::disableCallButtons()
{
qDebug() << "disableCallButtons";
// Prevents race enable / disable / onEnable, when it should be disabled
if (disableCallButtonsTimer)
{
disableCallButtonsTimer->stop();
delete disableCallButtonsTimer;
disableCallButtonsTimer = nullptr;
}
micButton->setObjectName("grey");
micButton->style()->polish(micButton);
micButton->setToolTip("");
@ -661,16 +534,6 @@ void ChatForm::enableCallButtons()
videoButton->style()->polish(videoButton);
videoButton->setToolTip("");
videoButton->disconnect();
if (disableCallButtonsTimer == nullptr)
{
disableCallButtonsTimer = new QTimer();
connect(disableCallButtonsTimer, SIGNAL(timeout()),
this, SLOT(onEnableCallButtons()));
disableCallButtonsTimer->start(1500); // 1.5sec
qDebug() << "timer started!!";
}
}
void ChatForm::onEnableCallButtons()
@ -700,7 +563,7 @@ void ChatForm::onMicMuteToggle()
{
if (audioInputFlag == true)
{
emit micMuteToggle(callId);
coreav->micMuteToggle(f->getFriendID());
if (micButton->objectName() == "red")
{
micButton->setObjectName("green");
@ -720,7 +583,7 @@ void ChatForm::onVolMuteToggle()
{
if (audioOutputFlag == true)
{
emit volMuteToggle(callId);
coreav->volMuteToggle(f->getFriendID());
if (volButton->objectName() == "red")
{
volButton->setObjectName("green");
@ -756,7 +619,7 @@ GenericNetCamView *ChatForm::createNetcam()
{
qDebug() << "creating netcam";
NetCamView* view = new NetCamView(f->getFriendID(), this);
view->show(Core::getInstance()->getVideoSourceFromCall(callId), f->getDisplayedName());
view->show(Core::getInstance()->getAv()->getVideoSourceFromCall(f->getFriendID()), f->getDisplayedName());
return view;
}

View File

@ -34,6 +34,7 @@ class CallConfirmWidget;
class QHideEvent;
class QMoveEvent;
class OfflineMsgEngine;
class CoreAV;
class ChatForm : public GenericChatForm
{
@ -52,32 +53,14 @@ public:
signals:
void sendFile(uint32_t friendId, QString, QString, long long);
void startCall(uint32_t FriendId, bool video);
void answerCall(int callId);
void hangupCall(int callId);
void cancelCall(int callId, uint32_t FriendId);
void rejectCall(int callId);
void micMuteToggle(int callId);
void volMuteToggle(int callId);
void aliasChanged(const QString& alias);
public slots:
void startFileSend(ToxFile file);
void onFileRecvRequest(ToxFile file);
void onAvInvite(uint32_t FriendId, int CallId, bool video);
void onAvStart(uint32_t FriendId, int CallId, bool video);
void onAvCancel(uint32_t FriendId, int CallId);
void onAvEnd(uint32_t FriendId, int CallId);
void onAvRinging(uint32_t FriendId, int CallId, bool video);
void onAvStarting(uint32_t FriendId, int CallId, bool video);
void onAvEnding(uint32_t FriendId, int CallId);
void onAvRequestTimeout(uint32_t FriendId, int CallId);
void onAvPeerTimeout(uint32_t FriendId, int CallId);
void onAvMediaChange(uint32_t FriendId, int CallId, bool video);
void onAvCallFailed(uint32_t FriendId);
void onAvRejected(uint32_t FriendId, int CallId);
void onMicMuteToggle();
void onVolMuteToggle();
void onAvInvite(uint32_t FriendId, bool video);
void onAvStart(uint32_t FriendId, bool video);
void onAvEnd(uint32_t FriendId);
void onAvatarChange(uint32_t FriendId, const QPixmap& pic);
void onAvatarRemoved(uint32_t FriendId);
@ -91,6 +74,8 @@ private slots:
void onHangupCallTriggered();
void onCancelCallTriggered();
void onRejectCallTriggered();
void onMicMuteToggle();
void onVolMuteToggle();
void onFileSendFailed(uint32_t FriendId, const QString &fname);
void onLoadHistory();
void onUpdateTime();
@ -102,6 +87,7 @@ private slots:
private:
void retranslateUi();
void showOutgoingCall(bool video);
protected:
virtual GenericNetCamView* createNetcam() final override;
@ -112,9 +98,9 @@ protected:
virtual void showEvent(QShowEvent* event) final override;
private:
CoreAV* coreav;
Friend* f;
CroppingLabel *statusMessageLabel;
int callId;
QLabel *callDuration;
QTimer *callDurationTimer;
QTimer typingTimer;
@ -129,6 +115,7 @@ private:
QString secondsToDHMS(quint32 duration);
CallConfirmWidget *callConfirm;
void enableCallButtons();
void disableCallButtons();
bool isTyping;
void SendMessageStr(QString msg);
};

View File

@ -25,6 +25,7 @@
#include "src/widget/tool/croppinglabel.h"
#include "src/widget/maskablepixmapwidget.h"
#include "src/core/core.h"
#include "src/core/coreav.h"
#include "src/widget/style.h"
#include "src/persistence/historykeeper.h"
#include "src/widget/flowlayout.h"
@ -282,13 +283,13 @@ void GroupChatForm::onMicMuteToggle()
{
if (micButton->objectName() == "red")
{
Core::getInstance()->enableGroupCallMic(group->getGroupId());
Core::getInstance()->getAv()->enableGroupCallMic(group->getGroupId());
micButton->setObjectName("green");
micButton->setToolTip(tr("Mute microphone"));
}
else
{
Core::getInstance()->disableGroupCallMic(group->getGroupId());
Core::getInstance()->getAv()->disableGroupCallMic(group->getGroupId());
micButton->setObjectName("red");
micButton->setToolTip(tr("Unmute microphone"));
}
@ -303,13 +304,13 @@ void GroupChatForm::onVolMuteToggle()
{
if (volButton->objectName() == "red")
{
Core::getInstance()->enableGroupCallVol(group->getGroupId());
Core::getInstance()->getAv()->enableGroupCallVol(group->getGroupId());
volButton->setObjectName("green");
volButton->setToolTip(tr("Mute call"));
}
else
{
Core::getInstance()->disableGroupCallVol(group->getGroupId());
Core::getInstance()->getAv()->disableGroupCallVol(group->getGroupId());
volButton->setObjectName("red");
volButton->setToolTip(tr("Unmute call"));
}
@ -322,7 +323,7 @@ void GroupChatForm::onCallClicked()
{
if (!inCall)
{
Core::getInstance()->joinGroupCall(group->getGroupId());
Core::getInstance()->getAv()->joinGroupCall(group->getGroupId());
audioInputFlag = true;
audioOutputFlag = true;
callButton->setObjectName("red");
@ -339,7 +340,7 @@ void GroupChatForm::onCallClicked()
}
else
{
Core::getInstance()->leaveGroupCall(group->getGroupId());
Core::getInstance()->getAv()->leaveGroupCall(group->getGroupId());
audioInputFlag = false;
audioOutputFlag = false;
callButton->setObjectName("green");
@ -375,10 +376,9 @@ void GroupChatForm::keyPressEvent(QKeyEvent* ev)
// Push to talk (CTRL+P)
if (ev->key() == Qt::Key_P && (ev->modifiers() & Qt::ControlModifier) && inCall)
{
Core* core = Core::getInstance();
if (!core->isGroupCallMicEnabled(group->getGroupId()))
if (!Core::getInstance()->getAv()->isGroupCallMicEnabled(group->getGroupId()))
{
core->enableGroupCallMic(group->getGroupId());
Core::getInstance()->getAv()->enableGroupCallMic(group->getGroupId());
micButton->setObjectName("green");
micButton->style()->polish(micButton);
Style::repolish(micButton);
@ -394,10 +394,9 @@ void GroupChatForm::keyReleaseEvent(QKeyEvent* ev)
// Push to talk (CTRL+P)
if (ev->key() == Qt::Key_P && (ev->modifiers() & Qt::ControlModifier) && inCall)
{
Core* core = Core::getInstance();
if (core->isGroupCallMicEnabled(group->getGroupId()))
if (Core::getInstance()->getAv()->isGroupCallMicEnabled(group->getGroupId()))
{
core->disableGroupCallMic(group->getGroupId());
Core::getInstance()->getAv()->disableGroupCallMic(group->getGroupId());
micButton->setObjectName("red");
micButton->style()->polish(micButton);
Style::repolish(micButton);

View File

@ -25,6 +25,8 @@
#include "src/video/cameradevice.h"
#include "src/video/videosurface.h"
#include "src/widget/translator.h"
#include "src/core/core.h"
#include "src/core/coreav.h"
#if defined(__APPLE__) && defined(__MACH__)
#include <OpenAL/al.h>
@ -202,14 +204,15 @@ void AVForm::onVideoDevChanged(int index)
qWarning() << "Invalid index";
return;
}
QString dev = videoDeviceList[index].first;
Settings::getInstance().setVideoDev(dev);
bool previouslyBlocked = bodyUI->videoModescomboBox->blockSignals(true);
updateVideoModes(index);
bodyUI->videoModescomboBox->blockSignals(previouslyBlocked);
camera.open(dev);
killVideoSurface();
createVideoSurface();
if (dev == "none")
Core::getInstance()->getAv()->sendNoVideo();
}
void AVForm::hideEvent(QHideEvent *)
@ -316,8 +319,8 @@ void AVForm::onInDevChanged(const QString &deviceDescriptor)
Settings::getInstance().setInDev(deviceDescriptor);
Audio& audio = Audio::getInstance();
audio.unsubscribeInput();
audio.subscribeInput();
if (audio.isInputReady())
audio.openInput(deviceDescriptor);
}
void AVForm::onOutDevChanged(const QString& deviceDescriptor)
@ -325,8 +328,8 @@ void AVForm::onOutDevChanged(const QString& deviceDescriptor)
Settings::getInstance().setOutDev(deviceDescriptor);
Audio& audio = Audio::getInstance();
audio.unsubscribeInput();
audio.subscribeInput();
if (audio.isOutputReady())
audio.openOutput(deviceDescriptor);
}
void AVForm::onFilterAudioToggled(bool filterAudio)

View File

@ -24,6 +24,7 @@
#include "src/persistence/settings.h"
#include "src/persistence/smileypack.h"
#include "src/core/core.h"
#include "src/core/coreav.h"
#include "src/widget/style.h"
#include "src/nexus.h"
#include "src/persistence/profile.h"
@ -358,7 +359,7 @@ void GeneralForm::onUseProxyUpdated()
void GeneralForm::onReconnectClicked()
{
if (Core::getInstance()->anyActiveCalls())
if (Core::getInstance()->getAv()->anyActiveCalls())
{
QMessageBox::warning(this, tr("Call active", "popup title"),
tr("You can't disconnect while a call is active!", "popup text"));

View File

@ -25,7 +25,7 @@ class GenericForm : public QWidget
Q_OBJECT
public:
GenericForm(const QPixmap &icon) : formIcon(icon) {;}
~GenericForm() {}
virtual ~GenericForm() {}
virtual QString getFormName() = 0;
QPixmap getFormIcon() {return formIcon;}

View File

@ -27,6 +27,7 @@ class AdjustingScrollArea : public QScrollArea
Q_OBJECT
public:
explicit AdjustingScrollArea(QWidget *parent = 0);
virtual ~AdjustingScrollArea() = default;
protected:
virtual void resizeEvent(QResizeEvent *ev) override;

View File

@ -60,18 +60,15 @@ void MicFeedbackWidget::paintEvent(QPaintEvent*)
void MicFeedbackWidget::timerEvent(QTimerEvent*)
{
const int framesize = (20 * 48000) / 1000;
const int bufsize = framesize * 2;
uint8_t buff[bufsize];
memset(buff, 0, bufsize);
const int framesize = AUDIO_FRAME_SAMPLE_COUNT * AUDIO_CHANNELS;
int16_t buff[framesize] = {0};
if (Audio::getInstance().tryCaptureSamples(buff, framesize))
if (Audio::getInstance().tryCaptureSamples(buff, AUDIO_FRAME_SAMPLE_COUNT))
{
double max = 0;
int16_t* buffReal = reinterpret_cast<int16_t*>(&buff[0]);
for (int i = 0; i < bufsize / 2; ++i)
max = std::max(max, fabs(buffReal[i] / 32767.0));
for (int i = 0; i < framesize; ++i)
max = std::max(max, fabs(buff[i] / 32767.0));
if (max > current)
current = max;

View File

@ -21,6 +21,7 @@
#include "contentlayout.h"
#include "ui_mainwindow.h"
#include "src/core/core.h"
#include "src/core/coreav.h"
#include "src/persistence/settings.h"
#include "contentdialog.h"
#include "src/friend.h"
@ -881,6 +882,7 @@ void Widget::addFriend(int friendId, const QString &userId)
contactListWidget->addFriendWidget(newfriend->getFriendWidget(),Status::Offline,Settings::getInstance().getFriendCircleID(newfriend->getToxId()));
Core* core = Nexus::getCore();
CoreAV* coreav = core->getAv();
connect(newfriend, &Friend::displayedNameChanged, this, &Widget::onFriendDisplayChanged);
connect(settingsWidget, &SettingsWidget::compactToggled, newfriend->getFriendWidget(), &GenericChatroomWidget::compactChange);
connect(newfriend->getFriendWidget(), SIGNAL(chatroomWidgetClicked(GenericChatroomWidget*,bool)), this, SLOT(onChatroomWidgetClicked(GenericChatroomWidget*,bool)));
@ -890,27 +892,11 @@ void Widget::addFriend(int friendId, const QString &userId)
connect(newfriend->getChatForm(), &GenericChatForm::sendMessage, core, &Core::sendMessage);
connect(newfriend->getChatForm(), &GenericChatForm::sendAction, core, &Core::sendAction);
connect(newfriend->getChatForm(), &ChatForm::sendFile, core, &Core::sendFile);
connect(newfriend->getChatForm(), &ChatForm::answerCall, core, &Core::answerCall);
connect(newfriend->getChatForm(), &ChatForm::hangupCall, core, &Core::hangupCall);
connect(newfriend->getChatForm(), &ChatForm::rejectCall, core, &Core::rejectCall);
connect(newfriend->getChatForm(), &ChatForm::startCall, core, &Core::startCall);
connect(newfriend->getChatForm(), &ChatForm::cancelCall, core, &Core::cancelCall);
connect(newfriend->getChatForm(), &ChatForm::micMuteToggle, core, &Core::micMuteToggle);
connect(newfriend->getChatForm(), &ChatForm::volMuteToggle, core, &Core::volMuteToggle);
connect(newfriend->getChatForm(), &ChatForm::aliasChanged, newfriend->getFriendWidget(), &FriendWidget::setAlias);
connect(core, &Core::fileReceiveRequested, newfriend->getChatForm(), &ChatForm::onFileRecvRequest);
connect(core, &Core::avInvite, newfriend->getChatForm(), &ChatForm::onAvInvite);
connect(core, &Core::avStart, newfriend->getChatForm(), &ChatForm::onAvStart);
connect(core, &Core::avCancel, newfriend->getChatForm(), &ChatForm::onAvCancel);
connect(core, &Core::avEnd, newfriend->getChatForm(), &ChatForm::onAvEnd);
connect(core, &Core::avRinging, newfriend->getChatForm(), &ChatForm::onAvRinging);
connect(core, &Core::avStarting, newfriend->getChatForm(), &ChatForm::onAvStarting);
connect(core, &Core::avEnding, newfriend->getChatForm(), &ChatForm::onAvEnding);
connect(core, &Core::avRequestTimeout, newfriend->getChatForm(), &ChatForm::onAvRequestTimeout);
connect(core, &Core::avPeerTimeout, newfriend->getChatForm(), &ChatForm::onAvPeerTimeout);
connect(core, &Core::avMediaChange, newfriend->getChatForm(), &ChatForm::onAvMediaChange);
connect(core, &Core::avCallFailed, newfriend->getChatForm(), &ChatForm::onAvCallFailed);
connect(core, &Core::avRejected, newfriend->getChatForm(), &ChatForm::onAvRejected);
connect(coreav, &CoreAV::avInvite, newfriend->getChatForm(), &ChatForm::onAvInvite, Qt::BlockingQueuedConnection);
connect(coreav, &CoreAV::avStart, newfriend->getChatForm(), &ChatForm::onAvStart, Qt::BlockingQueuedConnection);
connect(coreav, &CoreAV::avEnd, newfriend->getChatForm(), &ChatForm::onAvEnd, Qt::BlockingQueuedConnection);
connect(core, &Core::friendAvatarChanged, newfriend->getChatForm(), &ChatForm::onAvatarChange);
connect(core, &Core::friendAvatarChanged, newfriend->getFriendWidget(), &FriendWidget::onAvatarChange);
connect(core, &Core::friendAvatarRemoved, newfriend->getChatForm(), &ChatForm::onAvatarRemoved);
@ -1132,7 +1118,7 @@ void Widget::addGroupDialog(Group *group, ContentDialog *dialog)
connect(groupWidget, SIGNAL(chatroomWidgetClicked(GenericChatroomWidget*)), group->getChatForm(), SLOT(focusInput()));
}
bool Widget::newFriendMessageAlert(int friendId)
bool Widget::newFriendMessageAlert(int friendId, bool sound)
{
bool hasActive;
QWidget* currentWindow;
@ -1150,7 +1136,7 @@ bool Widget::newFriendMessageAlert(int friendId)
hasActive = f->getFriendWidget() == activeChatroomWidget;
}
if (newMessageAlert(currentWindow, hasActive))
if (newMessageAlert(currentWindow, hasActive, sound))
{
f->setEventFlag(true);
f->getFriendWidget()->updateStatusLight();
@ -1189,7 +1175,7 @@ bool Widget::newGroupMessageAlert(int groupId, bool notify)
hasActive = g->getGroupWidget() == activeChatroomWidget;
}
if (newMessageAlert(currentWindow, hasActive, notify))
if (newMessageAlert(currentWindow, hasActive, true, notify))
{
g->setEventFlag(true);
g->getGroupWidget()->updateStatusLight();
@ -1227,7 +1213,7 @@ QString Widget::fromDialogType(DialogType type)
}
}
bool Widget::newMessageAlert(QWidget* currentWindow, bool isActive, bool notify)
bool Widget::newMessageAlert(QWidget* currentWindow, bool isActive, bool sound, bool notify)
{
bool inactiveWindow = isMinimized() || !currentWindow->isActiveWindow();
@ -1249,43 +1235,13 @@ bool Widget::newMessageAlert(QWidget* currentWindow, bool isActive, bool notify)
currentWindow->activateWindow();
}
if (Settings::getInstance().getNotifySound())
{
static QFile sndFile(":audio/notification.pcm");
static QByteArray sndData;
if (sndData.isEmpty())
{
sndFile.open(QIODevice::ReadOnly);
sndData = sndFile.readAll();
sndFile.close();
}
Audio::getInstance().playMono16Sound(sndData);
}
if (Settings::getInstance().getNotifySound() && sound)
Audio::getInstance().playMono16Sound(":audio/notification.pcm");
}
return true;
}
void Widget::playRingtone()
{
if (ui->statusButton->property("status").toString() == "busy")
return;
// for whatever reason this plays slower/downshifted from what any other program plays the file as... but whatever
static QFile sndFile1(":audio/ToxicIncomingCall.pcm");
static QByteArray sndData1;
if (sndData1.isEmpty())
{
sndFile1.open(QIODevice::ReadOnly);
sndData1 = sndFile1.readAll();
sndFile1.close();
}
Audio::getInstance().playMono16Sound(sndData1);
}
void Widget::onFriendRequestReceived(const QString& userId, const QString& message)
{
FriendRequestDialog dialog(this, userId, message);
@ -1588,7 +1544,7 @@ Group *Widget::createGroup(int groupId)
Core* core = Nexus::getCore();
QString groupName = QString("Groupchat #%1").arg(groupId);
Group* newgroup = GroupList::addGroup(groupId, groupName, core->isGroupAvEnabled(groupId));
Group* newgroup = GroupList::addGroup(groupId, groupName, core->getAv()->isGroupAvEnabled(groupId));
contactListWidget->addGroupWidget(newgroup->getGroupWidget());
newgroup->getGroupWidget()->updateStatusLight();

View File

@ -67,7 +67,7 @@ public:
static Widget* getInstance();
void addFriendDialog(Friend* frnd, ContentDialog* dialog);
void addGroupDialog(Group* group, ContentDialog* dialog);
bool newFriendMessageAlert(int friendId);
bool newFriendMessageAlert(int friendId, bool sound=true);
bool newGroupMessageAlert(int groupId, bool notify);
bool getIsWindowMinimized();
void updateIcons();
@ -134,7 +134,6 @@ public slots:
void onGroupTitleChanged(int groupnumber, const QString& author, const QString& title);
void onGroupPeerAudioPlaying(int groupnumber, int peernumber);
void onGroupSendResult(int groupId, const QString& message, int result);
void playRingtone();
void onFriendTypingChanged(int friendId, bool isTyping);
void nextContact();
void previousContact();
@ -204,7 +203,7 @@ private:
};
private:
bool newMessageAlert(QWidget* currentWindow, bool isActive, bool notify = true);
bool newMessageAlert(QWidget* currentWindow, bool isActive, bool sound = true, bool notify = true);
void setActiveToolMenuButton(ActiveToolMenuButton newActiveButton);
void hideMainForms(GenericChatroomWidget* chatroomWidget);
Group *createGroup(int groupId);