Ethernet Streaming Implementation

Designing hardware that uses VLSI Solution's devices as slave codecs such as an external MP3 decoder chip for a host microcontroller.
Post Reply
tianyi
User
Posts: 9
Joined: Thu 2019-05-09 4:27

Ethernet Streaming Implementation

Post by tianyi » Tue 2019-06-04 10:50

Dear all,

I am attempting to use the VS1053 as a slave decoder (using the Adafruit Breakout Board)
uC: Teensy 3.5
Audio data comes in via UDP packets from my PC via an ENC28J60 Ethernet adapter, using the EtherCard arduino library.
The Teensy 3.5 has two SPI buses, so I am using one for each device.

From my PC, I use gstreamer:
gst-launch-1.0 filesrc location=./123.mp3 ! mpegaudioparse ! rtpmpapay mtu=32 ! udpsink host=192.168.137.2 port=1337
Where mtu=32 limits the size of packets to 32 bytes, thus making it easier for me to pump data in 32-byte chunks to the VS1053.

Currently, my strategy is to pump the recieved UDP packets directly to the VS1053 using SPI. However no sound comes out. When polling the HDAT registers, I just get mono audio channel with sample rate of 8000kHz.

Is there anything glaringly obvious I should be checking?
I attempted to implement a ring buffer system but realized that it is never full, so I decided to do without the added complexity.

My code is below:

Code: Select all

/*PINOUT
   13 - SCLK Serial Clock
   12 - MISO
   11 - MOSI
   04 - SDCS (not used)
*/
#define CSaudio 10 // /CS for VS1053
#define RESET 9 // Pull LOW to HARDWARE RESET
#define XDCS 8 // Pull LOW if DATA in
#define DREQ 3 // will be LOW if BUSY, HIGH if data is able to be accepted
#define CSethernet 7 // /CS for ENC28J60

// hardware config
#define VS1053_FILEPLAYER_TIMER0_INT 255 // allows useInterrupt to accept pins 0 to 254
#define VS1053_FILEPLAYER_PIN_INT 5 //default interrupt pin
// two different SPI control rates
#define CONTROL_SPI  SPISettings(250000,  MSBFIRST, SPI_MODE0)
#define DATA_SPI     SPISettings(8000000, MSBFIRST, SPI_MODE0)
// to read or write
#define SCI_WRITE 0x02 // to write to a control register
#define SCI_READ 0x03 // to read from a control register
// all the different data registers
#define VS1053_REG_MODE  0x00 // main mode control register
#define VS1053_REG_STATUS 0x01 // status register
#define VS1053_REG_BASS 0x02 // bass/treb control
#define VS1053_REG_CLOCKF 0x03 // clock freq + multiplier
#define VS1053_REG_DECODETIME 0x04 // decode time in seconds
#define VS1053_REG_AUDATA 0x05 // misc. audio data
#define VS1053_REG_WRAM 0x06 // RAM write/read
#define VS1053_REG_WRAMADDR 0x07 // base addr for RAM W/R
#define VS1053_REG_HDAT0 0x08 // stream header data 0 - corresponds to what is being decoded
#define VS1053_REG_HDAT1 0x09 // stream header data 1 - corresponds to what is being decoded
#define VS1053_REG_VOLUME 0x0B // volume control
#define VS1053_REG_AIADDR 0x0A // Start address for user applications
#define VS1053_REG_AICTRL0 0x0C // Application control register 1
#define VS1053_REG_AICTRL1 0x0D // App control register 2
#define VS1053_REG_AICTRL2 0x0E // App control register 3
#define VS1053_REG_AICTRL3 0x0F // App control register 4
// GPIO
#define VS1053_GPIO_DDR 0xC017
#define VS1053_GPIO_IDATA 0xC018
#define VS1053_GPIO_ODATA 0xC019
// enable interrupts
#define VS1053_INT_ENABLE  0xC01A
// different addresses in the MODE register 0x00
#define VS1053_MODE_SM_DIFF 0x0001 // write this to invert L channel audio
#define VS1053_MODE_SM_LAYER12 0x0002 // write this to allow MP3 Layer I and II decoding
#define VS1053_MODE_SM_RESET 0x0004 // write this for a software reset
#define VS1053_MODE_SM_CANCEL 0x0008 // write this to cancel decoding
#define VS1053_MODE_SM_EARSPKLO 0x0010
#define VS1053_MODE_SM_TESTS 0x0020 // write this to enable SDI tests
#define VS1053_MODE_SM_STREAM 0x0040 // change to stream mode (MP3 only, 512byte packets, bitrate <160kbit/s
#define VS1053_MODE_SM_EARSPKHI 0x0080
#define VS1053_MODE_SM_SDINEW 0x0800 // write this for default mode (written by default)
#define VS1053_MODE_SM_ADPCM 0x1000 // write this AND SM_RESET for ADPCM recording.
#define VS1053_MODE_SM_LINE1 0x4000 // write 1 for line-in, 0 for mic
#define VS1053_MODE_SM_CLKRANGE 0x8000 // write for clock divider

