From 19419ac8038f5703f708f12ff13f3a7eb2088c32 Mon Sep 17 00:00:00 2001 From: dingodoppelt Date: Wed, 30 Dec 2020 16:04:13 +0100 Subject: [PATCH 01/21] proof of concept jamstreamer first implementation (thanks to hselasky) --- Jamulus.pro | 6 ++++-- src/server.cpp | 9 +++++++++ src/server.h | 3 +++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/Jamulus.pro b/Jamulus.pro index 75fcdbf841..0fcffb7ea7 100755 --- a/Jamulus.pro +++ b/Jamulus.pro @@ -414,7 +414,8 @@ HEADERS += src/buffer.h \ src/recorder/jamrecorder.h \ src/recorder/creaperproject.h \ src/recorder/cwavestream.h \ - src/signalhandler.h + src/signalhandler.h \ + src/streamer/jamstreamer.h HEADERS_GUI = src/audiomixerboard.h \ src/chatdlg.h \ @@ -512,7 +513,8 @@ SOURCES += src/buffer.cpp \ src/util.cpp \ src/recorder/jamrecorder.cpp \ src/recorder/creaperproject.cpp \ - src/recorder/cwavestream.cpp + src/recorder/cwavestream.cpp \ + src/streamer/jamstreamer.cpp SOURCES_GUI = src/audiomixerboard.cpp \ src/chatdlg.cpp \ diff --git a/src/server.cpp b/src/server.cpp index 9ef505efb5..89a48f8a87 100755 --- a/src/server.cpp +++ b/src/server.cpp @@ -405,6 +405,13 @@ CServer::CServer ( const int iNewMaxNumChan, // that jam recorder needs the frame size which is given to the jam // recorder in the SetRecordingDir() function) SetRecordingDir ( strRecordingDirName ); + + // enable jam streaming + QThread* pthJamStreamer = new QThread; + streamer::CJamStreamer* pJamStreamer = new streamer::CJamStreamer(); + pJamStreamer->moveToThread(pthJamStreamer); + QObject::connect( this, &CServer::StreamFrame, pJamStreamer, &streamer::CJamStreamer::process ); + pthJamStreamer->start(); // enable all channels (for the server all channel must be enabled the // entire life time of the software) @@ -1257,6 +1264,8 @@ void CServer::MixEncodeTransmitData ( const int iChanCnt, } } + emit StreamFrame ( iServerFrameSizeSamples, vecsSendData ); + int iClientFrameSizeSamples = 0; // initialize to avoid a compiler warning OpusCustomEncoder* pCurOpusEncoder = nullptr; diff --git a/src/server.h b/src/server.h index b4e78b4462..b9755d424c 100755 --- a/src/server.h +++ b/src/server.h @@ -46,6 +46,7 @@ #include "serverlogging.h" #include "serverlist.h" #include "recorder/jamcontroller.h" +#include "streamer/jamstreamer.h" /* Definitions ****************************************************************/ // no valid channel number @@ -412,6 +413,8 @@ class CServer : const int iNumAudChan, const CVector vecsData ); + void StreamFrame ( const int iServerFrameSizeSamples, const CVector data ); + void CLVersionAndOSReceived ( CHostAddress InetAddr, COSUtil::EOpSystemType eOSType, QString strVersion ); From 02cddea1c214d93f826454fb01b6bb035a687f91 Mon Sep 17 00:00:00 2001 From: dingodoppelt Date: Wed, 30 Dec 2020 17:51:05 +0100 Subject: [PATCH 02/21] refactor, get rid of unneeded stuff. add comments --- src/streamer/jamstreamer.cpp | 24 ++++++++++++++++++++++++ src/streamer/jamstreamer.h | 19 +++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 src/streamer/jamstreamer.cpp create mode 100644 src/streamer/jamstreamer.h diff --git a/src/streamer/jamstreamer.cpp b/src/streamer/jamstreamer.cpp new file mode 100644 index 0000000000..5848059fad --- /dev/null +++ b/src/streamer/jamstreamer.cpp @@ -0,0 +1,24 @@ +#include "jamstreamer.h" + +using namespace streamer; + +// --- CONSTRUCTOR --- +CJamStreamer::CJamStreamer() { + // create a pipe to ffmpeg called "pipeout" to being able to put out the pcm data to it + pcm = 0; // it is necessary to initialise pcm + pipeout = popen("ffmpeg -y -f s16le -ar 48000 -ac 2 -i - /tmp/jamstream.wav", "w"); +} + +// --- PROCESS --- +// put the received pcm data into the pipe to ffmpeg +void CJamStreamer::process( int iServerFrameSizeSamples, CVector data ) { + if (pcm == 0) // thanks to hselasky + { + pcm = new int16_t [2 * iServerFrameSizeSamples]; + } + for ( int i = 0; i < ( 2 * iServerFrameSizeSamples ); i++ ) + { + pcm[i] = data[i]; + } + fwrite(pcm, 2, ( 2 * iServerFrameSizeSamples ), pipeout); +} diff --git a/src/streamer/jamstreamer.h b/src/streamer/jamstreamer.h new file mode 100644 index 0000000000..ac39ad95a8 --- /dev/null +++ b/src/streamer/jamstreamer.h @@ -0,0 +1,19 @@ +#include +#include "../util.h" + +namespace streamer { + +class CJamStreamer : public QObject { + Q_OBJECT + +public: + CJamStreamer(); + +public slots: + void process( int iServerFrameSizeSamples, CVector data ); + +private: + FILE *pipeout; // pipe for putting out the pcm data to ffmpeg + int16_t *pcm; // pointer to pcm which will hold the raw pcm data from the server, to be initialised in the constructor +}; +} From f097f3dcdff717d4a91b430f52518c21e2a70320 Mon Sep 17 00:00:00 2001 From: dingodoppelt Date: Wed, 30 Dec 2020 19:09:15 +0100 Subject: [PATCH 03/21] integrate better into the jamulus server --- src/server.cpp | 35 +++++++++++++++++++++++++++++++++-- src/server.h | 2 ++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/src/server.cpp b/src/server.cpp index 89a48f8a87..e40b23beed 100755 --- a/src/server.cpp +++ b/src/server.cpp @@ -870,6 +870,7 @@ static CTimingMeas JitterMeas ( 1000, "test2.dat" ); JitterMeas.Measure(); // TE vecNumAudioChannels, vecvecsData, vecChannelLevels ); + MixStream ( iNumClients ); for ( int iChanCnt = 0; iChanCnt < iNumClients; iChanCnt++ ) { @@ -1264,8 +1265,6 @@ void CServer::MixEncodeTransmitData ( const int iChanCnt, } } - emit StreamFrame ( iServerFrameSizeSamples, vecsSendData ); - int iClientFrameSizeSamples = 0; // initialize to avoid a compiler warning OpusCustomEncoder* pCurOpusEncoder = nullptr; @@ -1340,6 +1339,38 @@ opus_custom_encoder_ctl ( pCurOpusEncoder, OPUS_SET_BITRATE ( CalcBitRateBitsPer Q_UNUSED ( iUnused ) } +/// @brief Mix the audio data from all clients and send the mix to the jamstreamer +void CServer::MixStream ( const int iNumClients ) +{ + int i, j; + CVector& vecfIntermProcBuf = vecvecfIntermediateProcBuf[0]; // use reference for faster access + CVector& vecsSendData = vecvecsSendData[0]; // use reference for faster access + + // init intermediate processing vector with zeros since we mix all channels on that vector + vecfIntermProcBuf.Reset ( 0 ); + + // Stereo target channel ----------------------------------------------- + for ( j = 0; j < iNumClients; j++ ) + { + // get a reference to the audio data of the current client + const CVector& vecsData = vecvecsData[j]; + + // stereo + for ( i = 0; i < ( 2 * iServerFrameSizeSamples ); i++ ) + { + vecfIntermProcBuf[i] += vecsData[i]; + } + } + + // convert from double to short with clipping + for ( i = 0; i < ( 2 * iServerFrameSizeSamples ); i++ ) + { + vecsSendData[i] = Float2Short ( vecfIntermProcBuf[i] ); + } + + emit StreamFrame ( iServerFrameSizeSamples, vecsSendData ); +} + CVector CServer::CreateChannelList() { CVector vecChanInfo ( 0 ); diff --git a/src/server.h b/src/server.h index b9755d424c..c4cab880b8 100755 --- a/src/server.h +++ b/src/server.h @@ -315,6 +315,8 @@ class CServer : void MixEncodeTransmitData ( const int iChanCnt, const int iNumClients ); + void MixStream ( const int iNumClients ); + virtual void customEvent ( QEvent* pEvent ); // if server mode is normal or double system frame size From f5f829a416f237a2e39e241d8434c661311f15ac Mon Sep 17 00:00:00 2001 From: dingodoppelt Date: Wed, 30 Dec 2020 19:22:12 +0100 Subject: [PATCH 04/21] fix for mono-in --- src/server.cpp | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/src/server.cpp b/src/server.cpp index e40b23beed..2eae34380b 100755 --- a/src/server.cpp +++ b/src/server.cpp @@ -1342,7 +1342,7 @@ opus_custom_encoder_ctl ( pCurOpusEncoder, OPUS_SET_BITRATE ( CalcBitRateBitsPer /// @brief Mix the audio data from all clients and send the mix to the jamstreamer void CServer::MixStream ( const int iNumClients ) { - int i, j; + int i, j, k; CVector& vecfIntermProcBuf = vecvecfIntermediateProcBuf[0]; // use reference for faster access CVector& vecsSendData = vecvecsSendData[0]; // use reference for faster access @@ -1355,18 +1355,31 @@ void CServer::MixStream ( const int iNumClients ) // get a reference to the audio data of the current client const CVector& vecsData = vecvecsData[j]; - // stereo - for ( i = 0; i < ( 2 * iServerFrameSizeSamples ); i++ ) - { - vecfIntermProcBuf[i] += vecsData[i]; - } + if ( vecNumAudioChannels[j] == 1 ) + { + // mono: copy same mono data in both out stereo audio channels + for ( i = 0, k = 0; i < iServerFrameSizeSamples; i++, k += 2 ) + { + // left/right channel + vecfIntermProcBuf[k] += vecsData[i]; + vecfIntermProcBuf[k + 1] += vecsData[i]; + } + } + else + { + // stereo + for ( i = 0; i < ( 2 * iServerFrameSizeSamples ); i++ ) + { + vecfIntermProcBuf[i] += vecsData[i]; + } + } } - // convert from double to short with clipping - for ( i = 0; i < ( 2 * iServerFrameSizeSamples ); i++ ) - { - vecsSendData[i] = Float2Short ( vecfIntermProcBuf[i] ); - } + // convert from double to short with clipping + for ( i = 0; i < ( 2 * iServerFrameSizeSamples ); i++ ) + { + vecsSendData[i] = Float2Short ( vecfIntermProcBuf[i] ); + } emit StreamFrame ( iServerFrameSizeSamples, vecsSendData ); } From a17ec71b2389d48b4fd9d4863335821ced65a10d Mon Sep 17 00:00:00 2001 From: dingodoppelt Date: Wed, 30 Dec 2020 19:54:59 +0100 Subject: [PATCH 05/21] get rid of float2short conversion and mix on an int16_t vector --- src/server.cpp | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/server.cpp b/src/server.cpp index 2eae34380b..7dc0c83587 100755 --- a/src/server.cpp +++ b/src/server.cpp @@ -1343,11 +1343,10 @@ opus_custom_encoder_ctl ( pCurOpusEncoder, OPUS_SET_BITRATE ( CalcBitRateBitsPer void CServer::MixStream ( const int iNumClients ) { int i, j, k; - CVector& vecfIntermProcBuf = vecvecfIntermediateProcBuf[0]; // use reference for faster access CVector& vecsSendData = vecvecsSendData[0]; // use reference for faster access // init intermediate processing vector with zeros since we mix all channels on that vector - vecfIntermProcBuf.Reset ( 0 ); + vecsSendData.Reset ( 0 ); // Stereo target channel ----------------------------------------------- for ( j = 0; j < iNumClients; j++ ) @@ -1361,8 +1360,8 @@ void CServer::MixStream ( const int iNumClients ) for ( i = 0, k = 0; i < iServerFrameSizeSamples; i++, k += 2 ) { // left/right channel - vecfIntermProcBuf[k] += vecsData[i]; - vecfIntermProcBuf[k + 1] += vecsData[i]; + vecsSendData[k] += vecsData[i]; + vecsSendData[k + 1] += vecsData[i]; } } else @@ -1370,17 +1369,11 @@ void CServer::MixStream ( const int iNumClients ) // stereo for ( i = 0; i < ( 2 * iServerFrameSizeSamples ); i++ ) { - vecfIntermProcBuf[i] += vecsData[i]; + vecsSendData[i] += vecsData[i]; } } } - // convert from double to short with clipping - for ( i = 0; i < ( 2 * iServerFrameSizeSamples ); i++ ) - { - vecsSendData[i] = Float2Short ( vecfIntermProcBuf[i] ); - } - emit StreamFrame ( iServerFrameSizeSamples, vecsSendData ); } From 884fd2020dc60c81ec03ef3c3ddc2da7c9cae725 Mon Sep 17 00:00:00 2001 From: dingodoppelt Date: Sat, 2 Jan 2021 01:51:25 +0100 Subject: [PATCH 06/21] make the streaming and stream destination a commandline option --- src/main.cpp | 18 ++++++++++++++++++ src/server.cpp | 14 +++++++++----- src/server.h | 1 + src/streamer/jamstreamer.cpp | 7 +++++-- src/streamer/jamstreamer.h | 2 ++ 5 files changed, 35 insertions(+), 7 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index f47b790150..95dfc68036 100755 --- a/src/main.cpp +++ b/src/main.cpp @@ -81,6 +81,7 @@ int main ( int argc, char** argv ) QString strHTMLStatusFileName = ""; QString strLoggingFileName = ""; QString strRecordingDirName = ""; + QString strStreamDest = ""; QString strCentralServer = ""; QString strServerInfo = ""; QString strServerPublicIP = ""; @@ -369,6 +370,22 @@ int main ( int argc, char** argv ) } + // Stream destination ------------------------------------------------- + if ( GetStringArgument ( tsConsole, + argc, + argv, + i, + "--streamto", + "--streamto", + strArgument ) ) + { + strStreamDest = strArgument; + tsConsole << "- stream destination: " << strStreamDest << endl; + CommandLineOptions << "--streamto"; + continue; + } + + // Disable recording on startup ---------------------------------------- if ( GetFlagArgument ( argv, i, @@ -715,6 +732,7 @@ int main ( int argc, char** argv ) strServerListFilter, strWelcomeMessage, strRecordingDirName, + strStreamDest, bDisconnectAllClientsOnQuit, bUseDoubleSystemFrameSize, bUseMultithreading, diff --git a/src/server.cpp b/src/server.cpp index 7dc0c83587..be870fad17 100755 --- a/src/server.cpp +++ b/src/server.cpp @@ -229,6 +229,7 @@ CServer::CServer ( const int iNewMaxNumChan, const QString& strServerPublicIP, const QString& strNewWelcomeMessage, const QString& strRecordingDirName, + const QString& strStreamDest, const bool bNDisconnectAllClientsOnQuit, const bool bNUseDoubleSystemFrameSize, const bool bNUseMultithreading, @@ -407,11 +408,14 @@ CServer::CServer ( const int iNewMaxNumChan, SetRecordingDir ( strRecordingDirName ); // enable jam streaming - QThread* pthJamStreamer = new QThread; - streamer::CJamStreamer* pJamStreamer = new streamer::CJamStreamer(); - pJamStreamer->moveToThread(pthJamStreamer); - QObject::connect( this, &CServer::StreamFrame, pJamStreamer, &streamer::CJamStreamer::process ); - pthJamStreamer->start(); + if ( !strStreamDest.isEmpty() ) + { + QThread* pthJamStreamer = new QThread; + streamer::CJamStreamer* pJamStreamer = new streamer::CJamStreamer( strStreamDest ); + pJamStreamer->moveToThread(pthJamStreamer); + QObject::connect( this, &CServer::StreamFrame, pJamStreamer, &streamer::CJamStreamer::process ); + pthJamStreamer->start(); + } // enable all channels (for the server all channel must be enabled the // entire life time of the software) diff --git a/src/server.h b/src/server.h index c4cab880b8..2f6d07301b 100755 --- a/src/server.h +++ b/src/server.h @@ -180,6 +180,7 @@ class CServer : const QString& strServerPublicIP, const QString& strNewWelcomeMessage, const QString& strRecordingDirName, + const QString& strStreamDest, const bool bNDisconnectAllClientsOnQuit, const bool bNUseDoubleSystemFrameSize, const bool bNUseMultithreading, diff --git a/src/streamer/jamstreamer.cpp b/src/streamer/jamstreamer.cpp index 5848059fad..9074d92c0c 100644 --- a/src/streamer/jamstreamer.cpp +++ b/src/streamer/jamstreamer.cpp @@ -3,10 +3,13 @@ using namespace streamer; // --- CONSTRUCTOR --- -CJamStreamer::CJamStreamer() { +CJamStreamer::CJamStreamer() {} + +CJamStreamer::CJamStreamer( QString strStreamDest ) { // create a pipe to ffmpeg called "pipeout" to being able to put out the pcm data to it pcm = 0; // it is necessary to initialise pcm - pipeout = popen("ffmpeg -y -f s16le -ar 48000 -ac 2 -i - /tmp/jamstream.wav", "w"); + QString command = "ffmpeg -y -f s16le -ar 48000 -ac 2 -i - " + strStreamDest; + pipeout = popen(command.toUtf8().constData(), "w"); } // --- PROCESS --- diff --git a/src/streamer/jamstreamer.h b/src/streamer/jamstreamer.h index ac39ad95a8..e5f5b8dafe 100644 --- a/src/streamer/jamstreamer.h +++ b/src/streamer/jamstreamer.h @@ -8,11 +8,13 @@ class CJamStreamer : public QObject { public: CJamStreamer(); + CJamStreamer( QString strStreamDest ); public slots: void process( int iServerFrameSizeSamples, CVector data ); private: + QString strStreamDest; // stream destination to pass to ffmpeg as output part of arguments FILE *pipeout; // pipe for putting out the pcm data to ffmpeg int16_t *pcm; // pointer to pcm which will hold the raw pcm data from the server, to be initialised in the constructor }; From 1f34458116a9f15139428144ffaeef1f472fa8c5 Mon Sep 17 00:00:00 2001 From: dingodoppelt Date: Mon, 4 Jan 2021 15:57:40 +0100 Subject: [PATCH 07/21] handle jamulus server stoped and started signals --- src/server.cpp | 5 ++++- src/streamer/jamstreamer.cpp | 22 +++++++++++++++------- src/streamer/jamstreamer.h | 4 +++- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/src/server.cpp b/src/server.cpp index be870fad17..e2263a739f 100755 --- a/src/server.cpp +++ b/src/server.cpp @@ -411,8 +411,11 @@ CServer::CServer ( const int iNewMaxNumChan, if ( !strStreamDest.isEmpty() ) { QThread* pthJamStreamer = new QThread; - streamer::CJamStreamer* pJamStreamer = new streamer::CJamStreamer( strStreamDest ); + streamer::CJamStreamer* pJamStreamer = new streamer::CJamStreamer(); + pJamStreamer->Init( strStreamDest ); pJamStreamer->moveToThread(pthJamStreamer); + QObject::connect( this, &CServer::Started, pJamStreamer, &streamer::CJamStreamer::OnStarted ); + QObject::connect( this, &CServer::Stopped, pJamStreamer, &streamer::CJamStreamer::OnStopped ); QObject::connect( this, &CServer::StreamFrame, pJamStreamer, &streamer::CJamStreamer::process ); pthJamStreamer->start(); } diff --git a/src/streamer/jamstreamer.cpp b/src/streamer/jamstreamer.cpp index 9074d92c0c..4e29774ba9 100644 --- a/src/streamer/jamstreamer.cpp +++ b/src/streamer/jamstreamer.cpp @@ -5,13 +5,6 @@ using namespace streamer; // --- CONSTRUCTOR --- CJamStreamer::CJamStreamer() {} -CJamStreamer::CJamStreamer( QString strStreamDest ) { - // create a pipe to ffmpeg called "pipeout" to being able to put out the pcm data to it - pcm = 0; // it is necessary to initialise pcm - QString command = "ffmpeg -y -f s16le -ar 48000 -ac 2 -i - " + strStreamDest; - pipeout = popen(command.toUtf8().constData(), "w"); -} - // --- PROCESS --- // put the received pcm data into the pipe to ffmpeg void CJamStreamer::process( int iServerFrameSizeSamples, CVector data ) { @@ -25,3 +18,18 @@ void CJamStreamer::process( int iServerFrameSizeSamples, CVector data ) } fwrite(pcm, 2, ( 2 * iServerFrameSizeSamples ), pipeout); } + +void CJamStreamer::Init( const QString strStreamDest ) { + this->strStreamDest = strStreamDest; +} + +void CJamStreamer::OnStarted() { + // create a pipe to ffmpeg called "pipeout" to being able to put out the pcm data to it + pcm = 0; // it is necessary to initialise pcm + QString command = "ffmpeg -y -f s16le -ar 48000 -ac 2 -i - " + strStreamDest; + pipeout = popen(command.toUtf8().constData(), "w"); +} + +void CJamStreamer::OnStopped() { + pclose(pipeout); +} diff --git a/src/streamer/jamstreamer.h b/src/streamer/jamstreamer.h index e5f5b8dafe..70d5ecf0d5 100644 --- a/src/streamer/jamstreamer.h +++ b/src/streamer/jamstreamer.h @@ -8,10 +8,12 @@ class CJamStreamer : public QObject { public: CJamStreamer(); - CJamStreamer( QString strStreamDest ); + void Init( const QString strStreamDest ); public slots: void process( int iServerFrameSizeSamples, CVector data ); + void OnStarted(); + void OnStopped(); private: QString strStreamDest; // stream destination to pass to ffmpeg as output part of arguments From 082dca52b69298a4f94346bee80de37a45ec19f7 Mon Sep 17 00:00:00 2001 From: dingodoppelt Date: Mon, 8 Feb 2021 01:34:31 +0100 Subject: [PATCH 08/21] fix build2 --- src/main.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 95dfc68036..fcd6d110e5 100755 --- a/src/main.cpp +++ b/src/main.cpp @@ -371,8 +371,7 @@ int main ( int argc, char** argv ) // Stream destination ------------------------------------------------- - if ( GetStringArgument ( tsConsole, - argc, + if ( GetStringArgument ( argc, argv, i, "--streamto", @@ -380,7 +379,8 @@ int main ( int argc, char** argv ) strArgument ) ) { strStreamDest = strArgument; - tsConsole << "- stream destination: " << strStreamDest << endl; + qInfo() << qUtf8Printable( QString("- stream destination: %1" ) + .arg( strStreamDest ) ); CommandLineOptions << "--streamto"; continue; } From 909a6b0bb61f96d332a013fe57bd9d1b66f54a7d Mon Sep 17 00:00:00 2001 From: dingodoppelt Date: Mon, 8 Feb 2021 19:55:43 +0100 Subject: [PATCH 09/21] fix build win32 --- src/main.cpp | 6 +++--- src/server.cpp | 11 ++++++++--- src/server.h | 3 +++ src/streamer/jamstreamer.cpp | 2 ++ src/streamer/jamstreamer.h | 2 ++ 5 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index fcd6d110e5..09d111fca1 100755 --- a/src/main.cpp +++ b/src/main.cpp @@ -370,16 +370,16 @@ int main ( int argc, char** argv ) } - // Stream destination ------------------------------------------------- + // Stream destination --------------------------------------------------------- if ( GetStringArgument ( argc, argv, i, - "--streamto", + "--streamto", // no short form "--streamto", strArgument ) ) { strStreamDest = strArgument; - qInfo() << qUtf8Printable( QString("- stream destination: %1" ) + qInfo() << qUtf8Printable( QString( "- stream destination: %1" ) .arg( strStreamDest ) ); CommandLineOptions << "--streamto"; continue; diff --git a/src/server.cpp b/src/server.cpp index e2263a739f..f190f37211 100755 --- a/src/server.cpp +++ b/src/server.cpp @@ -406,10 +406,11 @@ CServer::CServer ( const int iNewMaxNumChan, // that jam recorder needs the frame size which is given to the jam // recorder in the SetRecordingDir() function) SetRecordingDir ( strRecordingDirName ); - +#ifndef _WIN32 // enable jam streaming if ( !strStreamDest.isEmpty() ) { + bStream = true; QThread* pthJamStreamer = new QThread; streamer::CJamStreamer* pJamStreamer = new streamer::CJamStreamer(); pJamStreamer->Init( strStreamDest ); @@ -419,7 +420,7 @@ CServer::CServer ( const int iNewMaxNumChan, QObject::connect( this, &CServer::StreamFrame, pJamStreamer, &streamer::CJamStreamer::process ); pthJamStreamer->start(); } - +#endif // enable all channels (for the server all channel must be enabled the // entire life time of the software) for ( i = 0; i < iMaxNumChannels; i++ ) @@ -877,7 +878,11 @@ static CTimingMeas JitterMeas ( 1000, "test2.dat" ); JitterMeas.Measure(); // TE vecNumAudioChannels, vecvecsData, vecChannelLevels ); - MixStream ( iNumClients ); +#ifndef _WIN32 + if ( bStream == true ) { + MixStream ( iNumClients ); + } +#endif for ( int iChanCnt = 0; iChanCnt < iNumClients; iChanCnt++ ) { diff --git a/src/server.h b/src/server.h index 2f6d07301b..6381daf257 100755 --- a/src/server.h +++ b/src/server.h @@ -395,6 +395,9 @@ class CServer : recorder::CJamController JamController; bool bDisableRecording; + // jam streamer + bool bStream = false; + // GUI settings bool bAutoRunMinimized; diff --git a/src/streamer/jamstreamer.cpp b/src/streamer/jamstreamer.cpp index 4e29774ba9..03b4aeedbe 100644 --- a/src/streamer/jamstreamer.cpp +++ b/src/streamer/jamstreamer.cpp @@ -1,3 +1,4 @@ +#ifndef _WIN32 #include "jamstreamer.h" using namespace streamer; @@ -33,3 +34,4 @@ void CJamStreamer::OnStarted() { void CJamStreamer::OnStopped() { pclose(pipeout); } +#endif diff --git a/src/streamer/jamstreamer.h b/src/streamer/jamstreamer.h index 70d5ecf0d5..4f7603f802 100644 --- a/src/streamer/jamstreamer.h +++ b/src/streamer/jamstreamer.h @@ -1,3 +1,4 @@ +#ifndef _WIN32 #include #include "../util.h" @@ -21,3 +22,4 @@ public slots: int16_t *pcm; // pointer to pcm which will hold the raw pcm data from the server, to be initialised in the constructor }; } +#endif From cc48ad5a9211a4cb68bea8532511687f1d30de79 Mon Sep 17 00:00:00 2001 From: dingodoppelt Date: Wed, 10 Feb 2021 11:56:46 +0100 Subject: [PATCH 10/21] refactor, get rid of redundant code (thanks to npostavs) --- src/streamer/jamstreamer.cpp | 15 ++------------- src/streamer/jamstreamer.h | 1 - 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/src/streamer/jamstreamer.cpp b/src/streamer/jamstreamer.cpp index 03b4aeedbe..71cb0f79a5 100644 --- a/src/streamer/jamstreamer.cpp +++ b/src/streamer/jamstreamer.cpp @@ -3,30 +3,19 @@ using namespace streamer; -// --- CONSTRUCTOR --- CJamStreamer::CJamStreamer() {} -// --- PROCESS --- // put the received pcm data into the pipe to ffmpeg void CJamStreamer::process( int iServerFrameSizeSamples, CVector data ) { - if (pcm == 0) // thanks to hselasky - { - pcm = new int16_t [2 * iServerFrameSizeSamples]; - } - for ( int i = 0; i < ( 2 * iServerFrameSizeSamples ); i++ ) - { - pcm[i] = data[i]; - } - fwrite(pcm, 2, ( 2 * iServerFrameSizeSamples ), pipeout); + fwrite(&data[0], 2, ( 2 * iServerFrameSizeSamples ), pipeout); } void CJamStreamer::Init( const QString strStreamDest ) { this->strStreamDest = strStreamDest; } +// create a pipe to ffmpeg called "pipeout" to being able to put out the pcm data to it void CJamStreamer::OnStarted() { - // create a pipe to ffmpeg called "pipeout" to being able to put out the pcm data to it - pcm = 0; // it is necessary to initialise pcm QString command = "ffmpeg -y -f s16le -ar 48000 -ac 2 -i - " + strStreamDest; pipeout = popen(command.toUtf8().constData(), "w"); } diff --git a/src/streamer/jamstreamer.h b/src/streamer/jamstreamer.h index 4f7603f802..017389dc61 100644 --- a/src/streamer/jamstreamer.h +++ b/src/streamer/jamstreamer.h @@ -19,7 +19,6 @@ public slots: private: QString strStreamDest; // stream destination to pass to ffmpeg as output part of arguments FILE *pipeout; // pipe for putting out the pcm data to ffmpeg - int16_t *pcm; // pointer to pcm which will hold the raw pcm data from the server, to be initialised in the constructor }; } #endif From 2fef42650217c718bb47648bc32a75ec1ea1042d Mon Sep 17 00:00:00 2001 From: Noam Postavsky Date: Wed, 10 Feb 2021 11:16:39 -0500 Subject: [PATCH 11/21] Make CJamStreamer::process signal & slot pass vector by const ref To avoid redundant copying. --- src/server.h | 2 +- src/streamer/jamstreamer.cpp | 2 +- src/streamer/jamstreamer.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/server.h b/src/server.h index 6381daf257..08dc80b869 100755 --- a/src/server.h +++ b/src/server.h @@ -419,7 +419,7 @@ class CServer : const int iNumAudChan, const CVector vecsData ); - void StreamFrame ( const int iServerFrameSizeSamples, const CVector data ); + void StreamFrame ( const int iServerFrameSizeSamples, const CVector& data ); void CLVersionAndOSReceived ( CHostAddress InetAddr, COSUtil::EOpSystemType eOSType, diff --git a/src/streamer/jamstreamer.cpp b/src/streamer/jamstreamer.cpp index 71cb0f79a5..dbd80daeb9 100644 --- a/src/streamer/jamstreamer.cpp +++ b/src/streamer/jamstreamer.cpp @@ -6,7 +6,7 @@ using namespace streamer; CJamStreamer::CJamStreamer() {} // put the received pcm data into the pipe to ffmpeg -void CJamStreamer::process( int iServerFrameSizeSamples, CVector data ) { +void CJamStreamer::process( int iServerFrameSizeSamples, const CVector& data ) { fwrite(&data[0], 2, ( 2 * iServerFrameSizeSamples ), pipeout); } diff --git a/src/streamer/jamstreamer.h b/src/streamer/jamstreamer.h index 017389dc61..9201c2cd07 100644 --- a/src/streamer/jamstreamer.h +++ b/src/streamer/jamstreamer.h @@ -12,7 +12,7 @@ class CJamStreamer : public QObject { void Init( const QString strStreamDest ); public slots: - void process( int iServerFrameSizeSamples, CVector data ); + void process( int iServerFrameSizeSamples, const CVector& data ); void OnStarted(); void OnStopped(); From 8829d1acc0c78eec30a2bb8cf3571f33368e255d Mon Sep 17 00:00:00 2001 From: Noam Postavsky Date: Thu, 11 Feb 2021 08:43:17 -0500 Subject: [PATCH 12/21] Use qproc --- src/streamer/jamstreamer.cpp | 11 +++++++---- src/streamer/jamstreamer.h | 3 ++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/streamer/jamstreamer.cpp b/src/streamer/jamstreamer.cpp index dbd80daeb9..453859e3c5 100644 --- a/src/streamer/jamstreamer.cpp +++ b/src/streamer/jamstreamer.cpp @@ -7,7 +7,7 @@ CJamStreamer::CJamStreamer() {} // put the received pcm data into the pipe to ffmpeg void CJamStreamer::process( int iServerFrameSizeSamples, const CVector& data ) { - fwrite(&data[0], 2, ( 2 * iServerFrameSizeSamples ), pipeout); + qproc.write ( reinterpret_cast (&data[0]), sizeof (int16_t) * ( 2 * iServerFrameSizeSamples ) ); } void CJamStreamer::Init( const QString strStreamDest ) { @@ -16,11 +16,14 @@ void CJamStreamer::Init( const QString strStreamDest ) { // create a pipe to ffmpeg called "pipeout" to being able to put out the pcm data to it void CJamStreamer::OnStarted() { - QString command = "ffmpeg -y -f s16le -ar 48000 -ac 2 -i - " + strStreamDest; - pipeout = popen(command.toUtf8().constData(), "w"); + QStringList argumentList = { "ffmpeg", "-y", "-f", "s16le", + "-ar", "48000", "-ac", "2", + "-i", "-", strStreamDest }; + // Note that program name is also repeated as first argument + qproc.start ( "ffmpeg", argumentList, QIODevice::WriteOnly ); } void CJamStreamer::OnStopped() { - pclose(pipeout); + qproc.closeWriteChannel (); } #endif diff --git a/src/streamer/jamstreamer.h b/src/streamer/jamstreamer.h index 9201c2cd07..b2fe64c406 100644 --- a/src/streamer/jamstreamer.h +++ b/src/streamer/jamstreamer.h @@ -1,5 +1,6 @@ #ifndef _WIN32 #include +#include #include "../util.h" namespace streamer { @@ -18,7 +19,7 @@ public slots: private: QString strStreamDest; // stream destination to pass to ffmpeg as output part of arguments - FILE *pipeout; // pipe for putting out the pcm data to ffmpeg + QProcess qproc; // ffmpeg subprocess }; } #endif From e3001c54d96ad2e056aef3395c3691ca2b0502d6 Mon Sep 17 00:00:00 2001 From: Noam Postavsky Date: Fri, 12 Feb 2021 10:41:06 -0500 Subject: [PATCH 13/21] make qproc into a pointer --- src/streamer/jamstreamer.cpp | 10 +++++++--- src/streamer/jamstreamer.h | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/streamer/jamstreamer.cpp b/src/streamer/jamstreamer.cpp index 453859e3c5..bd6c73000f 100644 --- a/src/streamer/jamstreamer.cpp +++ b/src/streamer/jamstreamer.cpp @@ -7,7 +7,7 @@ CJamStreamer::CJamStreamer() {} // put the received pcm data into the pipe to ffmpeg void CJamStreamer::process( int iServerFrameSizeSamples, const CVector& data ) { - qproc.write ( reinterpret_cast (&data[0]), sizeof (int16_t) * ( 2 * iServerFrameSizeSamples ) ); + qproc->write ( reinterpret_cast (&data[0]), sizeof (int16_t) * ( 2 * iServerFrameSizeSamples ) ); } void CJamStreamer::Init( const QString strStreamDest ) { @@ -16,14 +16,18 @@ void CJamStreamer::Init( const QString strStreamDest ) { // create a pipe to ffmpeg called "pipeout" to being able to put out the pcm data to it void CJamStreamer::OnStarted() { + if ( !qproc ) + { + qproc = new QProcess; + } QStringList argumentList = { "ffmpeg", "-y", "-f", "s16le", "-ar", "48000", "-ac", "2", "-i", "-", strStreamDest }; // Note that program name is also repeated as first argument - qproc.start ( "ffmpeg", argumentList, QIODevice::WriteOnly ); + qproc->start ( "ffmpeg", argumentList, QIODevice::WriteOnly ); } void CJamStreamer::OnStopped() { - qproc.closeWriteChannel (); + qproc->closeWriteChannel (); } #endif diff --git a/src/streamer/jamstreamer.h b/src/streamer/jamstreamer.h index b2fe64c406..5d30a9c094 100644 --- a/src/streamer/jamstreamer.h +++ b/src/streamer/jamstreamer.h @@ -19,7 +19,7 @@ public slots: private: QString strStreamDest; // stream destination to pass to ffmpeg as output part of arguments - QProcess qproc; // ffmpeg subprocess + QProcess* qproc; // ffmpeg subprocess }; } #endif From 2374af5019d73d0cbba09dcf5f439206b4a1dcaa Mon Sep 17 00:00:00 2001 From: Noam Postavsky Date: Fri, 12 Feb 2021 11:14:47 -0500 Subject: [PATCH 14/21] add some error checking --- src/streamer/jamstreamer.cpp | 31 +++++++++++++++++++++++++++++++ src/streamer/jamstreamer.h | 2 ++ 2 files changed, 33 insertions(+) diff --git a/src/streamer/jamstreamer.cpp b/src/streamer/jamstreamer.cpp index bd6c73000f..afc0a7aa52 100644 --- a/src/streamer/jamstreamer.cpp +++ b/src/streamer/jamstreamer.cpp @@ -19,6 +19,7 @@ void CJamStreamer::OnStarted() { if ( !qproc ) { qproc = new QProcess; + QObject::connect ( qproc, &QProcess::errorOccurred, this, &CJamStreamer::onError ); } QStringList argumentList = { "ffmpeg", "-y", "-f", "s16le", "-ar", "48000", "-ac", "2", @@ -27,6 +28,36 @@ void CJamStreamer::OnStarted() { qproc->start ( "ffmpeg", argumentList, QIODevice::WriteOnly ); } +void CJamStreamer::onError(QProcess::ProcessError error) +{ + QString errDesc; + switch (error) { + case QProcess::FailedToStart: + errDesc = "failed to start"; + break; + case QProcess::Crashed: + errDesc = "crashed"; + break; + case QProcess::Timedout: + errDesc = "timed out"; + break; + case QProcess::WriteError: + errDesc = "write error"; + break; + case QProcess::ReadError: + errDesc = "read error"; + break; + case QProcess::UnknownError: + errDesc = "unknown error"; + break; + default: + errDesc = "UNKNOWN unknown error"; + break; + } + qWarning() << "QProcess Error: " << errDesc << "\n"; +} + + void CJamStreamer::OnStopped() { qproc->closeWriteChannel (); } diff --git a/src/streamer/jamstreamer.h b/src/streamer/jamstreamer.h index 5d30a9c094..1e8556093f 100644 --- a/src/streamer/jamstreamer.h +++ b/src/streamer/jamstreamer.h @@ -16,6 +16,8 @@ public slots: void process( int iServerFrameSizeSamples, const CVector& data ); void OnStarted(); void OnStopped(); +private slots: + void onError(QProcess::ProcessError error); private: QString strStreamDest; // stream destination to pass to ffmpeg as output part of arguments From e8e269293ea07147745548711ece3c4d3933b6cc Mon Sep 17 00:00:00 2001 From: Noam Postavsky Date: Fri, 12 Feb 2021 11:31:09 -0500 Subject: [PATCH 15/21] adjust logging --- src/streamer/jamstreamer.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/streamer/jamstreamer.cpp b/src/streamer/jamstreamer.cpp index afc0a7aa52..7972d03a78 100644 --- a/src/streamer/jamstreamer.cpp +++ b/src/streamer/jamstreamer.cpp @@ -20,8 +20,11 @@ void CJamStreamer::OnStarted() { { qproc = new QProcess; QObject::connect ( qproc, &QProcess::errorOccurred, this, &CJamStreamer::onError ); + qproc->setStandardErrorFile ( "ffmpeg.stderr.txt" ); + qproc->setStandardOutputFile ( QProcess::nullDevice() ); } - QStringList argumentList = { "ffmpeg", "-y", "-f", "s16le", + QStringList argumentList = { "ffmpeg", "-loglevel", "fatal", + "-y", "-f", "s16le", "-ar", "48000", "-ac", "2", "-i", "-", strStreamDest }; // Note that program name is also repeated as first argument From 7b38912c22d96fdb4dd2668047a9dc7cae0500c2 Mon Sep 17 00:00:00 2001 From: Christian Hoffmann Date: Sat, 13 Feb 2021 00:28:15 +0100 Subject: [PATCH 16/21] streamer: Add logging of ffmpeg output Signed-off-by: Christian Hoffmann --- src/streamer/jamstreamer.cpp | 10 ++++++++-- src/streamer/jamstreamer.h | 1 + 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/streamer/jamstreamer.cpp b/src/streamer/jamstreamer.cpp index 7972d03a78..fd6f0a812a 100644 --- a/src/streamer/jamstreamer.cpp +++ b/src/streamer/jamstreamer.cpp @@ -20,7 +20,7 @@ void CJamStreamer::OnStarted() { { qproc = new QProcess; QObject::connect ( qproc, &QProcess::errorOccurred, this, &CJamStreamer::onError ); - qproc->setStandardErrorFile ( "ffmpeg.stderr.txt" ); + QObject::connect ( qproc, QOverload::of( &QProcess::finished ), this, &CJamStreamer::onFinished ); qproc->setStandardOutputFile ( QProcess::nullDevice() ); } QStringList argumentList = { "ffmpeg", "-loglevel", "fatal", @@ -28,7 +28,7 @@ void CJamStreamer::OnStarted() { "-ar", "48000", "-ac", "2", "-i", "-", strStreamDest }; // Note that program name is also repeated as first argument - qproc->start ( "ffmpeg", argumentList, QIODevice::WriteOnly ); + qproc->start ( "ffmpeg", argumentList ); } void CJamStreamer::onError(QProcess::ProcessError error) @@ -60,6 +60,12 @@ void CJamStreamer::onError(QProcess::ProcessError error) qWarning() << "QProcess Error: " << errDesc << "\n"; } +void CJamStreamer::onFinished( int exitCode, QProcess::ExitStatus exitStatus ) +{ + Q_UNUSED ( exitStatus ); + QByteArray stderr = qproc->readAllStandardError (); + qInfo() << "ffmpeg exited with exitCode" << exitCode << ", stderr:" << stderr; +} void CJamStreamer::OnStopped() { qproc->closeWriteChannel (); diff --git a/src/streamer/jamstreamer.h b/src/streamer/jamstreamer.h index 1e8556093f..94b16744b5 100644 --- a/src/streamer/jamstreamer.h +++ b/src/streamer/jamstreamer.h @@ -18,6 +18,7 @@ public slots: void OnStopped(); private slots: void onError(QProcess::ProcessError error); + void onFinished( int exitCode, QProcess::ExitStatus exitStatus ); private: QString strStreamDest; // stream destination to pass to ffmpeg as output part of arguments From dc0a184e4775ad7c75d37cea7294ddf0b99b16df Mon Sep 17 00:00:00 2001 From: Christian Hoffmann Date: Sat, 13 Feb 2021 00:29:08 +0100 Subject: [PATCH 17/21] streamer: Fix ffmpeg invocation - QProcess automatically populates argv[0]. Explicitly passing "ffmpeg" as first argument will end up as argv[1] and will break ffmpeg. - Increase ffmpeg log level to "error" so that startup errors are really output. - Add support for space-delimited multiple parameters for the --streamto argument. Signed-off-by: Christian Hoffmann --- src/streamer/jamstreamer.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/streamer/jamstreamer.cpp b/src/streamer/jamstreamer.cpp index fd6f0a812a..310d065370 100644 --- a/src/streamer/jamstreamer.cpp +++ b/src/streamer/jamstreamer.cpp @@ -23,10 +23,11 @@ void CJamStreamer::OnStarted() { QObject::connect ( qproc, QOverload::of( &QProcess::finished ), this, &CJamStreamer::onFinished ); qproc->setStandardOutputFile ( QProcess::nullDevice() ); } - QStringList argumentList = { "ffmpeg", "-loglevel", "fatal", + QStringList argumentList = { "-loglevel", "error", "-y", "-f", "s16le", "-ar", "48000", "-ac", "2", - "-i", "-", strStreamDest }; + "-i", "-" }; + argumentList += strStreamDest.split( QRegExp("\\s+") ); // Note that program name is also repeated as first argument qproc->start ( "ffmpeg", argumentList ); } From b8a8297af8a1e0de03a37c513a6f140cd602b8b6 Mon Sep 17 00:00:00 2001 From: dingodoppelt Date: Sat, 13 Feb 2021 12:59:27 +0100 Subject: [PATCH 18/21] add help text --- src/main.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main.cpp b/src/main.cpp index 09d111fca1..5b603321e1 100755 --- a/src/main.cpp +++ b/src/main.cpp @@ -851,6 +851,8 @@ QString UsageArguments ( char **argv ) " --serverpublicip specify your public IP address when\n" " running a slave and your own central server\n" " behind the same NAT\n" + " --streamto pass ffmpeg output arguments to stream a stereo mix\n" + " from the server (see \"ffmpeg -h\" for reference)\n" "\nClient only:\n" " -M, --mutestream starts the application in muted state\n" " --mutemyown mute me in my personal mix (headless only)\n" From 04eec1cd08ec8b21226f9d10a650e657d50ae081 Mon Sep 17 00:00:00 2001 From: dingodoppelt <62596379+dingodoppelt@users.noreply.github.com> Date: Wed, 17 Feb 2021 19:55:39 +0100 Subject: [PATCH 19/21] initialize qproc --- src/streamer/jamstreamer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/streamer/jamstreamer.cpp b/src/streamer/jamstreamer.cpp index 310d065370..23e32452b5 100644 --- a/src/streamer/jamstreamer.cpp +++ b/src/streamer/jamstreamer.cpp @@ -3,7 +3,7 @@ using namespace streamer; -CJamStreamer::CJamStreamer() {} +CJamStreamer::CJamStreamer() : qproc ( NULL ) {} // put the received pcm data into the pipe to ffmpeg void CJamStreamer::process( int iServerFrameSizeSamples, const CVector& data ) { From 2981836149f6883c849a87263564378a94808aba Mon Sep 17 00:00:00 2001 From: dingodoppelt <62596379+dingodoppelt@users.noreply.github.com> Date: Wed, 17 Feb 2021 19:57:55 +0100 Subject: [PATCH 20/21] remove comment --- src/streamer/jamstreamer.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/streamer/jamstreamer.cpp b/src/streamer/jamstreamer.cpp index 23e32452b5..ffb7306597 100644 --- a/src/streamer/jamstreamer.cpp +++ b/src/streamer/jamstreamer.cpp @@ -14,7 +14,6 @@ void CJamStreamer::Init( const QString strStreamDest ) { this->strStreamDest = strStreamDest; } -// create a pipe to ffmpeg called "pipeout" to being able to put out the pcm data to it void CJamStreamer::OnStarted() { if ( !qproc ) { From 10400f54fbc62894445b6920fd0a49d1cd1785dc Mon Sep 17 00:00:00 2001 From: dingodoppelt <62596379+dingodoppelt@users.noreply.github.com> Date: Wed, 17 Feb 2021 19:58:32 +0100 Subject: [PATCH 21/21] remove comment --- src/streamer/jamstreamer.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/streamer/jamstreamer.cpp b/src/streamer/jamstreamer.cpp index ffb7306597..1254b4b6d6 100644 --- a/src/streamer/jamstreamer.cpp +++ b/src/streamer/jamstreamer.cpp @@ -5,7 +5,6 @@ using namespace streamer; CJamStreamer::CJamStreamer() : qproc ( NULL ) {} -// put the received pcm data into the pipe to ffmpeg void CJamStreamer::process( int iServerFrameSizeSamples, const CVector& data ) { qproc->write ( reinterpret_cast (&data[0]), sizeof (int16_t) * ( 2 * iServerFrameSizeSamples ) ); }