send and receive RTP stream using Teensy 4.1

ajlapp

Member
We're looking to replace an RPI4 with a teensy 4.1 for some audio tasks.

So far I've been able to get the audio adapter working and I have microphone input working.

Is there any straightforward example of project that could help me make and send that data as an RTP stream?

On the RPI4 we use this command to send audio to another device on the network:
ffmpeg -f alsa -i plughw:2 -acodec pcm_s16be -ar 44100 -f rtp rtp://192.168.144.20:55555

I'm naive to networking stuff. I'm making some headway but I could use help.

Thanks in advance.
 
We're looking to replace an RPI4 with a teensy 4.1 for some audio tasks.

So far I've been able to get the audio adapter working and I have microphone input working.

Is there any straightforward example of project that could help me make and send that data as an RTP stream?

On the RPI4 we use this command to send audio to another device on the network:
ffmpeg -f alsa -i plughw:2 -acodec pcm_s16be -ar 44100 -f rtp rtp://192.168.144.20:55555

I'm naive to networking stuff. I'm making some headway but I could use help.

Thanks in advance.
We're looking to replace an RPI4 with a teensy 4.1 for some audio tasks.

So far I've been able to get the audio adapter working and I have microphone input working.

Is there any straightforward example of project that could help me make and send that data as an RTP stream?

On the RPI4 we use this command to send audio to another device on the network:
ffmpeg -f alsa -i plughw:2 -acodec pcm_s16be -ar 44100 -f rtp rtp://192.168.144.20:55555

I'm naive to networking stuff. I'm making some headway but I could use help.

Thanks in advance.
 
Okay...some success. I've been able to make an RTP stream by processing microphone data and sending it out as an RTP stream. It is delayed but the quality is good.

Now I'm trying to opposite....receiving an RTP stream and playing out the headphone speaker jack.

I have "something" coming through. I can see packets and I get noises that correspond with tapping on the mic but something is clearly still wrong. I'm sending the data using rtpmic app on an android device that is connected to my network. I've also tried sending a stream using ffmpeg from an RPi4....I've validated the streams using other devices so I know the audio is good.

Here is the teensy code:

Code:
#include <Audio.h>
#include <QNEthernet.h>
#include <Wire.h>

using namespace qindesign::network;

// Define Teensy Audio components
AudioPlayQueue playQueue;
AudioOutputI2S i2sOutput;
AudioConnection patchCord1(playQueue, 0, i2sOutput, 0);
AudioConnection patchCord2(playQueue, 0, i2sOutput, 1);
AudioControlSGTL5000 audioControl;

// Network configuration
IPAddress staticIP(192, 168, 144, 32);  // Teensy's static IP
IPAddress subnet(255, 255, 255, 0);     // Subnet mask
IPAddress gateway(192, 168, 144, 12);    // Gateway IP
const int localPort = 55555;            // Teensy's listening port for RTP
const int rtpHeaderSize = 12;           // RTP header size for PCM

EthernetUDP udp;

void setup() {
  AudioMemory(12);
  Serial.begin(115200);
  while (!Serial);  // Wait for Serial Monitor to connect

  // Initialize Ethernet with a static IP
  Ethernet.begin(staticIP, subnet, gateway);
  udp.begin(localPort);

  // Initialize audio control (SGTL5000)
  audioControl.enable();
  audioControl.volume(0.5);

  Serial.println("Setup complete, ready to receive RTP audio");
}

