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

  • 3
  • Question
  • Updated 6 days ago
  • (Edited)

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.
Photo of Cliff - G4PZK

Cliff - G4PZK

  • 21 Posts
  • 8 Reply Likes

Posted 1 week ago

  • 3
Photo of Cliff - G4PZK

Cliff - G4PZK

  • 21 Posts
  • 8 Reply Likes
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'.


Photo of James Whiteway

James Whiteway

  • 898 Posts
  • 215 Reply Likes
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
(Edited)
Photo of Cliff - G4PZK

Cliff - G4PZK

  • 21 Posts
  • 8 Reply Likes
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