jamulus/src/server.h

464 lines
16 KiB
C
Raw Normal View History

/******************************************************************************\
2020-01-01 15:41:43 +01:00
* Copyright (c) 2004-2020
*
* Author(s):
* Volker Fischer
*
******************************************************************************
*
* 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 2 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.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
\******************************************************************************/
2019-07-09 08:52:38 +02:00
#pragma once
#include <QObject>
#include <QTimer>
#include <QDateTime>
#include <QHostAddress>
2020-04-04 23:57:16 +02:00
#include <algorithm>
#ifdef USE_OPUS_SHARED_LIB
# include "opus/opus_custom.h"
#else
# include "opus_custom.h"
#endif
#include "global.h"
#include "buffer.h"
#include "signalhandler.h"
#include "socket.h"
#include "channel.h"
#include "util.h"
#include "serverlogging.h"
#include "serverlist.h"
#include "multicolorledbar.h"
Add recording support with Reaper Project generation Includes the following changes * Initial .gitignore Administrative * Fix up warning message * Not all Windows file systems are case insensitive Bugfixes * (Qt5) Use QCoreApplication for headless Possible solution to get the application to run as a headless server but it loses the nice history graph, so not ideal. * Avoid ESC closing chat Because ESC shouldn't close the chat window. Or the main app window. * Add console logging support for Windows Whilst looking for the headless support, I found this idea for Windows logging. New improved version. This makes far fewer changes. ---- * Add recording support with Reaper Project generation The main feature! * New -r option to enable recording of PCM files and conversion to Reaper RPP with WAV files * New -R option to set the directory in which to create recording sessions You need to specify the -R option, there's no default... so I guess -r and -R could be combined. * New -T option to convert a session directory with PCM files into a Reaper RPP with WAV files You can use -T on "failed" sessions, if the -r option captures the PCMs but the RPP converter doesn't run for some reaon. (It was useful during development, maybe less so once things seem stable.) The recorder is implemented as a new thread with queuing from the main "real time" server thread. When a new client connects or if its audio format changes (e.g. mono to stereo), a new RIFF WAVE file is started. Each frame of decompressed audio for each client written out as LPCM to the file. When the client disconnects, the RIFF WAVE headers are updated to reflect the file length. Once all clients disconnect, the session is considered ended and a Reaper RPP file is written.
2019-04-03 19:12:45 +02:00
#include "recorder/jamrecorder.h"
/* Definitions ****************************************************************/
// no valid channel number
#define INVALID_CHANNEL_ID ( MAX_NUM_CHANNELS + 1 )
/* Classes ********************************************************************/
#if ( defined ( WIN32 ) || defined ( _WIN32 ) )
// using QTimer for Windows
class CHighPrecisionTimer : public QObject
{
Q_OBJECT
public:
CHighPrecisionTimer ( const bool bNewUseDoubleSystemFrameSize );
void Start();
void Stop();
bool isActive() const { return Timer.isActive(); }
protected:
QTimer Timer;
CVector<int> veciTimeOutIntervals;
int iCurPosInVector;
int iIntervalCounter;
bool bUseDoubleSystemFrameSize;
public slots:
void OnTimer();
signals:
void timeout();
};
#else
// using mach timers for Mac and nanosleep for Linux
2013-12-27 17:01:35 +01:00
# if defined ( __APPLE__ ) || defined ( __MACOSX )
# include <mach/mach.h>
# include <mach/mach_error.h>
# include <mach/mach_time.h>
# else
# include <sys/time.h>
# endif
class CHighPrecisionTimer : public QThread
{
Q_OBJECT
public:
CHighPrecisionTimer ( const bool bUseDoubleSystemFrameSize );
void Start();
void Stop();
bool isActive() { return bRun; }
protected:
virtual void run();
bool bRun;
2013-12-27 17:01:35 +01:00
# if defined ( __APPLE__ ) || defined ( __MACOSX )
uint64_t Delay;
uint64_t NextEnd;
2013-12-27 17:01:35 +01:00
# else
2013-03-04 17:11:37 +01:00
long Delay;
timespec NextEnd;
2013-12-27 17:01:35 +01:00
# endif
signals:
void timeout();
};
#endif
template<unsigned int slotId>
class CServerSlots : public CServerSlots<slotId - 1>
{
public:
void OnSendProtMessCh ( CVector<uint8_t> mess ) { SendProtMessage ( slotId - 1, mess ); }
void OnReqConnClientsListCh() { CreateAndSendChanListForThisChan ( slotId - 1 ); }
void OnChatTextReceivedCh ( QString strChatText )
{
CreateAndSendChatTextForAllConChannels ( slotId - 1, strChatText );
}
void OnMuteStateHasChangedCh ( int iChanID, bool bIsMuted )
{
CreateOtherMuteStateChanged ( slotId - 1, iChanID, bIsMuted );
}
void OnServerAutoSockBufSizeChangeCh ( int iNNumFra )
{
CreateAndSendJitBufMessage ( slotId - 1, iNNumFra );
}
protected:
virtual void SendProtMessage ( int iChID,
CVector<uint8_t> vecMessage ) = 0;
virtual void CreateAndSendChanListForThisChan ( const int iCurChanID ) = 0;
virtual void CreateAndSendChatTextForAllConChannels ( const int iCurChanID,
const QString& strChatText ) = 0;
virtual void CreateOtherMuteStateChanged ( const int iCurChanID,
const int iOtherChanID,
const bool bIsMuted ) = 0;
virtual void CreateAndSendJitBufMessage ( const int iCurChanID,
const int iNNumFra ) = 0;
};
template<>
class CServerSlots<0> {};
class CServer :
public QObject,
public CServerSlots<MAX_NUM_CHANNELS>
{
Q_OBJECT
public:
CServer ( const int iNewMaxNumChan,
2020-03-21 19:57:18 +01:00
const int iMaxDaysHistory,
const QString& strLoggingFileName,
const quint16 iPortNumber,
const QString& strHTMLStatusFileName,
const QString& strHistoryFileName,
const QString& strServerNameForHTMLStatusFile,
const QString& strCentralServer,
const QString& strServerInfo,
const QString& strNewWelcomeMessage,
Add recording support with Reaper Project generation Includes the following changes * Initial .gitignore Administrative * Fix up warning message * Not all Windows file systems are case insensitive Bugfixes * (Qt5) Use QCoreApplication for headless Possible solution to get the application to run as a headless server but it loses the nice history graph, so not ideal. * Avoid ESC closing chat Because ESC shouldn't close the chat window. Or the main app window. * Add console logging support for Windows Whilst looking for the headless support, I found this idea for Windows logging. New improved version. This makes far fewer changes. ---- * Add recording support with Reaper Project generation The main feature! * New -r option to enable recording of PCM files and conversion to Reaper RPP with WAV files * New -R option to set the directory in which to create recording sessions You need to specify the -R option, there's no default... so I guess -r and -R could be combined. * New -T option to convert a session directory with PCM files into a Reaper RPP with WAV files You can use -T on "failed" sessions, if the -r option captures the PCMs but the RPP converter doesn't run for some reaon. (It was useful during development, maybe less so once things seem stable.) The recorder is implemented as a new thread with queuing from the main "real time" server thread. When a new client connects or if its audio format changes (e.g. mono to stereo), a new RIFF WAVE file is started. Each frame of decompressed audio for each client written out as LPCM to the file. When the client disconnects, the RIFF WAVE headers are updated to reflect the file length. Once all clients disconnect, the session is considered ended and a Reaper RPP file is written.
2019-04-03 19:12:45 +02:00
const QString& strRecordingDirName,
const bool bNCentServPingServerInList,
const bool bNDisconnectAllClientsOnQuit,
const bool bNUseDoubleSystemFrameSize,
const ELicenceType eNLicenceType );
void Start();
void Stop();
bool IsRunning() { return HighPrecisionTimer.isActive(); }
bool PutAudioData ( const CVector<uint8_t>& vecbyRecBuf,
const int iNumBytesRead,
const CHostAddress& HostAdr,
int& iCurChanID );
void GetConCliParam ( CVector<CHostAddress>& vecHostAddresses,
CVector<QString>& vecsName,
CVector<int>& veciJitBufNumFrames,
CVector<int>& veciNetwFrameSizeFact );
bool GetRecordingEnabled() { return bEnableRecording; }
// Server list management --------------------------------------------------
void UpdateServerList() { ServerListManager.Update(); }
void UnregisterSlaveServer() { ServerListManager.SlaveServerUnregister(); }
void SetServerListEnabled ( const bool bState )
{ ServerListManager.SetEnabled ( bState ); }
bool GetServerListEnabled() { return ServerListManager.GetEnabled(); }
void SetServerListCentralServerAddress ( const QString& sNCentServAddr )
{ ServerListManager.SetCentralServerAddress ( sNCentServAddr ); }
QString GetServerListCentralServerAddress()
{ return ServerListManager.GetCentralServerAddress(); }
void SetCentralServerAddressType ( const ECSAddType eNCSAT )
{ ServerListManager.SetCentralServerAddressType ( eNCSAT ); }
ECSAddType GetCentralServerAddressType()
{ return ServerListManager.GetCentralServerAddressType(); }
void SetServerName ( const QString& strNewName )
{ ServerListManager.SetServerName ( strNewName ); }
QString GetServerName() { return ServerListManager.GetServerName(); }
void SetServerCity ( const QString& strNewCity )
{ ServerListManager.SetServerCity ( strNewCity ); }
QString GetServerCity() { return ServerListManager.GetServerCity(); }
void SetServerCountry ( const QLocale::Country eNewCountry )
{ ServerListManager.SetServerCountry ( eNewCountry ); }
QLocale::Country GetServerCountry()
{ return ServerListManager.GetServerCountry(); }
ESvrRegStatus GetSvrRegStatus() { return ServerListManager.GetSvrRegStatus(); }
2020-04-18 08:39:08 +02:00
// GUI settings ------------------------------------------------------------
void SetAutoRunMinimized ( const bool NAuRuMin ) { bAutoRunMinimized = NAuRuMin; }
bool GetAutoRunMinimized() { return bAutoRunMinimized; }
void SetLicenceType ( const ELicenceType NLiType ) { eLicenceType = NLiType; }
ELicenceType GetLicenceType() { return eLicenceType; }
protected:
// access functions for actual channels
bool IsConnected ( const int iChanNum )
{ return vecChannels[iChanNum].IsConnected(); }
void StartStatusHTMLFileWriting ( const QString& strNewFileName,
const QString& strNewServerNameWithPort );
int GetFreeChan();
int FindChannel ( const CHostAddress& CheckAddr );
int GetNumberOfConnectedClients();
CVector<CChannelInfo> CreateChannelList();
virtual void CreateAndSendChanListForAllConChannels();
virtual void CreateAndSendChanListForThisChan ( const int iCurChanID );
2020-05-14 21:12:06 +02:00
virtual void CreateAndSendChatTextForAllConChannels ( const int iCurChanID,
const QString& strChatText );
virtual void CreateOtherMuteStateChanged ( const int iCurChanID,
const int iOtherChanID,
const bool bIsMuted );
virtual void CreateAndSendJitBufMessage ( const int iCurChanID,
const int iNNumFra );
virtual void SendProtMessage ( int iChID,
CVector<uint8_t> vecMessage );
template<unsigned int slotId>
inline void connectChannelSignalsToServerSlots();
void WriteHTMLChannelList();
void ProcessData ( const CVector<CVector<int16_t> >& vecvecsData,
const CVector<double>& vecdGains,
2020-04-26 00:55:28 +02:00
const CVector<double>& vecdPannings,
const CVector<int>& vecNumAudioChannels,
CVector<int16_t>& vecsOutData,
const int iCurNumAudChan,
const int iNumClients );
virtual void customEvent ( QEvent* pEvent );
// if server mode is normal or double system frame size
bool bUseDoubleSystemFrameSize;
int iServerFrameSizeSamples;
2020-04-04 23:57:16 +02:00
void CreateLevelsForAllConChannels ( const int iNumClients,
const CVector<int>& vecNumAudioChannels,
const CVector<CVector<int16_t> > vecvecsData,
CVector<uint16_t>& vecLevelsOut );
2020-05-15 22:52:13 +02:00
void RequestNewRecording();
// do not use the vector class since CChannel does not have appropriate
// copy constructor/operator
CChannel vecChannels[MAX_NUM_CHANNELS];
int iMaxNumChannels;
CProtocol ConnLessProtocol;
QMutex Mutex;
// audio encoder/decoder
OpusCustomMode* Opus64Mode[MAX_NUM_CHANNELS];
OpusCustomEncoder* Opus64EncoderMono[MAX_NUM_CHANNELS];
OpusCustomDecoder* Opus64DecoderMono[MAX_NUM_CHANNELS];
OpusCustomEncoder* Opus64EncoderStereo[MAX_NUM_CHANNELS];
OpusCustomDecoder* Opus64DecoderStereo[MAX_NUM_CHANNELS];
OpusCustomMode* OpusMode[MAX_NUM_CHANNELS];
OpusCustomEncoder* OpusEncoderMono[MAX_NUM_CHANNELS];
OpusCustomDecoder* OpusDecoderMono[MAX_NUM_CHANNELS];
OpusCustomEncoder* OpusEncoderStereo[MAX_NUM_CHANNELS];
OpusCustomDecoder* OpusDecoderStereo[MAX_NUM_CHANNELS];
CConvBuf<int16_t> DoubleFrameSizeConvBufIn[MAX_NUM_CHANNELS];
CConvBuf<int16_t> DoubleFrameSizeConvBufOut[MAX_NUM_CHANNELS];
CVector<QString> vstrChatColors;
CVector<int> vecChanIDsCurConChan;
CVector<CVector<double> > vecvecdGains;
2020-04-26 00:55:28 +02:00
CVector<CVector<double> > vecvecdPannings;
CVector<CVector<int16_t> > vecvecsData;
CVector<int> vecNumAudioChannels;
2020-04-05 17:35:40 +02:00
CVector<int> vecNumFrameSizeConvBlocks;
CVector<int> vecUseDoubleSysFraSizeConvBuf;
CVector<EAudComprType> vecAudioComprType;
CVector<int16_t> vecsSendData;
CVector<uint8_t> vecbyCodedData;
2020-04-04 23:57:16 +02:00
// Channel levels
CVector<uint16_t> vecChannelLevels;
// actual working objects
CHighPrioSocket Socket;
// logging
CServerLogging Logging;
2020-04-04 23:57:16 +02:00
// channel level update frame interval counter
uint16_t iFrameCount;
Add recording support with Reaper Project generation Includes the following changes * Initial .gitignore Administrative * Fix up warning message * Not all Windows file systems are case insensitive Bugfixes * (Qt5) Use QCoreApplication for headless Possible solution to get the application to run as a headless server but it loses the nice history graph, so not ideal. * Avoid ESC closing chat Because ESC shouldn't close the chat window. Or the main app window. * Add console logging support for Windows Whilst looking for the headless support, I found this idea for Windows logging. New improved version. This makes far fewer changes. ---- * Add recording support with Reaper Project generation The main feature! * New -r option to enable recording of PCM files and conversion to Reaper RPP with WAV files * New -R option to set the directory in which to create recording sessions You need to specify the -R option, there's no default... so I guess -r and -R could be combined. * New -T option to convert a session directory with PCM files into a Reaper RPP with WAV files You can use -T on "failed" sessions, if the -r option captures the PCMs but the RPP converter doesn't run for some reaon. (It was useful during development, maybe less so once things seem stable.) The recorder is implemented as a new thread with queuing from the main "real time" server thread. When a new client connects or if its audio format changes (e.g. mono to stereo), a new RIFF WAVE file is started. Each frame of decompressed audio for each client written out as LPCM to the file. When the client disconnects, the RIFF WAVE headers are updated to reflect the file length. Once all clients disconnect, the session is considered ended and a Reaper RPP file is written.
2019-04-03 19:12:45 +02:00
// recording thread
2019-04-12 18:48:20 +02:00
recorder::CJamRecorder JamRecorder;
bool bEnableRecording;
Add recording support with Reaper Project generation Includes the following changes * Initial .gitignore Administrative * Fix up warning message * Not all Windows file systems are case insensitive Bugfixes * (Qt5) Use QCoreApplication for headless Possible solution to get the application to run as a headless server but it loses the nice history graph, so not ideal. * Avoid ESC closing chat Because ESC shouldn't close the chat window. Or the main app window. * Add console logging support for Windows Whilst looking for the headless support, I found this idea for Windows logging. New improved version. This makes far fewer changes. ---- * Add recording support with Reaper Project generation The main feature! * New -r option to enable recording of PCM files and conversion to Reaper RPP with WAV files * New -R option to set the directory in which to create recording sessions You need to specify the -R option, there's no default... so I guess -r and -R could be combined. * New -T option to convert a session directory with PCM files into a Reaper RPP with WAV files You can use -T on "failed" sessions, if the -r option captures the PCMs but the RPP converter doesn't run for some reaon. (It was useful during development, maybe less so once things seem stable.) The recorder is implemented as a new thread with queuing from the main "real time" server thread. When a new client connects or if its audio format changes (e.g. mono to stereo), a new RIFF WAVE file is started. Each frame of decompressed audio for each client written out as LPCM to the file. When the client disconnects, the RIFF WAVE headers are updated to reflect the file length. Once all clients disconnect, the session is considered ended and a Reaper RPP file is written.
2019-04-03 19:12:45 +02:00
// HTML file server status
bool bWriteStatusHTMLFile;
QString strServerHTMLFileListName;
QString strServerNameWithPort;
CHighPrecisionTimer HighPrecisionTimer;
// server list
CServerListManager ServerListManager;
// GUI settings
bool bAutoRunMinimized;
// messaging
QString strWelcomeMessage;
ELicenceType eLicenceType;
bool bDisconnectAllClientsOnQuit;
2020-05-11 21:12:16 +02:00
CSignalHandler* pSignalHandler;
signals:
void Started();
void Stopped();
void ClientDisconnected ( const int iChID );
void SvrRegStatusChanged();
void AudioFrame ( const int iChID,
const QString stChName,
const CHostAddress RecHostAddr,
const int iNumAudChan,
const CVector<int16_t> vecsData );
2020-05-15 22:52:13 +02:00
void RestartRecorder();
void RecordingSessionStarted ( QString sessionDir );
public slots:
void OnTimer();
void OnNewConnection ( int iChID,
CHostAddress RecHostAddr );
void OnServerFull ( CHostAddress RecHostAddr );
void OnSendCLProtMessage ( CHostAddress InetAddr,
CVector<uint8_t> vecMessage );
void OnProtcolCLMessageReceived ( int iRecID,
CVector<uint8_t> vecbyMesBodyData,
CHostAddress RecHostAddr );
void OnProtcolMessageReceived ( int iRecCounter,
int iRecID,
CVector<uint8_t> vecbyMesBodyData,
CHostAddress RecHostAddr );
void OnCLPingReceived ( CHostAddress InetAddr, int iMs )
{ ConnLessProtocol.CreateCLPingMes ( InetAddr, iMs ); }
void OnCLPingWithNumClientsReceived ( CHostAddress InetAddr,
int iMs,
int )
{
ConnLessProtocol.CreateCLPingWithNumClientsMes ( InetAddr,
iMs,
GetNumberOfConnectedClients() );
}
void OnCLSendEmptyMes ( CHostAddress TargetInetAddr )
{
// only send empty message if server list is enabled and this is not
// the central server
if ( ServerListManager.GetEnabled() &&
!ServerListManager.GetIsCentralServer() )
{
ConnLessProtocol.CreateCLEmptyMes ( TargetInetAddr );
}
}
void OnCLReqServerList ( CHostAddress InetAddr )
{ ServerListManager.CentralServerQueryServerList ( InetAddr ); }
void OnCLReqVersionAndOS ( CHostAddress InetAddr )
{ ConnLessProtocol.CreateCLVersionAndOSMes ( InetAddr ); }
void OnCLReqConnClientsList ( CHostAddress InetAddr )
{ ConnLessProtocol.CreateCLConnClientsListMes ( InetAddr, CreateChannelList() ); }
void OnCLRegisterServerReceived ( CHostAddress InetAddr,
CHostAddress LInetAddr,
CServerCoreInfo ServerInfo )
{
ServerListManager.CentralServerRegisterServer ( InetAddr, LInetAddr, ServerInfo );
}
void OnCLRegisterServerResp ( CHostAddress /* unused */,
ESvrRegResult eResult )
{
ServerListManager.StoreRegistrationResult ( eResult );
}
void OnCLUnregisterServerReceived ( CHostAddress InetAddr )
{
ServerListManager.CentralServerUnregisterServer ( InetAddr );
}
void OnCLDisconnection ( CHostAddress InetAddr );
void OnAboutToQuit();
2020-05-15 21:01:57 +02:00
void OnHandledSignal ( int sigNum );
};