Diagnosing AudioPlaySdRaw failures

Status
Not open for further replies.

ihatemornings

Active member
I’ve wired up a Teensy 4.0 with two Audio boards for the quad output setup with a 32GB SanDisk Ultra SD card and it’s working splendidly. This forum (especially https://forum.pjrc.com/threads/61123-Teensy-4-1-and-4-channel-audio-(AudioInputI2SQuad) ) was a goldmine of useful info. But now I’ve run into a problem that I’m having trouble diagnosing.

IMG_0101.jpg
IMG_0102.jpg
IMG_0103.jpg

I’ve put together a simplified version of my code, which reproduces the problem every time for me (View attachment 07_bug_report.ino):

Code:
#include <elapsedMillis.h>

#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>

// Define SD card pins (Teensy 4.0)
#define SDCARD_CS_PIN    10
#define SDCARD_MOSI_PIN  11
#define SDCARD_SCK_PIN   13

// GUItool: begin automatically generated code
AudioPlaySdRaw       playSdRaw1;
AudioPlaySdRaw       playSdRaw2;
AudioOutputI2SQuad   i2s_quad1;
AudioConnection      patchCord1(playSdRaw1, 0, i2s_quad1, 0);
AudioConnection      patchCord2(playSdRaw2, 0, i2s_quad1, 1);
AudioControlSGTL5000 sgtl5000_1;
AudioControlSGTL5000 sgtl5000_2;
// GUItool: end automatically generated code

unsigned int tickLength; // ms
elapsedMillis sinceTick;
int alternator = 0;
bool success = false;

void setup() {
  Serial.begin(9600);
  tickLength = 1000; //ms

  AudioMemory(12);

  // Set up the two audio shields with different addresses.
  sgtl5000_1.setAddress(LOW);
  sgtl5000_1.enable();
  sgtl5000_1.volume(0.5);
  sgtl5000_2.setAddress(HIGH);
  sgtl5000_2.enable();
  sgtl5000_2.volume(0.5);

  // Initialise SD card access.
  SPI.setMOSI(SDCARD_MOSI_PIN);
  SPI.setSCK(SDCARD_SCK_PIN);
  if (!(SD.begin(SDCARD_CS_PIN))) {
    while (1) {
      Serial.println("Unable to access the SD card");
      delay(500);
    }
  }

  delay(1000);
}

void loop() {
  if (sinceTick >= tickLength) {
    sinceTick = sinceTick - tickLength;

    if (alternator) {
      success = playSdRaw1.play("LOOP1.RAW");
    } else {
      success = playSdRaw2.play("LOOP2.RAW");
    }

    alternator = (alternator + 1) % 2;

    if (success) {
      Serial.print("playing ");
    } else {
      Serial.print("failed to load ");
    }
    if (alternator) {
      Serial.println("LOOP1.RAW");
    } else {
      Serial.println("LOOP2.RAW");
    }
  }
}

There are two AudioPlaySdRaw objects playing different RAW files from the SD card in parallel, and every second I restart one of them (alternating). It works for a few seconds, then one of the files fails to start playing. The failures form a pretty regular pattern, where one file will fail a few times then both will succeed, then the other file will fail a few times...

Code:
playing LOOP1.RAW
playing LOOP2.RAW
playing LOOP1.RAW
failed to load LOOP2.RAW
playing LOOP1.RAW
playing LOOP2.RAW
playing LOOP1.RAW
playing LOOP2.RAW
playing LOOP1.RAW
playing LOOP2.RAW
playing LOOP1.RAW
playing LOOP2.RAW
failed to load LOOP1.RAW
playing LOOP2.RAW
failed to load LOOP1.RAW
playing LOOP2.RAW
failed to load LOOP1.RAW
playing LOOP2.RAW
failed to load LOOP1.RAW
playing LOOP2.RAW
playing LOOP1.RAW
playing LOOP2.RAW
playing LOOP1.RAW
playing LOOP2.RAW
playing LOOP1.RAW
playing LOOP2.RAW
playing LOOP1.RAW
playing LOOP2.RAW
playing LOOP1.RAW
failed to load LOOP2.RAW
playing LOOP1.RAW
failed to load LOOP2.RAW
playing LOOP1.RAW
failed to load LOOP2.RAW
playing LOOP1.RAW
[crash]

This goes on for anywhere between 5 seconds and 5 minutes, then the Teensy locks up – I get a high tone from all the audio outputs and the board is unresponsive until I press the reset button.

Playing a single audio file over and over works fine, as does a single AudioPlaySdRaw object playing alternating files. It’s when I have two objects (or more) playing at once that the failures start.

I added some memory reporting code and saw that the max memory used by the Audio library was 6 and the max processor % was around 50. The board is powered via a USB 2 to USB C adapter connected to a MacBook Pro.

My hunch is that the SD read speed might be the bottleneck. I’m using the SD reader on one of the audio boards. But I might be missing something obvious. I’m new to the Teensy and could do with some pointers.:)
 
