Thread priority question


#1

My Juce-based audio app can play 4 MP3’s simulteaneously. The MP3 (buffered) decoding is done at a high priority ( 8 ), the sound is output via ASIO (using AudioDeviceManager), so its priority is also very high (!?).

Everything works alright, no clicks no pops…

… until I choose to make a “scan” of the hard disk while the MP3’s are playing. The scan is basically recursively scanning the folder structure of the hard disk into a customized TreeView, which then displays its contents. This is done at lowest possible priority ( 0 ).

Why does this scanning lead to clicks or pops (very rarely) although it is done at lowest priority? It is really not acceptable. There are many apps (like NI Traktor) who do file analyzing/scanning during sound play and show no signs of pops or crackles during their analyzing/scanning.

What can I do about it? Put some sleep() into the scanning process?


#2

Might be worth turning up the process priority to high with Process::setPriority.


#3

I will try that. Thanks. But what would be the reason for the clicks? I mean, the scanning is already running at lowest possible priority, so why should it interfere the high priority threads?


#4

Could be a million reasons… Got any criticalsections that both threads use? And you need to find out if the glitching is because of the audio thread stalling or the reader thread stalling.


#5

How do you determine if glitching is due to audio thread stalling or reader thread stalling?


#6

Well that’s easy - your audio thread needs to get audio from its reader thread via some kind of fifo, so you can just check whether the fifo ever becomes empty.


#7

What´s sure is that it is not because of some Critical Section. I am not at home right now, but will check if it is the Audio Buffering or the whole Audio Thread that is stalling. But still, this should not be the case, because the scanning Thread runs at such low priority compared to the Audio buffering & Audio IO Thread. None of both threads should have some time-off anyway.


#8

What makes you so sure it’s not a critical section? You must be using some, and there could be interaction between all the threads that ends up blocking the audio thread. That’s usually the case when you get stuff like this happening.


#9

Why should I use a Critical Section? I don´t use one.


#10

So you’re passing audio from the reader thread to the audio thread without any thread-safety! Not surprised it’s all going wrong then!


#11

It works differently. I have for each Audio Reader one special Thread. So there are 4 Threads, which always buffer in blocks of 64K-samples ahead of playing time. Each of those Threads only accesses 1 Audio Reader. The Audio IO merely reads out of the 64K buffers by just reading into it, so pure memory access here. If the Audio Buffering Thread did not have enough time to fill the buffer up to the playing position, ofcourse one will hear something that was in the buffer previously.

I don´t access any AudioReader from the Audio IO Thread itself, that would be too dangerous.

The used scheme works very well and I used it in this and other apps without any problems.

The stuttering has nothing to do with the scheme and, as said, only occurs during the TreeView scanning, which is not related to the Audio processing in any way.


#12

Regarding Zamrate’s comment: “I don´t access any AudioReader from the Audio IO Thread itself, that would be too dangerous.”, I’m doing exactly that. During audioDeviceIOCallback, I’m calling the getNextAudioBlock of two AudioFormatReaderSource instances. There are no critical sections unless the user decides to move the time position slider or to record (see next paragraph). During repositioning, the critical section assures that the repositioning is done as an atomic operation. During record, a C.S. synchronizes a producer and consumer accessing a ringbuffer aka circular buffer. There are no problems here and my code takes up a comfortable 8% of the cpu which is comparable to Tracktion doing the same thing (playing back two files simultaneously). If this is dangerous, please enlighten me as to why and suggest a better way.

My problem occurs when I decide to record the incoming line in along with playing back the files. That goes into a ringbuffer and is written to disk by a separate thread. Here I use a combination of a read index, a write index, and a variable denoting the number of samples available. The read and write indices are accessed only by the respective consumer and producer threads, the third variable is shared and is protected by a critical section. This works most of the time, but takes up an alarming 54% of the cpu and will stutter occassionally unless my disk is completely defragmented. I will look into seeing whether I’ve got starvation in any of the threads as outlined in Jule’s previous post.


#13

To mrblasto:

Let’s say your latency is 128 (approx. 3ms @ 44.1kHz) . So you always read 128 samples from the AudioReader, if I understood correctly. The AudioReader in turn asks the OS to transfer 128 samples (@16Bit stereo, this would be 512 bytes). What if the OS takes more than 3 ms to read the 512 bytes? Well, then it will take more time than the latency itself and you get dropouts. Plus, your CPU time is wasted in the Audio IO Thread. Other very bad thing: Reading only 512 bytes at once also surely causes some CPU time lost because each time you read something, there’s surely many lines of code involved before the actual transfer starts, i.e. big overhead.

So, always read like 1 second in advance, always read a large quantity of samples at once, and do this in a special Thread which just puts all read samples into a circular buffer. The Audio IO will just read from this circular buffer. Don’t make it blocking! If the Audio Thread could not fill up the buffer up to the read position, then just output zeroes or whatever but don’t block anything.


#14

I tried now, when i use Process::setPriority(Process::HighPriority) my sound is crackling all the time (with ASIO!).
Before it was only during the scanning or if I moved the TaskManager window very fast (all the time) around over the window of my app.

What I noticed now with Process::HighPriority is that when I move the window of my app in a way that the Wave Displays are not visible, there is no crackling.
(The Wave Displays show the waves of the played songs, like an oscilloscope, at play position and are asked to repaint every 20 ms).

So the whole thing has something to do with the painting.!? I suppose the painting is done at “normal” priority, since is is (again I suppose) done from the Message Loop itself by processing WM_PAINT ?


#15

That makes it sound even more likely that you’re doing something in the audio thread that’s getting blocked by the UI or some other thread. Try making your audio thread just play a sine wave, with absolutely no calls to anything else, and see if that crackles.


#16

I just tried with a sin wave output, it’s just the same. Lots of crackles with Process::setPriority(Process::HighPriority). And again, if I move the window down, so the repainted waveform region is out of screen, there are no crackles.

When I turn off the Timers that call repaint() for the WaveForm display, there is no crackling anymore. But then, when I move the app’s window, there are crackles again!

When I move some other app’s window, there are now crackles if I don’t move it over the JUCE window, but as soon as I move it over the JUCE window, there are crackles again.

All of the described above happens with Process::setPriority(Process::HighPriority).

It must have something to do with the painting?


#17

Does tracktion play without crackling? What about the juce audio demo? What soundcard is it?


#18

… well according to the above quite, you’re still using a sine wave display to show your audio data - which means that the UI is drawing based on your audio; where is it getting its data from, and at what point is it instructed to refresh itself?


#19

I don’t have Traktion, but I just tested with the Juce Demo.

I first tried with normal priority, then it’s ok (unless I move the Juce Demo window very bad back and forth).

Then I modified the Juce Demo by just introducing the line:

Process::setPriority(Process::HighPriority);

Et voilà, I get exactly the same problem as with my app. As soon as moving the window I get crackles.

So it is not my app.

I have lots of music software and none has this crackling problem when moving some windows.

I use a M-Audio Delta 1010LT, always ASIO 256 samples. I swear I never have any crackles with other software like Cubase SX, Ableton, or my own DJ app “Quad”.

My personal impression is, that before AudioIODeviceCallback gets called, something is happening.

Is JUCE calling AudioIODeviceCallback synchronously from the ASIO bufferSwitch() / bufferSwitchTimeInfo() functions?


#20

Yes, of course it is. As far as I know, Tracktion (why can nobody spell it right!) runs happily with the 1010, and that uses high-priority mode. It’s the same code underneath.

Have you actually tried increasing the latency?