From cc461f2f0ff01267587c3c5753414438d0645f8b Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 29 Jan 2018 01:59:03 +0100 Subject: [PATCH] Added RTP Sink based on JRTPLib and AudioNetSink to handle sending audio over the network via UDP or RTP --- CMakeLists.txt | 2 +- cmake/Modules/FindJRTPLib.cmake | 37 ++++++++ sdrbase/CMakeLists.txt | 17 +++- sdrbase/audio/audionetsink.cpp | 160 ++++++++++++++++++++++++++++++++ sdrbase/audio/audionetsink.h | 65 +++++++++++++ sdrbase/util/rtpsink.h | 143 ++++++++++++++++++++++++++++ sdrbase/util/udpsink.h | 22 ++++- 7 files changed, 442 insertions(+), 4 deletions(-) create mode 100644 cmake/Modules/FindJRTPLib.cmake create mode 100644 sdrbase/audio/audionetsink.cpp create mode 100644 sdrbase/audio/audionetsink.h create mode 100644 sdrbase/util/rtpsink.h 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 */