void loop() {
  int packetSize = udp.parsePacket();

  // Process only packets with expected size
  if (packetSize > rtpHeaderSize) {
    // Read packet data
    uint8_t packetBuffer[packetSize];
    int bytesRead = udp.read(packetBuffer, packetSize);

    // Debug: Print packet size, IP, Port, and bytes read
    Serial.print("Received packet from IP: ");
    Serial.print(udp.remoteIP());
    Serial.print(" Port: ");
    Serial.print(udp.remotePort());
    Serial.print(" Packet Size: ");
    Serial.print(packetSize);
    Serial.print(" Bytes Read: ");
    Serial.println(bytesRead);

    // Check if bytesRead matches the packetSize
    if (bytesRead != packetSize) {
      Serial.println("Error: Bytes read do not match packet size.");
      return;  // Skip processing if read size is incorrect
    }

    // Check IP and Port (if needed for security)
    IPAddress remoteIP = udp.remoteIP();
    int remotePort = udp.remotePort();
    if (remoteIP != IPAddress(192, 168, 144, 20)) {//|| remotePort != 34855) {
      Serial.println("Warning: Unexpected IP or port");
      return;
    }

    // Prepare to copy audio data, skipping RTP header
    int audioDataSize = (packetSize - rtpHeaderSize) / 2;  // Size in int16 samples
    int16_t audioData[audioDataSize];

    // Extract audio data and print first and last sample for verification
    for (int i = 0; i < audioDataSize; i++) {
      audioData[i] = (packetBuffer[rtpHeaderSize + i * 2] << 8) | (packetBuffer[rtpHeaderSize + i * 2 + 1]);
    }

    // Debug: Print entire buffer in HEX format
    Serial.print("RTP Packet Data (HEX): ");
    for (int i = 0; i < packetSize; i++) {
      if (i % 16 == 0) Serial.println();  // New line every 16 bytes
      Serial.print(packetBuffer[i], HEX);
      Serial.print(" ");
    }
    Serial.println();

    // Print first and last audio samples
    Serial.print("First audio sample: ");
    Serial.println(audioData[0]);
    Serial.print("Last audio sample: ");
    Serial.println(audioData[audioDataSize - 1]);

    // Queue audio data for playback
    if (playQueue.available() > 0) {
      int16_t *queueBuffer = playQueue.getBuffer();
      memcpy(queueBuffer, audioData, audioDataSize * sizeof(int16_t));
      playQueue.playBuffer();
      Serial.println("Audio data queued for playback");
    } else {
      Serial.println("playQueue not available");
    }
  } else {
    Serial.print("Invalid packet size (too small or read error): ");
    Serial.println(packetSize);
  }

  delay(10);  // Allow some time for other processes
}

Here is a sample of the serial data that I'm receiving:

