Skip to content

Commit

Permalink
[stereo-mix] Add a stereo mix server
Browse files Browse the repository at this point in the history
A stereo mix server is a local TCP server that outputs the mixed sound as s16le stereo @ 48000 hz.

The output can be streamed to ffmpeg for sending to (e.g.) an Icecast server:

     nc localhost $STEREO_MIX_PORT \
       | ffmpeg -f s16le -ar 48000 -ac 2 -i - \
                -acodec libmp3lame -ab 128k -f mp3 $ICECAST_URL

The implementation for `CServer::MixStream` comes from the Streamer2 PR:
jamulussoftware#967

By making the stream system pull-based (consumers ask Jamulus for data),
rather than push-based (Jamulus sends data to a streaming server), the
implementation is greatly simplified.

* No need to implement ways to toggle streaming on/off, as streaming
  starts when a client connects and ends when the client disconnects.
* No need to deal with launching, monitoring, and killing sub-processes.
* Works with all OSes.

Co-authored-by: dingodoppelt <[email protected]>
  • Loading branch information
dtinth and dingodoppelt committed Sep 17, 2021
1 parent c40d39c commit 04e71bd
Show file tree
Hide file tree
Showing 5 changed files with 166 additions and 0 deletions.
2 changes: 2 additions & 0 deletions Jamulus.pro
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,7 @@ HEADERS += src/buffer.h \
src/serverlogging.h \
src/serverrpc.h \
src/rpcserver.h \
src/stereomixserver.h \
src/settings.h \
src/socket.h \
src/soundbase.h \
Expand Down Expand Up @@ -533,6 +534,7 @@ SOURCES += src/buffer.cpp \
src/serverlogging.cpp \
src/serverrpc.cpp \
src/rpcserver.cpp \
src/stereomixserver.cpp \
src/settings.cpp \
src/signalhandler.cpp \
src/socket.cpp \
Expand Down
20 changes: 20 additions & 0 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ extern void qt_set_sequence_auto_mnemonic ( bool bEnable );
#include "rpcserver.h"
#include "serverrpc.h"
#include "clientrpc.h"
#include "stereomixserver.h"

// Implementation **************************************************************

Expand Down Expand Up @@ -89,6 +90,7 @@ int main ( int argc, char** argv )
int iNumServerChannels = DEFAULT_USED_NUM_CHANNELS;
quint16 iPortNumber = DEFAULT_PORT_NUMBER;
int iJsonRpcPortNumber = INVALID_PORT;
int iStereoMixPortNumber = INVALID_PORT;
quint16 iQosNumber = DEFAULT_QOS_NUMBER;
ELicenceType eLicenceType = LT_NO_LICENCE;
QString strMIDISetup = "";
Expand Down Expand Up @@ -176,6 +178,15 @@ int main ( int argc, char** argv )
continue;
}

// Stereo mix port number ----------------------------------------------
if ( GetNumericArgument ( argc, argv, i, "--stereomixport", "--stereomixport", 0, 65535, rDbleArgument ) )
{
iStereoMixPortNumber = static_cast<quint16> ( rDbleArgument );
qInfo() << qUtf8Printable ( QString ( "- stereo mix port number: %1" ).arg ( iStereoMixPortNumber ) );
CommandLineOptions << "--stereomixport";
continue;
}

// Quality of Service --------------------------------------------------
if ( GetNumericArgument ( argc, argv, i, "-Q", "--qos", 0, 255, rDbleArgument ) )
{
Expand Down Expand Up @@ -899,6 +910,12 @@ int main ( int argc, char** argv )
new CServerRpc ( pRpcServer, &Server, pRpcServer );
}

if ( iStereoMixPortNumber != INVALID_PORT )
{
auto pStereoMixServer = new CStereoMixServer ( &Server, iStereoMixPortNumber );
pStereoMixServer->Start();
}