// misc stuff specific to VS1053
//#define VS1053_DATABUFFERLEN 32 // 32 bytes through SDI interface
#define FILE_BUFFER_SIZE 512 // max size of files through the buffer
#define SDI_MAX_TRANSFER_SIZE 32 // max size of files through one single SDI transmission
#define SDI_END_FILL_BYTES_FLAC 12288
#define SDI_END_FILL_BYTES       2050
#define REC_BUFFER_SIZE 512

// parameters to write into VS1053_REG_WRAMADDR to get data
#define PAR_VERSION         0x1e02 // Structure version – it should read 0x0003
#define PAR_CONFIG1         0x1e03 // controls MIDI Reverb and AAC’s SBR and PS settings
#define PAR_PLAY_SPEED      0x1e04 // 0,1 = normal speed, 2 = twice, 3 = three times etc.
#define PAR_BYTERATE        0x1e05 // average byte rate
#define PAR_END_FILL_BYTE   0x1e06 // byte to send after a file
#define PAR_JUMP_POINTS     0x1e16 // Packet offsets for WMA and AAC
#define PAR_LATEST_JUMP     0x1e26 // Index to latest jumpPoint
#define PAR_POSITION_MSEC   0x1e27 // File position in milliseconds, if available. 32-bit value
#define PAR_RESYNC          0x1e29 // Automatic Resync Selector

#define PAR_PLAY_MODE       0x1e09 // what am i reading???? this doesnt exist for vs1053.

// for setting clock frequency, goes into VS1053_REG_CLOCKF
// if i write 12288000 i get 1072.5
#define HZ_TO_SC_FREQ(hz)   (((hz)-8000000+2000)/4000)
// clock multiplier, goes into VS1053_REG_CLOCKF
#define SC_MULT_53_10X      0x0000
#define SC_MULT_53_20X      0x2000
#define SC_MULT_53_25X      0x4000
#define SC_MULT_53_30X      0x6000
#define SC_MULT_53_35X      0x8000
#define SC_MULT_53_40X      0xa000
#define SC_MULT_53_45X      0xc000
#define SC_MULT_53_50X      0xe000
// allows temporary adding to the clock multiplier (for decoding WMA/AAC). goes into VS1053_REG_CLOCKF.
#define SC_ADD_53_00X       0x0000
#define SC_ADD_53_10X       0x0800
#define SC_ADD_53_15X       0x1000
#define SC_ADD_53_20X       0x1800

#define PAR_CONFIG1_AAC_SBR_SELECTIVE_UPSAMPLE 0x0010 // I have no idea what this does.

/* How many transferred bytes between collecting data.
   A value between 1-8 KiB is typically a good value. */
#define REPORT_INTERVAL 4096
#define REPORT_INTERVAL_MIDI 512

// Audio formats
enum AudioFormat {
  afUnknown,
  afRiff,
  afOggVorbis,
  afMp1,
  afMp2,
  afMp3,
  afAacMp4,
  afAacAdts,
  afAacAdif,
  afFlac,
  afWma,
  afMidi,
} audioFormat = afUnknown;

// audio format names
const char *afName[] PROGMEM = {
  "unknown",
  "RIFF",
  "Ogg",
  "MP1",
  "MP2",
  "MP3",
  "AAC MP4",
  "AAC ADTS",
  "AAC ADIF",
  "FLAC",
  "WMA",
  "MIDI",
};

#include <EtherCard.h>
#include <IPAddress.h>
#include<SPI.h>

static byte myip[] = {192, 168, 137, 2}; // 192.168.137.1 is our computer's address on the Ethernet network.
static byte gwip[] = {169, 254, 194, 97}; // dont care cos not connected to internet
static byte mymac[] = { 0x1A, 0x2B, 0x3C, 0x4D, 0x5E, 0x6F }; // don't need to change this
const byte dstPort PROGMEM = 61769; // arguments required to sendUDP.
const byte srcPort PROGMEM = 1337; // arguments required to sendUDP.
const static byte ipDestinationAddress[] PROGMEM = {192, 168, 137, 1};

