#pragma once // SYSTEM INCLUDES #include #include #include #include #include #include // APPLICATION INCLUDES #include "rtp/rtp.h" #include "IODeviceSink.h" #include "IODeviceSource.h" // DEFINES // EXTERNAL FUNCTIONS // EXTERNAL VARIABLES // CONSTANTS // STRUCTS // TYPEDEFS // FORWARD DECLARATIONS class QTimer; class QTimerEvent; class QUdpSocket; class QAudioSource; class QAudioBuffer; class QAudioSink; const auto gHeaderLenLambda = [](const QByteArray& rRawHeader)->qsizetype { const auto header = reinterpret_cast(rRawHeader.constData()); qsizetype headerLen = sizeof(rtp_hdr_t) + header->cc * sizeof(uint32_t); // header extension if (header->x != 0u) { const auto ext = reinterpret_cast( rRawHeader.constData() + headerLen); const auto ext_len = ntohs(ext->len); headerLen += static_cast( sizeof(uint32_t) + (ext_len * sizeof(uint32_t))); } // must have sufficient bytes in raw header if (headerLen > rRawHeader.size()) { return -1; } return headerLen; }; /** * Active Object QT pattern - see tutorial https://www.youtube.com/watch?v=SncJ3D-fO7g * See also: https://forum.qt.io/topic/140465/qt-6-x-qaudiosink-potential-bug */ class RtpWorker final : public QObject { Q_OBJECT public: //! mpThread cannot have parent, as we need to move this object //! Also, note most QT class members need 'this' as parent for //! thread affinity to work correctly. RtpWorker(); //! these deleted constructors and assignment operators are not //! strictly required as the QObject superclass prevents copy //! but left in for clarity RtpWorker(const RtpWorker&) = delete; RtpWorker(RtpWorker&&) noexcept = delete; RtpWorker& operator=(const RtpWorker&) = delete; RtpWorker& operator=(RtpWorker&&) noexcept = delete; //! virtual destructor overload ~RtpWorker() override { QMetaObject::invokeMethod(this, "cleanup"); mpThread->wait(); } Q_SIGNALS: void stop(); void start(const qint64, const QString&, const quint16, const qint64, const QByteArray&, const QByteArrayList&); void datagramsReady(const QByteArrayList&); void bytesWritten(qint64, bool); void watchdogStatusChanged(bool, const QString&); /** * Signal to asynchronously notify application thread . * * @param rStatusText [in] Status area notification text. * @param rStyleSheet [in] Text stylesheet. * @param timeout [in] Timeout after which the text * disappears. */ void workerStatusChange(const QString& rStatusText, const QString& rStyleSheet, int timeout = 0) const; /** * IPC Signal from worker thread used to safely update * mTXBufferList. * * @param aTSIncrement [in] Timestamp increment per datagram. * @param rAudioData [in] raw multichannel audio. */ void updateAudio(const qint64 aTSIncrement, const QByteArrayList& rAudioData); //! Add log entry void addLogEntry(const spdlog::level::level_enum, const QString&); // slots are all called while in the worker thread context private Q_SLOTS: /** * Start signal handler. * *

This slot runs in the worker thread context. Creates a * buffered list of transmit UDP datagrams. Each datagram is prefixed * with an RTP header (including a sequence# & timestamp). * *

The first datagram will always have the marker bit set. * * @param aTSIncrement [in] Timestamp increment per datagram. * @param rTargetIP [in] Target IP address. * @param aTargetPort [in] Target UDP port. * @param aDurationUsecs * [in] Duration (specified in microseconds) to * sleep between recurring timer callbacks. * This time represents the duration for all * mTXBufferList datagrams. * @param rHeader [in] RTP header consisting of a QByteArray * containing an rtp_hdr_t with optional contributing * sources (up to 16) & rtp_hdr_ext_t fields & data if * the x bit of the rtp_hdr_t is set. * @param rAudioData [in] raw multichannel audio. */ void handleStart( const qint64 aTSIncrement, const QString& rTargetIP, const quint16 aTargetPort, const qint64 aDurationUsecs, const QByteArray& rHeader, const QByteArrayList& rAudioData); /** * Slot to handle captured audio data. * *

Saves the audio data to a QIODevice. * * @param rAudioBuffer [in] audio data with (containing valid * QAudioFormat). */ void handleAudioCaptured(const QAudioBuffer& rAudioBuffer); /** * Audio updated slot handler. * * @param aTSIncrement [in] Timestamp increment per datagram. * @param rAudioData [in] raw multichannel audio. */ void handleAudioUpdated( const qint64 aTSIncrement, const QByteArrayList& rAudioData); /** * QUdpSocket slot readyRead handler. * *

Slot called when UDP data returned via echo to this * thread. When called drain any pending datagrams and pet the * inactivity watchdog timer. */ void readyRead(); /** * Slot to handle bytes written. * *

Called when bytes successfully sent to target where we * emit the bytesWritten signal to update the stats area in the * GUI. * * @param aBytesWritten [in] number of bytes written to socket. */ void handleBytesWritten(qint64 aBytesWritten) { // forward to GUI Q_EMIT bytesWritten(aBytesWritten, false); } /** * Stop slot handler. * *

This is called in the worker thread context. Close open * timers, sockets, QAudioSources and QAudioSinks. */ void handleStop(); /** * Cleanup method - runs in worker thread context. */ void cleanup(); /** * Timer callback event handler. * * @param event [in] timer event. */ void timerEvent(QTimerEvent* event) override; private: std::unique_ptr mpThread; std::unique_ptr mpTimer; std::unique_ptr mpWatchdog; std::unique_ptr mpSocket; //! Audio Source (microphone). std::unique_ptr mpAudioSource; //! IODevice connected to mpAudioSource std::unique_ptr mpIODeviceSource; //! Audio Sink (speaker). std::unique_ptr mpAudioSink; //! IODevice connected to mpAudioSink std::unique_ptr mpIODeviceSink; QByteArrayList mTXBufferList; QHostAddress mTargetIP; quint16 mTargetPort; qint64 mTSIncrement; qint64 mTXCounter; };