Try adding playSdRaw1.stop()/playSdRaw.stop() in the appropriate places so that the currently playing object is stopped properly before being restarted.

Pete
 
Try adding playSdRaw1.stop()/playSdRaw.stop() in the appropriate places so that the currently playing object is stopped properly before being restarted.

Thanks, Pete. I tried this but it doesn’t make a difference. And in fact it looks like the play() function already calls stop() itself:
https://github.com/PaulStoffregen/Audio/blob/master/play_sd_raw.cpp#L42

I’m starting to think it might be a hardware issue. My soldering is not amazing and I trimmed down some headers/spacers that I had lying around to connect the three boards. Could that cause an issue like this?
 
I ran the SdCartTest sketch to check whether it was just a case of the SD reads being slow or inconsistent, but the results were (I think) totally fine. I consistently got values close to:

Code:
Reading LOOP1.RAW, LOOP2.RAW, LOOP3.RAW, LOOP4.RAW:
  Overall speed = 1.33 Mbyte/sec
  Worst block time = 2.29 ms
    78.79% of audio frame time

Reading LOOP1.RAW, LOOP2.RAW, LOOP3.RAW, LOOP4.RAW staggered:
  Overall speed = 1.33 Mbyte/sec
  Worst block time = 1.91 ms
    65.87% of audio frame time

Are there any interrupts around which I should be treading carefully?
 
I haven't got the hardware to be able to play around with the code so I can only suggest things to try to see if they make things better or worse.
The thing I would try is to add a delay between stopping a playback and starting it up again to see if there's a timing issue.

After this statement:
Code:
    sinceTick = sinceTick - tickLength;
try adding this:
Code:
    if (alternator) {
      playSdRaw1.stop();
    } else {
      playSdRaw2.stop();
    }
    delay(100);
This allows us to insert a delay between stop() and play() without having to meddle with the audio library itself.

I don't think there's any interrupt you need to be careful of. You aren't explicitly using any, only implicitly through the use of the audio and Sd libraries.

Pete
 
Thanks, Pete. I tried adding the delay (and a longer delay just in case) but it had no effect.

Keep the suggestions coming! In the meantime I’m going to try adding some debug logging to the Audio library to see if I can figure out exactly where and why it’s failing.
 
After a lot of experimentation and digging I came across this in the Audio library source code:
Code:
// When changing multiple audio object settings that must update at
// the same time, these functions allow the audio library interrupt
// to be disabled.  For example, you may wish to begin playing a note
// in response to reading an analog sensor.  If you have "velocity"
// information, you might start the sample playing and also adjust
// the gain of a mixer channel.  Use AudioNoInterrupts() first, then
// make both changes to the 2 separate objects.  Then allow the audio
// library to update with AudioInterrupts().  Both changes will happen
// at the same time, because AudioNoInterrupts() prevents any updates
// while you make changes.
//
#define AudioNoInterrupts() (NVIC_DISABLE_IRQ(IRQ_SOFTWARE))
#define AudioInterrupts()   (NVIC_ENABLE_IRQ(IRQ_SOFTWARE))

I haven’t figured out why yet, but adding these around the play() calls totally solves the problem!

