Rev 461 | Rev 466 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed
/*
* Copyright (C) 2024 by Andreas Theofilu <andreas@theosys.at>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <cstring>
#include <QUdpSocket>
#include <QAudioSink>
#include <QAudioSource>
#include <QMediaDevices>
#include <QMediaPlayer>
#include <QNetworkDatagram>
#include <QBuffer>
#include "tqintercom.h"
#include "tconfig.h"
#include "terror.h"
#define PACKETSIZE 16384
#define PACKET_SIZE 172
#define SIGN_BIT (0x80) /* Sign bit for a A-law byte. */
#define QUANT_MASK (0xf) /* Quantization field mask. */
#define NSEGS (8) /* Number of A-law segments. */
#define SEG_SHIFT (4) /* Left shift for segment number. */
#define SEG_MASK (0x70) /* Segment field mask. */
#define BIAS (0x84) /* Bias for linear code. */
using std::string;
using std::min;
TQIntercom::TQIntercom(QObject *parent)
: QObject(parent)
{
DECL_TRACER("TQIntercom::TQIntercom()");
mSpkLevel = TConfig::getSystemVolume();
mMicLevel = TConfig::getSystemGain();
}
TQIntercom::TQIntercom(QObject *parent, INTERCOM_t ic)
: QObject(parent)
{
DECL_TRACER("TQIntercom::TQIntercom(INTERCOM_t ic)");
mSpkLevel = TConfig::getSystemVolume();
mMicLevel = TConfig::getSystemGain();
setIntercom(ic);
}
TQIntercom::~TQIntercom()
{
DECL_TRACER("TQIntercom::~TQIntercom()");
if (mRemote)
delete mRemote;
if (mMicrophone)
delete mMicrophone;
if (mUdpTalker)
{
if (mUdpTalker->isOpen())
mUdpTalker->close();
delete mUdpTalker;
}
if (mUdpListener)
{
if (mUdpListener->isOpen())
mUdpListener->close();
delete mUdpListener;
}
if (mBuffer)
delete mBuffer;
}
void TQIntercom::setIntercom(INTERCOM_t ic)
{
DECL_TRACER("TQIntercom::setIntercom(INTERCOM_t ic)");
if (ic.ip.empty())
{
MSG_ERROR("No valid IP address!");
return;
}
if (ic.rxPort < 0 || ic.rxPort > 0x0ffff)
{
MSG_ERROR("Receiver port is invalid! (" << ic.rxPort << ")");
return;
}
if (ic.txPort < 0 || ic.txPort > 0x0ffff)
{
MSG_ERROR("Transmit port is invalid! (" << ic.txPort << ")");
return;
}
if (ic.rxPort == 0 && ic.txPort == 0)
{
MSG_ERROR("No transmit and no receive port!");
return;
}
if (ic.mode < 0 || ic.mode > 2)
{
MSG_ERROR("Invalid mode " << ic.mode << "!");
return;
}
if (ic.mode == 0 && ic.rxPort == 0) // listen / receive
{
MSG_ERROR("No network port for listening!");
return;
}
if (ic.mode == 1 && ic.txPort == 0) // talk / send
{
MSG_ERROR("No network port for talking!");
return;
}
mIntercom = ic;
mAudioFormat.setSampleRate(8000);
mAudioFormat.setSampleFormat(QAudioFormat::UInt8);
mAudioFormat.setChannelCount(1);
mAudioFormat.setChannelConfig(QAudioFormat::ChannelConfigMono);
if (mIntercom.mode == 0 || mIntercom.mode == 2)
{
MSG_DEBUG("Connecting to \"" << mIntercom.ip << "\" on port " << mIntercom.txPort);
}
if (mIntercom.mode == 1 || mIntercom.mode == 2)
{
MSG_DEBUG("Reveiving on port " << mIntercom.rxPort);
}
mInitialized = true;
}
void TQIntercom::setSpeakerLevel(int level)
{
DECL_TRACER("TQIntercom::setSpeakerLevel(int level)");
if (level < 0 || level > 100)
return;
mSpkLevel = level;
if (mIntercom.mode == 0 || mIntercom.mode == 2)
{
if (mRemote)
mRemote->setVolume(level);
}
}
void TQIntercom::setMicrophoneLevel(int level)
{
DECL_TRACER("TQIntercom::setMicrophoneLevel(int level)");
if (level < 0 || level > 100)
return;
mMicLevel = level;
if (mIntercom.mode == 1 || mIntercom.mode == 2)
{
if (mMicrophone)
mMicrophone->setVolume(level);
}
}
void TQIntercom::start()
{
DECL_TRACER("TQIntercom::start()");
if ((mIntercom.mode == 0 || mIntercom.mode == 2) && mIntercom.rxPort) // listen
{
if (!spawnServer())
return;
if (mRemote)
delete mRemote;
mRemote = new QAudioSink(mAudioFormat, this);
mRemote->setVolume(mSpkLevel);
}
if ((mIntercom.mode == 1 || mIntercom.mode == 2) && mIntercom.txPort) // talk
{
if (!connectTalker())
return;
if (mMicrophone)
delete mMicrophone;
mMicrophone = new QAudioSource(mAudioFormat, this);
mMicrophone->setVolume(mMicLevel);
mMicrophone->start(mUdpTalker);
}
}
void TQIntercom::stop()
{
DECL_TRACER("TQIntercom::stop()");
if ((mIntercom.mode == 0 || mIntercom.mode == 2) && mIntercom.rxPort) // listen
{
if (mRemote)
mRemote->stop();
if (mUdpListener)
{
mUdpListener->close();
delete mUdpListener;
mUdpListener = nullptr;
}
}
if ((mIntercom.mode == 1 || mIntercom.mode == 2) && mIntercom.txPort) // talk
{
if (mMicrophone)
mMicrophone->stop();
if (mUdpTalker)
mUdpTalker->close();
}
}
void TQIntercom::setMute(bool mute)
{
DECL_TRACER("TQIntercom::setMute(bool mute)");
if (mIntercom.mode == 1 || mIntercom.mode == 2)
{
if (mMicrophone)
mMicrophone->setVolume(mute ? 0 : mMicLevel);
}
}
bool TQIntercom::connectTalker()
{
DECL_TRACER("TQIntercom::connectTalker()");
// First we initialize the socket
QHostAddress hostAddress(QString(mIntercom.ip.c_str()));
if (mUdpTalker)
{
if (mUdpTalker->isOpen())
mUdpTalker->close();
delete mUdpTalker;
}
mUdpTalker = new QUdpSocket(this);
connect(mUdpTalker, &QUdpSocket::stateChanged, this, &TQIntercom::onOutputStateChanged);
connect(mUdpTalker, &QUdpSocket::errorOccurred, this, &TQIntercom::onOutputErrorOccurred);
mUdpTalker->connectToHost(mIntercom.ip.c_str(), mIntercom.txPort);
return true;
}
bool TQIntercom::spawnServer()
{
DECL_TRACER("TQIntercom::spawnServer()");
if (mUdpListener)
{
if (mUdpListener->isOpen())
mUdpListener->close();
delete mUdpListener;
}
mUdpListener = new QUdpSocket(this);
mUdpListener->setReadBufferSize(PACKET_SIZE);
if (!mUdpListener->bind(QHostAddress::Any, mIntercom.rxPort))
{
delete mUdpListener;
mUdpListener = nullptr;
MSG_WARNING("Couldn't bind to devices at port " << mIntercom.rxPort << "!");
return false;
}
connect(mUdpListener, &QUdpSocket::stateChanged, this, &TQIntercom::onInputStateChanged);
connect(mUdpListener, &QUdpSocket::errorOccurred, this, &TQIntercom::onInputErrorOccurred);
connect(mUdpListener, &QUdpSocket::readyRead, this, &TQIntercom::onReadPendingDatagrams);
return true;
}
void TQIntercom::onReadPendingDatagrams()
{
DECL_TRACER("TQIntercom::onReadPendingDatagrams()");
while (mUdpListener->hasPendingDatagrams())
{
QNetworkDatagram datagram = mUdpListener->receiveDatagram();
QByteArray data = datagram.data();
if (!mBuffer || !mBuffer->isOpen())
{
if (!mBuffer)
mBuffer = new QBuffer(&data);
else
mBuffer->setData(data);
mBuffer->open(QIODevice::ReadOnly);
mBuffer->seek(0);
}
else
{
mBuffer->close();
mBuffer->setData(data);
mBuffer->open(QIODevice::ReadOnly);
mBuffer->seek(0);
}
if (mRemote)
mRemote->start(mBuffer);
}
}
void TQIntercom::onInputStateChanged(QAbstractSocket::SocketState socketState)
{
DECL_TRACER("TQIntercom::onInputStateChanged(QAbstractSocket::SocketState socketState)");
if (socketState == QAbstractSocket::ConnectedState)
{
// connect socket to speaker
QAudioDevice info(QMediaDevices::defaultAudioInput());
if (!info.isFormatSupported(mAudioFormat))
{
MSG_WARNING("Raw audio device can not be captured!");
return;
}
/*
if (mRemote)
mRemote->start(mUdpListener); */
}
stateChangedMessage(socketState, true);
}
void TQIntercom::onOutputStateChanged(QAbstractSocket::SocketState socketState)
{
DECL_TRACER("TQIntercom::onOutputStateChanged(QAbstractSocket::SocketState socketState)");
stateChangedMessage(socketState, false);
}
void TQIntercom::stateChangedMessage(QAbstractSocket::SocketState socketState, bool in)
{
if (!TStreamError::checkFilter(HLOG_DEBUG))
return;
DECL_TRACER("TQIntercom::stateChangedMessage(QAbstractSocket::SocketState socketState, bool in)");
string dir = in ? "input" : "output";
if (TStreamError::checkFilter(HLOG_DEBUG))
{
switch(socketState)
{
case QAbstractSocket::UnconnectedState: MSG_DEBUG("State " << dir << ": Unconnected"); break;
case QAbstractSocket::HostLookupState: MSG_DEBUG("State " << dir << ": Looking up host"); break;
case QAbstractSocket::ConnectingState: MSG_DEBUG("State " << dir << ": Connecting"); break;
case QAbstractSocket::ConnectedState: MSG_DEBUG("State " << dir << ": Connected"); break;
case QAbstractSocket::BoundState: MSG_DEBUG("State " << dir << ": Bound"); break;
case QAbstractSocket::ListeningState: MSG_DEBUG("State " << dir << ": Listening"); break;
case QAbstractSocket::ClosingState: MSG_DEBUG("State " << dir << ": Closing"); break;
}
}
}
void TQIntercom::onInputErrorOccurred(QAbstractSocket::SocketError socketError)
{
DECL_TRACER("TQIntercom::onInputErrorOccurred(QAbstractSocket::SocketError socketError)");
socketErrorMessages(socketError, "Receive packet");
}
void TQIntercom::onOutputErrorOccurred(QAbstractSocket::SocketError socketError)
{
DECL_TRACER("TQIntercom::onOutputErrorOccurred(QAbstractSocket::SocketError socketError)");
socketErrorMessages(socketError, "Send packet");
}
void TQIntercom::socketErrorMessages(QAbstractSocket::SocketError socketError, const string& msg)
{
DECL_TRACER("TQIntercom::socketErrorMessages(QAbstractSocket::SocketError socketError, const string& msg)");
switch(socketError)
{
case QAbstractSocket::ConnectionRefusedError: MSG_ERROR(msg << ": The connection was refused by the peer (or timed out)."); break;
case QAbstractSocket::RemoteHostClosedError: MSG_ERROR(msg << ": The remote host closed the connection."); break;
case QAbstractSocket::HostNotFoundError: MSG_ERROR(msg << ": The host address was not found."); break;
case QAbstractSocket::SocketAccessError: MSG_ERROR(msg << ": The socket operation failed because the application lacked the required privileges."); break;
case QAbstractSocket::SocketResourceError: MSG_ERROR(msg << ": The local system ran out of resources (e.g., too many sockets)."); break;
case QAbstractSocket::SocketTimeoutError: MSG_ERROR(msg << ": The socket operation timed out."); break;
case QAbstractSocket::DatagramTooLargeError: MSG_ERROR(msg << ": The datagram was larger than the operating system's limit."); break;
case QAbstractSocket::NetworkError: MSG_ERROR(msg << ": An error occurred with the network (e.g., the network cable was accidentally plugged out)."); break;
case QAbstractSocket::AddressInUseError: MSG_ERROR(msg << ": The address specified to QAbstractSocket::bind() is already in use and was set to be exclusive."); break;
case QAbstractSocket::SocketAddressNotAvailableError: MSG_ERROR(msg << ": The address specified to QAbstractSocket::bind() does not belong to the host."); break;
case QAbstractSocket::UnsupportedSocketOperationError: MSG_ERROR(msg << ": The requested socket operation is not supported by the local operating system (e.g., lack of IPv6 support)."); break;
case QAbstractSocket::ProxyAuthenticationRequiredError: MSG_ERROR(msg << ": The socket is using a proxy, and the proxy requires authentication."); break;
case QAbstractSocket::SslHandshakeFailedError: MSG_ERROR(msg << ": The SSL/TLS handshake failed, so the connection was closed"); break;
case QAbstractSocket::UnfinishedSocketOperationError: MSG_ERROR(msg << ": The last operation attempted has not finished yet (still in progress in the background)."); break;
case QAbstractSocket::ProxyConnectionRefusedError: MSG_ERROR(msg << ": Could not contact the proxy server because the connection to that server was denied"); break;
case QAbstractSocket::ProxyConnectionClosedError: MSG_ERROR(msg << ": The connection to the proxy server was closed unexpectedly (before the connection to the final peer was established)"); break;
case QAbstractSocket::ProxyConnectionTimeoutError: MSG_ERROR(msg << ": The connection to the proxy server timed out or the proxy server stopped responding in the authentication phase."); break;
case QAbstractSocket::ProxyNotFoundError: MSG_ERROR(msg << ": The proxy address set with setProxy() (or the application proxy) was not found."); break;
case QAbstractSocket::ProxyProtocolError: MSG_ERROR(msg << ": The connection negotiation with the proxy server failed, because the response from the proxy server could not be understood."); break;
case QAbstractSocket::OperationError: MSG_ERROR(msg << ": An operation was attempted while the socket was in a state that did not permit it."); break;
case QAbstractSocket::SslInternalError: MSG_ERROR(msg << ": The SSL library being used reported an internal error. This is probably the result of a bad installation or misconfiguration of the library."); break;
case QAbstractSocket::SslInvalidUserDataError: MSG_ERROR(msg << ": Invalid data (certificate, key, cypher, etc.) was provided and its use resulted in an error in the SSL library."); break;
case QAbstractSocket::TemporaryError: MSG_ERROR(msg << ": A temporary error occurred (e.g., operation would block and socket is non-blocking)."); break;
case QAbstractSocket::UnknownSocketError: MSG_ERROR(msg << ": An unidentified error occurred."); break;
}
}