const int MEDIA_BUFFER_LEN = 256;
byte Ethernet::buffer[100]; // TCP/IP and UDP send and receive buffer
static byte mediaBuffer[MEDIA_BUFFER_LEN]; // data ring buffer
static uint8_t writeDex = 0; // index 256 values before overflow, meaning 8 chunks
static uint8_t readDex = 0; // read index.
static int counter = 0;
static int cycles = 0;
static int buffersize = 0;
byte *bufferPtr = mediaBuffer; // ring buffer position pointer (to the first element of the array)
byte bufferPtrX = bufferPtr;

long timer;
const char payload[] PROGMEM = "Routine UDP Packet.";

// callback
void udpStreamMusic(uint16_t dest_port, uint8_t src_ip[IP_LEN], uint16_t src_port, const char *data, uint16_t len) {
  IPAddress src(src_ip[0], src_ip[1], src_ip[2], src_ip[3]); // creating an IPAddress object

  digitalWrite(21, HIGH);

  if(readyForData()){
    SPI1.beginTransaction(DATA_SPI); // two SPI modes, have to switch between them
    digitalWrite(XDCS, LOW);
    for(int i=0; i<strlen(data); i++){
      SPI1.transfer(data[0]);
      data++;
    }
    digitalWrite(XDCS, HIGH);
    SPI1.endTransaction();
  } else {
    Serial.println("Not ready for Data!");
  }

  cycles ++;
  counter ++;
}

void setup() {
  pinMode(14, OUTPUT);
  digitalWrite(14, LOW); // DREQ High buffer empty
  pinMode(21, OUTPUT);
  digitalWrite(23, LOW); // data sending errors
  pinMode(23, OUTPUT);
  digitalWrite(23, LOW); // when recieveing data

  Serial.begin(115200);
  while (!Serial);
  Serial.println("Starting UDP audio transmission."); // So that I know what is loaded.

  if (ether.begin(sizeof Ethernet::buffer, mymac, CSethernet) == 0) // Initialises Ethernet controller
    Serial.println(F("Failed to access Ethernet controller"));
  ether.staticSetup(myip, gwip);
  ether.printIp("IP:  ", ether.myip);
  ether.printIp("GW:  ", ether.gwip);
  ether.printIp("DNS: ", ether.dnsip);

  SPI1.begin();
  if (!VS1005_init()) { // initialise the music player
    Serial.println(F("Couldn't find VS1053, do you have the right pins defined?"));
    while (1);
  } Serial.println(F("VS1053 initialized!"));
  Serial.println("Resetting decode time.");
  sciWrite(VS1053_REG_DECODETIME, 0x00);
  sciWrite(VS1053_REG_DECODETIME, 0x00);       // reset Decode Time
  Serial.println("Stream beginning...");

  ether.udpServerListenOnPort(&udpStreamMusic, 1337); // Send UDP packets to port 1337 using PacketSender
}

void loop() {
  digitalWrite(14, LOW);
  digitalWrite(21, LOW);
  digitalWrite(23, LOW);

  ether.packetLoop(ether.packetReceive()); //this must be called for ethercard functions to work.
  
  if (readyForData() && counter>=4) {
    // STATS FOR NERDS
    uint16_t sampleRate;
    uint32_t byteRate;
    Serial.println("Reading HDAT:\t");
    uint16_t h1 = sciRead(VS1053_REG_HDAT1); // to see what is being decoded
    uint16_t h0 = sciRead(VS1053_REG_HDAT0);
    
    Serial.print("Reading WRAMADDR - PAR_END_FILL_BYTE\t");
    sciWrite(VS1053_REG_WRAMADDR, PAR_END_FILL_BYTE);
    // endFillByte = sciRead(VS1053_REG_WRAM);

    sampleRate = sciRead(VS1053_REG_AUDATA); // reads miscellaneous audio data

    Serial.print("Reading WRAMADDR - PAR_BYTERATE\t");              
    sciWrite(VS1053_REG_WRAMADDR, PAR_BYTERATE);
    byteRate = sciRead(VS1053_REG_WRAM);
    /* FLAC:   byteRate = bitRate / 32. Others: byteRate = bitRate /  8. Here we compensate for that difference. */

    //Status Output BECAUSE ARDUINO DOESNT HAVE sprintf.
//    Serial.print("Position: ");
//    Serial.print(pos/1024, DEC);
//    Serial.println("KiB");
    Serial.print("Cycles: ");
    Serial.println(cycles);
    Serial.print("Byte Rate: ");
    Serial.print(byteRate * (8.0/1000.0), 2);
    Serial.print("kb/s\tSample Rate: ");
    Serial.print(sampleRate & 0xFFFE);
    Serial.println("Hz");
    Serial.print("Audio channel: ");
    Serial.println((sampleRate & 1) ? "stereo" : "mono");
    Serial.print("Audio format: ");
    Serial.println(afName[audioFormat]);
    Serial.print("HDAT1 value: ");
    Serial.println(h1, HEX);
    Serial.print("HDAT0 value: ");
    Serial.println(h0, HEX);
    Serial.println("");

    counter = 0;

    // END STATS FOR NERDS

  } else if (buffersize == 0 && !readyForData()) {
    digitalWrite(23, LOW);
    digitalWrite(21, LOW);
  } else if (readyForData()) {
    digitalWrite(14, HIGH);
    digitalWrite(21, LOW);
    digitalWrite(23, LOW);
  } else {
    digitalWrite(21, LOW);
    digitalWrite(23, LOW);
  }

}

void setvolume(uint8_t left, uint8_t right) {
  Serial.print("Setting volume (L/R): ");
  Serial.print(left);
  Serial.print("\t");
  Serial.println(right);
  uint16_t v;
  v = left;
  v <<= 8;
  v |= right;
  sciWrite(VS1053_REG_VOLUME, v);
}

/*lowest level*/

bool VS1005_init() { // initializes the VS1005 by doing a hardware and software reset
  Serial.println("\nInitializing VS1005...");

  pinMode(CSaudio, OUTPUT);
  pinMode(RESET, OUTPUT);
  pinMode(XDCS, OUTPUT);
  pinMode(DREQ, INPUT);
  digitalWrite(XDCS, HIGH); // No media in, pull high
  digitalWrite(CSaudio, HIGH);  // No commands in, pull high

  digitalWrite(RESET, LOW); // Hardware reset
  delay(100);
  digitalWrite(RESET, HIGH);

  sciRead(VS1053_REG_MODE);

  sciWrite(VS1053_REG_MODE, VS1053_MODE_SM_SDINEW | VS1053_MODE_SM_STREAM | VS1053_MODE_SM_RESET);
  // Software reset, Stream mode on to manage the thingy
  delay(1000); // some time to get our wits together

  /* A quick sanity check: write to two registers, then test if we
    get the same results. Note that if you use a too high SPI
    speed, the MSB is the most likely to fail when read again. */
  Serial.println("Sanity Check...");
  sciWrite(VS1053_REG_AICTRL1, 0xABAD);
  sciWrite(VS1053_REG_AICTRL2, 0x7E57);
  if (sciRead(VS1053_REG_AICTRL1) != 0xABAD || sciRead(VS1053_REG_AICTRL2) != 0x7E57) {
    Serial.println("There is something wrong with VS1053 SCI registers\n");
    return 0;
  }
  sciWrite(VS1053_REG_AICTRL1, 0);
  sciWrite(VS1053_REG_AICTRL2, 0);
  Serial.println("Sanity Check complete, done resetting values...");

  if ( ((sciRead(VS1053_REG_STATUS) >> 4) & 0x0F) == 4) {
    // checking if it returns the version number, which if it's 4 indicates that the chip is ready for communication.
    //Set clock
    Serial.println("Setting clock."); // 3.5+1.0X
    sciWrite(VS1053_REG_CLOCKF, SC_MULT_53_35X | SC_ADD_53_10X);
    //sciWrite(VS1053_REG_CLOCKF, SC_MULT_53_25X);
    //sciWrite(VS1053_REG_CLOCKF, HZ_TO_SC_FREQ(12288000) | SC_MULT_53_35X | SC_ADD_53_10X);
    // Set up other parameters for faster clocking.
    Serial.println("Setting other parameters...");
    sciWrite(VS1053_REG_WRAMADDR, PAR_CONFIG1);
    sciWrite(VS1053_REG_WRAM, PAR_CONFIG1_AAC_SBR_SELECTIVE_UPSAMPLE);
    // not sure what this changes. but it's in the example code
  } else {
    Serial.println("VS1053 version number incorrect!");
    return 0;
  }
  setvolume(20, 20);
}

void sciWrite(uint8_t addr, uint16_t data) { // writes data to SCI registers.
  SPI1.beginTransaction(CONTROL_SPI); // two SPI modes, have to switch between them

  Serial.print("Data to be sent: ");
  Serial.println(data, HEX);

  digitalWrite(CSaudio, LOW);
  //  digitalWrite(CSethernet, HIGH);
  SPI1.transfer(SCI_WRITE); // WRITE mode, as per data sheet
  SPI1.transfer(addr); // goes to address
  SPI1.transfer16(data); // the first 8 bits of the 16-byte data block
  //  SPI.transfer(data >> 8); // the first 8 bits of the 16-byte data block
  //  SPI.transfer(data & 0xFF); // the last 8 bits of the 16-byte data block (0xFF is a bitmask)
  digitalWrite(CSaudio, HIGH);
  //  digitalWrite(CSethernet, LOW);
  SPI1.endTransaction();
}

uint16_t sciRead(uint8_t addr) { // reads data from the SCI interface.
  uint16_t data;
  SPI1.beginTransaction(CONTROL_SPI);
  //  digitalWrite(CSethernet, HIGH);
  digitalWrite(CSaudio, LOW);
  SPI1.transfer(SCI_READ); // read mode, as per data sheet
  //  Serial.println("Addr to be read: ");
  //  Serial.println(addr, HEX);
  SPI1.transfer(addr);
  delayMicroseconds(10);
  data = SPI1.transfer16(0); // reads the 2-byte value
  //  data <<= 8; // shift bits to the left to make more space for the next 8
  //  data |= SPI.transfer(0); // union with the next 8 bits
  digitalWrite(CSaudio, HIGH);
  //  digitalWrite(CSethernet, LOW);

  //  Serial.println("Data recieved: ");
  //  Serial.println(data, HEX);

  SPI1.endTransaction();
  return data;
}

//void sdiWrite(uint8_t *data, uint8_t t) { // writes MUSIC via SDI
//  SPI1.beginTransaction(DATA_SPI); // two SPI modes, have to switch between them
//  digitalWrite(XDCS, LOW);
//
//  for (int i = 0; i < t; i++) {
//    SPI1.transfer(data[i]);
//    memset(data + i, NULL, 1);
//    data++;
//  }
//
//  //  while(t--){ // loops and transfers data byte by byte for the the number of bytes to be transferred
//  //    SPI1.transfer(data[0]); //byte by byte
//  //    memset(data, NULL, 1); // erase data after it is transferred
//  //    data++;
//  //  }
//  digitalWrite(XDCS, HIGH);
//  SPI1.endTransaction();
//}


void sdiWrite(uint8_t t) { // writes MUSIC via SDI
  SPI1.beginTransaction(DATA_SPI); // two SPI modes, have to switch between them
  digitalWrite(XDCS, LOW);
  
  for(int i=0; i<t; i++){
    SPI1.transfer(mediaBuffer[readDex]);
    memset(bufferPtr+readDex, NULL, 1);
    readDex++;
  }
    
  digitalWrite(XDCS, HIGH);
  SPI1.endTransaction();
}

bool readyForData() { // reads DREQ to see if it can accept more data
  return digitalRead(DREQ);
}
tinkerthinker

Hannu
Senior User
Posts: 72
Joined: Mon 2016-05-30 11:54

Re: Ethernet Streaming Implementation

Post by Hannu » Wed 2019-06-05 10:49

Hello!

How much does your project work? Are you sure you get correct data from network? Can you decode any MP3 file from for example from SD card?
If you haven't got any audio out yet, start working something like Hello project from here: viewtopic.php?t=65

There is VS1053 port also there. After you got the VS1053 playing audio, start working with network data.

Few points to think.

1. Are you getting all data ?
What happens if packet arrives during writing audio file to VS1053? Mainly thinking about how much your data is buffered on network interface.

2. Are you feeding too much data to VS1053?
RTMP has some headers around the media file and VS1053 can't handle them. You can check this easily by calculating the bytes which would be sent to VS1053 and they should match with file length. If the bytes you are going to differ from the size of 123.mp3 file, you need to parse the packets and remove the extra rtmp layer. And another thing is that you are using UDP. Most of the time packets will arrive in order and never get lost. So that can make things interesting, but let's not blame that yet.

3. Your reset could be little bit different.
VS1053 datasheet wrote: After a hardware reset (or at power-up) DREQ will stay down for around 22000 clock cycles,which means an approximate 1.8 ms delay if VS1053b is run at 12.288 MHz. After this theuser should set such basic software registers as SCI_MODE, SCI_BASS, SCI_CLOCKF, andSCI_VOL before starting decoding. See section 9.6 for details

Code: Select all

/* Wait for DREQ after reset */
digitalWrite(XRESET, HIGH);
while(digitalRead(DREQ) == 0)
  ;
/* Set CLK, vol and other basic stuff through SCI. */
4. What the updStreamMusic callback function will get? the packet? payload? But my guess is that you should use data and length of the packet to constrain your for-loop. Something like:

Code: Select all

uint8_t *s = data;
uint16 i;
for (i = 0; i < len; i++) {
  SPI1.transfer(*s++);
}
There are probably more things to take a look at but those got my eye first.

tianyi
User
Posts: 9
Joined: Thu 2019-05-09 4:27

Re: Ethernet Streaming Implementation

Post by tianyi » Wed 2019-06-05 17:53

