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

Thread: AudioPlayQueue playBuffer() taking 2 - 3ms / old blocks not being freed

  1. #1

    AudioPlayQueue playBuffer() taking 2 - 3ms / old blocks not being freed

    Hello all. I've come across an interesting problem. I'm working on a kick drum generator that uses a sine wavetable. I've been having issues with timing and narrowed it down to AudioPlayQueue playBuffer() taking 2 - 3 milliseconds to complete. Since playBuffer() is in a loop to process the length of the kick drum playBuffer() occurs many times adding up to over 300ms of time used on playBuffer(). I have simplified my code below and have confirmed the code below produces the issue I am having. I am using a Teensy 3.2 clocked to 96mhz. My AudioMemory is set to 48.

    Code:
    int16_t buffer[AUDIO_BUFFER_SIZE];
    
    while(triggered)
    {
    	if (sampleCount >= numSamples)
    	{
    		triggered = false;
    		break;
    	}
    	
    	if (bufferCount == AUDIO_BUFFER_SIZE) //128
    	{
    		int16_t* bufM = audioPlayQueueMono.getBuffer();
    		memcpy(bufM, &buffer[0], AUDIO_BUFFER_SIZE << 1);
    
    		audioPlayQueueMono.playBuffer();
    
    		bufferCount = 0;
    	}
    	
    	buffer[bufferCount] = sample;
    	
    	bufferCount++;
            sampleCount++;
    }
    I noticed that for some reason old blocks are not being cleared out of the queue. Below is output from a timer of playBuffer() in milliseconds and the corresponding AudioMemoryUsed(). As you can see the first 32 memory blocks complete in 0ms but then after the queue is full at 32 it begins to take 2 - 3 ms for each playBuffer() call. I understand once the queue is full it waits for a block to become free but shouldn't blocks become free before hitting the max of 32? At no point does it ever manage to free any blocks from the queue. I'm not sure what to try next.

    Code:
    playBuffer() millis: 0 AudioMemoryUsed: 1
    playBuffer() millis: 0 AudioMemoryUsed: 2
    playBuffer() millis: 0 AudioMemoryUsed: 3
    playBuffer() millis: 0 AudioMemoryUsed: 4
    playBuffer() millis: 0 AudioMemoryUsed: 5
    playBuffer() millis: 0 AudioMemoryUsed: 6
    playBuffer() millis: 0 AudioMemoryUsed: 7
    playBuffer() millis: 0 AudioMemoryUsed: 8
    playBuffer() millis: 0 AudioMemoryUsed: 9
    playBuffer() millis: 0 AudioMemoryUsed: 10
    playBuffer() millis: 0 AudioMemoryUsed: 11
    playBuffer() millis: 0 AudioMemoryUsed: 12
    playBuffer() millis: 0 AudioMemoryUsed: 13
    playBuffer() millis: 0 AudioMemoryUsed: 14
    playBuffer() millis: 0 AudioMemoryUsed: 15
    playBuffer() millis: 0 AudioMemoryUsed: 16
    playBuffer() millis: 0 AudioMemoryUsed: 17
    playBuffer() millis: 0 AudioMemoryUsed: 17
    playBuffer() millis: 0 AudioMemoryUsed: 18
    playBuffer() millis: 0 AudioMemoryUsed: 19
    playBuffer() millis: 0 AudioMemoryUsed: 20
    playBuffer() millis: 0 AudioMemoryUsed: 21
    playBuffer() millis: 0 AudioMemoryUsed: 21
    playBuffer() millis: 0 AudioMemoryUsed: 22
    playBuffer() millis: 0 AudioMemoryUsed: 23
    playBuffer() millis: 0 AudioMemoryUsed: 24
    playBuffer() millis: 0 AudioMemoryUsed: 24
    playBuffer() millis: 0 AudioMemoryUsed: 25
    playBuffer() millis: 0 AudioMemoryUsed: 26
    playBuffer() millis: 0 AudioMemoryUsed: 27
    playBuffer() millis: 0 AudioMemoryUsed: 27
    playBuffer() millis: 0 AudioMemoryUsed: 28
    playBuffer() millis: 0 AudioMemoryUsed: 29
    playBuffer() millis: 0 AudioMemoryUsed: 30
    playBuffer() millis: 0 AudioMemoryUsed: 31
    playBuffer() millis: 0 AudioMemoryUsed: 31
    playBuffer() millis: 0 AudioMemoryUsed: 32
    playBuffer() millis: 1 AudioMemoryUsed: 32
    playBuffer() millis: 2 AudioMemoryUsed: 32
    playBuffer() millis: 3 AudioMemoryUsed: 32
    playBuffer() millis: 3 AudioMemoryUsed: 32
    playBuffer() millis: 3 AudioMemoryUsed: 32
    playBuffer() millis: 3 AudioMemoryUsed: 32
    playBuffer() millis: 3 AudioMemoryUsed: 32
    ...

  2. #2
    I found a sort of work around for now. I set my clock to 24mhz and added in a delayMicroseconds(500) after the playBuffer() call. This fixes the buffer queue and timing issues. However this doesn't seem like the right thing to do? Does this make sense?

    I figured I was filling the queue faster than the audio system could process it which lead me to slow things down.

  3. #3
    Senior Member
    Join Date
    Jul 2020
    Posts
    1,372
    You overflowed the queue. I think what you need to do is ensure AudioMemory is 32 or less, so that getBuffer() will block before
    the queue can overflow, that will give you flow control. I note the docs in the Audio library tool say "lots of caveats" for this object.

  4. #4
    Quote Originally Posted by MarkT View Post
    You overflowed the queue. I think what you need to do is ensure AudioMemory is 32 or less, so that getBuffer() will block before
    the queue can overflow, that will give you flow control. I note the docs in the Audio library tool say "lots of caveats" for this object.
    Yeah I figured so. Setting the AudioMemory to 32 or less (without the delay) does not work. getBuffer() does not seem to block properly to stop the queue from overflowing.

    See what I'm doing is generating a sine wave from a look up table of values. I have a table of 1024 samples which is 1 cycle and so if I want a sine wave 20 cycles long that will be 20480 samples. Which then means I need to process 160 blocks of 128 samples (20480 / 128 = 160). Adding in the delay of 500 microseconds seems to work ok for now but I noticed any change in code that is in the loop with playBuffer() will throw off the timing and will either overflow the queue or cause audio artifacts because there is to much delay. I then need to manually adjust the delay using trial and error to get it working again. I just wish there was a more elegant way to solve this issue but I can't seem to find one at the moment.

  5. #5
    Senior Member
    Join Date
    Jul 2020
    Posts
    1,372
    I think the whole class needs a good looking at - there are two methods not implemented and the semantics seem
    hazy - I presume it should be blocking when the queue is full, and I would like the max queue size to be a parameter
    not a fixed constant.

    I implemented my own input queue class because I needed sample-by-sample queuing which isn't implemented in
    this class, so I'll have a look at this class to see if it can improved easily and if so issue a pull-request.

  6. #6
    Senior Member
    Join Date
    Nov 2012
    Posts
    1,768
    shouldn't blocks become free before hitting the max of 32? At no point does it ever manage to free any blocks from the queue.
    It does free blocks, but you immediately consume them again and put them back in the buffer queue. The delay of 2-3ms once it reaches 32 buffers is because at 44100Hz it takes 2.9ms to play the samples in one buffer and therefore it takes 2.9ms to free up each buffer.
    Both the getBuffer and playBuffer functions are blocking. getBuffer blocks if it can't provide a free buffer. playBuffer blocks if the queue is full and it has to wait for one buffer to be freed up.
    I suspect that you are seeing getBuffer block rather than playBuffer but without seeing your complete code that's only a guess. Another guess is that although you say that "AudioMemory is set to 48", it sure looks like it is actually 32.

    Pete

  7. #7
    Quote Originally Posted by el_supremo View Post
    It does free blocks, but you immediately consume them again and put them back in the buffer queue. The delay of 2-3ms once it reaches 32 buffers is because at 44100Hz it takes 2.9ms to play the samples in one buffer and therefore it takes 2.9ms to free up each buffer.
    Both the getBuffer and playBuffer functions are blocking. getBuffer blocks if it can't provide a free buffer. playBuffer blocks if the queue is full and it has to wait for one buffer to be freed up.
    I suspect that you are seeing getBuffer block rather than playBuffer but without seeing your complete code that's only a guess. Another guess is that although you say that "AudioMemory is set to 48", it sure looks like it is actually 32.

    Pete
    Yeah this is what I figured after thinking about it for a bit. I'm feeding the queue too fast. As for which of the two. getBuffer or playBuffer is blocking I found it is playBuffer that is taking all the time. getBuffer takes 0ms but playBuffer takes 2 - 3ms when the queue reaches the max of 32.

    As for the AudioMemory setting I can set it to anything > 32 and it will still max out at 32 because the AudioPlayQueue class in play_queue.h has a max_buffers size constant declared that is set to 32. I've tried manually increasing the max buffer size in order to use more audio memory but it actually makes things worse after a certain amount.

  8. #8
    Senior Member
    Join Date
    Nov 2012
    Posts
    1,768
    If you have something else to do when a buffer is not available, you can call available() which will return false until a buffer is available during which time you can accomplish something else. Then proceed with getBuffer and playBuffer and they won't block.

    max_buffers size constant declared that is set to 32
    Ooopss. Duh

    I'm almost certain that getBuffer is blocking you. After you have played the last free buffer, you then create your new block of data in your buffer[]. Then you call getBuffer. But there are no free buffers because one is still being played. When, eventually, a buffer has been played and freed, getBuffer returns it to you and you immediately fill that buffer and call playBuffer again. At this point there's no reason for playBuffer to block so it puts that buffer in the queue which fills the queue again.
    Follow it through assuming that AudioPlayQueue only has one buffer allocated instead of 32.

    Pete

  9. #9
    Senior Member
    Join Date
    Jul 2020
    Posts
    1,372
    Quote Originally Posted by djex81 View Post
    Yeah this is what I figured after thinking about it for a bit. I'm feeding the queue too fast.
    I've had a play with the class and realized it would make sense for its max buffer to be a settable
    parameter, so that it can play nicely with other classes when being used like this.

    If you could, say, set the max buffers to 3 then you'd never be able to jump ahead more than about 10ms
    and not hog lots of audio buffers. In many situations I think 1 buffer would be plenty.

    I think it would be good if available returned the number of buffers available, so you can monitor the queue draining
    if you want.

    I also think it might work better if the buffer handling was all hidden from the user, and just the (currently unimplemented)
    play() methods were the way to put samples in. available() could report sample count rather than buffer count, and there
    would be a setMaxSamples() method

  10. #10
    Quote Originally Posted by MarkT View Post
    If you could, say, set the max buffers to 3 then you'd never be able to jump ahead more than about 10ms
    and not hog lots of audio buffers. In many situations I think 1 buffer would be plenty.
    It seems from my initial tests you would be correct in reducing the max_buffers to limit the amount of time ahead it buffers. I don't want to say for sure that this is the answer since I have more testing to do however setting the max_buffers to anywhere between 3 - 5 so far seems to work well and I do not even need the delayMicroseconds now. It's counter intuitive to set the max lower as one would think you would need more buffer space but in this case we are setting the max_buffer to something significantly lower in order to purposely fill the queue so getBuffer will block just enough time to keep things from backing up. I hope that makes sense.

    With max_buffer set to 3 a single playBuffer() now takes 0 - 1 ms. Much better than 2 - 3ms. Setting the max to 1 buffer will not play any audio. Not sure why.

  11. #11
    Senior Member
    Join Date
    Jul 2020
    Posts
    1,372
    I've added a pull request for some improvements to this class:
    https://github.com/PaulStoffregen/Audio/pull/410

    Added implementations for the two play() methods and created a new one, setMaxBuffers(), intended to be used once
    before adding data to the queue, so the number of blocks used can be limited.

  12. #12
    Quote Originally Posted by MarkT View Post
    I've added a pull request for some improvements to this class:
    https://github.com/PaulStoffregen/Audio/pull/410

    Added implementations for the two play() methods and created a new one, setMaxBuffers(), intended to be used once
    before adding data to the queue, so the number of blocks used can be limited.
    Oh awesome. Looked over the changes and they look good. However did you test if using 1 buffer will actually work? When I tested with max_buffers set to 1 no audio would play at all. It may be because I was using a low pass filter also which seems to want at least 2 buffers by default. The reason I mention it is because in your setMaxBuffers function you set it to 1 if less than 1 which may still end up in a broken state if 1 buffer is insufficient.

  13. #13
    Senior Member
    Join Date
    Jul 2020
    Posts
    1,372
    Ah, yes I think you are right, this type of buffering needs at least blocks to work, will fix.

  14. #14
    Senior Member
    Join Date
    Apr 2021
    Location
    Cambridgeshire, UK
    Posts
    163
    Just to alert @djex81, I've built on @MarkT's update to make a version of AudioPlayQueue that has the option not to stall the application if audio blocks or queue entries run out: you can find this at https://github.com/h4yn0nnym0u5e/Aud...ay_queue_stall for now. I also raised an issue at https://github.com/PaulStoffregen/Audio/issues/415.

    There are three changed files (play_queue.cpp and .h, and updates to the documentation in index.html), and a new example in examples/Queues/PlayQueueDemo/PlayQueueDemo.ino. Note I've preserved the old behaviour by default so existing sketches will behave as before; to achieve non-blocking operation, call queue1.setBehaviour(AudioPlayQueue::NON_STALLING) or similar - this needs to be done for each play queue individually. You will then have to deal with the new return values from play(), getBuffer() and playBuffer() in order to complete operations that couldn't be executed due to a lack of resources. However, this will leave your main application with more time to do stuff, rather than burning cycles waiting for the audio system to catch up...

    I'd appreciate it if you test it and post your results, good or bad - I'd rather not submit a PR until someone other than me has given it a thrashing!

    Cheers

    Jonathan

Posting Permissions

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