I’m only making a single call to a single audio object, but maybe something to do with the double-board quad-audio setup makes it necessary to wrap the play() call to avoid it being interrupted. Maybe I’ll discover the answer, or maybe someone will see this and know exactly what’s going on...
 
I had been looking at the code for playSDRaw and was wondering if it was handling interrupts properly but hadn't got around to actually doing anything about it. Specifically, I was wondering if it was correct for play() to use __disable_irq and __enable_irq around "rawfile = SD.open(filename);" instead of just turning audio interrupts off and on. The stop() function is also suspect.

Could you modify the code I have in #5, remove the delay() and change the rest to this:
Code:
    AudioNoInterrupts();
    if (alternator) {
      playSdRaw1.stop();
    } else {
      playSdRaw2.stop();
    AudioInterrupts();
    }
If the problem is solved with this, then it is only stop() that is the problem. Otherwise, both stop() and play() have a problem.

Pete
 
Adding AudioNoInterrupts() around the stop() calls in my sketch makes no difference – the play() calls still sporadically fail (with or without a delay() between the stop() and the play()).

This is my working version – no stop() or delay() calls, just wrapping the play() with AudioNoInterrupts()/AudioInterrupts().

Code:
#include <elapsedMillis.h>

#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>

// Define SD card pins (Teensy 4.0)
#define SDCARD_CS_PIN    10
#define SDCARD_MOSI_PIN  11
#define SDCARD_SCK_PIN   13

// GUItool: begin automatically generated code
AudioPlaySdRaw       playSdRaw1;
AudioPlaySdRaw       playSdRaw2;
AudioPlaySdRaw       playSdRaw3;
AudioPlaySdRaw       playSdRaw4;
AudioOutputI2SQuad   i2s_quad1;
AudioConnection      patchCord2(playSdRaw1, 0, i2s_quad1, 0);
AudioConnection      patchCord1(playSdRaw2, 0, i2s_quad1, 1);
AudioConnection      patchCord3(playSdRaw3, 0, i2s_quad1, 2);
AudioConnection      patchCord4(playSdRaw4, 0, i2s_quad1, 3);
AudioControlSGTL5000 sgtl5000_1;
AudioControlSGTL5000 sgtl5000_2;
// GUItool: end automatically generated code

unsigned int tickLength; // ms
elapsedMillis sinceTick;
int alternator = 0;
bool success = false;

void setup() {
  Serial.begin(9600);
  tickLength = 1000; //ms

  AudioMemory(12);

  // Set up the two audio shields with different addresses.
  sgtl5000_1.setAddress(LOW);
  sgtl5000_1.enable();
  sgtl5000_1.volume(0.5);
  sgtl5000_2.setAddress(HIGH);
  sgtl5000_2.enable();
  sgtl5000_2.volume(0.5);

  // Initialise SD card access.
  SPI.setMOSI(SDCARD_MOSI_PIN);
  SPI.setSCK(SDCARD_SCK_PIN);
  if (!(SD.begin(SDCARD_CS_PIN))) {
    while (1) {
      Serial.println("Unable to access the SD card");
      delay(500);
    }
  }

  delay(1000);
}

void loop() {
  if (sinceTick >= tickLength) {
    sinceTick = sinceTick - tickLength;

    AudioNoInterrupts();
    if (alternator) {
      success = playSdRaw1.play("LOOP1.RAW");
    } else {
      success = playSdRaw2.play("LOOP2.RAW");
    }
    AudioInterrupts();

    if (success) {
      Serial.print("playing ");
    } else {
      Serial.print("failed to load ");
    }
    if (alternator) {
      Serial.println("LOOP1.RAW");
    } else {
      Serial.println("LOOP2.RAW");
    }

    alternator = (alternator + 1) % 2;
  }
}

FWIW I also reproduced the same issue (and fix) without the quad audio setup, just using the AudioOutputI2S object and a single AudioControlSGTL5000. Both audio boards were still attached to the Teensy though, in case that makes a difference.
 
Status
Not open for further replies.
Back
Top