Thanks for the reply, Hannu!

Yes, I have managed to get SD card files streaming (both using the AdaFruit board's SD card reader, as well as the Teesny's internal SD card reader).
My MP3 file is the same for both projects, so I know what I should be reading out of HDAT0/1 and my bit rate (44.1kHz).
Your reset could be little bit different.
I have implemented this suggestion, inserting the wait for DREQ to come high after a hardware reset.
What happens if packet arrives during writing audio file to VS1053? Mainly thinking about how much your data is buffered on network interface.
I check DREQ before making a SDI transfer and log if it is LOW. If it is I increment a "missedTransfer" counter which I Serial.print every few transfer cycles.
So far, it seems like I'm not getting any missed transfers at all.
RTMP has some headers around the media file and VS1053 can't handle them. You can check this easily by calculating the bytes which would be sent to VS1053 and they should match with file length. If the bytes you are going to differ from the size of 123.mp3 file, you need to parse the packets and remove the extra rtmp layer. And another thing is that you are using UDP. Most of the time packets will arrive in order and never get lost. So that can make things interesting, but let's not blame that yet.
With the help of WireShark, I have found a way to isolate the data from the UDP header - ignoring the first 42 bytes of the transfer and subsequently using the rest of the bytes in Ethernet::buffer.

I haven't tested the RTP header aspect yet - my file is quite large so I never play the full length of it. I will try to find a smaller audio file and do this test. I have a hunch that this might be the issue, as I notice that no matter what size I set the minimum data chunk as, the beginning few bytes are the same.
What the updStreamMusic callback function will get? the packet? payload?
It gets the packet data. udpStreamMusic function executes everytime there is a new data packet.

Well, so far my code within the callback function looks like this.

Code: Select all

  SPI1.beginTransaction(DATA_SPI);
  digitalWrite(XDCS, LOW);

  for(uint16_t i=42; i<sizeof(Ethernet::buffer); i++){ // ALL of the Ethernet Buffer. Start at i=42 to ignore the UDP header.
    byte *x = Ethernet::buffer; // pointer to Ethernet buffer index 0
    x = x + i;
    byte XFER = x[0];
    if(i%32==0){ // It's the beginning of another DREQ cycle, check DREQ!
      if(!readyForData()){
        i--; //Freeze the transfer!!
        missedTransfers++; // There's a jam in the data being sent!
      } else {
        SPI1.transfer(XFER);
      }
    } else {
      SPI1.transfer(XFER);
    }
  }

  digitalWrite(XDCS, HIGH);
  SPI1.endTransaction();
I serial print out some data read from the HDAT registers every few cycles (as per the VS1053 player.c code - this has worked well with playing music from SD.)
What I get is a byte rate of 524.28kb/s, a sample rate fluctuating between 0 and 48002 Hz, a Audio Channel fluctuating between mono and stereo, and an unknown audio format.
HDAT usually reads 0 for both registers as well.

The code for reading the registers is as follows (perhaps it might be reading erroneous data?...)
Of course, 'counter' and 'cycles' are incremented after each instance of the udpStreamMusic callback.

Code: Select all

