arduino-audio-tools
Loading...
Searching...
No Matches
RTSPAudioStreamer.h
Go to the documentation of this file.
1/*
2 * Author: Phil Schatzmann
3 *
4 * Based on Micro-RTSP library:
5 * https://github.com/Tomp0801/Micro-RTSP-Audio
6 * https://github.com/geeksville/Micro-RTSP
7 *
8 */
9
10#pragma once
11
12#include <stdio.h>
13
14#ifdef __linux__
16#else
18#endif
21#include "IAudioSource.h"
22#include "RTSPPlatform.h"
23
24namespace audio_tools {
25
49template <typename Platform>
51 public:
64 LOGD("Creating RTSP Audio streamer base");
67
69 m_Timestamp = 0;
70 m_SendIdx = 0;
71
72 m_RtpSocket = Platform::NULL_UDP_SOCKET;
73 m_RtcpSocket = Platform::NULL_UDP_SOCKET;
74
75 m_prevMsec = 0;
76
77 m_udpRefCount = 0;
78
79 m_useTcpInterleaved = false;
80 m_RtspTcpSocket = Platform::NULL_TCP_SOCKET;
83
85 }
86
104
114 // mRtpBuf is automatically managed by Vector
115 }
116
130 virtual void setAudioSource(IAudioSource *source) {
131 m_audioSource = source;
133 LOGI("RTSP Audio streamer created. Fragment size: %i bytes",
135 }
136
151 LOGI("initAudioSource");
152 if (getAudioSource() == nullptr) {
153 LOGE("audio_source is null");
154 return false;
155 }
158 m_fragmentSize = fmt.fragmentSize();
159 m_timer_period_us = fmt.timerPeriodUs();
160 LOGI("m_fragmentSize (bytes): %d", m_fragmentSize);
161 return true;
162 }
163
185
186 m_SequenceNumber = random(65536);
187
188 if (m_udpRefCount != 0) {
190 return true;
191 }
192
193 for (u_short P = 6970; P < 0xFFFE; P += 2) {
194 m_RtpSocket = Platform::createUdpSocket(P);
195 if (m_RtpSocket) { // Rtp socket was bound successfully. Lets try to bind
196 // the consecutive Rtsp socket
197 m_RtcpSocket = Platform::createUdpSocket(P + 1);
198 if (m_RtcpSocket) {
199 m_RtpServerPort = P;
200 m_RtcpServerPort = P + 1;
201 break;
202 } else {
203 Platform::closeUdpSocket(m_RtpSocket);
204 Platform::closeUdpSocket(m_RtcpSocket);
205 };
206 }
207 };
209
210 LOGI("RTP Streamer set up with client IP %s and client Port %i",
211 m_ClientIP.toString().c_str(), m_ClientPort);
212
213 // If client IP is unknown (0.0.0.0), try to learn it from an inbound UDP packet
215
216 return true;
217 }
218
230 void initTcpInterleavedTransport(typename Platform::TcpClientType *tcpSock,
231 int rtpChannel, int rtcpChannel) {
235 m_useTcpInterleaved = true;
236 m_SequenceNumber = random(65536);
237 LOGI("Using RTP over RTSP TCP interleaved: ch=%d/%d", rtpChannel,
239 }
240
254 if (m_udpRefCount == 0) {
255 m_RtpServerPort = 0;
257 Platform::closeUdpSocket(m_RtpSocket);
258 Platform::closeUdpSocket(m_RtcpSocket);
259
260 m_RtpSocket = Platform::NULL_UDP_SOCKET;
261 m_RtcpSocket = Platform::NULL_UDP_SOCKET;
262 }
263 }
264
286 // check buffer
287 if (mRtpBuf.size() == 0) {
288 LOGE("mRtpBuf is empty");
289 return -1;
290 }
291
292 // append data to header
293 if (m_audioSource == nullptr) {
294 LOGE("No audio source provided");
295 return -1;
296 }
297
298 // unsigned char * dataBuf = &mRtpBuf[m_fragmentSize];
300 LOGE(
301 "STREAMIN_BUFFER_SIZE too small for the sampling rate: increase to "
302 "%d",
304 return -1;
305 }
306
307 // Generic path (PCM, G711, etc.)
310
311 unsigned char *dataBuf = &mRtpBuf[HEADER_SIZE];
315
317 if (toRead > maxPayload) {
318 LOGW("Fragment exceeds payload capacity (%d > %d); clamping", toRead, maxPayload);
320 }
322 LOGI("Read %d bytes from audio source", bytesRead);
324
325 // Compute samples sent for timestamp increment
329 return bytesNet;
330 }
331
344 virtual void start() {
345 LOGI("Starting audio source (base)");
346
347 if (mRtpBuf.size() == 0) {
349 }
350
351 if (m_audioSource != nullptr) {
354 LOGI("Audio source started - ready for manual streaming");
355 } else {
356 LOGE("No streaming source");
357 }
358 }
359
372 virtual void stop() {
373 LOGI("Stopping audio source (base)");
374
375 if (m_audioSource != nullptr) {
377 }
378
379 LOGI("Audio source stopped");
380 }
381
388
395
402
409
415 uint32_t getTimerPeriodMs() const { return m_timer_period_us / 1000; }
416
422
428
432 uint32_t currentSsrc() const { return m_Ssrc; }
433
446 if (m_audioSource == nullptr) {
447 return false;
448 }
449
451 if (newPeriod != m_timer_period_us && newPeriod > 0) {
452 LOGI("Timer period changed from %u us to %u us",
453 (unsigned)m_timer_period_us, (unsigned)newPeriod);
455 return true;
456 }
457 return false;
458 }
459
477 static void timerCallback(void *audioStreamerObj) {
478 LOGD("timerCallback");
479 if (audioStreamerObj == nullptr) {
480 LOGE("audioStreamerObj is null");
481 return;
482 };
485 unsigned long start, stop;
486
487 start = micros();
488
489 int bytes = streamer->sendRtpPacketDirect();
490
491 if (bytes < 0) {
492 LOGW("Direct sending of RTP stream failed");
493 } else if (bytes > 0) {
495 streamer->m_Timestamp += inc;
496 LOGD("%i samples (ts inc) sent; timestamp: %u", inc,
497 (unsigned)streamer->m_Timestamp);
498 }
499
500 stop = micros();
501 if (stop - start > streamer->m_timer_period_us) {
502 LOGW("RTP Stream can't keep up (took %lu us, %d is max)!", stop - start,
503 streamer->m_timer_period_us);
504 }
505 }
506
507 protected:
508 const int STREAMING_BUFFER_SIZE = 1024 * 3;
510
512 int m_fragmentSize = 0; // changed from samples to bytes !
513 int m_timer_period_us = 20000;
514 const int HEADER_SIZE = 12; // size of the RTP header
516 false; // Flag for dynamic timer restart
517
518 typename Platform::UdpSocketType
519 *m_RtpSocket; // RTP socket for streaming RTP packets to client
520 typename Platform::UdpSocketType
521 *m_RtcpSocket; // RTCP socket for sending/receiving RTCP packages
522
523 uint16_t m_RtpServerPort; // RTP sender port on server
524 uint16_t m_RtcpServerPort; // RTCP sender port on server
525
529
533
535
536 // TCP interleaved transport
538 typename Platform::TcpClientType *m_RtspTcpSocket;
541
544 uint32_t m_Ssrc = 0x13F97E67; // default fixed SSRC
545 // MP3 packetization carry buffer to ensure whole-frame packets
548
562 // Prefer exact samples sent (set by sender) if available
563 int samples = 0;
564 if (m_lastSamplesSent > 0) {
565 samples = m_lastSamplesSent;
566 } else if (m_audioSource) {
568 } else {
569 samples = bytesSent / 2; // crude fallback
570 }
571 return (uint32_t)samples;
572 }
573
574 inline void buildRtpHeader() {
575 mRtpBuf[0] = 0x80; // V=2
576 mRtpBuf[1] = (uint8_t)(m_payloadType & 0x7F);
577 if (m_payloadType == 14) {
578 // Set Marker bit on each complete MP3 frame packet
579 mRtpBuf[1] |= 0x80;
580 }
581 mRtpBuf[2] = (uint8_t)((m_SequenceNumber >> 8) & 0xFF);
582 mRtpBuf[3] = (uint8_t)(m_SequenceNumber & 0xFF);
583 mRtpBuf[4] = (uint8_t)((m_Timestamp >> 24) & 0xFF);
584 mRtpBuf[5] = (uint8_t)((m_Timestamp >> 16) & 0xFF);
585 mRtpBuf[6] = (uint8_t)((m_Timestamp >> 8) & 0xFF);
586 mRtpBuf[7] = (uint8_t)(m_Timestamp & 0xFF);
587 // SSRC
588 mRtpBuf[8] = (uint8_t)((m_Ssrc >> 24) & 0xFF);
589 mRtpBuf[9] = (uint8_t)((m_Ssrc >> 16) & 0xFF);
590 mRtpBuf[10] = (uint8_t)((m_Ssrc >> 8) & 0xFF);
591 mRtpBuf[11] = (uint8_t)(m_Ssrc & 0xFF);
592 }
593
594 inline void sendOut(uint16_t totalLen) {
595 if (m_useTcpInterleaved && m_RtspTcpSocket != Platform::NULL_TCP_SOCKET) {
596 LOGD("Sending TCP: %d", totalLen);
597 uint8_t hdr[4];
598 hdr[0] = 0x24; // '$'
599 hdr[1] = (uint8_t)m_TcpRtpChannel;
600 hdr[2] = (uint8_t)((totalLen >> 8) & 0xFF);
601 hdr[3] = (uint8_t)(totalLen & 0xFF);
602 Platform::sendSocket(m_RtspTcpSocket, hdr, sizeof(hdr));
603 Platform::sendSocket(m_RtspTcpSocket, mRtpBuf.data(), totalLen);
604 } else {
605 // If client IP is still unknown, attempt to learn it just-in-time
607 LOGI("Sending UDP: %d bytes (to %s:%d)", totalLen,
608 m_ClientIP.toString().c_str(), m_ClientPort);
609 Platform::sendUdpSocket(m_RtpSocket, mRtpBuf.data(), totalLen, m_ClientIP,
611 }
612 }
613
615 if (m_ClientIP == IPAddress(0, 0, 0, 0) && m_RtpSocket) {
616 int avail = m_RtpSocket->parsePacket();
617 if (avail > 0) {
618 IPAddress learnedIp = m_RtpSocket->remoteIP();
619 uint16_t learnedPort = m_RtpSocket->remotePort();
620 if (learnedIp != IPAddress(0, 0, 0, 0)) {
623 LOGI("RTP learned client via UDP: %s:%u",
624 m_ClientIP.toString().c_str(), (unsigned)m_ClientPort);
625 }
626 } else if (warnIfNone) {
627 LOGW("Client IP unknown (0.0.0.0) and no inbound UDP yet");
628 }
629 }
630 }
631};
632
649template <typename Platform>
650class RTSPAudioStreamer : public RTSPAudioStreamerBase<Platform> {
651 public:
662 LOGD("Creating RTSP Audio streamer with timer");
663
664 // Setup timer callback for RTP streaming
666
667 // CRITICAL FIX: Force timer to run in task context, not ISR context
668 // ISR context (ESP_TIMER_ISR) has severe limitations that cause
669 // LoadProhibited crashes Task context (ESP_TIMER_TASK) allows full object
670 // access and FreeRTOS calls
672 true); // true = use TimerCallbackInThread (ESP_TIMER_TASK)
673 LOGI("RTSPAudioStreamer: Timer set to safe task mode (ESP_TIMER_TASK)");
674 }
675
687 this->setAudioSource(&source);
688 }
689
703 void start() override {
704 LOGI("Starting RTP Stream with timer");
705
706 // Call base class start to initialize audio source and buffer
708
709 if (this->m_audioSource != nullptr) {
710 // Start timer with period in microseconds using specialized callback
713 LOGE("Could not start timer");
714 }
715 LOGI("timer: %u us", (unsigned)this->m_timer_period_us);
716#ifdef ESP32
717 LOGI("Free heap size: %i KB", esp_get_free_heap_size() / 1000);
718#endif
719 }
720 }
721
730 void updateTimer() {
731 if (this->checkTimerPeriodChange()) {
732 LOGI("Updating timer period to %u us", (unsigned)this->m_timer_period_us);
733 rtpTimer.begin(this->m_timer_period_us, audio_tools::US);
734 }
735 }
736
749 void stop() override {
750 LOGI("Stopping RTP Stream with timer");
751
752 // Stop timer first to prevent callbacks during cleanup
753 rtpTimer.end();
754
755 // Add small delay to ensure any running callbacks complete
756 delay(50);
757
758 // Call base class stop to stop audio source
760
761 LOGI("RTP Stream stopped - ready for restart");
762 }
763
764 protected:
766};
767
768
776template <typename Platform>
778 public:
780 : RTSPAudioStreamerBase<Platform>(), m_throttled(throttled) {
781 m_lastSendUs = 0;
782 m_fixed_delay_ms = 1;
783 m_throttle_interval = 50;
784 m_send_counter = 0;
785 m_last_throttle_us = 0;
786 }
788 : RTSPAudioStreamerBase<Platform>(source), m_throttled(throttled) {
789 m_lastSendUs = 0;
790 m_fixed_delay_ms = 1;
791 m_throttle_interval = 50;
792 m_send_counter = 0;
793 m_last_throttle_us = 0;
794 }
795
796 void setThrottled(bool isThrottled) { m_throttled = isThrottled; }
797 void setFixedDelayMs(uint32_t delayMs) { m_fixed_delay_ms = delayMs; m_throttled = false; }
798 void setThrottleInterval(uint32_t interval) { m_throttle_interval = interval; }
799
800 void start() override {
802 m_lastSendUs = micros();
803 m_send_counter = 0;
804 m_last_throttle_us = micros();
805 }
806
807 void stop() override {
809 }
810
814 void doLoop() {
815 unsigned long nowUs = micros();
816 if (nowUs - m_lastSendUs >= this->getTimerPeriodUs()) {
818 m_lastSendUs = nowUs;
819 applyThrottling(nowUs);
820 }
821 }
822
823 private:
824 void applyThrottling(unsigned long iterationStartUs) {
825 ++m_send_counter;
826 delay(m_fixed_delay_ms);
827 if (m_throttled && m_throttle_interval > 0) {
828 if (this->checkTimerPeriodChange()) {
829 m_send_counter = 0;
830 m_last_throttle_us = iterationStartUs;
831 return;
832 }
833 if (m_send_counter >= m_throttle_interval) {
834 uint64_t expectedUs = (uint64_t)m_throttle_interval * (uint64_t)this->getTimerPeriodUs();
835 unsigned long nowUs = micros();
836 uint64_t actualUs = (uint64_t)(nowUs - m_last_throttle_us);
837 if (actualUs < expectedUs) {
839 if (remainingUs >= 1000) delay(remainingUs / 1000);
840 uint32_t remUs = remainingUs % 1000;
841 if (remUs > 0) delayMicroseconds(remUs);
842 }
843 m_send_counter = 0;
844 m_last_throttle_us = micros();
845 }
846 }
847 }
848
849 unsigned long m_lastSendUs;
850 bool m_throttled;
851 uint16_t m_fixed_delay_ms;
852 uint32_t m_throttle_interval;
853 uint32_t m_send_counter;
854 unsigned long m_last_throttle_us;
855};
856
857
858} // namespace audio_tools
#define LOGW(...)
Definition AudioLoggerIDF.h:29
#define LOGI(...)
Definition AudioLoggerIDF.h:28
#define LOGD(...)
Definition AudioLoggerIDF.h:27
#define LOGE(...)
Definition AudioLoggerIDF.h:30
Audio Source Interface - Contract for Audio Data Providers.
Definition IAudioSource.h:19
virtual RTSPFormat & getFormat()=0
Get the audio format configuration.
virtual void start()
Initialize audio source for streaming.
Definition IAudioSource.h:65
virtual int readBytes(void *dest, int maxSamples)=0
Read audio data into provided buffer.
virtual void stop()
Cleanup audio source after streaming.
Definition IAudioSource.h:78
RTSPAudioStreamerBase - Core RTP Audio Streaming Engine.
Definition RTSPAudioStreamer.h:50
volatile bool m_timer_restart_needed
Definition RTSPAudioStreamer.h:515
bool initUdpTransport(IPAddress aClientIP, uint16_t aClientPort)
Initialize UDP transport for RTP streaming.
Definition RTSPAudioStreamer.h:182
uint32_t m_prevMsec
Definition RTSPAudioStreamer.h:532
IAudioSource * getAudioSource()
Get the configured audio source.
Definition RTSPAudioStreamer.h:401
audio_tools::Vector< uint8_t > mMp3Carry
Definition RTSPAudioStreamer.h:546
int m_SendIdx
Definition RTSPAudioStreamer.h:528
Platform::UdpSocketType * m_RtcpSocket
Definition RTSPAudioStreamer.h:521
Platform::TcpClientType * m_RtspTcpSocket
Definition RTSPAudioStreamer.h:538
const int HEADER_SIZE
Definition RTSPAudioStreamer.h:514
int m_fragmentSize
Definition RTSPAudioStreamer.h:512
void tryLearnClientFromUdp(bool warnIfNone)
Definition RTSPAudioStreamer.h:614
void buildRtpHeader()
Definition RTSPAudioStreamer.h:574
Platform::UdpSocketType * m_RtpSocket
Definition RTSPAudioStreamer.h:519
int m_timer_period_us
Definition RTSPAudioStreamer.h:513
virtual void start()
Start audio source (base implementation)
Definition RTSPAudioStreamer.h:344
RTSPAudioStreamerBase()
Default constructor for RTSPAudioStreamerBase.
Definition RTSPAudioStreamer.h:63
bool initAudioSource()
Initialize audio source configuration.
Definition RTSPAudioStreamer.h:150
uint32_t currentRtpTimestamp() const
Get current RTP timestamp value that will be used in the next packet.
Definition RTSPAudioStreamer.h:427
virtual ~RTSPAudioStreamerBase()
Destructor.
Definition RTSPAudioStreamer.h:113
uint32_t computeTimestampIncrement(int bytesSent)
Compute RTP timestamp increment based on samples sent.
Definition RTSPAudioStreamer.h:561
virtual void setAudioSource(IAudioSource *source)
Configure the audio source for streaming.
Definition RTSPAudioStreamer.h:130
uint16_t m_RtpServerPort
Definition RTSPAudioStreamer.h:523
int m_TcpRtpChannel
Definition RTSPAudioStreamer.h:539
static void timerCallback(void *audioStreamerObj)
Static timer callback function for periodic RTP streaming.
Definition RTSPAudioStreamer.h:477
int m_lastSamplesSent
Definition RTSPAudioStreamer.h:543
IAudioSource * m_audioSource
Definition RTSPAudioStreamer.h:511
uint32_t m_Timestamp
Definition RTSPAudioStreamer.h:527
uint32_t currentSsrc() const
Get current SSRC used in RTP header.
Definition RTSPAudioStreamer.h:432
uint32_t getTimerPeriodMs() const
Get the timer period in milliseconds.
Definition RTSPAudioStreamer.h:415
uint16_t currentSeq() const
Get current RTP sequence number that will be used in the next packet.
Definition RTSPAudioStreamer.h:421
int mMp3CarryLen
Definition RTSPAudioStreamer.h:547
u_short getRtcpServerPort()
Get the RTCP server port number.
Definition RTSPAudioStreamer.h:394
const int STREAMING_BUFFER_SIZE
Definition RTSPAudioStreamer.h:508
void sendOut(uint16_t totalLen)
Definition RTSPAudioStreamer.h:594
IPAddress m_ClientIP
Definition RTSPAudioStreamer.h:530
u_short m_SequenceNumber
Definition RTSPAudioStreamer.h:526
int m_udpRefCount
Definition RTSPAudioStreamer.h:534
uint16_t m_RtcpServerPort
Definition RTSPAudioStreamer.h:524
int m_TcpRtcpChannel
Definition RTSPAudioStreamer.h:540
bool m_useTcpInterleaved
Definition RTSPAudioStreamer.h:537
u_short getRtpServerPort()
Get the RTP server port number.
Definition RTSPAudioStreamer.h:387
int sendRtpPacketDirect()
Send a single RTP packet with audio data.
Definition RTSPAudioStreamer.h:285
RTSPAudioStreamerBase(IAudioSource &source)
Constructor with audio source.
Definition RTSPAudioStreamer.h:101
int m_payloadType
Definition RTSPAudioStreamer.h:542
audio_tools::Vector< uint8_t > mRtpBuf
Definition RTSPAudioStreamer.h:509
void initTcpInterleavedTransport(typename Platform::TcpClientType *tcpSock, int rtpChannel, int rtcpChannel)
Initialize TCP interleaved transport for RTP over RTSP.
Definition RTSPAudioStreamer.h:230
uint32_t getTimerPeriodUs() const
Get the timer period in microseconds.
Definition RTSPAudioStreamer.h:408
virtual void stop()
Stop audio source (base implementation)
Definition RTSPAudioStreamer.h:372
void releaseUdpTransport(void)
Release UDP transport resources.
Definition RTSPAudioStreamer.h:252
bool checkTimerPeriodChange()
Check if timer period has changed and update if necessary.
Definition RTSPAudioStreamer.h:445
uint32_t m_Ssrc
Definition RTSPAudioStreamer.h:544
uint16_t m_ClientPort
Definition RTSPAudioStreamer.h:531
RTSPAudioStreamer - Timer-driven RTP Audio Streaming Engine.
Definition RTSPAudioStreamer.h:650
void updateTimer()
Update timer period if audio format has changed.
Definition RTSPAudioStreamer.h:730
void start() override
Start timer-driven RTP streaming.
Definition RTSPAudioStreamer.h:703
RTSPAudioStreamer(IAudioSource &source)
Constructor with audio source.
Definition RTSPAudioStreamer.h:686
RTSPAudioStreamer()
Default constructor for RTSPAudioStreamer.
Definition RTSPAudioStreamer.h:661
void stop() override
Stop timer-driven RTP streaming.
Definition RTSPAudioStreamer.h:749
audio_tools::TimerAlarmRepeating rtpTimer
Definition RTSPAudioStreamer.h:765
RTSPAudioStreamerTaskless - Manual RTP Audio Streaming Engine (no Task)
Definition RTSPAudioStreamer.h:777
void setFixedDelayMs(uint32_t delayMs)
Definition RTSPAudioStreamer.h:797
void setThrottled(bool isThrottled)
Definition RTSPAudioStreamer.h:796
void doLoop()
Call this in your Arduino loop() to send RTP packets at the correct rate.
Definition RTSPAudioStreamer.h:814
void start() override
Start audio source (base implementation)
Definition RTSPAudioStreamer.h:800
void setThrottleInterval(uint32_t interval)
Definition RTSPAudioStreamer.h:798
void stop() override
Stop audio source (base implementation)
Definition RTSPAudioStreamer.h:807
RTSPAudioStreamerTaskless(IAudioSource &source, bool throttled=true)
Definition RTSPAudioStreamer.h:787
RTSPAudioStreamerTaskless(bool throttled=true)
Definition RTSPAudioStreamer.h:779
Audio Format Definition - Base class for RTSP audio formats.
Definition RTSPFormat.h:48
virtual int timerPeriodUs()
Timer period in microseconds.
Definition RTSPFormat.h:86
virtual int readHeader(uint8_t *data)
Optional header: e.g. rfc2250.
Definition RTSPFormat.h:92
virtual int convert(void *data, int sampleCount)
Definition RTSPFormat.h:55
virtual int timestampIncrement()
Fragment size in samples.
Definition RTSPFormat.h:75
virtual int rtpPayloadType()
default dynamic
Definition RTSPFormat.h:89
Common Interface definition for TimerAlarmRepeating.
Definition AudioTimer.h:29
void setIsSave(bool is_save)
Definition AudioTimer.h:66
void setCallbackParameter(void *obj)
Definition AudioTimer.h:56
bool begin(repeating_timer_callback_t callback_f, uint32_t time, TimeUnit unit=MS)
Definition AudioTimer.h:43
bool end()
Definition AudioTimer.h:51
Vector implementation which provides the most important methods as defined by std::vector....
Definition Vector.h:21
bool resize(int newSize, T value)
Definition Vector.h:266
T * data()
Definition Vector.h:316
int size()
Definition Vector.h:178
@ US
Definition AudioTypes.h:48
Generic Implementation of sound input and output for desktop environments using portaudio.
Definition AudioCodecsBase.h:10
void delayMicroseconds(unsigned int us)
Definition Time.h:28
void delay(unsigned long ms)
Definition Time.h:23
unsigned long micros(void)
Definition Time.h:33
size_t writeData(Print *p_out, T *data, int samples, int maxSamples=512)
Definition AudioTypes.h:512