diff --git a/CMakeLists.txt b/CMakeLists.txt
index 8e6359a87..6c46eea7e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -57,9 +57,9 @@ find_package(Qt5Multimedia 5.0 REQUIRED)
#find_package(QT5OpenGL 5.0 REQUIRED)
find_package(OpenGL REQUIRED)
find_package(PkgConfig)
-
find_package(Boost REQUIRED)
find_package(FFTW3F)
+find_package(JRTPLib)
if (NOT BUILD_DEBIAN)
find_package(LibDSDcc)
diff --git a/cmake/Modules/FindJRTPLib.cmake b/cmake/Modules/FindJRTPLib.cmake
new file mode 100644
index 000000000..eb2674f0a
--- /dev/null
+++ b/cmake/Modules/FindJRTPLib.cmake
@@ -0,0 +1,37 @@
+INCLUDE(FindPkgConfig)
+PKG_CHECK_MODULES(PC_JRTPLIB "jrtplib")
+
+FIND_PATH(JRTPLIB_INCLUDE_DIR
+ NAMES rtpsession.h
+ HINTS ${PC_JRTPLIB_INCLUDE_DIR}
+ ${CMAKE_INSTALL_PREFIX}/include/jrtplib3
+ ${JRTPLIB_INSTALL_PREFIX}/include/jrtplib3
+ PATHS
+ /usr/local/include/jrtplib3
+ /usr/include/jrtplib3
+)
+
+FIND_LIBRARY(JRTPLIB_LIBRARIES
+ NAMES libjrtp
+ HINTS ${PC_JRTPLIB_LIBDIR}
+ ${CMAKE_INSTALL_PREFIX}/lib
+ ${CMAKE_INSTALL_PREFIX}/lib64
+ PATHS
+ ${JRTPLIB_INCLUDE_DIR}/../lib
+ /usr/local/lib
+ /usr/local/lib64
+ /usr/lib
+ /usr/lib64
+)
+
+if(JRTPLIB_INCLUDE_DIR AND JRTPLIB_LIBRARIES)
+ set(JRTPLIB_FOUND TRUE CACHE INTERNAL "JRTPLib found")
+ message(STATUS "Found JRTPLib: ${JRTPLIB_INCLUDE_DIR}, ${JRTPLIB_LIBRARIES}")
+else()
+ set(JRTPLIB_FOUND FALSE CACHE INTERNAL "JRTPLib found")
+ message(STATUS "JRTPLib not found")
+endif()
+
+INCLUDE(FindPackageHandleStandardArgs)
+FIND_PACKAGE_HANDLE_STANDARD_ARGS(JRTPLIB DEFAULT_MSG JRTPLIB_LIBRARIES JRTPLIB_INCLUDE_DIR)
+MARK_AS_ADVANCED(JRTPLIB_LIBRARIES JRTPLIB_INCLUDE_DIR)
diff --git a/sdrbase/CMakeLists.txt b/sdrbase/CMakeLists.txt
index 182f40258..7fdbddfcd 100644
--- a/sdrbase/CMakeLists.txt
+++ b/sdrbase/CMakeLists.txt
@@ -7,7 +7,8 @@ set(sdrbase_SOURCES
audio/audiofifo.cpp
audio/audiooutput.cpp
audio/audioinput.cpp
-
+ audio/audionetsink.cpp
+
channel/channelsinkapi.cpp
channel/channelsourceapi.cpp
commands/command.cpp
@@ -85,6 +86,7 @@ set(sdrbase_HEADERS
audio/audiofifo.h
audio/audiooutput.h
audio/audioinput.h
+ audio/audionetsink.h
channel/channelsinkapi.h
channel/channelsourceapi.h
@@ -209,6 +211,15 @@ else(FFTW3F_FOUND)
add_definitions(-DUSE_KISSFFT)
endif(FFTW3F_FOUND)
+if (JRTPLIB_FOUND)
+ set(sdrbase_HEADERS
+ ${sdrbase_HEADERS}
+ util/rtpsink.h
+ )
+ add_definitions(-DHAS_JRTPLIB)
+ include_directories(${JRTPLIB_INCLUDE_DIR})
+endif(JRTPLIB_FOUND)
+
if (LIBSERIALDV_FOUND)
set(sdrbase_SOURCES
${sdrbase_SOURCES}
@@ -271,6 +282,10 @@ if(FFTW3F_FOUND)
target_link_libraries(sdrbase ${FFTW3F_LIBRARIES})
endif(FFTW3F_FOUND)
+if (JRTPLIB_FOUND)
+ target_link_libraries(sdrbase ${JRTPLIB_LIBRARIES})
+endif(JRTPLIB_FOUND)
+
if(LIBSERIALDV_FOUND)
target_link_libraries(sdrbase ${LIBSERIALDV_LIBRARY})
endif(LIBSERIALDV_FOUND)
diff --git a/sdrbase/audio/audionetsink.cpp b/sdrbase/audio/audionetsink.cpp
new file mode 100644
index 000000000..c68fcd50a
--- /dev/null
+++ b/sdrbase/audio/audionetsink.cpp
@@ -0,0 +1,160 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2018 F4EXB //
+// written by Edouard Griffiths //
+// //
+// 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 as version 3 of the License, or //
+// //
+// 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 V3 for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#include "audionetsink.h"
+
+const int AudioNetSink::m_udpBlockSize = 512;
+
+AudioNetSink::AudioNetSink(QObject *parent, bool stereo) :
+ m_type(SinkUDP),
+ m_udpBufferAudioMono(0),
+ m_udpBufferAudioStereo(0)
+{
+ if (stereo)
+ {
+ m_udpBufferAudioStereo = new UDPSink(parent, m_udpBlockSize);
+#ifdef HAS_JRTPLIB
+ m_rtpBufferAudioStereo = new RTPSink("127.0.0.1", 9999, 48000);
+ m_rtpBufferAudioMono = 0;
+#endif
+ }
+ else
+ {
+ m_udpBufferAudioMono = new UDPSink(parent, m_udpBlockSize);
+#ifdef HAS_JRTPLIB
+ m_rtpBufferAudioMono = new RTPSink("127.0.0.1", 9999, 48000);
+ m_rtpBufferAudioStereo = 0;
+#endif
+ }
+}
+
+AudioNetSink::~AudioNetSink()
+{
+ if (m_udpBufferAudioMono) { delete m_udpBufferAudioMono; }
+ if (m_udpBufferAudioStereo) { delete m_udpBufferAudioStereo; }
+#ifdef HAS_JRTPLIB
+ if (m_rtpBufferAudioMono) { delete m_rtpBufferAudioMono; }
+ if (m_rtpBufferAudioStereo) { delete m_rtpBufferAudioStereo; }
+#endif
+}
+
+bool AudioNetSink::selectType(SinkType type)
+{
+ if (type == SinkUDP)
+ {
+ m_type = SinkUDP;
+ return true;
+ }
+ else if (type == SinkRTP)
+ {
+#ifdef HAS_JRTPLIB
+ m_type = SinkRTP;
+ return true;
+#else
+ m_type = SinkUDP;
+ return false;
+#endif
+ }
+ else
+ {
+ return false;
+ }
+}
+
+void AudioNetSink::setDestination(const QString& address, uint16_t port)
+{
+ if (m_udpBufferAudioMono) {
+ m_udpBufferAudioMono->setDestination(address, port);
+ }
+ if (m_udpBufferAudioStereo) {
+ m_udpBufferAudioStereo->setDestination(address, port);
+ }
+#ifdef HAS_JRTPLIB
+ if (m_rtpBufferAudioMono) {
+ m_rtpBufferAudioMono->setDestination(address, port);
+ }
+ if (m_rtpBufferAudioStereo) {
+ m_rtpBufferAudioStereo->setDestination(address, port);
+ }
+#endif
+}
+
+void AudioNetSink::addDestination(const QString& address, uint16_t port)
+{
+#ifdef HAS_JRTPLIB
+ if (m_rtpBufferAudioMono) {
+ m_rtpBufferAudioMono->addDestination(address, port);
+ }
+ if (m_rtpBufferAudioStereo) {
+ m_rtpBufferAudioStereo->addDestination(address, port);
+ }
+#endif
+}
+
+void AudioNetSink::deleteDestination(const QString& address, uint16_t port)
+{
+#ifdef HAS_JRTPLIB
+ if (m_rtpBufferAudioMono) {
+ m_rtpBufferAudioMono->deleteDestination(address, port);
+ }
+ if (m_rtpBufferAudioStereo) {
+ m_rtpBufferAudioStereo->deleteDestination(address, port);
+ }
+#endif
+}
+
+void AudioNetSink::setSampleRate(int sampleRate)
+{
+#ifdef HAS_JRTPLIB
+ if (m_rtpBufferAudioMono) {
+ m_rtpBufferAudioMono->setSampleRate(sampleRate);
+ }
+ if (m_rtpBufferAudioStereo) {
+ m_rtpBufferAudioStereo->setSampleRate(sampleRate);
+ }
+#endif
+}
+
+void AudioNetSink::write(qint16 sample)
+{
+ if (m_udpBufferAudioMono == 0) {
+ return;
+ }
+
+ if (m_type == SinkUDP) {
+ m_udpBufferAudioMono->write(sample);
+ } else if (m_type == SinkRTP) {
+#ifdef HAS_JRTPLIB
+#endif
+ }
+}
+
+void AudioNetSink::write(const AudioSample& sample)
+{
+ if (m_udpBufferAudioStereo == 0) {
+ return;
+ }
+
+ if (m_type == SinkUDP) {
+ m_udpBufferAudioStereo->write(sample);
+ } else if (m_type == SinkRTP) {
+#ifdef HAS_JRTPLIB
+#endif
+ }
+}
+
+
diff --git a/sdrbase/audio/audionetsink.h b/sdrbase/audio/audionetsink.h
new file mode 100644
index 000000000..2dc97b20d
--- /dev/null
+++ b/sdrbase/audio/audionetsink.h
@@ -0,0 +1,65 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2018 F4EXB //
+// written by Edouard Griffiths //
+// //
+// 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 as version 3 of the License, or //
+// //
+// 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 V3 for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#ifndef SDRBASE_AUDIO_AUDIONETSINK_H_
+#define SDRBASE_AUDIO_AUDIONETSINK_H_
+
+#include
+#include "dsp/dsptypes.h"
+#include "util/export.h"
+#include "util/udpsink.h"
+#ifdef HAS_JRTPLIB
+#include "util/rtpsink.h"
+#endif
+
+class SDRANGEL_API AudioNetSink {
+public:
+ typedef enum
+ {
+ SinkUDP,
+ SinkRTP
+ } SinkType;
+
+ AudioNetSink(QObject *parent, bool stereo = false);
+ ~AudioNetSink();
+
+ void setDestination(const QString& address, uint16_t port);
+ void addDestination(const QString& address, uint16_t port);
+ void deleteDestination(const QString& address, uint16_t port);
+ void setSampleRate(int sampleRate);
+
+ void write(qint16 sample);
+ void write(const AudioSample& sample);
+
+ bool selectType(SinkType type);
+
+ static const int m_udpBlockSize;
+
+protected:
+ SinkType m_type;
+ UDPSink *m_udpBufferAudioMono;
+ UDPSink *m_udpBufferAudioStereo;
+#ifdef HAS_JRTPLIB
+ RTPSink *m_rtpBufferAudioMono;
+ RTPSink *m_rtpBufferAudioStereo;
+#endif
+};
+
+
+
+
+#endif /* SDRBASE_AUDIO_AUDIONETSINK_H_ */
diff --git a/sdrbase/util/rtpsink.h b/sdrbase/util/rtpsink.h
new file mode 100644
index 000000000..648f1498f
--- /dev/null
+++ b/sdrbase/util/rtpsink.h
@@ -0,0 +1,143 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2018 F4EXB //
+// written by Edouard Griffiths //
+// //
+// 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 as version 3 of the License, or //
+// //
+// 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 V3 for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#ifndef SDRBASE_UTIL_RTPSINK_H_
+#define SDRBASE_UTIL_RTPSINK_H_
+
+#include
+#include
+#include
+
+// jrtplib includes
+#include "rtpsession.h"
+#include "rtpudpv4transmitter.h"
+#include "rtpipv4address.h"
+#include "rtpsessionparams.h"
+#include "rtperrors.h"
+#include "rtplibraryversion.h"
+
+template
+class RTPSink
+{
+public:
+ RTPSink(const QString& address, uint16_t port, int sampleRate) :
+ m_destport(port),
+ m_sampleBuffer(0),
+ m_sampleBufferIndex(0),
+ m_mutex(QMutex::Recursive)
+ {
+ m_destip = inet_addr(address.toStdString().c_str());
+ m_destip = ntohl(m_destip);
+ m_rtpSessionParams.SetOwnTimestampUnit(1.0/(double) sampleRate);
+ m_rtpTransmissionParams.SetPortbase(8092); // FIXME: sort this out
+
+ int status = m_rtpSession.Create(m_rtpSessionParams, &m_rtpTransmissionParams);
+
+ if (status < 0)
+ {
+ qCritical("RTPSink::RTPSink: cannot create session: %s", jrtplib::RTPGetErrorString(status).c_str());
+ return;
+ }
+
+ status = m_rtpSession.AddDestination(jrtplib::RTPIPv4Address(m_destip, m_destport));
+
+ if (status < 0)
+ {
+ qCritical("RTPSink::RTPSink: cannot set destination address: %s", jrtplib::RTPGetErrorString(status).c_str());
+ return;
+ }
+
+ m_sampleBuffer = new SampleType[sampleRate]; // store 1 second
+ }
+
+ ~RTPSink()
+ {
+ jrtplib::RTPTime delay = jrtplib::RTPTime(10.0);
+ m_rtpSession.BYEDestroy(delay, "Time's up", 9);
+
+ if (m_sampleBuffer) { delete[] m_sampleBuffer; }
+ }
+
+ void setDestination(const QString& address, uint16_t port)
+ {
+ if (!m_sampleBuffer) { return; }
+
+ m_rtpSession.ClearDestinations();
+ m_rtpSession.DeleteDestination(jrtplib::RTPIPv4Address(m_destip, m_destport));
+ m_destip = inet_addr(address.toStdString().c_str());
+ m_destip = ntohl(m_destip);
+ m_destport = port;
+
+ int status = m_rtpSession.AddDestination(jrtplib::RTPIPv4Address(m_destip, m_destport));
+
+ if (status < 0) {
+ qCritical("RTPSink::setDestination: cannot set destination address: %s", jrtplib::RTPGetErrorString(status).c_str());
+ }
+ }
+
+ void deleteDestination(const QString& address, uint16_t port)
+ {
+ uint32_t destip = inet_addr(address.toStdString().c_str());
+ destip = ntohl(m_destip);
+
+ int status = m_rtpSession.DeleteDestination(jrtplib::RTPIPv4Address(destip, port));
+
+ if (status < 0) {
+ qCritical("RTPSink::deleteDestination: cannot delete destination address: %s", jrtplib::RTPGetErrorString(status).c_str());
+ }
+ }
+
+ void addDestination(const QString& address, uint16_t port)
+ {
+ uint32_t destip = inet_addr(address.toStdString().c_str());
+ destip = ntohl(m_destip);
+
+ int status = m_rtpSession.AddDestination(jrtplib::RTPIPv4Address(destip, port));
+
+ if (status < 0) {
+ qCritical("RTPSink::addDestination: cannot add destination address: %s", jrtplib::RTPGetErrorString(status).c_str());
+ }
+ }
+
+ void setSampleRate(int sampleRate)
+ {
+ QMutexLocker locker(&m_mutex);
+ if (m_sampleBuffer) { delete[] m_sampleBuffer; }
+ m_sampleBuffer = new SampleType[sampleRate]; // store 1 second
+
+ int status = m_rtpSession.SetTimestampUnit(1.0 / (double) sampleRate);
+
+ if (status < 0)
+ {
+ qCritical("RTPSink::setSampleRate: cannot set timestamp unit: %s", jrtplib::RTPGetErrorString(status).c_str());
+ return;
+ }
+ }
+
+protected:
+ uint32_t m_destip;
+ uint16_t m_destport;
+ jrtplib::RTPSession m_rtpSession;
+ jrtplib::RTPSessionParams m_rtpSessionParams;
+ jrtplib::RTPUDPv4TransmissionParams m_rtpTransmissionParams;
+ SampleType *m_sampleBuffer;
+ int m_sampleBufferIndex;
+ QMutex m_mutex;
+};
+
+
+#endif /* SDRBASE_UTIL_RTPSINK_H_ */
diff --git a/sdrbase/util/udpsink.h b/sdrbase/util/udpsink.h
index 9c95b09b2..c271b172e 100644
--- a/sdrbase/util/udpsink.h
+++ b/sdrbase/util/udpsink.h
@@ -29,11 +29,11 @@ template
class UDPSink
{
public:
- UDPSink(QObject *parent, unsigned int udpSize, unsigned int port) :
+ UDPSink(QObject *parent, unsigned int udpSize) :
m_udpSize(udpSize),
m_udpSamples(udpSize/sizeof(T)),
m_address(QHostAddress::LocalHost),
- m_port(port),
+ m_port(9999),
m_sampleBufferIndex(0)
{
assert(m_udpSamples > 0);
@@ -41,6 +41,18 @@ public:
m_socket = new QUdpSocket(parent);
}
+ UDPSink(QObject *parent, unsigned int udpSize, unsigned int port) :
+ m_udpSize(udpSize),
+ m_udpSamples(udpSize/sizeof(T)),
+ m_address(QHostAddress::LocalHost),
+ m_port(port),
+ m_sampleBufferIndex(0)
+ {
+ assert(m_udpSamples > 0);
+ m_sampleBuffer = new T[m_udpSamples];
+ m_socket = new QUdpSocket(parent);
+ }
+
UDPSink (QObject *parent, unsigned int udpSize, QHostAddress& address, unsigned int port) :
m_udpSize(udpSize),
m_udpSamples(udpSize/sizeof(T)),
@@ -62,6 +74,12 @@ public:
void setAddress(QString& address) { m_address.setAddress(address); }
void setPort(unsigned int port) { m_port = port; }
+ void setDestination(const QString& address, int port)
+ {
+ m_address.setAddress(const_cast(address));
+ m_port = port;
+ }
+
/**
* Write one sample
*/