if (readyForData() && counter>=20) {
    // STATS FOR PLAYBACK
    uint16_t sampleRate;
    uint32_t byteRate;
    Serial.println("Reading HDAT:\t");
    uint16_t h1 = sciRead(VS1053_REG_HDAT1); // to see what is being decoded
    uint16_t h0 = sciRead(VS1053_REG_HDAT0);
    
    Serial.print("Reading WRAMADDR - PAR_END_FILL_BYTE\t");
    sciWrite(VS1053_REG_WRAMADDR, PAR_END_FILL_BYTE);
    // endFillByte = sciRead(VS1053_REG_WRAM);

    if (h1 == 0x7665) { // commented out END_FILL_BYTES as it is a stream and the file doesn't end - is this the issue?
    audioFormat = afRiff;
    // endFillBytes = SDI_END_FILL_BYTES;
  } else if (h1 == 0x4154) {
    audioFormat = afAacAdts;
    // endFillBytes = SDI_END_FILL_BYTES;
  } else if (h1 == 0x4144) {
    audioFormat = afAacAdif;
    // endFillBytes = SDI_END_FILL_BYTES;
  } else if (h1 == 0x574d) {
    audioFormat = afWma;
    // endFillBytes = SDI_END_FILL_BYTES;
  } else if (h1 == 0x4f67) {
    audioFormat = afOggVorbis;
    // endFillBytes = SDI_END_FILL_BYTES;
  } else if (h1 == 0x664c) {
    audioFormat = afFlac;
    // endFillBytes = SDI_END_FILL_BYTES_FLAC;
  } else if (h1 == 0x4d34) {
    audioFormat = afAacMp4;
    // endFillBytes = SDI_END_FILL_BYTES;
  } else if (h1 == 0x4d54) {
    audioFormat = afMidi;
    // endFillBytes = SDI_END_FILL_BYTES;
  } else if ((h1 & 0xffe6) == 0xffe2) {
    audioFormat = afMp3;
    // endFillBytes = SDI_END_FILL_BYTES;
  } else if ((h1 & 0xffe6) == 0xffe4) {
    audioFormat = afMp2;
    // endFillBytes = SDI_END_FILL_BYTES;
  } else if ((h1 & 0xffe6) == 0xffe6) {
    audioFormat = afMp1;
    // endFillBytes = SDI_END_FILL_BYTES;
  } else {
    audioFormat = afUnknown;
    // endFillBytes = SDI_END_FILL_BYTES_FLAC;
  }
    sampleRate = sciRead(VS1053_REG_AUDATA); // reads miscellaneous audio data
    Serial.print("Reading WRAMADDR - PAR_BYTERATE\t\t");              
    sciWrite(VS1053_REG_WRAMADDR, PAR_BYTERATE);
    byteRate = sciRead(VS1053_REG_WRAM);

    //Status Output in many lines as Arduino doesn't support sprintf fully.
    
    Serial.print("Cycles: ");
    Serial.println(cycles);
    Serial.print("Missed SDI Transfers: ");
    Serial.println(missedTransfers);
    Serial.print("Byte Rate: ");
    Serial.print(byteRate * (8.0/1000.0), 2);
    Serial.print("kb/s\tSample Rate: ");
    Serial.print(sampleRate & 0xFFFE);
    Serial.println("Hz");
    Serial.print("Audio channel: ");
    Serial.println((sampleRate & 1) ? "stereo" : "mono");
    Serial.print("Audio format: ");
    Serial.println(afName[audioFormat]);
    Serial.print("HDAT1 value: ");
    Serial.println(h1, HEX);
    Serial.print("HDAT0 value: ");
    Serial.println(h0, HEX);
    Serial.println("");

    counter = 0; // reset counter after 20 iterations

  } else if (counter>=20 && !readyForData()) {
    Serial.println("Not ready to display stats yet!\n"); // if DREQ is Low, but we're due to present stats
    counter = 0; 
  }
  // END PLAYBACK STATS
  
I noticed that the sample rate is unusually high. Would it mean that it is a clock rate issue that I need to resolve? What should I do to solve it?
tinkerthinker

User avatar
Panu
VLSI Staff. Currently on holiday.
Posts: 2697
Joined: Tue 2010-06-22 13:43

Re: Ethernet Streaming Implementation

Post by Panu » Thu 2019-06-06 7:18

Hi!

Hmm, maybe you could try to store the MP3 bits to your SD card and play the resulting MP3 file with a PC.

In 2009, I made a working Shoutcast/Icecast receiver for just the VS1053 and the ENC28J60 (I just wired VS1053 Prototyping board to some Microchip ENC28J60 breakout board, might have been the PICtail board). I doubt that it would be useful to you, but you can browse the source in case you get some ideas... :) First I made it receive a raw MP3 file from Apache server running in my laptop and then I added DHCP IP address conficuration and stripping the Icecast metadata and made it receive a few commercial radio stations.

viewtopic.php?f=7&t=931

-Panu
Info: Line In and Line Out, VS1000 User interface, Overlay howto, Latest VSIDE, MCU Howto, Youtube
Panu-Kristian Poiksalo, VLSI Solution Oy

tianyi
User
Posts: 9
Joined: Thu 2019-05-09 4:27

Re: Ethernet Streaming Implementation

Post by tianyi » Thu 2019-06-06 9:09

Hmm, in my application, I can't use an SD card, so unfortunately that's not an option.

I've done some further testing and reading around, and am honestly quite perplexed.
Opening up the mp3 file in Sublime Text allows me to see the distinct data bytes present.
I have analyzed what I send to the VS1053 by Serial.print -ing the distinct bytes:

Code: Select all

  SPI1.beginTransaction(DATA_SPI);