#ifndef HEADLESS
if ( bUseGUI )
{
Expand Down Expand Up @@ -1013,6 +1030,9 @@ QString UsageArguments ( char** argv )
" --norecord disables recording (when enabled by default by -R)\n"
" -s, --server start server\n"
" --serverbindip IP address the server will bind to (rather than all)\n"
" --stereomixport enable Stereo Mix server which streams PCM samples\n"
" at 48000 Hz in s16le stereo format, set TCP port number\n"
" (only accessible from localhost)\n"
" -T, --multithreading use multithreading to make better use of\n"
" multi-core CPUs and support more clients\n"
" -u, --numchannels maximum number of channels\n"
Expand Down
2 changes: 2 additions & 0 deletions src/server.h
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,8 @@ class CServer : public QObject, public CServerSlots<MAX_NUM_CHANNELS>
const int iNumAudChan,
const CVector<int16_t> vecsData );

void StreamFrame ( const int iServerFrameSizeSamples, const CVector<int16_t>& data );

void CLVersionAndOSReceived ( CHostAddress InetAddr, COSUtil::EOpSystemType eOSType, QString strVersion );

// pass through from jam controller
Expand Down
86 changes: 86 additions & 0 deletions src/stereomixserver.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/******************************************************************************\
* Copyright (c) 2021
*
* Author(s):
* dtinth
*
******************************************************************************
*
* 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*
\******************************************************************************/

#include "stereomixserver.h"

CStereoMixServer::CStereoMixServer ( CServer* pServer, int iPort ) :
QObject ( pServer ),
pServer ( pServer ),
iPort ( iPort ),
pTransportServer ( new QTcpServer ( this ) )
{
connect ( pTransportServer, &QTcpServer::newConnection, this, &CStereoMixServer::OnNewConnection );
connect ( pServer, &CServer::StreamFrame, this, &CStereoMixServer::OnStreamFrame );
}

CStereoMixServer::~CStereoMixServer()
{
if ( pTransportServer->isListening() )
{
qInfo() << "- stopping stereo mix server";
pTransportServer->close();
}
}

void CStereoMixServer::Start()
{
if ( iPort < 0 )
{
return;
}
if ( pTransportServer->listen ( QHostAddress ( "127.0.0.1" ), iPort ) )
{
qInfo() << "- stereo mix server started on port" << pTransportServer->serverPort();
}
else
{
qInfo() << "- unable to start stereo mix server:" << pTransportServer->errorString();
}
}

void CStereoMixServer::OnNewConnection()
{
QTcpSocket* pSocket = pTransportServer->nextPendingConnection();
if ( !pSocket )
{
return;
}

qInfo() << "- sending stereo mix to:" << pSocket->peerAddress().toString();
vecClients.append ( pSocket );

connect ( pSocket, &QTcpSocket::disconnected, [this, pSocket]() {
qInfo() << "- finish sending stereo mix to:" << pSocket->peerAddress().toString();
vecClients.removeAll ( pSocket );
pSocket->deleteLater();
} );
}

void CStereoMixServer::OnStreamFrame ( const int iServerFrameSizeSamples, const CVector<int16_t>& data )
{
for ( auto socket : vecClients )
{
socket->write ( reinterpret_cast<const char*> ( &data[0] ), sizeof ( int16_t ) * ( 2 * iServerFrameSizeSamples ) );
}
}
56 changes: 56 additions & 0 deletions src/stereomixserver.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/******************************************************************************\
* Copyright (c) 2021
*
* Author(s):
* dtinth
*
******************************************************************************
*
* 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*
\******************************************************************************/

#pragma once

#include <QObject>
#include <QTcpServer>
#include <QTcpSocket>
#include <memory>
#include "util.h"
#include "server.h"

typedef std::function<void ( const QJsonObject&, QJsonObject& )> CRpcHandler;

/* Classes ********************************************************************/
class CStereoMixServer : public QObject
{
Q_OBJECT

public:
CStereoMixServer ( CServer* pServer, int iPort );
virtual ~CStereoMixServer();

void Start();

private:
int iPort;
QTcpServer* pTransportServer;
CServer* pServer;
QVector<QTcpSocket*> vecClients;

protected slots:
void OnNewConnection();
void OnStreamFrame ( const int iServerFrameSizeSamples, const CVector<int16_t>& data );
};

0 comments on commit 04e71bd

Please sign in to comment.