Welcome to the new FlexRadio Community! Please review the new Community Rules and other important new Community information on the Message Board.
If you are having a problem, please refer to the product documentation or check the Help Center for known solutions.
Need technical support from FlexRadio? It's as simple as Creating a HelpDesk ticket.

DAX Format / How to play DAX samples in C# - FlexAPI

Cliff - G4PZK
Cliff - G4PZK Member ✭✭
edited January 2020 in SmartSDR API

I'm a competent C# programmer but have little knowledge of playing audio streams. I've written a simple proof of concept app which allows me to discover, connect to and configure my Flex 6300 and 6500 radios.
I have enabled DAX channel 1 for audio output and can see the UDP audio packets with class 0x03e3 on wireshark. I can successful enable/disable them as well as control the radio via my TCP socket. My questions are firstly what is the format of the data after the 0x03e3 classid and secondly does anyone have a simple code example (using NAudio maybe) to play them back? I'm aware that Opus audio is also available but don't want to add the complexity of extra decoding at this time plus the DAX output is lossless and independent of GUI app.
I realise that I could use the FlexLib solution but I'm only using C# as a convenient platform for testing, the final solution may well be in pure C and totally unmanaged.

Answers

  • Cliff - G4PZK
    Cliff - G4PZK Member ✭✭
    edited October 2018
    So a progress report as there's been no feedback.

    In case anybody else has ever fought this, and with thanks to the various old posts in this community support site, I've managed to dump the incoming data into a file having split the data into left and right channels. I can also dump the data as a stereo or two channel image.

    The DAX packets are formatted as below:

    Assuming a UdpClient socket is used in C# and the delivered payload in placed in a byte array rxData then the relevant bytes are used thus:

    // Note that this is basic code
    // get data packet length
    int datalen = 4*((rxData[2] * 256) + rxData[3]);

    // get packet class
    string classID = rxData[14].ToString("X2") + rxData[15].ToString("X2");

    // look for DAX channel 1
    if (classID=="03E3")
    {
              // filter out channels into L and R
              // declare target arrays for saving
              byte[] LChan = new byte[(datalen - 28) / 2];
              byte[] RChan = new byte[(datalen - 28) / 2];

               int loop;
               int pos=0;

                // copy over left  and right channels
                for (loop=0; loop<(datalen-28)/8; loop++)
                {
                      // Left channel
                      LChan[4*loop] = rxData[28 + (8 * loop)];
                      LChan[(4 * loop) + 1] = rxData[29 + (8 * loop)];
                      LChan[(4 * loop) + 2] = rxData[30 + (8 * loop)];
                      LChan[(4 * loop) + 3] = rxData[31 + (8 * loop)];

                     // Right channel
                     RChan[4*loop] = rxData[32 + (8 * loop)];
                     RChan[(4 * loop) + 1] = rxData[33 + (8 * loop)];
                     RChan[(4 * loop) + 2] = rxData[34 + (8 * loop)];
                     RChan[(4 * loop) + 3] = rxData[35 + (8 * loop)];
                }
               // write byte array to disk file using a BinaryWriter object
               // to dump the samples as a two channel stereo file
               // just write all bytes from position 28 to the end of the packet
               // IE rxData[28] to rxData[datalen-1]
    }

    If the resulting file is imported into Audacity as a big endian, mono 32 bit float sampled at 24kHz then the audio can be heard!
    If the audio isn't deinterleaved but simply dumped as one file from position 28 in the rxData array then it can be imported as a stereo 32 bit float file.

    The next stage is to persuade NAudio to play the 'stream'.


  • James Whiteway
    edited October 2018
    Well done! I have devoted a small amount of time ( over the road truck driver) to this but, have had little success. Just not enough time to dive deeper into it.
    Please continue sharing. I know others besides myself are interested in this.
    James
    WD5GWY
  • Cliff - G4PZK
    Cliff - G4PZK Member ✭✭
    edited October 2018
    Thanks for the encouragement James! I have wasted far too much time fighting this but now have a trivial solution to play the audio.
    Firstly a correction to my earlier post. It turns out that the bytes are little endian rather than big endian so it's necessary to swap them around before trying to play them.

    I should have spotted that in the _extensive_ documentation ;-)

    The routine receiving the UDP packets is shown below

    using System;
    using System.Net;
    using System.Net.Sockets;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows.Forms;
    using System.IO;

    namespace FlexDebug
    {
        class FlexUDP
        {
            private UdpClient _udp = null;
            private int _port;
            private FlexDebug _parent;
            private bool _stopUdp = false;

            public bool StopUDP
            {
                get { return _stopUdp; }
                set { _stopUdp = value; }
            }

            public FlexUDP(FlexDebug parent, int port)
            {
                int loop;
                byte temp1;
                byte temp2;
                // save parent
                _parent = parent;
                // save port
                _port = port;
                // create client
                try
                {
                    Task.Run(() =>
                    {
                        // create UDP client
                        _udp = new UdpClient(_port);
                        IPEndPoint remoteIpEndPoint = new IPEndPoint(IPAddress.Any, 0);
                        while (_stopUdp == false)
                        {
                            byte[] rxData = _udp.Receive(ref remoteIpEndPoint);
                            int datalen = 4 * ((rxData[2] * 256) + rxData[3]);
                            int samples = (datalen - 28) / 4;
                            string classID = rxData[14].ToString("X2") + rxData[15].ToString("X2");
                            // check for 03e3 (DAX channel 1)
                            if (classID == "03E3")
                            {
                                // convert from little endian to big endian
                                for (loop=0; loop<samples; loop++)
                                {
                                    temp1 = rxData[(loop * 4) + 28];
                                    temp2 = rxData[(loop * 4) + 29];
                                    rxData[(loop * 4) + 28] = rxData[(loop * 4) + 31];
                                    rxData[(loop * 4) + 29] = rxData[(loop * 4) + 30];
                                    rxData[(loop * 4) + 30] = temp2;
                                    rxData[(loop * 4) + 31] = temp1;
                                }
                                _parent.ProcessSound(rxData);
                            }
                        }
                    });
                }
                catch (Exception ex)
                {
                    MessageBox.Show("Error: " + ex.Message);
                }
            }
        }
    }

    The following code will actually play the samples. Note that it runs in the main window thread. When I create the UDP class I pass the parent to it.

    using NAudio.CoreAudioApi;
    using NAudio.Wave;



            private bool _streamActive = false;
            private BufferedWaveProvider bufferedWaveProvider = null;
            private WaveOut waveOut = null;
            private WaveFormat waveFormat=null;

            public void ProcessSound(byte[] rxData)
            {
                // get data length
                int datalen = rxData.Length;
                // check to activate player
                if (_streamActive == true)
                {
                    // add samples to buffer
                    bufferedWaveProvider.AddSamples(rxData, 28, datalen - 28);
                    return;
                }
                // set up format
                waveFormat = WaveFormat.CreateIeeeFloatWaveFormat(24000, 2);
                // create buffer to allow samples to be added
                bufferedWaveProvider = new BufferedWaveProvider(waveFormat);
                // add samples to buffer
                bufferedWaveProvider.AddSamples(rxData, 28, datalen - 28);
                // create waveOut player
                waveOut = new WaveOut();
                waveOut.Init(bufferedWaveProvider);
                waveOut.Volume = 0.25f;
                waveOut.Play();
                // mark stream is active
                _streamActive = true;
            }

    I've not looked into the latency as yet but it can be tuned to a certain extent in any case. The next problem is to try to figure out how to actually send audio back to the flex for transmission.

    I'd be interested to know if anybody uses this code in any projects, more likely it might be useful as a starting point.

    Cliff, G4PZK

Leave a Comment

Rich Text Editor. To edit a paragraph's style, hit tab to get to the paragraph menu. From there you will be able to pick one style. Nothing defaults to paragraph. An inline formatting menu will show up when you select text. Hit tab to get into that menu. Some elements, such as rich link embeds, images, loading indicators, and error messages may get inserted into the editor. You may navigate to these using the arrow keys inside of the editor and delete them with the delete or backspace key.