Code:
Received packet from IP: 192.168.144.20 Port: 49889 Packet Size: 892 Bytes Read: 892
RTP Packet Data (HEX):
80 B 7B CC 0 D6 4E F8 0 0 0 1 FF F0 FF F0
FF E8 FF F8 FF E8 FF F0 0 8 FF F0 FF F0 0 0
FF F0 FF E8 FF F0 FF F0 FF F8 FF E0 FF F0 0 0
FF F8 FF F0 FF F8 0 10 FF E8 FF F8 0 8 FF F0
0 0 FF F8 FF F8 0 10 FF E0 FF F8 0 18 FF D8
FF F0 FF F8 FF E8 FF F8 FF E0 FF F8 FF F8 FF E0
FF F8 FF F8 FF F8 0 0 FF E8 0 0 0 0 FF E0
FF F8 FF F8 FF D8 FF F8 FF F8 FF E8 FF F0 FF E8
FF F8 FF F0 FF D8 FF F8 FF E8 FF E8 FF E8 FF E8
FF F0 FF E0 FF E0 FF F8 FF E0 FF F0 FF F8 FF F0
0 0 FF F8 0 0 0 0 0 0 FF F8 0 0 0 10
FF F8 FF F8 0 10 FF F8 0 0 0 8 0 0 0 10
0 0 0 8 0 10 0 0 0 8 0 8 0 0 0 0
0 0 0 0 FF F8 0 0 0 8 0 0 0 8 0 8
0 8 0 8 0 8 0 0 0 0 0 8 FF F8 0 10
FF F8 FF F8 0 10 FF F8 0 0 0 0 0 8 0 0
0 0 0 0 0 8 0 0 0 10 0 8 0 10 0 10
0 18 0 10 0 20 0 18 0 0 0 30 0 10 FF F8
0 28 0 8 0 0 0 28 FF F8 0 18 0 10 FF F0
0 18 0 0 FF F0 0 8 FF F8 0 8 0 0 0 8
0 10 0 0 0 18 0 8 0 0 0 18 0 0 0 0
0 18 0 8 0 0 0 20 0 0 0 0 0 18 FF F8
0 10 0 0 0 0 0 18 0 8 0 10 0 10 0 8
0 18 0 8 0 8 0 20 0 0 0 8 0 10 0 0
0 10 FF F8 0 8 0 18 FF F8 0 8 0 8 0 0
0 0 FF F8 0 10 0 0 FF F8 0 8 0 0 0 0
0 0 FF F8 0 0 FF F0 FF F8 FF F8 FF F0 0 0
FF F8 FF F0 0 0 FF F0 FF F0 0 0 FF E8 FF F0
FF F8 FF F8 FF E0 FF F0 0 0 FF E8 FF E8 0 0
FF E8 FF E8 FF F8 FF E8 FF F8 FF F8 0 0 0 0
0 0 0 8 0 8 0 8 0 10 0 8 0 0 0 10
0 0 0 10 0 8 0 0 0 8 0 8 0 0 0 10
0 10 0 8 0 8 0 0 0 0 0 0 FF F8 0 0
0 0 FF F8 FF F8 FF F8 FF F8 FF F8 FF F8 FF F8
0 0 FF F8 FF F8 FF F8 0 0 0 0 0 0 0 0
0 8 0 8 0 8 0 8 0 8 0 8 0 8 0 10
0 10 0 10 0 10 0 18 0 18 0 18 0 18 0 18
0 18 0 18 0 18 0 18 0 18 0 18 0 20 0 20
0 20 0 20 0 20 0 28 0 20 0 10 0 18 0 18
0 18 0 18 0 18 0 18 0 18 0 18 0 18 0 18
0 10 0 10 0 10 0 10 0 10 0 10 0 10 0 10
0 10 0 10 0 10 0 10 0 8 0 8 0 8 0 8
0 0 0 0 0 0 0 0 0 0 0 0 FF F8 FF F8
FF F8 FF F8 FF F8 FF F8 FF F8 FF F8 FF F8 FF F8
FF F8 FF F8 FF F8 FF F8 FF F8 FF F8 FF F8 FF F8
FF F8 FF F8 FF F8 FF F8 FF F0 FF F0 FF F0 FF F0
FF F0 FF F0 FF F0 FF F0 FF F0 FF F0 FF F8 FF F8
FF F8 FF F8 FF F8 FF F8 FF F8 FF F8 FF F8 FF F8
FF F8 FF F8 FF F8 FF F0 FF F0 FF F0 FF E8 FF E8
FF E8 FF E8 FF E8 FF E8 FF E8 FF E8 FF E8 FF E8
FF E8 FF E0 FF E0 FF E0 FF D8 FF D8 FF D0 FF D0
FF D0 FF D0 FF D0 FF D0 FF D0 FF D0 FF D8 FF D8
FF D8 FF E0 FF E0 FF E0 FF E0 FF E0 FF E0 FF E0
FF E0 FF E0 FF E0 FF E0 FF E0 FF E0 FF E0 FF E0
FF E0 FF E0 FF E0 FF E0 FF E0 FF E0 FF E0 FF E0
FF E8 FF E8 FF E8 FF E8 FF E8 FF E8
First audio sample: -16
Last audio sample: -24
Audio data queued for playback
Received packet from IP: 192.168.144.20 Port: 49889 Packet Size: 892 Bytes Read: 892
RTP Packet Data (HEX):
80 B 7B CD 0 D6 50 B0 0 0 0 1 FF E8 FF E8
FF E8 FF E8 FF E8 FF F0 FF F0 FF F8 FF F8 FF F8
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 8
0 8 0 8 0 8 0 8 0 10 0 10 0 10 0 10
0 8 0 20 0 20 0 10 0 30 0 20 0 20 0 38
0 30 0 28 0 38 0 20 0 30 0 40 0 28 0 38
0 38 0 28 0 38 0 30 0 38 0 38 0 30 0 30
0 38 0 40 0 38 0 30 0 40 0 40 0 38 0 48
0 40 0 30 0 48 0 40 0 38 0 40 0 38 0 48
0 40 0 30 0 30 0 30 0 28 0 28 0 28 0 30
0 28 0 20 0 30 0 28 0 20 0 20 0 20 0 18
0 18 0 20 0 10 0 20 0 10 0 18 0 20 0 0
0 8 0 10 FF F0 0 8 0 0 FF F0 0 8 FF F0
FF F8 0 0 FF F0 FF F0 FF F8 FF E8 FF E8 FF E0
FF E0 FF D8 FF E0 FF E8 FF D8 FF E0 FF E0 FF E0
FF E0 FF E8 FF D0 FF D8 FF E0 FF D8 FF D8 FF D0
FF D8 FF C8 FF D8 FF D0 FF C0 FF E8 FF D0 FF B8
FF F0 FF C0 FF D0 FF E8 FF B8 FF D8 FF D0 FF B0
FF E0 FF C0 FF B8 FF E0 FF B8 FF D8 FF C8 FF D0
FF E8 FF C8 FF E0 FF E0 FF C8 FF E0 FF E0 FF D0
FF E0 FF D8 FF E0 FF F0 FF E0 0 0 FF F8 FF F8
0 10 0 0 0 0 0 10 0 8 0 18 0 20 0 10
0 28 0 28 0 28 0 20 0 28 0 30 0 18 0 28
0 40 0 28 0 30 0 40 0 38 0 48 0 38 0 48
0 48 0 30 0 48 0 40 0 40 0 48 0 38 0 48
0 40 0 40 0 50 0 48 0 50 0 58 0 50 0 60
0 58 0 50 0 70 0 60 0 58 0 68 0 60 0 60
0 68 0 68 0 70 0 60 0 70 0 78 0 70 0 70
0 70 0 78 0 70 0 68 0 68 0 68 0 60 0 68
0 68 0 60 0 60 0 60 0 58 0 58 0 50 0 50
0 48 0 48 0 40 0 38 0 38 0 38 0 38 0 38
0 30 0 38 0 28 0 28 0 28 0 20 0 20 0 18
0 18 0 18 0 18 0 18 0 10 0 10 0 10 0 10
0 10 0 8 0 8 0 0 0 0 0 0 FF F0 FF F0
FF E8 FF E0 FF D8 FF D0 FF D0 FF C8 FF C0 FF C0
FF C0 FF B0 FF B8 FF B0 FF B0 FF B0 FF A8 FF A8
FF A8 FF A0 FF A0 FF A0 FF A0 FF 98 FF 98 FF 98
FF 98 FF 98 FF 90 FF 90 FF 90 FF 90 FF 90 FF 90
FF 90 FF 90 FF 90 FF 90 FF 90 FF 88 FF 88 FF 88
FF 80 FF 80 FF 78 FF 78 FF 78 FF 78 FF 78 FF 70
FF 80 FF 78 FF 80 FF 68 FF 88 FF 80 FF 58 FF 90
FF 70 FF 58 FF 90 FF 60 FF 58 FF 88 FF 48 FF 70
FF 78 FF 50 FF 88 FF 70 FF 78 FF 98 FF 80 FF A0
FF 98 FF A0 FF B0 FF A8 FF C8 FF A8 FF A8 FF D8
FF B8 FF B8 FF D8 FF C8 FF C8 FF E0 FF E8 FF D0
FF D8 FF F8 FF F0 FF E8 0 0 FF F8 FF F8 0 18
FF E8 0 8 0 20 FF F0 0 8 0 20 FF F0 0 10
0 18 0 8 0 30 0 18 0 30 0 48 0 30 0 48
0 50 0 58 0 58 0 48 0 68 0 60 0 58 0 80
0 68 0 78 0 80 0 78 0 98 0 88 0 90 0 A0
0 A8 0 A0 0 A0 0 A0 0 B8 0 98 0 A0 0 C0
0 98 0 A0 0 A0 0 A8 0 98 0 98 0 A0 0 90
0 90 0 A0 0 90 0 98 0 98 0 88 0 90 0 98
0 88 0 88 0 90 0 80 0 80 0 70 0 80 0 70
0 60 0 78 0 68 0 70 0 78 0 68 0 78 0 70
0 68 0 70 0 60 0 58 0 68 0 58
First audio sample: -24
Last audio sample: 88
 
A tip: with QNEthernet, you can access a UDP packet’s data and size directly from the object. You don’t need to read into an external array. Use udp.data() and udp.size() to access these.

A note: VLAs (Variable Length Arrays), where you create an array having the length chosen by a variable, isn’t a great practice. (Dan Saks talks about this more in one of his talks. I love his stuff.)

Update: from a quick search on VLAs:
https://stackoverflow.com/a/21519062
 
Back
Top