//  digitalWrite(XDCS, LOW);

    for(uint16_t i=16; i<272; i++){ // This corresponds to the right bytes, somehow.
    // we use 16 because byte 0-43 is UDP header and byte 44-59 are the RTP headers. Data starts at 60.
    // In the data register it automatically gets rid of the UDP header - so from 0-15 it's RTP, and subsequently 16-47 it's the data.
      byte *x = data; // pointer to Ethernet buffer index 0
      x = x + i;
      byte XFER = x[0];
      if((i-16)%32==0){ // cycles of 32
        if(readyForData()){
          digitalWrite(XDCS, HIGH);
          Serial.println("");
          Serial.print(XFER, HEX);
          Serial.print(" "); // send data
          digitalWrite(XDCS, LOW);
          SPI1.transfer(XFER);
        } else {
          digitalWrite(XDCS, HIGH);
          missedTransfers++;
        }
        } else {
          Serial.print(XFER, HEX); // transferring data!
          Serial.print(" "); // send data
          SPI1.transfer(XFER);
        }
    }
    Serial.println("");
  
  digitalWrite(XDCS, HIGH);
  SPI1.endTransaction();
I cross-compare the data I send with the MP3 file at several different locations, and they do match, even from packet to packet.
However, DREQ stays HIGH and the HDAT registers seem to not be recognizing that I'm feeding it MP3 data, refusing to decode.

I write to VS1053_REG_MODE as VS1053_MODE_SM_SDINEW, VS1053_MODE_SM_STREAM (0x0840).
I write to VS1053_REG_CLOCKF at HZ_TO_SC_FREQ(12288000), SC_MULT_53_35X, SC_ADD_53_10X (0x8C30).
And also to VS1053_REG_AUDATA as 44100Hz Stereo sample rate (0xAC45).

Am I doing anything wrong in my setup?

If it means anything, the RTP stream has a clock rate of 90000Hz.
https://gstreamer.freedesktop.org/docum ... language=c
tinkerthinker

User avatar
Panu
VLSI Staff. Currently on holiday.
Posts: 2697
Joined: Tue 2010-06-22 13:43

Re: Ethernet Streaming Implementation

Post by Panu » Thu 2019-06-06 9:47

Are you sure the XDCS remains low during the entire SPI transmission?
Info: Line In and Line Out, VS1000 User interface, Overlay howto, Latest VSIDE, MCU Howto, Youtube
Panu-Kristian Poiksalo, VLSI Solution Oy

tianyi
User
Posts: 9
Joined: Thu 2019-05-09 4:27

Re: Ethernet Streaming Implementation

Post by tianyi » Thu 2019-06-06 10:15

Dear Panu,

Yes, it does turn out that I made a mistake with XDCS. I rectified it, but still am getting the same error.

Code: Select all

for(uint16_t i=16; i<272; i++){ // This corresponds to the right bytes, somehow.
    // we use 16 because byte 0-43 is UDP header and byte 44-59 are the RTP headers. Data starts at 60.
    // In the data register it automatically gets rid of the UDP header - so from 0-15 it's RTP, and subsequently 16-47 it's the data.
      byte *x = data; // pointer to Ethernet buffer index 0
      x = x + i;
      byte XFER = x[0];
      if((i-16)%32==0){ // cycles of 32
        if(readyForData()){
          digitalWrite(XDCS, HIGH);
          Serial.println("^"); // check for dreq here
          Serial.print("v"); // send data
          Serial.print(XFER, HEX); // sub for SPI1.Xfer
          digitalWrite(XDCS, LOW);
          SPI1.transfer(XFER);
        } else {
          missedTransfers++;
        }
        } else {
          Serial.print(XFER, HEX); // transferring data!
          Serial.print(" "); // send data
          SPI1.transfer(XFER);
        }
    }
tinkerthinker

User avatar
Panu
VLSI Staff. Currently on holiday.
Posts: 2697
Joined: Tue 2010-06-22 13:43

Re: Ethernet Streaming Implementation

Post by Panu » Thu 2019-06-06 10:25

Hmm... does missedTransfers increment? If you ever skip a packet, it's impossible to decode the audio..

Also, the way you transfer, can you be sure that XDCS is returned to high before any other activity in the SPI bus, such as SCI register reads/writes?
Info: Line In and Line Out, VS1000 User interface, Overlay howto, Latest VSIDE, MCU Howto, Youtube
Panu-Kristian Poiksalo, VLSI Solution Oy

tianyi
User
Posts: 9
Joined: Thu 2019-05-09 4:27

Re: Ethernet Streaming Implementation

Post by tianyi » Thu 2019-06-06 11:26

Hmm... does missedTransfers increment? If you ever skip a packet, it's impossible to decode the audio..
missedTransfers never increments. However, I analyzed the initial packet sent to VS1053 vs the initial packet sent in WireShark, and it seems like the uC misses the first ~150 bytes of the file consistently. Would you know any workaround to this, specifically in UDP transmission?

In the meantime, I will work on troubleshooting this issue. Thank you so much for your help in this topic so far! :)
tinkerthinker

Post Reply