mirror of
https://github.com/qTox/qTox.git
synced 2024-03-22 14:00:36 +08:00
commit
ba7f1d337a
|
@ -10,6 +10,7 @@ However, it is not a fork.
|
||||||
- Friends chat
|
- Friends chat
|
||||||
- Group chats (experimental, can only accept invitations)
|
- Group chats (experimental, can only accept invitations)
|
||||||
- File transfers, with previewing of images
|
- File transfers, with previewing of images
|
||||||
|
- Audio calls (hearing only, not talking at the moment)
|
||||||
|
|
||||||
<h2>Requirements</h2>
|
<h2>Requirements</h2>
|
||||||
|
|
||||||
|
@ -19,5 +20,6 @@ Linux and Mac users will have compile the source code themselves.
|
||||||
<a href="https://jenkins.libtoxcore.so/job/tux3-toxgui-win32/lastSuccessfulBuild/artifact/toxgui-win32.zip">Windows download</a>
|
<a href="https://jenkins.libtoxcore.so/job/tux3-toxgui-win32/lastSuccessfulBuild/artifact/toxgui-win32.zip">Windows download</a>
|
||||||
|
|
||||||
<h3>Screenshots</h3>
|
<h3>Screenshots</h3>
|
||||||
|
<h5>Note: The screenshots may not always be up to date, but they should give a good idea of the general look and features</h5>
|
||||||
<img src="http://i.imgur.com/eMxaxib.png"/>
|
<img src="http://i.imgur.com/eMxaxib.png"/>
|
||||||
<img src="http://i.imgur.com/66ARBGC.png"/>
|
<img src="http://i.imgur.com/66ARBGC.png"/>
|
||||||
|
|
41
audiobuffer.cpp
Normal file
41
audiobuffer.cpp
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
#include "audiobuffer.h"
|
||||||
|
|
||||||
|
AudioBuffer::AudioBuffer() :
|
||||||
|
QIODevice(0)
|
||||||
|
{
|
||||||
|
open(QIODevice::ReadOnly);
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioBuffer::~AudioBuffer()
|
||||||
|
{
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
qint64 AudioBuffer::readData(char *data, qint64 len)
|
||||||
|
{
|
||||||
|
const qint64 total = qMin((qint64)buffer.size(), len);
|
||||||
|
memcpy(data, buffer.constData(), total);
|
||||||
|
buffer = buffer.mid(total);
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
qint64 AudioBuffer::writeData(const char* data, qint64 len)
|
||||||
|
{
|
||||||
|
buffer.append(data, len);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
qint64 AudioBuffer::bytesAvailable() const
|
||||||
|
{
|
||||||
|
return buffer.size() + QIODevice::bytesAvailable();
|
||||||
|
}
|
||||||
|
|
||||||
|
qint64 AudioBuffer::bufferSize() const
|
||||||
|
{
|
||||||
|
return buffer.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioBuffer::clear()
|
||||||
|
{
|
||||||
|
buffer.clear();
|
||||||
|
}
|
24
audiobuffer.h
Normal file
24
audiobuffer.h
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
#ifndef AUDIOBUFFER_H
|
||||||
|
#define AUDIOBUFFER_H
|
||||||
|
|
||||||
|
#include <QIODevice>
|
||||||
|
#include <QByteArray>
|
||||||
|
|
||||||
|
class AudioBuffer : public QIODevice
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit AudioBuffer();
|
||||||
|
~AudioBuffer();
|
||||||
|
|
||||||
|
qint64 readData(char *data, qint64 maxlen);
|
||||||
|
qint64 writeData(const char *data, qint64 len);
|
||||||
|
qint64 bytesAvailable() const;
|
||||||
|
qint64 bufferSize() const;
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QByteArray buffer;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // AUDIOBUFFER_H
|
119
core.cpp
119
core.cpp
|
@ -17,6 +17,7 @@
|
||||||
#include "core.h"
|
#include "core.h"
|
||||||
#include "cdata.h"
|
#include "cdata.h"
|
||||||
#include "cstring.h"
|
#include "cstring.h"
|
||||||
|
#include "settings.h"
|
||||||
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
|
@ -27,18 +28,10 @@
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
#include <QtConcurrent/QtConcurrent>
|
#include <QtConcurrent/QtConcurrent>
|
||||||
|
|
||||||
#include "settings.h"
|
|
||||||
|
|
||||||
#define GROUPCHAT_MAX_SIZE 32
|
|
||||||
#define TOX_SAVE_INTERVAL 30*1000
|
|
||||||
#define TOX_FILE_INTERVAL 20
|
|
||||||
#define TOX_BOOTSTRAP_INTERVAL 10*1000
|
|
||||||
#define TOXAV_MAX_CALLS 32
|
|
||||||
#define TOXAV_RINGING_TIME 15
|
|
||||||
|
|
||||||
const QString Core::CONFIG_FILE_NAME = "tox_save";
|
const QString Core::CONFIG_FILE_NAME = "tox_save";
|
||||||
QList<ToxFile> Core::fileSendQueue;
|
QList<ToxFile> Core::fileSendQueue;
|
||||||
QList<ToxFile> Core::fileRecvQueue;
|
QList<ToxFile> Core::fileRecvQueue;
|
||||||
|
ToxCall Core::calls[TOXAV_MAX_CALLS];
|
||||||
|
|
||||||
Core::Core() :
|
Core::Core() :
|
||||||
tox(nullptr)
|
tox(nullptr)
|
||||||
|
@ -629,7 +622,9 @@ void Core::process()
|
||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
#endif
|
#endif
|
||||||
checkConnection();
|
checkConnection();
|
||||||
toxTimer->start(tox_do_interval(tox));
|
int toxInterval = tox_do_interval(tox);
|
||||||
|
//qDebug() << QString("Tox interval %1").arg(toxInterval);
|
||||||
|
toxTimer->start(50);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Core::checkConnection()
|
void Core::checkConnection()
|
||||||
|
@ -899,6 +894,8 @@ void Core::onAvStart(int32_t call_index, void* core)
|
||||||
}
|
}
|
||||||
qDebug() << QString("Core: AV start from %1").arg(friendId);
|
qDebug() << QString("Core: AV start from %1").arg(friendId);
|
||||||
|
|
||||||
|
prepareCall(friendId, call_index, static_cast<Core*>(core)->toxav);
|
||||||
|
|
||||||
emit static_cast<Core*>(core)->avStart(friendId, call_index);
|
emit static_cast<Core*>(core)->avStart(friendId, call_index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -930,6 +927,8 @@ void Core::onAvEnd(int32_t call_index, void* core)
|
||||||
}
|
}
|
||||||
qDebug() << QString("Core: AV end from %1").arg(friendId);
|
qDebug() << QString("Core: AV end from %1").arg(friendId);
|
||||||
|
|
||||||
|
cleanupCall(call_index);
|
||||||
|
|
||||||
emit static_cast<Core*>(core)->avEnd(friendId, call_index);
|
emit static_cast<Core*>(core)->avEnd(friendId, call_index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -956,6 +955,8 @@ void Core::onAvStarting(int32_t call_index, void* core)
|
||||||
}
|
}
|
||||||
qDebug() << QString("Core: AV starting %1").arg(friendId);
|
qDebug() << QString("Core: AV starting %1").arg(friendId);
|
||||||
|
|
||||||
|
prepareCall(friendId, call_index, static_cast<Core*>(core)->toxav);
|
||||||
|
|
||||||
emit static_cast<Core*>(core)->avStarting(friendId, call_index);
|
emit static_cast<Core*>(core)->avStarting(friendId, call_index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -969,6 +970,8 @@ void Core::onAvEnding(int32_t call_index, void* core)
|
||||||
}
|
}
|
||||||
qDebug() << QString("Core: AV ending from %1").arg(friendId);
|
qDebug() << QString("Core: AV ending from %1").arg(friendId);
|
||||||
|
|
||||||
|
cleanupCall(call_index);
|
||||||
|
|
||||||
emit static_cast<Core*>(core)->avEnding(friendId, call_index);
|
emit static_cast<Core*>(core)->avEnding(friendId, call_index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1019,3 +1022,99 @@ void Core::cancelCall(int callId, int friendId)
|
||||||
qDebug() << QString("Core: Cancelling call with %1").arg(friendId);
|
qDebug() << QString("Core: Cancelling call with %1").arg(friendId);
|
||||||
toxav_cancel(toxav, callId, friendId, 0);
|
toxav_cancel(toxav, callId, friendId, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Core::prepareCall(int friendId, int callId, ToxAv* toxav)
|
||||||
|
{
|
||||||
|
qDebug() << QString("Core: preparing call %1").arg(callId);
|
||||||
|
calls[callId].callId = callId;
|
||||||
|
calls[callId].friendId = friendId;
|
||||||
|
calls[callId].codecSettings = av_DefaultSettings;
|
||||||
|
toxav_prepare_transmission(toxav, callId, &calls[callId].codecSettings, false);
|
||||||
|
QAudioFormat format;
|
||||||
|
format.setSampleRate(calls[callId].codecSettings.audio_sample_rate);
|
||||||
|
format.setChannelCount(calls[callId].codecSettings.audio_channels);
|
||||||
|
format.setSampleSize(16);
|
||||||
|
format.setCodec("audio/pcm");
|
||||||
|
format.setByteOrder(QAudioFormat::LittleEndian);
|
||||||
|
format.setSampleType(QAudioFormat::SignedInt);
|
||||||
|
QAudioDeviceInfo info(QAudioDeviceInfo::defaultOutputDevice());
|
||||||
|
if (!info.isFormatSupported(format)) {
|
||||||
|
calls[callId].audioOutput = nullptr;
|
||||||
|
qWarning() << "Core: Raw audio format not supported by backend, cannot play audio.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
calls[callId].audioOutput = new QAudioOutput(format);
|
||||||
|
calls[callId].active = true;
|
||||||
|
|
||||||
|
calls[callId].audioOutput->setBufferSize(24000);
|
||||||
|
calls[callId].audioOutput->start(&calls[callId].audioBuffer);
|
||||||
|
if (calls[callId].audioOutput->state() == QAudio::StoppedState
|
||||||
|
&& calls[callId].audioOutput->error() == QAudio::OpenError)
|
||||||
|
{
|
||||||
|
qWarning() << "Core: Unable to start audio";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
qDebug() << QString("Core: Audio started, buffer size %1").arg(calls[callId].audioOutput->bufferSize());
|
||||||
|
calls[callId].playFuture = QtConcurrent::run(playCallAudio, callId, toxav);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Core::cleanupCall(int callId)
|
||||||
|
{
|
||||||
|
qDebug() << QString("Core: cleaning up call %1").arg(callId);
|
||||||
|
calls[callId].active = false;
|
||||||
|
calls[callId].playFuture.waitForFinished();
|
||||||
|
if (calls[callId].audioOutput != nullptr)
|
||||||
|
{
|
||||||
|
delete calls[callId].audioOutput;
|
||||||
|
}
|
||||||
|
calls[callId].audioBuffer.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Core::playCallAudio(int callId, ToxAv* toxav)
|
||||||
|
{
|
||||||
|
while (calls[callId].active)
|
||||||
|
{
|
||||||
|
int framesize = (calls[callId].codecSettings.audio_frame_duration * calls[callId].codecSettings.audio_sample_rate) / 1000;
|
||||||
|
uint8_t buf[framesize*2];
|
||||||
|
int len = toxav_recv_audio(toxav, callId, framesize, (int16_t*)buf);
|
||||||
|
if (len < 0)
|
||||||
|
{
|
||||||
|
if (len == -3) // Not in call !
|
||||||
|
{
|
||||||
|
qWarning("Core: Trying to play audio in an inactive call!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
qDebug() << QString("Core::playCallAudio: Error receiving audio: %1").arg(len);
|
||||||
|
QThread::msleep(5);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (len == 0)
|
||||||
|
{
|
||||||
|
qApp->processEvents();
|
||||||
|
QThread::msleep(5);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
//qDebug() << QString("Core: Received %1 bytes, %2 audio bytes free, %3 core buffer size")
|
||||||
|
//.arg(len*2).arg(calls[callId].audioOutput->bytesFree()).arg(calls[callId].audioBuffer.bufferSize());
|
||||||
|
calls[callId].audioBuffer.writeData((char*)buf, len*2);
|
||||||
|
int state = calls[callId].audioOutput->state();
|
||||||
|
if (state != QAudio::ActiveState)
|
||||||
|
{
|
||||||
|
qDebug() << QString("Core: Audio state is %1").arg(state);
|
||||||
|
}
|
||||||
|
int error = calls[callId].audioOutput->error();
|
||||||
|
if (error != QAudio::NoError)
|
||||||
|
qWarning() << QString("Core::playCallAudio: Error: %1").arg(error);
|
||||||
|
|
||||||
|
QThread::msleep(5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Core::sendCallAudio(int callId, ToxAv* toxav)
|
||||||
|
{
|
||||||
|
while (calls[callId].active)
|
||||||
|
{
|
||||||
|
QThread::msleep(calls[callId].codecSettings.audio_frame_duration / 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
28
core.h
28
core.h
|
@ -18,6 +18,7 @@
|
||||||
#define CORE_HPP
|
#define CORE_HPP
|
||||||
|
|
||||||
#include "status.h"
|
#include "status.h"
|
||||||
|
#include "audiobuffer.h"
|
||||||
|
|
||||||
#include <tox/tox.h>
|
#include <tox/tox.h>
|
||||||
#include <tox/toxav.h>
|
#include <tox/toxav.h>
|
||||||
|
@ -30,6 +31,15 @@
|
||||||
#include <QList>
|
#include <QList>
|
||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
#include <QFuture>
|
#include <QFuture>
|
||||||
|
#include <QBuffer>
|
||||||
|
#include <QAudioOutput>
|
||||||
|
|
||||||
|
#define TOXAV_MAX_CALLS 16
|
||||||
|
#define GROUPCHAT_MAX_SIZE 32
|
||||||
|
#define TOX_SAVE_INTERVAL 30*1000
|
||||||
|
#define TOX_FILE_INTERVAL 20
|
||||||
|
#define TOX_BOOTSTRAP_INTERVAL 10*1000
|
||||||
|
#define TOXAV_RINGING_TIME 15
|
||||||
|
|
||||||
struct DhtServer
|
struct DhtServer
|
||||||
{
|
{
|
||||||
|
@ -70,6 +80,18 @@ struct ToxFile
|
||||||
QFuture<void> sendFuture;
|
QFuture<void> sendFuture;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct ToxCall
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
AudioBuffer audioBuffer;
|
||||||
|
QAudioOutput* audioOutput;
|
||||||
|
ToxAvCodecSettings codecSettings;
|
||||||
|
int callId;
|
||||||
|
int friendId;
|
||||||
|
bool active;
|
||||||
|
QFuture<void> playFuture;
|
||||||
|
};
|
||||||
|
|
||||||
class Core : public QObject
|
class Core : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
@ -208,6 +230,11 @@ private:
|
||||||
static void onAvRequestTimeout(int32_t call_index, void* toxav);
|
static void onAvRequestTimeout(int32_t call_index, void* toxav);
|
||||||
static void onAvPeerTimeout(int32_t call_index, void* toxav);
|
static void onAvPeerTimeout(int32_t call_index, void* toxav);
|
||||||
|
|
||||||
|
static void prepareCall(int friendId, int callId, ToxAv *toxav);
|
||||||
|
static void cleanupCall(int callId);
|
||||||
|
static void playCallAudio(int callId, ToxAv* toxav); // Blocking, start in a thread
|
||||||
|
static void sendCallAudio(int callId, ToxAv* toxav); // Blocking, start in a thread
|
||||||
|
|
||||||
void checkConnection();
|
void checkConnection();
|
||||||
void onBootstrapTimer();
|
void onBootstrapTimer();
|
||||||
|
|
||||||
|
@ -227,6 +254,7 @@ private:
|
||||||
QList<DhtServer> dhtServerList;
|
QList<DhtServer> dhtServerList;
|
||||||
int dhtServerId;
|
int dhtServerId;
|
||||||
static QList<ToxFile> fileSendQueue, fileRecvQueue;
|
static QList<ToxFile> fileSendQueue, fileRecvQueue;
|
||||||
|
static ToxCall calls[TOXAV_MAX_CALLS];
|
||||||
|
|
||||||
static const QString CONFIG_FILE_NAME;
|
static const QString CONFIG_FILE_NAME;
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,6 +6,9 @@ QList<Friend*> FriendList::friendList;
|
||||||
|
|
||||||
Friend* FriendList::addFriend(int friendId, QString userId)
|
Friend* FriendList::addFriend(int friendId, QString userId)
|
||||||
{
|
{
|
||||||
|
for (Friend* f : friendList)
|
||||||
|
if (f->friendId == friendId)
|
||||||
|
qWarning() << "FriendList::addFriend: friendId already taken";
|
||||||
Friend* newfriend = new Friend(friendId, userId);
|
Friend* newfriend = new Friend(friendId, userId);
|
||||||
friendList.append(newfriend);
|
friendList.append(newfriend);
|
||||||
return newfriend;
|
return newfriend;
|
||||||
|
|
18
main.cpp
18
main.cpp
|
@ -33,3 +33,21 @@ int main(int argc, char *argv[])
|
||||||
* An extra side panel for groupchats, like Venom does (?)
|
* An extra side panel for groupchats, like Venom does (?)
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/** NAMES :
|
||||||
|
Botox
|
||||||
|
Ricin
|
||||||
|
Anthrax
|
||||||
|
Sarin
|
||||||
|
Cyanide
|
||||||
|
Polonium
|
||||||
|
Mercury
|
||||||
|
Arsenic
|
||||||
|
qTox
|
||||||
|
plague
|
||||||
|
Britney
|
||||||
|
Nightshade
|
||||||
|
Belladonna
|
||||||
|
toxer
|
||||||
|
GoyIM
|
||||||
|
*/
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
#
|
#
|
||||||
#-------------------------------------------------
|
#-------------------------------------------------
|
||||||
|
|
||||||
QT += core gui
|
QT += core gui multimedia
|
||||||
|
|
||||||
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
|
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
|
||||||
|
|
||||||
|
@ -33,7 +33,8 @@ HEADERS += widget/form/addfriendform.h \
|
||||||
core.h \
|
core.h \
|
||||||
friendlist.h \
|
friendlist.h \
|
||||||
cdata.h \
|
cdata.h \
|
||||||
cstring.h
|
cstring.h \
|
||||||
|
audiobuffer.h
|
||||||
|
|
||||||
FORMS += widget.ui
|
FORMS += widget.ui
|
||||||
|
|
||||||
|
@ -68,4 +69,5 @@ SOURCES += \
|
||||||
settings.cpp \
|
settings.cpp \
|
||||||
status.cpp \
|
status.cpp \
|
||||||
cdata.cpp \
|
cdata.cpp \
|
||||||
cstring.cpp
|
cstring.cpp \
|
||||||
|
audiobuffer.cpp
|
||||||
|
|
|
@ -383,6 +383,8 @@ void Widget::onFriendRequestReceived(const QString& userId, const QString& messa
|
||||||
void Widget::removeFriend(int friendId)
|
void Widget::removeFriend(int friendId)
|
||||||
{
|
{
|
||||||
Friend* f = FriendList::findFriend(friendId);
|
Friend* f = FriendList::findFriend(friendId);
|
||||||
|
if (f->widget == activeFriendWidget)
|
||||||
|
activeFriendWidget = nullptr;
|
||||||
FriendList::removeFriend(friendId);
|
FriendList::removeFriend(friendId);
|
||||||
core->removeFriend(friendId);
|
core->removeFriend(friendId);
|
||||||
delete f;
|
delete f;
|
||||||
|
@ -449,6 +451,8 @@ void Widget::onGroupWidgetClicked(GroupWidget* widget)
|
||||||
void Widget::removeGroup(int groupId)
|
void Widget::removeGroup(int groupId)
|
||||||
{
|
{
|
||||||
Group* g = GroupList::findGroup(groupId);
|
Group* g = GroupList::findGroup(groupId);
|
||||||
|
if (g->widget == activeGroupWidget)
|
||||||
|
activeGroupWidget == nullptr;
|
||||||
GroupList::removeGroup(groupId);
|
GroupList::removeGroup(groupId);
|
||||||
core->removeGroup(groupId);
|
core->removeGroup(groupId);
|
||||||
delete g;
|
delete g;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user