Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 21 of 21

Thread: Bug report: Teensy 4.0 and AudioPlayQueue

  1. #1
    Junior Member
    Join Date
    Apr 2020
    Posts
    9

    Bug report: Teensy 4.0 and AudioPlayQueue

    When AudioPlayQueue.getBuffer() and AudioPlayQueue.playBuffer() are used in an audio rate ISR, they slowly consume AudioMemory buffers and T4.0 crashes when all buffers are used (default 80 for IMXRT1062). The same code works flawlessly on Teensy 3.6 for hours, on 4.0 it cracks up in ~570s. I tried everything I could to bypass this issue, but no luck so far.

    Here's a code to test/reproduce this issue. I would be thankful if someone could run this on T4.0 and report back. If you reduce to AudioMemory(16) in Setup, it will freeze in ~100s.
    Code:
    #include <Audio.h>
    #include <SerialFlash.h>
    
    AudioPlayQueue           audio;
    AudioPlayQueue           audio2;
    AudioOutputI2S           i2s1;
    AudioConnection          patchCord1(audio, 0, i2s1, 0);
    AudioConnection          patchCord2(audio2, 0, i2s1, 1);
    
    uint8_t cycle=0;
    elapsedMillis tme;
    
    void audioISR()    //dummy ISR routine to test getBuffer()/playBuffer()
    {
      if((cycle % 128) == 127){     //for 128 int16 array
        int16_t *p1 = audio.getBuffer();
        p1 = p1; // to avoid unused variable compiler warning
        audio.playBuffer();
      }
      cycle++;
    }
    
    void setup()
    {
      AudioNoInterrupts();
      noInterrupts();
      AudioMemory(512);
      Serial.begin(9600);
      IntervalTimer *t1 = new IntervalTimer();
      t1->begin(audioISR, 1000000.0f / 44100.0f);
      interrupts();
      AudioInterrupts();
    }
    
    void loop()
    {
      if (tme>10000)
      {
        tme=0;
        Serial.printf("Run time: %i s \n", (millis()/1000));
        Serial.printf("CPU: %.2f (%.2f),  MEM: %i (%i)\n",
        AudioProcessorUsage(), AudioProcessorUsageMax(),
        AudioMemoryUsage(), AudioMemoryUsageMax());
        AudioProcessorUsageMaxReset();
        AudioMemoryUsageMaxReset();
      }
    }
    Reports from Serial Monitor:

    Teensy 4.0, no AudioPlayBuffer

    Run time: 10s
    CPU: 0.51 (0.54), MEM: 2 (2)

    Run time: 200s
    CPU: 0.51 (0.54), MEM: 2 (2)

    Run time: 2340s
    CPU: 0.51 (0.54), MEM: 2 (2)
    Teensy 4.0, one AudioPlayBuffer

    Run time: 10s
    CPU: 0.52 (0.57), MEM: 3 (4)

    Run time: 20s
    CPU: 0.52 (0.56), MEM: 4 (5)

    Run time: 30s
    CPU: 0.52 (0.57), MEM: 6 (7)

    Run time: 200s
    CPU: 0.52 (0.57), MEM: 29 (30)

    Run time: 560s
    CPU: 0.52 (0.57), MEM: 79 (80)

    Run time: 570s
    CPU: 0.52 (0.57), MEM: 80 (81)

    ======= CRASH =======
    Teensy 4.0, two AudioPlayBuffers

    Run time: 10s
    CPU: 0.52 (0.55), MEM: 4 (6)

    Run time: 20s
    CPU: 0.52 (0.55), MEM: 6 (8)

    Run time: 30s
    CPU: 0.52 (0.55), MEM: 10 (12)

    Run time: 200s
    CPU: 0.52 (0.55), MEM: 56 (58)

    Run time: 560s
    CPU: 0.52 (0.56), MEM: 156 (158)

    Run time: 570s
    CPU: 0.52 (0.56), MEM: 158 (160)

    ======= CRASH =======
    Teensy 3.6, two AudioPlayBuffers

    Run time: 10s
    CPU: 0.89 (0.96), MEM: 4 (4)

    Run time: 200s
    CPU: 0.89 (0.97), MEM: 2 (4)

    Run time: 2340s
    CPU: 0.89 (0.95), MEM: 2 (4)
    Thanks in advance for your kind answers!

  2. #2
    Senior Member
    Join Date
    Nov 2012
    Posts
    1,476
    It is not safe to use getBuffer or playBuffer in an interrupt routine. The getBuffer function can call yield() and playBuffer has a while loop which won't terminate unless something external (in an interrupt) happens.
    I don't know why it doesn't crash in a T3.6, perhaps you need to let it run much longer.

    Pete

  3. #3
    Junior Member
    Join Date
    Apr 2020
    Posts
    9
    Like I said, it was tested on T3.6 for hours and it works very well. I'm aware it's potentially unsafe, but:
    a) It doesn't crash just like that, I specified exactly what's going on. I know why it happens and when it happens, I just have no idea why or how to prevent/solve it
    b) It's AFAIK the only way to communicate data from Arduino sketch to the AudioLibrary. That's a bummer
    c) How else are you supposed to use getBuffer and playBuffer with just 128 words of data? It gets exhausted in 0.003s, certainly calling it in Void Loop won't do the trick

    If anybody knows a way to use Teensy 4 I2S/TDM without audio library, suggestions are welcome.

  4. #4
    Senior Member
    Join Date
    Nov 2012
    Posts
    1,476
    certainly calling it in Void Loop won't do the trick
    Why not? The T4 chugs along at 600MHz. It can do a lot in 3 milliseconds.

    Try this:
    Code:
    #include <Audio.h>
    #include <SerialFlash.h>
    
    AudioPlayQueue           audio;
    AudioPlayQueue           audio2;
    AudioOutputI2S           i2s1;
    AudioConnection          patchCord1(audio, 0, i2s1, 0);
    AudioConnection          patchCord2(audio2, 0, i2s1, 1);
    
    uint8_t cycle=0;
    elapsedMillis tme;
    
    // set by the ISR
    volatile uint8_t int_flag = 0;
    
    void audioISR()    //dummy ISR routine to test getBuffer()/playBuffer()
    {
      int_flag = 1;
    }
    
    void setup()
    {
      AudioNoInterrupts();
      noInterrupts();
      AudioMemory(512);
      Serial.begin(9600);
      IntervalTimer *t1 = new IntervalTimer();
      t1->begin(audioISR, 1000000.0f / 44100.0f);
      interrupts();
      AudioInterrupts();
    }
    
    void loop()
    {
      if( int_flag) {
        int_flag = 0;
        if((cycle % 128) == 127) {    //for 128 int16 array
          int16_t *p1 = audio.getBuffer();
          p1 = p1; // to avoid unused variable compiler warning
          audio.playBuffer();
        }
        cycle++;
      }
      if (tme>10000) {
        tme=0;
        Serial.printf("Run time: %i s \n", (millis()/1000));
        Serial.printf("CPU: %.2f (%.2f),  MEM: %i (%i)\n",
              AudioProcessorUsage(), AudioProcessorUsageMax(),
              AudioMemoryUsage(), AudioMemoryUsageMax());
        AudioProcessorUsageMaxReset();
        AudioMemoryUsageMaxReset();
      }
    }
    Pete

  5. #5
    Senior Member DD4WH's Avatar
    Join Date
    Oct 2015
    Location
    Central Europe
    Posts
    680
    I am a little puzzled why you use TWO interrupt routines, the queue in the audio lib and an additional one!? But maybe I am misunderstanding your approach.
    I use the record and the play queue with great success with both, the T3.6 and the T4.0 without problems.
    But I do NOT use an IntervalTimer, but I only use the audio lib with its built-in interrupt.
    Maybe also you have to use FreeBuffer in order to release audio blocks?

  6. #6
    Junior Member
    Join Date
    Apr 2020
    Posts
    9
    Quote Originally Posted by el_supremo View Post
    Why not? The T4 chugs along at 600MHz. It can do a lot in 3 milliseconds.
    Well, because I'm doing some other not-so-timing-sensitive things in Void Loop
    Reading inputs, calculating huge amounts of side data and drawing a meaningful representation of all that to SPI LCD takes exactly 7ms per one loop (and I had to write my own specialized routine to drive the LCD, otherwise with the generic library it took 36ms ). You can imagine what it sounds like with 7ms delay...

    Quote Originally Posted by el_supremo View Post
    Try this:
    // set by the ISR
    volatile uint8_t int_flag = 0;

    void audioISR()
    {
    int_flag = 1;
    }
    Thanks for your sugestion Pete, that's exactly where I was heading! In fact I'm trying to do just that right now, even called the variable int_flag (but declared volatile bool instead of uint8) I'll try to prioritize playBuffer() routine and use the remaining time for everything else, hopefully that will not screw up the rest of things going on in the loop.

  7. #7
    Junior Member
    Join Date
    Apr 2020
    Posts
    9
    Quote Originally Posted by DD4WH View Post
    I am a little puzzled why you use TWO interrupt routines, the queue in the audio lib and an additional one!? But maybe I am misunderstanding your approach.
    I'm actually using three interrupt routines in my code, not counting the Audio Lib (which I don't have any control over) One fast at audio rate (44.100Hz) to produce the complex waveforms, one medium at 8.000Hz to crunch and prepare the math calculations for the filters and one slow at 1.000Hz to process the inputs

    Quote Originally Posted by DD4WH View Post
    I use the record and the play queue with great success with both, the T3.6 and the T4.0 without problems. But I do NOT use an IntervalTimer, but I only use the audio lib with its built-in interrupt.
    That's exactly the problem: Audio Lib just doesn't do what I'm trying to accomplish, and the methods to communicate between Arduino sketch and the library are severely limited. So I'm doing everything that's needed in my own routines, and then just serve them to the Audio Lib playBuffer() for output.

    Quote Originally Posted by DD4WH View Post
    Maybe also you have to use FreeBuffer in order to release audio blocks?
    I was under impression that's done automatically by the Audio Lib when all audio blocks are used? At least it works that way on T3.6, beats me why the things would be any different on T4.0. But I'll certainly try if nothing else works, thanks for your suggestion.

  8. #8
    Senior Member+ KurtE's Avatar
    Join Date
    Jan 2014
    Posts
    7,870
    Warning, I more or less never use Audio library... Not because anything is wrong with it, but more because most Audio I typically need is a series of Beeps...

    But if it were me and I needed (or really wanted to control this with ISR doing this.)

    The things I would experiment with include:

    a) Don't call getBuffer from ISR as this can call yield... Instead call available and bail if it returns false. Or better yet remember it failed and try again on your next ISR.

    b) See if one could add a new member function which tries to queue the buffer, but fails if queue is full, and again remember this and try to queue it on the next ISR.
    This function would be simple... Not sure what @PaulStoffregen would think of it worked and you did a PR... But something like:
    Code:
    bool AudioPlayQueue::queueBufferNoWait(void)
    {
    	uint32_t h;
    
    	if (!userblock) return false;
    	h = head + 1;
    	if (h >= max_buffers) h = 0;
    	if (tail == h) return false;
    	queue[h] = userblock;
    	head = h;
    	userblock = NULL;
    	return true;
    }
    But again I don't know if this will fix your issue or not. Hard to know exactly where you are, what crashed...

  9. #9
    Junior Member
    Join Date
    Apr 2020
    Posts
    9
    Quote Originally Posted by KurtE View Post
    Warning, I more or less never use Audio library... Not because anything is wrong with it, but more because most Audio I typically need is a series of Beeps...
    It's OK, we all have our thing and I really want to hear all sugestions and/or feedback

    Quote Originally Posted by KurtE View Post
    But if it were me and I needed (or really wanted to control this with ISR doing this.)

    The things I would experiment with include:

    a) Don't call getBuffer from ISR as this can call yield... Instead call available and bail if it returns false. Or better yet remember it failed and try again on your next ISR.
    Ugh. I've tried to send a flag from ISR and use getBuffer() and playBuffer() in the loop() - been testing this for a few days - but it's extremely unstable. I have to put all blocking stuff in the loop, so there's a 7ms delay between cycles... Even if I comment out all blocking routines, the results are unpredictable - ISRs suck the most of available cycles and it produces all kinds of noises and bumps. All this must be pretty much confusing for the guys who use Teensy to do simpler stuff, but I'm seriously thinking about installing a cooler and bumping it up to 1GHz
    TL/DR: loop() is not usable.

    Quote Originally Posted by KurtE View Post
    b) See if one could add a new member function which tries to queue the buffer, but fails if queue is full, and again remember this and try to queue it on the next ISR.
    This function would be simple... Not sure what @PaulStoffregen would think of it worked and you did a PR...
    Thanks for your suggestion Kurt, I'll try this for sure!

    Quote Originally Posted by KurtE View Post
    But again I don't know if this will fix your issue or not. Hard to know exactly where you are, what crashed...
    We'll see, I've been testing this for a long time. Lowering the proc speed doesn't help, if the ISR is slowed down to 22.050Hz everything works, if I skip every second cycle everything works, if I use T3.6 everything works, so obviously the ISR on T4.0 is interrupting the Audio Lib before it can release memory. I've described the problem in details, and would really like to hear from @PaulStoffregen about this

  10. #10
    Junior Member
    Join Date
    Jun 2018
    Location
    Berlin
    Posts
    14

    Question

    Hello,

    this thread is a bit older, but the problem is still actual.
    I tried a simple code without any extras on a Teensy 4.1.
    At the defined max of 80 blocks the loop stucks.

    I'm using Arduino 1.8.5 and Teensyduino 1.53.

    Code:
    #include <Audio.h>
    
    AudioPlayQueue  playQueue;
    
    int16_t   sampleBuffer[AUDIO_BLOCK_SAMPLES];
    int16_t   *queueTransmitBuffer;
    
    
    void PlayQueue() {
        queueTransmitBuffer = playQueue.getBuffer();
        if (queueTransmitBuffer) {
          memcpy(queueTransmitBuffer, sampleBuffer, AUDIO_BLOCK_SAMPLES * 2);
          Serial.print("Audio Mem Used: ");  
          Serial.println(AudioMemoryUsage());
          playQueue.playBuffer();
        }
        else { digitalWriteFast(13, HIGH); }
    }
    
    void setup() {
        Serial.begin(115200);
        delay(250);
        AudioMemory(100);
        pinMode(13, OUTPUT);
    }
    
    void loop() {
        PlayQueue();
        delay(10);
    }
    The AudioPlayQueue.getBuffer() function always returns a valid buffer address. The pointer is never NULL.

    Ok i don't understand how AudioPlayQueue.getBuffer() should return a NULL.
    Because the function stays in a while-loop until a userblock is allocated:

    Code:
    int16_t * AudioPlayQueue::getBuffer(void)
    {
    	if (userblock) return userblock->data;
    	while (1) {
    		userblock = allocate();
    		if (userblock) return userblock->data;
    		yield();
    	}
    }
    Seems to be a bigger problem

  11. #11
    Junior Member
    Join Date
    Apr 2020
    Posts
    9
    Quote Originally Posted by Werkstatt Kreuzberg View Post
    At the defined max of 80 blocks the loop stucks.

    Yeah, I figured out loop() cycle is a very low priority process and it gets intetrupted very often by a bunch of other things. Lesson learned - don't use loop() with GetBuffer/PlayBuffer, or in fact anything that needs to be executed in <3ms.
    I eventually transferred all my code to custom AudioLibrary objects and now everything works pretty smooth. But it took me A LOT of time.

    Quote Originally Posted by Werkstatt Kreuzberg View Post
    The AudioPlayQueue.getBuffer() function always returns a valid buffer address. The pointer is never NULL.

    Ok i don't understand how AudioPlayQueue.getBuffer() should return a NULL.

    Why should a pointer to AudioPlayQueue.getBuffer() be NULL? You mean you never run out of buffers to play and audio still stutters?
    One 128 words cycle gets emptied in ~2.9ms, 80 buffers can hold approx. ~23ms of audio. It should be more than enough, especially for crazy fast T4 - the problem isn't there
    Btw. this
    Code:
    AudioMemory(100);
    is too much for sure. Check AudioMemoryUsage(), it probably reports under 10 buffers.

  12. #12
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    12,655
    To start loop() cycles some millions of times each second on a t_4.x - slowed by added code or any delay()'s. The main code is loop() - _isr()'s and background tasks will take time. If core processing takes time in loop() of course then that has to be factored in.

  13. #13
    Junior Member
    Join Date
    Jun 2018
    Location
    Berlin
    Posts
    14
    I'm tried the same code on a Teensy 3.6 and it's the same behavior.

  14. #14
    Junior Member
    Join Date
    Jun 2018
    Location
    Berlin
    Posts
    14
    Quote Originally Posted by Subfanatic View Post
    Yeah, I figured out loop() cycle is a very low priority process and it gets intetrupted very often by a bunch of other things. Lesson learned - don't use loop() with GetBuffer/PlayBuffer, or in fact anything that needs to be executed in <3ms.
    I eventually transferred all my code to custom AudioLibrary objects and now everything works pretty smooth. But it took me A LOT of time.

    Why should a pointer to AudioPlayQueue.getBuffer() be NULL? You mean you never run out of buffers to play and audio still stutters?
    One 128 words cycle gets emptied in ~2.9ms, 80 buffers can hold approx. ~23ms of audio. It should be more than enough, especially for crazy fast T4 - the problem isn't there
    Btw. this
    Code:
    AudioMemory(100);
    is too much for sure. Check AudioMemoryUsage(), it probably reports under 10 buffers.
    Yes you are right. It takes 2.9mS for a block of 128 samples. Thats why i put a delay of 10 mS in the loop.
    There should be more than enough time to release all used blocks.
    But the library allocates a new one each call and doesn't release the old ones.
    Just run the code

  15. #15
    Senior Member
    Join Date
    Nov 2012
    Posts
    1,476
    The bug is in your code. The Audio library expects that when you play a buffer, you have allocated an audio object that will receive and process that buffer. All you are doing is allocating buffers without providing the library with a way to use them.
    Add these statements after AudioPlayQueue
    Code:
    AudioOutputI2S   audioOutput;
    AudioControlSGTL5000 audioShield;
    AudioConnection c1(playQueue, 0, audioOutput, 0);
    That will allow the library to pass a buffer from playQueue to audioOutput.
    Your code will now always print:
    Code:
    Audio Mem Used: 1
    Pete

  16. #16
    Junior Member
    Join Date
    Jun 2018
    Location
    Berlin
    Posts
    14
    Thank you Pete!!! In my actual project on a Teensy 3.6 i was using AudioOutputUSB and the dummy to use it was AudioOutputAnalog.
    After compiling the code and running on a Teensy 4.1 i had these memory problems. Yes there is no DAC on Teensy 4.1, so it will not work.
    A test with AudioOutputMQS as dummy looks good. In the end i have the same problem like Subfanatic.
    I have to feed a large sample buffer block by block in the right moment into the queue. Unfortunately there is no way to check the actual playing position
    of AudioPlayQueue.playBuffer(). So have to feed it "blind". When you feed it too slow it crackles, when you feed it too fast the
    used audio memory is growing. When you reach the memory limit, the consumed processing time by interrupts is also growing.
    But you helped me a lot to understand how the library works. Thanks

  17. #17
    Senior Member DD4WH's Avatar
    Join Date
    Oct 2015
    Location
    Central Europe
    Posts
    680
    Quote Originally Posted by Werkstatt Kreuzberg View Post
    Thank you Pete!!! In my actual project on a Teensy 3.6 i was using AudioOutputUSB and the dummy to use it was AudioOutputAnalog.
    After compiling the code and running on a Teensy 4.1 i had these memory problems. Yes there is no DAC on Teensy 4.1, so it will not work.
    A test with AudioOutputMQS as dummy looks good. In the end i have the same problem like Subfanatic.
    I have to feed a large sample buffer block by block in the right moment into the queue. Unfortunately there is no way to check the actual playing position
    of AudioPlayQueue.playBuffer(). So have to feed it "blind". When you feed it too slow it crackles, when you feed it too fast the
    used audio memory is growing. When you reach the memory limit, the consumed processing time by interrupts is also growing.
    But you helped me a lot to understand how the library works. Thanks
    Not sure if I understand what the problem is. I use Teensy audio lib in many projects only as a queue in - queue out helper.

    Maybe you would like to have a look at an example which works:

    https://github.com/DD4WH/Uniformly_p...et_impulse.ino

    There are some things missing in your sketch. I assume you would like to do the following:

    * let the audio lib do the interrupt and DMA handling
    * deliver one (or as many as you desire) block(s) of audio
    * then your code does some DSP on this block
    * then you push the processed audio into the queue
    * the audio lib plays that block at exactly the right time

    To achieve that you have to do the following (Pseudocode):

    Code:
    AudioRecordQueue QUEUE_IN;
    
    AudioPlayQueue QUEUE_OUT;
    
    int16_t *Audio;
    
    //in setup:
    
    QUEUE_IN.begin();
    
    //in loop:
    
    if(QUEUE_IN.available() > NO_OF_DESIRED_BLOCKS)
    { 
    Audio = QUEUE_IN.readBuffer();
    QUEUE_IN.freeBuffer();
    
    // do some DSP magic with your Audio block
    Audio = QUEUE_OUT.getBuffer();
    Audio = Whatever_your_DSP_has_done;
    
    QUEUE_OUT.playBuffer();
    
    }
    else
    {// wait for the audio blocks}
    I have used the input and output queues of the audio lib in many sketches on the Teensy 3.6, T4.0 and T4.1 without any problems.

  18. #18
    Junior Member
    Join Date
    Jun 2018
    Location
    Berlin
    Posts
    14
    Hello DD4WH,

    thank you for your answer and your recommendations.
    I already took a look at your example. But in my case there is no need for realtime processing.
    I just want to record audio into a ram buffer and play it back in an endless loop.

    Recording audio with AudioRecordQueue is easy. But playing audio with AudioPlayQueue is not.
    One audio block holds approximately 2.9mS of audio. To play it in an endless loop without any
    clicks or crackle it is important to feed the queue fast enough.
    The AudioPlayQueue does not give a feedback when it's time to transmit the next block.
    So you have to feed it in an interval below 2.9mS.
    But this leads me always in a fast increasing amount of the used AudioMemory and also in a
    massive load of the processor.

    I wrote a little program to explain you my problem. It transmits audio blocks in a loop, shows the AudioMemory usage
    and also the counted loops per second. The blinking led is just a dummy load.


    Code:
    /* Teensy 4.1, Arduino 1.8.5, Teensyduino 1.53 */
    
    #include <Audio.h>
    
    AudioPlayQueue    playQueue;
    AudioOutputSPDIF3 spdif3Output;
    
    AudioConnection patchCord1(playQueue, 0, spdif3Output, 0);
    
    uint32_t  blinkLedTicks = 0;
    uint32_t  serialTerminalOutMillis = 0;
    uint32_t  serialTerminalOutRuns = 0;
    uint32_t  serialTerminalRowCnt = 0;
    uint32_t  transmitAudioBlockIntervalMicros = 0;
    
    int16_t   sampleBuffer[AUDIO_BLOCK_SAMPLES];
    int16_t   *queueTransmitBuffer;
    uint32_t  audioMemUsed;
    uint32_t  maxAudioMemUsed;
    uint32_t  audioCpuUsed;
    uint32_t  maxAudioCpuUsed;
    
    uint32_t  playQueueCalls = 0;
    uint32_t  loopCnt = 0;
    uint32_t  avgLoopsPerSecond = 0;
    uint32_t  testState = 0;
    
    #define   AUDIO_MEMORY_BLOCKS             100
    #define   AUDIO_BLOCK_TRANSMIT_INTERVAL   2900      // In uS
    
    #define   SERIAL_TERMINAL_OUT_INTERVAL    500       // In mS
    #define   MAX_SERIAL_TERMINAL_OUT_RUNS    20        // Max rows for the test. If it's Zero the test runs until Loops/S drops.
    
    void setup() {
        Serial.begin(115200);
        delay(250);
        AudioMemory(AUDIO_MEMORY_BLOCKS);
        Serial.printf("\n*AudioPlayQueue Test*    AudioBlockSamples: %d    AudioMemory: %d    AudioBlock Tx Interval: %d uS \n\n", AUDIO_BLOCK_SAMPLES, AUDIO_MEMORY_BLOCKS, AUDIO_BLOCK_TRANSMIT_INTERVAL);
        pinMode(13, OUTPUT);
    }
    
    void loop() {
        loopCnt++;
        PlayQueue();
        SerialTerminalOut();
        BlinkLed();
    }
    
    void PlayQueue() {
        if (micros() - transmitAudioBlockIntervalMicros >= AUDIO_BLOCK_TRANSMIT_INTERVAL) {
          transmitAudioBlockIntervalMicros = micros();
          queueTransmitBuffer = playQueue.getBuffer();
          memcpy(queueTransmitBuffer, sampleBuffer, AUDIO_BLOCK_SAMPLES * 2);
          playQueue.playBuffer();
          audioMemUsed = AudioMemoryUsage();
          audioCpuUsed = AudioProcessorUsage();
          if (audioMemUsed > maxAudioMemUsed) { maxAudioMemUsed = audioMemUsed; }
          if (audioCpuUsed > maxAudioCpuUsed) { maxAudioCpuUsed = audioCpuUsed; }
          playQueueCalls++;
        }
    }
    
    void SerialTerminalOut() {
        if (MAX_SERIAL_TERMINAL_OUT_RUNS == 0) { serialTerminalOutRuns = 0; }
        if (testState < 3) {
          if (millis() - serialTerminalOutMillis > SERIAL_TERMINAL_OUT_INTERVAL) {
            uint32_t loopspersecond = float(loopCnt * (1000.0f / (millis() - serialTerminalOutMillis)));
            serialTerminalOutMillis = millis();
            avgLoopsPerSecond = (avgLoopsPerSecond + loopspersecond) / 2;
            loopCnt = 0;
            Serial.printf("%3d    PlayQueue Calls: %5d    AudioMemory Usage: %2d    Loops/S: %7d\n", ++serialTerminalRowCnt, playQueueCalls, audioMemUsed, loopspersecond);
            if (playQueueCalls > 1 && loopspersecond < avgLoopsPerSecond / 4) { testState++; }            // Loops/S droped? Stop the test.
            if (serialTerminalOutRuns++ +1 == MAX_SERIAL_TERMINAL_OUT_RUNS) {  testState = 3; }           // Max runs reached? Stop the test.
          }
        }
        if (testState == 3) {
          Serial.printf("\nMax AudioProcessor Usage: %2d\n", maxAudioCpuUsed);
          testState++;
        }
    }
    
    void BlinkLed() {
      blinkLedTicks++;
      if (blinkLedTicks > 100) {
        blinkLedTicks = 0;
        digitalWriteFast(13, !digitalReadFast(13));
      }
    }
    With an interval of 2900 microseconds between the audio blocks everything seems to be fine.
    But the audio will crackle because of the gap between the blocks (the interval between the rows is 500 milliseconds):

    Code:
    *AudioPlayQueue Test*    AudioBlockSamples: 128    AudioMemory: 100    AudioBlock Tx Interval: 2900 uS 
    
      1    PlayQueue Calls:     1    AudioMemory Usage:  1    Loops/S:       1
      2    PlayQueue Calls:   173    AudioMemory Usage:  2    Loops/S: 5828221
      3    PlayQueue Calls:   346    AudioMemory Usage:  2    Loops/S: 5833714
      4    PlayQueue Calls:   518    AudioMemory Usage:  2    Loops/S: 5833928
      5    PlayQueue Calls:   691    AudioMemory Usage:  2    Loops/S: 5833716
      6    PlayQueue Calls:   864    AudioMemory Usage:  2    Loops/S: 5833716
      7    PlayQueue Calls:  1036    AudioMemory Usage:  2    Loops/S: 5833928
      8    PlayQueue Calls:  1209    AudioMemory Usage:  2    Loops/S: 5833712
      9    PlayQueue Calls:  1381    AudioMemory Usage:  2    Loops/S: 5833934
     10    PlayQueue Calls:  1554    AudioMemory Usage:  2    Loops/S: 5833710
     11    PlayQueue Calls:  1727    AudioMemory Usage:  2    Loops/S: 5833701
     12    PlayQueue Calls:  1899    AudioMemory Usage:  2    Loops/S: 5833916
     13    PlayQueue Calls:  2072    AudioMemory Usage:  2    Loops/S: 5833705
     14    PlayQueue Calls:  2244    AudioMemory Usage:  2    Loops/S: 5833918
     15    PlayQueue Calls:  2417    AudioMemory Usage:  2    Loops/S: 5833746
     16    PlayQueue Calls:  2590    AudioMemory Usage:  2    Loops/S: 5833701
     17    PlayQueue Calls:  2762    AudioMemory Usage:  2    Loops/S: 5833918
     18    PlayQueue Calls:  2935    AudioMemory Usage:  2    Loops/S: 5833703
     19    PlayQueue Calls:  3108    AudioMemory Usage:  2    Loops/S: 5833703
     20    PlayQueue Calls:  3280    AudioMemory Usage:  2    Loops/S: 5833916
    
    Max AudioProcessor Usage:  0
    With an interval of 2800 microseconds audio is fine, but first grows the used AudioMemory and
    then rises the processor load very fast:

    Code:
    *AudioPlayQueue Test*    AudioBlockSamples: 128    AudioMemory: 100    AudioBlock Tx Interval: 2800 uS 
    
      1    PlayQueue Calls:     1    AudioMemory Usage:  1    Loops/S:       1
      2    PlayQueue Calls:   179    AudioMemory Usage:  9    Loops/S: 5715750
      3    PlayQueue Calls:   358    AudioMemory Usage: 15    Loops/S: 5721120
      4    PlayQueue Calls:   537    AudioMemory Usage: 21    Loops/S: 5721325
      5    PlayQueue Calls:   716    AudioMemory Usage: 28    Loops/S: 5721120
      6    PlayQueue Calls:   895    AudioMemory Usage: 34    Loops/S: 5721116
      7    PlayQueue Calls:  1074    AudioMemory Usage: 40    Loops/S: 5721331
      8    PlayQueue Calls:  1253    AudioMemory Usage: 46    Loops/S: 5721132
      9    PlayQueue Calls:  1432    AudioMemory Usage: 53    Loops/S: 5721315
     10    PlayQueue Calls:  1611    AudioMemory Usage: 59    Loops/S: 5721118
     11    PlayQueue Calls:  1790    AudioMemory Usage: 65    Loops/S: 5721130
     12    PlayQueue Calls:  1969    AudioMemory Usage: 72    Loops/S: 5721333
     13    PlayQueue Calls:  2147    AudioMemory Usage: 78    Loops/S: 5721112
     14    PlayQueue Calls:  2323    AudioMemory Usage: 80    Loops/S: 2664213
     15    PlayQueue Calls:  2496    AudioMemory Usage: 80    Loops/S:     344
     16    PlayQueue Calls:  2669    AudioMemory Usage: 80    Loops/S:     344
     17    PlayQueue Calls:  2842    AudioMemory Usage: 80    Loops/S:     344
    
    Max AudioProcessor Usage:  0
    And with an interval of 1000 microseconds:
    Code:
    *AudioPlayQueue Test*    AudioBlockSamples: 128    AudioMemory: 100    AudioBlock Tx Interval: 1000 uS 
    
      1    PlayQueue Calls:     1    AudioMemory Usage:  1    Loops/S:       1
      2    PlayQueue Calls:   252    AudioMemory Usage: 80    Loops/S: 1367332
      3    PlayQueue Calls:   425    AudioMemory Usage: 80    Loops/S:     344
      4    PlayQueue Calls:   598    AudioMemory Usage: 80    Loops/S:     344
      5    PlayQueue Calls:   771    AudioMemory Usage: 80    Loops/S:     344
    
    Max AudioProcessor Usage:  0
    That's my problem. May be it is not a good idea to use the AudioPlayQueue for looped playback...
    May be i'm totally wrong and i have to use the AudioPlayQueue in an other way.


  19. #19
    Junior Member
    Join Date
    Jun 2018
    Location
    Berlin
    Posts
    14
    Ok i guess i'm a bit closer to the issue.

    I changed the cpu speed to the minimum of 24MHz.
    That's the result with an interval of 2700 microseconds:

    Code:
    *AudioPlayQueue Test*
    
    AudioBlockSamples: 128    AudioMemory: 100    AudioBlock Tx Interval: 2700 uS    F_CPU: 24 MHz
    
      1    PlayQueue Calls:     1    AudioMemory Usage:  1    Loops/S:       1
      2    PlayQueue Calls:   173    AudioMemory Usage:  2    Loops/S:  194754
      3    PlayQueue Calls:   346    AudioMemory Usage:  2    Loops/S:  194950
      4    PlayQueue Calls:   518    AudioMemory Usage:  2    Loops/S:  195141
      5    PlayQueue Calls:   691    AudioMemory Usage:  2    Loops/S:  194958
      6    PlayQueue Calls:   864    AudioMemory Usage:  2    Loops/S:  195039
      7    PlayQueue Calls:  1036    AudioMemory Usage:  2    Loops/S:  195223
      8    PlayQueue Calls:  1209    AudioMemory Usage:  2    Loops/S:  194948
      9    PlayQueue Calls:  1382    AudioMemory Usage:  2    Loops/S:  195077
     10    PlayQueue Calls:  1554    AudioMemory Usage:  2    Loops/S:  195017
     11    PlayQueue Calls:  1727    AudioMemory Usage:  2    Loops/S:  195023
     12    PlayQueue Calls:  1899    AudioMemory Usage:  2    Loops/S:  195131
     13    PlayQueue Calls:  2072    AudioMemory Usage:  2    Loops/S:  195019
     14    PlayQueue Calls:  2244    AudioMemory Usage:  2    Loops/S:  195002
     15    PlayQueue Calls:  2417    AudioMemory Usage:  2    Loops/S:  195081
     16    PlayQueue Calls:  2590    AudioMemory Usage:  2    Loops/S:  194942
     17    PlayQueue Calls:  2762    AudioMemory Usage:  2    Loops/S:  195209
     18    PlayQueue Calls:  2935    AudioMemory Usage:  2    Loops/S:  195021
     19    PlayQueue Calls:  3108    AudioMemory Usage:  2    Loops/S:  195025
     20    PlayQueue Calls:  3280    AudioMemory Usage:  2    Loops/S:  195135
    
    Max AudioProcessor Usage:  0
    Everything seems to be fine. But i should check the audio output for a meaningful test in a next step.

    Now the same for the next possible cpu speed at 150MHz:

    Code:
    *AudioPlayQueue Test*
    
    AudioBlockSamples: 128    AudioMemory: 100    AudioBlock Tx Interval: 2700 uS    F_CPU: 150 MHz
    
      1    PlayQueue Calls:     1    AudioMemory Usage:  1    Loops/S:       1
      2    PlayQueue Calls:   186    AudioMemory Usage: 15    Loops/S: 1401570
      3    PlayQueue Calls:   371    AudioMemory Usage: 28    Loops/S: 1402838
      4    PlayQueue Calls:   557    AudioMemory Usage: 41    Loops/S: 1402950
      5    PlayQueue Calls:   742    AudioMemory Usage: 54    Loops/S: 1402918
      6    PlayQueue Calls:   928    AudioMemory Usage: 67    Loops/S: 1402832
      7    PlayQueue Calls:  1113    AudioMemory Usage: 79    Loops/S: 1403028
      8    PlayQueue Calls:  1288    AudioMemory Usage: 80    Loops/S:  165173
      9    PlayQueue Calls:  1461    AudioMemory Usage: 80    Loops/S:     344
     10    PlayQueue Calls:  1634    AudioMemory Usage: 80    Loops/S:     344
    
    Max AudioProcessor Usage:  0
    So to me this looks like an internal problem of the library or am i wrong?

  20. #20
    Junior Member
    Join Date
    Jun 2018
    Location
    Berlin
    Posts
    14
    To be sure that audio is working correctly with the transmit interval of 2700 microseconds and 24MHz cpu clock i have added usb outs
    and generated a sine wave in the sample buffer:

    Code:
    /* Teensy 4.1, Arduino 1.8.5, Teensyduino 1.53 */
    #include <Audio.h>
    
    AudioPlayQueue    playQueue;
    AudioOutputSPDIF3 spdif3Output;
    AudioOutputUSB    usbOutput;
    
    AudioConnection patchCord1(playQueue, 0, spdif3Output, 0);
    AudioConnection patchCord2(playQueue, 0, usbOutput, 0);
    AudioConnection patchCord3(playQueue, 0, usbOutput, 1);
    
    uint32_t  blinkLedTicks = 0;
    uint32_t  serialTerminalOutMillis = 0;
    uint32_t  serialTerminalOutRuns = 0;
    uint32_t  serialTerminalRowCnt = 0;
    uint32_t  transmitAudioBlockIntervalMicros = 0;
    
    const int32_t  samples = AUDIO_BLOCK_SAMPLES * 64;
    int16_t   sampleBuffer[samples];
    uint32_t  bufferPosition = 0;
    int16_t   *queueTransmitBuffer;
    uint32_t  audioMemUsed;
    uint32_t  maxAudioMemUsed;
    uint32_t  audioCpuUsed;
    uint32_t  maxAudioCpuUsed;
    
    uint32_t  playQueueCalls = 0;
    uint32_t  loopCnt = 0;
    uint32_t  avgLoopsPerSecond = 0;
    uint32_t  testState = 0;
    
    #define   AUDIO_MEMORY_BLOCKS             100
    #define   AUDIO_BLOCK_TRANSMIT_INTERVAL   2700      // In uS
    
    #define   SERIAL_TERMINAL_OUT_INTERVAL    500       // In mS
    #define   MAX_SERIAL_TERMINAL_OUT_RUNS    10        // Max rows for the test. If it's Zero the test runs until Loops/S drops.
    
    
    void setup() {
        Serial.begin(115200);
        delay(500);
        AudioMemory(AUDIO_MEMORY_BLOCKS);
        Serial.printf("\n*AudioPlayQueue Test*\n\n");
        Serial.printf("AudioBlockSamples: %d    AudioMemory: %d    SampleTime: %d mS    AudioBlock Tx Interval: %d uS    F_CPU: %d MHz\n\n", AUDIO_BLOCK_SAMPLES, AUDIO_MEMORY_BLOCKS, samples * 1000 / 44100, AUDIO_BLOCK_TRANSMIT_INTERVAL, F_CPU/1000000);
        pinMode(13, OUTPUT);
    
        /* Build a sine wave */
        uint32_t cycles = (((samples-1) * 1000) / 44100);
        for (uint32_t i = 0; i < samples; i++) {
          sampleBuffer[i] = int16_t((7000 * (sin((i * (2 * PI * cycles)) / samples))) / 2);
        }
    }
    
    void loop() {
        loopCnt++;
        PlayQueue();
        SerialTerminalOut();
        BlinkLed();
    }
    
    void PlayQueue() {
        if (micros() - transmitAudioBlockIntervalMicros >= AUDIO_BLOCK_TRANSMIT_INTERVAL) {
          transmitAudioBlockIntervalMicros = micros();
          queueTransmitBuffer = playQueue.getBuffer();
          memcpy(queueTransmitBuffer, sampleBuffer + bufferPosition, AUDIO_BLOCK_SAMPLES * 2);
          playQueue.playBuffer();
          audioMemUsed = AudioMemoryUsage();
          audioCpuUsed = AudioProcessorUsage();
          if (audioMemUsed > maxAudioMemUsed) { maxAudioMemUsed = audioMemUsed; }
          if (audioCpuUsed > maxAudioCpuUsed) { maxAudioCpuUsed = audioCpuUsed; }
          playQueueCalls++;
          bufferPosition = bufferPosition + AUDIO_BLOCK_SAMPLES;
          if (bufferPosition == samples) { bufferPosition = 0; }
        }
    }
    
    void SerialTerminalOut() {
        if (MAX_SERIAL_TERMINAL_OUT_RUNS == 0) { serialTerminalOutRuns = 0; }
        if (testState < 3) {
          if (millis() - serialTerminalOutMillis > SERIAL_TERMINAL_OUT_INTERVAL) {
            uint32_t loopspersecond = float(loopCnt * (1000.0f / (millis() - serialTerminalOutMillis)));
            serialTerminalOutMillis = millis();
            avgLoopsPerSecond = (avgLoopsPerSecond + loopspersecond) / 2;
            loopCnt = 0;
            Serial.printf("%3d    PlayQueue Calls: %5d    AudioMemory Usage: %2d    Loops/S: %7d\n", ++serialTerminalRowCnt, playQueueCalls, audioMemUsed, loopspersecond);
            if (playQueueCalls > 1 && loopspersecond < avgLoopsPerSecond / 4) { testState++; }            // Loops/S droped? Stop the test.
            if (serialTerminalOutRuns++ +1 == MAX_SERIAL_TERMINAL_OUT_RUNS) {  testState = 3; }           // Max runs reached? Stop the test.
          }
        }
        if (testState == 3) {
          Serial.printf("\nMax AudioProcessor Usage: %2d\n", maxAudioCpuUsed);
          testState++;
        }
    }
    
    void BlinkLed() {
      blinkLedTicks++;
      if (blinkLedTicks > 100) {
        blinkLedTicks = 0;
        digitalWriteFast(13, !digitalReadFast(13));
      }
    }
    The result looks like in the previous test:

    Code:
    *AudioPlayQueue Test*
    
    AudioBlockSamples: 128    AudioMemory: 100    SampleTime: 185 mS    AudioBlock Tx Interval: 2700 uS    F_CPU: 24 MHz
    
      1    PlayQueue Calls:     1    AudioMemory Usage:  1    Loops/S:       1
      2    PlayQueue Calls:   173    AudioMemory Usage:  2    Loops/S:  192824
      3    PlayQueue Calls:   346    AudioMemory Usage:  2    Loops/S:  192888
      4    PlayQueue Calls:   519    AudioMemory Usage:  2    Loops/S:  192952
      5    PlayQueue Calls:   691    AudioMemory Usage:  2    Loops/S:  193033
      6    PlayQueue Calls:   864    AudioMemory Usage:  2    Loops/S:  192886
      7    PlayQueue Calls:  1036    AudioMemory Usage:  2    Loops/S:  193089
      8    PlayQueue Calls:  1209    AudioMemory Usage:  2    Loops/S:  192914
      9    PlayQueue Calls:  1382    AudioMemory Usage:  2    Loops/S:  192882
     10    PlayQueue Calls:  1554    AudioMemory Usage:  2    Loops/S:  193081
    
    Max AudioProcessor Usage:  1
    And the audio signal sounds absolutely clean. I use this little tool to route the Teensy usb outs to my soundcard:

    Click image for larger version. 

Name:	AudioRepeater.PNG 
Views:	8 
Size:	43.7 KB 
ID:	22055

    For a cpu speed of 24MHz i got the following transmit interval values:
    2475 microseconds is the shortest transmit interval that still works. 2474 microseconds leads to the described problem.
    2898 microseconds is the longest transmit interval before some crackle starts.

    For a cpu speed of 600MHz the shortest usable interval is 2885 microseconds. The longest usable interval is 2902 microseconds.
    That means there is a tolerance range of 8 microseconds when i use an interval of 2894 microseconds.
    That's not much. In a "normal" application it will fail.
    So what can i do? Using an interval timer like Subfanatic did??

  21. #21
    Junior Member
    Join Date
    Jun 2018
    Location
    Berlin
    Posts
    14

    Red face

    Quote Originally Posted by Werkstatt Kreuzberg View Post
    So what can i do? Using an interval timer like Subfanatic did??
    And here comes the answer. I have tried different types of timers with no success. All kinds of calling the AudioPlayQueue from an interrupt are leading to problems.
    So i decided to link the PlayQueue to a RecordQueue to get the right timing for the audio library. Every time the RecordQueue receives a new audio packet it triggers the PlayQueue.
    And this looks very good to me.

    Sample played from EXTMEM:

    Code:
    *AudioPlayQueue Test*
    
    AudioBlockSamples: 128    AudioMem: 10    BufSize: 32768 Bytes    SampleT: 371 mS    FCPU: 600 MHz    EXTMEM    TestInterval: 500 mS
    
      1    PlayQueueCalls   0    LastTxIntervals   Min    0   Avg    0   Max     0 uS    AudioMemUsed  0    Loops/S       1
      2    PlayQueueCalls 173    LastTxIntervals   Min    0   Avg    0   Max     0 uS    AudioMemUsed  2    Loops/S 6813184
      3    PlayQueueCalls 172    LastTxIntervals   Min 2896   Avg 2901   Max  2907 uS    AudioMemUsed  2    Loops/S 6832277
      4    PlayQueueCalls 173    LastTxIntervals   Min 2898   Avg 2902   Max  2908 uS    AudioMemUsed  2    Loops/S 6832345
      5    PlayQueueCalls 173    LastTxIntervals   Min 2895   Avg 2902   Max  2907 uS    AudioMemUsed  2    Loops/S 6832297
      6    PlayQueueCalls 172    LastTxIntervals   Min 2896   Avg 2901   Max  2910 uS    AudioMemUsed  2    Loops/S 6832990
      7    PlayQueueCalls 173    LastTxIntervals   Min 2897   Avg 2901   Max  2907 uS    AudioMemUsed  2    Loops/S 6832443
      8    PlayQueueCalls 172    LastTxIntervals   Min 2894   Avg 2901   Max  2909 uS    AudioMemUsed  2    Loops/S 6832742
      9    PlayQueueCalls 173    LastTxIntervals   Min 2895   Avg 2902   Max  2908 uS    AudioMemUsed  2    Loops/S 6832591
     10    PlayQueueCalls 173    LastTxIntervals   Min 2895   Avg 2901   Max  2909 uS    AudioMemUsed  2    Loops/S 6832639
    
    MaxAudioCpu Used:  0    ElapsedTime: 5 Seconds    TxIntervals   Min: 2894   Avg: 2901   Max: 2910 uS
    Sample played from DMAMEM:

    Code:
    *AudioPlayQueue Test*
    
    AudioBlockSamples: 128    AudioMem: 10    BufSize: 32768 Bytes    SampleT: 371 mS    FCPU: 600 MHz    DMAMEM    TestInterval: 500 mS
    
      1    PlayQueueCalls   0    LastTxIntervals   Min    0   Avg    0   Max     0 uS    AudioMemUsed  0    Loops/S       1
      2    PlayQueueCalls 173    LastTxIntervals   Min    0   Avg    0   Max     0 uS    AudioMemUsed  2    Loops/S 6828643
      3    PlayQueueCalls 172    LastTxIntervals   Min 2900   Avg 2901   Max  2903 uS    AudioMemUsed  2    Loops/S 6834204
      4    PlayQueueCalls 173    LastTxIntervals   Min 2901   Avg 2902   Max  2903 uS    AudioMemUsed  2    Loops/S 6833840
      5    PlayQueueCalls 173    LastTxIntervals   Min 2901   Avg 2902   Max  2903 uS    AudioMemUsed  2    Loops/S 6833998
      6    PlayQueueCalls 172    LastTxIntervals   Min 2901   Avg 2902   Max  2903 uS    AudioMemUsed  2    Loops/S 6834184
      7    PlayQueueCalls 173    LastTxIntervals   Min 2901   Avg 2902   Max  2903 uS    AudioMemUsed  2    Loops/S 6834008
      8    PlayQueueCalls 172    LastTxIntervals   Min 2901   Avg 2902   Max  2903 uS    AudioMemUsed  2    Loops/S 6834233
      9    PlayQueueCalls 173    LastTxIntervals   Min 2900   Avg 2901   Max  2903 uS    AudioMemUsed  2    Loops/S 6834066
     10    PlayQueueCalls 173    LastTxIntervals   Min 2900   Avg 2901   Max  2903 uS    AudioMemUsed  2    Loops/S 6834120
    
    MaxAudioCpu Used:  0    ElapsedTime: 5 Seconds    TxIntervals   Min: 2900   Avg: 2901   Max: 2903 uS
    No crackle, no problems


    Code:
    /* Teensy 4.1, Arduino 1.8.5, Teensyduino 1.53 */
    #include <Audio.h>
    
    AudioSynthWaveformDc  dcSynth;
    AudioRecordQueue      recordQueue;
    AudioPlayQueue        playQueue;
    AudioOutputSPDIF3     spdif3Output;
    AudioOutputUSB        usbOutput;
    
    AudioConnection patchCord1(dcSynth, 0, recordQueue, 0);
    AudioConnection patchCord2(playQueue, 0, spdif3Output, 0);
    AudioConnection patchCord3(playQueue, 0, usbOutput, 0);
    AudioConnection patchCord4(playQueue, 0, usbOutput, 1);
    
    uint32_t  blinkLedTicks = 0;
    uint32_t  serialTerminalOutMillis = 0;
    uint32_t  serialTerminalOutRuns = 0;
    uint32_t  serialTerminalRowCnt = 0;
    uint32_t  transmitAudioBlockIntervalMicros = 0;
    uint32_t  elapsedTime = 0;
    
    #define MEM_TO_USE    2
    
    const int32_t  SAMPLES = AUDIO_BLOCK_SAMPLES * 128;
    #if MEM_TO_USE == 1
    EXTMEM int16_t   sampleBuffer[SAMPLES];
    char* memText = "EXTMEM";
    #elif MEM_TO_USE == 2
    DMAMEM int16_t   sampleBuffer[SAMPLES];
    char* memText = "DMAMEM";
    #elif MEM_TO_USE == 3
    int16_t   sampleBuffer[SAMPLES];
    char* memText = "RAM";
    #endif
    
    uint32_t  bufferPosition = 0;
    int16_t   *queueTransmitBuffer;
    uint32_t  audioMemUsed;
    uint32_t  maxAudioMemUsed;
    uint32_t  audioCpuUsed;
    uint32_t  maxAudioCpuUsed;
    
    uint32_t  triggerPlayQueueCnt = 0;
    uint32_t  playQueueCalls = 0;
    uint32_t  playQueueCallsSum = 0;
    uint32_t  loopCnt = 0;
    uint32_t  avgLoopsPerSecond = 0;
    uint32_t  testState = 0;
    
    bool      buildNewSineWave = true;
    uint32_t  sineWaveFrequency = 1000;
    uint32_t  sineWaveFrequencyDirection = 0;
    #define   SINE_WAVE_MIN_FREQUENCY         100
    #define   SINE_WAVE_MAX_FREQUENCY         1000
    #define   SINE_WAVE_FREQUENCY_CHANGE      100
    
    #define   AUDIO_MEMORY_BLOCKS             10
    
    #define   SERIAL_TERMINAL_OUT_INTERVAL    500             // In mS
    #define   MAX_SERIAL_TERMINAL_OUT_RUNS    10              // Max rows for the test. If it's Zero the test runs until Loops/S drops.
    
    
    const uint32_t  CLOCK_CYCLES_PER_MICROSECOND = F_CPU / 1000000;
    uint32_t  lastTxAudioBlockIntervalCycCnt = 0;
    uint32_t  txAudioBlockIntervalCycleCountMicros = 0;
    uint32_t  txAudioBlockIntervalCycleCountMicrosSum = 0;
    uint32_t  txAudioBlockIntervalCycleCountMicrosSumSum = 0;
    uint32_t  lastMinTxAudioBlockIntervalCycleCountMicros = 0;
    uint32_t  lastMaxTxAudioBlockIntervalCycleCountMicros = 0;
    uint32_t  overallMinTxAudioBlockIntervalCycleCountMicros = 9999;
    uint32_t  overallMaxTxAudioBlockIntervalCycleCountMicros = 0;
    
    
    void setup() {
        /* Enable Clock Cycle Counter */
        ARM_DEMCR |= ARM_DEMCR_TRCENA;
        ARM_DWT_CTRL |= ARM_DWT_CTRL_CYCCNTENA;
      
        Serial.begin(115200);
        delay(500);
        AudioMemory(AUDIO_MEMORY_BLOCKS);
        Serial.printf("\n*AudioPlayQueue Test*\n\n");
        Serial.printf("AudioBlockSamples: %d    AudioMem: %d    BufSize: %d Bytes    SampleT: %d mS    FCPU: %d MHz    %s    TestInterval: %d mS\n\n", AUDIO_BLOCK_SAMPLES, AUDIO_MEMORY_BLOCKS, SAMPLES * 2, SAMPLES * 1000 / 44100, F_CPU/1000000, memText, SERIAL_TERMINAL_OUT_INTERVAL);
        pinMode(13, OUTPUT);
        BuildSineWave();
        recordQueue.begin();
        elapsedTime = millis();
    }
    
    void loop() {
        loopCnt++;
        CheckRecordQueue();
        PlayQueue();
        //BuildSineWave();              // Generate a new sine wave each buffer end
        SerialTerminalOut();
        BlinkLed();
    }
    
    void CheckRecordQueue() {
        if (recordQueue.available()) {
          recordQueue.readBuffer();
          recordQueue.freeBuffer();
          triggerPlayQueueCnt++;
        }
    }
    
    void PlayQueue() {
        if (triggerPlayQueueCnt > 0) {
          triggerPlayQueueCnt--;
          queueTransmitBuffer = playQueue.getBuffer();
          memcpy(queueTransmitBuffer, sampleBuffer + bufferPosition, AUDIO_BLOCK_SAMPLES * 2);
          playQueue.playBuffer();
          if (lastTxAudioBlockIntervalCycCnt) txAudioBlockIntervalCycleCountMicros = (ARM_DWT_CYCCNT - lastTxAudioBlockIntervalCycCnt) / CLOCK_CYCLES_PER_MICROSECOND;
          lastTxAudioBlockIntervalCycCnt = ARM_DWT_CYCCNT;
          if (serialTerminalRowCnt > 1) {
            txAudioBlockIntervalCycleCountMicrosSum = txAudioBlockIntervalCycleCountMicrosSum + txAudioBlockIntervalCycleCountMicros;
            txAudioBlockIntervalCycleCountMicrosSumSum = txAudioBlockIntervalCycleCountMicrosSumSum + txAudioBlockIntervalCycleCountMicros;
            if (txAudioBlockIntervalCycleCountMicros < lastMinTxAudioBlockIntervalCycleCountMicros || lastMinTxAudioBlockIntervalCycleCountMicros == 0) lastMinTxAudioBlockIntervalCycleCountMicros = txAudioBlockIntervalCycleCountMicros;
            if (txAudioBlockIntervalCycleCountMicros > lastMaxTxAudioBlockIntervalCycleCountMicros) lastMaxTxAudioBlockIntervalCycleCountMicros = txAudioBlockIntervalCycleCountMicros;
            playQueueCallsSum++;
          }
          playQueueCalls++;
          bufferPosition = bufferPosition + AUDIO_BLOCK_SAMPLES;
          if (bufferPosition == SAMPLES) {
            bufferPosition = 0;
            buildNewSineWave = true;
          }
        }
    }
    
    void SerialTerminalOut() {
        if (MAX_SERIAL_TERMINAL_OUT_RUNS == 0) { serialTerminalOutRuns = 0; }
        if (testState < 3) {
          if (millis() - serialTerminalOutMillis > SERIAL_TERMINAL_OUT_INTERVAL) {
            uint32_t loopspersecond = float(loopCnt * (1000.0f / (millis() - serialTerminalOutMillis)));
            serialTerminalOutMillis = millis();
            avgLoopsPerSecond = (avgLoopsPerSecond + loopspersecond) / 2;
            loopCnt = 0;
            audioMemUsed = AudioMemoryUsage();
            audioCpuUsed = AudioProcessorUsage();
            if (audioMemUsed > maxAudioMemUsed) maxAudioMemUsed = audioMemUsed;
            if (audioCpuUsed > maxAudioCpuUsed) maxAudioCpuUsed = audioCpuUsed;
            Serial.printf("%3d    PlayQueueCalls %3d    LastTxIntervals   Min%5d   Avg%5d   Max%6d uS    AudioMemUsed %2d    Loops/S %7d\n", ++serialTerminalRowCnt, playQueueCalls, lastMinTxAudioBlockIntervalCycleCountMicros, txAudioBlockIntervalCycleCountMicrosSum / playQueueCalls, lastMaxTxAudioBlockIntervalCycleCountMicros, audioMemUsed, loopspersecond);
            if (serialTerminalRowCnt > 1) {
              if (lastMinTxAudioBlockIntervalCycleCountMicros < overallMinTxAudioBlockIntervalCycleCountMicros || overallMinTxAudioBlockIntervalCycleCountMicros == 0) overallMinTxAudioBlockIntervalCycleCountMicros = lastMinTxAudioBlockIntervalCycleCountMicros;
              if (lastMaxTxAudioBlockIntervalCycleCountMicros > overallMaxTxAudioBlockIntervalCycleCountMicros) overallMaxTxAudioBlockIntervalCycleCountMicros = lastMaxTxAudioBlockIntervalCycleCountMicros;
              lastMinTxAudioBlockIntervalCycleCountMicros = 9999;
              lastMaxTxAudioBlockIntervalCycleCountMicros = 0;
              txAudioBlockIntervalCycleCountMicrosSum = 0;
            }
            playQueueCalls = 0;
            if (playQueueCallsSum > 1 && loopspersecond < avgLoopsPerSecond / 4) { testState++; }         // Loops/S droped? Stop the test.
            if (serialTerminalOutRuns++ +1 == MAX_SERIAL_TERMINAL_OUT_RUNS) {  testState = 3; }           // Max runs reached? Stop the test.
    
          }
        }
        if (testState == 3) {
          Serial.printf("\nMaxAudioCpu Used: %2d    ElapsedTime: %d Seconds    TxIntervals   Min: %4d   Avg: %4d   Max: %4d uS\n", maxAudioCpuUsed, (millis() - elapsedTime + SERIAL_TERMINAL_OUT_INTERVAL) / 1000, overallMinTxAudioBlockIntervalCycleCountMicros, txAudioBlockIntervalCycleCountMicrosSumSum / playQueueCallsSum, overallMaxTxAudioBlockIntervalCycleCountMicros);
          testState++;
        }
    }
    
    void BlinkLed() {
      blinkLedTicks++;
      if (blinkLedTicks > 100) {
        blinkLedTicks = 0;
        digitalWriteFast(13, !digitalReadFast(13));
      }
    }
    
    void BuildSineWave() {
        if (buildNewSineWave == true) {
          buildNewSineWave = false;
          if (sineWaveFrequencyDirection == 0) {
            sineWaveFrequency = sineWaveFrequency - SINE_WAVE_FREQUENCY_CHANGE;
            if (sineWaveFrequency <= SINE_WAVE_MIN_FREQUENCY) {
              sineWaveFrequencyDirection = 1;
            }
          }
          else if (sineWaveFrequencyDirection == 1) {
            sineWaveFrequency = sineWaveFrequency + SINE_WAVE_FREQUENCY_CHANGE;
            if (sineWaveFrequency >= SINE_WAVE_MAX_FREQUENCY) {
              sineWaveFrequencyDirection = 0;
            }
          }
          uint32_t cycles = (((SAMPLES-1) * sineWaveFrequency) / 44100);
          for (uint32_t i = 0; i < SAMPLES; i++) {
            sampleBuffer[i] = int16_t((7000 * (sin((i * (2 * PI * cycles)) / SAMPLES))) / 2);
          }
        }
    }
    Last edited by Werkstatt Kreuzberg; 10-13-2020 at 04:53 PM. Reason: Code added

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •