How to Properly Write Received UDP Audio Data to ALSA with C++ on Raspberry Pi
Asked Answered
B

1

8

I have 2 Raspberry Pis and 1 of them transmits UDP frames of Audio data to the other Raspberry Pi. The UDP Packets received are 160 Bytes each. The transmitting Raspberry Pi is sending 8KHz 8-bit Mono samples. The receiving Raspberry Pi uses Qt 5.4.0 with QUDPSocket and tries to play the received data with ALSA. The code is below. Each time the "readyRead " signal is fired when bytes arrive on the receiving Raspberry Pi, the Buffer is written to ALSA. I have very Choppy and Glitchy sound coming out of the headphone Jack on the Receiving Pi - but it is recognizable. So it is working but sounds Terrible.

  1. Is there anything glaringly wrong with my configuration below for ALSA?
  2. How should I approach writing the received UDP packets to ALSA with snd_pcm_writei?

Thank you for any suggestions.

UdpReceiver::UdpReceiver(QObject *parent) : QObject(parent)
{

    // Debug
    qDebug() << "Setting up a UDP Socket...";

    // Create a socket
    m_Socket = new QUdpSocket(this);

    // Bind to the 2616 port
    bool didBind = m_Socket->bind(QHostAddress::Any, 0x2616);
    if ( !didBind ) {
        qDebug() << "Error - could not bind to UDP Port!";
    }
    else {
        qDebug() << "Success binding to port 0x2616!";
    }

    // Get notified that data is incoming to the socket
    connect(m_Socket, SIGNAL(readyRead()), this, SLOT(readyRead()));

    // Init to Zero
    m_NumberUDPPacketsReceived = 0;

}

void UdpReceiver::readyRead() {

    // When data comes in
    QByteArray buffer;
    buffer.resize(m_Socket->pendingDatagramSize());

    QHostAddress sender;
    quint16 senderPort;

    // Cap buffer size
    int lenToRead = buffer.size();
    if ( buffer.size() > NOMINAL_AUDIO_BUFFER_SIZE ) {
        lenToRead = NOMINAL_AUDIO_BUFFER_SIZE;
    }

    // Read the data from the UDP Port
    m_Socket->readDatagram(buffer.data(), lenToRead,
                         &sender, &senderPort);

    // Kick off audio playback
    if ( m_NumberUDPPacketsReceived == 0 ) {

        qDebug() << "Received Data - Setting up ALSA Now....";

        // Error handling
        int err;

        // Device to Write to
        char *snd_device_out  = "hw:0,0";

        if ((err = snd_pcm_open (&playback_handle, snd_device_out, SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
            fprintf (stderr, "cannot open audio device %s (%s)\n",
                    snd_device_out,
                    snd_strerror (err));
            exit (1);
        }

        if ((err = snd_pcm_hw_params_malloc (&hw_params)) < 0) {
            fprintf (stderr, "cannot allocate hardware parameter structure (%s)\n",
                     snd_strerror (err));
            exit (1);
        }

        if ((err = snd_pcm_hw_params_any (playback_handle, hw_params)) < 0) {
            fprintf (stderr, "cannot initialize hardware parameter structure (%s)\n",
                     snd_strerror (err));
            exit (1);
        }

        if ((err = snd_pcm_hw_params_set_access (playback_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
            fprintf (stderr, "cannot set access type (%s)\n",
                     snd_strerror (err));
            exit (1);
        }

        if ((err = snd_pcm_hw_params_set_format (playback_handle, hw_params, SND_PCM_FORMAT_U8)) < 0) { // Unsigned 8 bit
            fprintf (stderr, "cannot set sample format (%s)\n",
                     snd_strerror (err));
            exit (1);

        }

        uint sample_rate = 8000;
        if ((err = snd_pcm_hw_params_set_rate (playback_handle, hw_params, sample_rate, 0)) < 0) { // 8 KHz
            fprintf (stderr, "cannot set sample rate (%s)\n",
                     snd_strerror (err));
            exit (1);
        }

        if ((err = snd_pcm_hw_params_set_channels (playback_handle, hw_params, 1)) < 0) { // 1 Channel Mono
            fprintf (stderr, "cannot set channel count (%s)\n",
                     snd_strerror (err));
            exit (1);
        }

        if ((err = snd_pcm_hw_params (playback_handle, hw_params)) < 0) {
            fprintf (stderr, "cannot set parameters (%s)\n",
                     snd_strerror (err));
            exit (1);
        }

        snd_pcm_hw_params_free (hw_params);

        // Flush handle prepare for playback
        snd_pcm_drop(playback_handle);

        if ((err = snd_pcm_prepare (playback_handle)) < 0) {
            fprintf (stderr, "cannot prepare audio interface for use (%s)\n",
                     snd_strerror (err));
            exit (1);
        }

        qDebug() << "Done Setting up ALSA....";

    }

    // Grab the buffer
    m_Buffer = buffer.data();

    // Write the data to the ALSA device
    int error;
    if ((error = snd_pcm_writei (playback_handle, m_Buffer, NOMINAL_AUDIO_BUFFER_SIZE)) != NOMINAL_AUDIO_BUFFER_SIZE) {
        fprintf (stderr, "write to audio interface failed (%s)\n",
                 snd_strerror (error));
        exit (1);
    }

    // Count up
    m_NumberUDPPacketsReceived++;

}
Bastinado answered 8/2, 2015 at 23:41 Comment(6)
Can you play sounds better when using another program? The jack sound stuff (but not HDMI) of some(?) models just is terrible itself, independent from the software.Kallick
Thanks for the input. But if I use ALSA utilities' "aplay" program then the Audio output sounds great. I'm pretty sure this is a configuration problem.Bastinado
You still are not setting buffer/period sizes.Hereford
You should process the incoming datagrams with a while inside the SLOT, as done for instance here to ensure that all the available data is processed with the current signal.Libreville
Set up your audio buffer to gather 1 full second and then play in chunks of 1 second buffers. See if quality gets better (it should). Change the design to have a listening thread that reads data and fills a buffer while another thread plays it with a delay of, let's say, 0.1 seconds by continuously feeding data. You have 8000 samples per second. 5 UDP messages means 800 samples, which is 0.1 seconds. So you can start with this. By feeding 160 samples in a run you play 0.02 seconds at a time. Then you stop playing, and on the same thread you read another UDP message.Inherited
In your readyRead slot I suggest also using while loop with QUdpSocket::hasPendingDatagrams() as the condition. Sometimes a single readyRead might have multiple datagrams ready. And dont cap the size to read. If you have write size restriction on the other end, read all but chop it separately to smaller portion before writing.Backrest
E
1

I'm not understanding the "capping the buffer size" part of the code. If the incoming data is larger than your arbitrary NOMINAL_AUDIO_BUFFER_SIZE then you're chopping off that incoming data. Have you tried removing that bit of code?

Engagement answered 9/6, 2015 at 21:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.