Help with CPU Scaling problem on Note 8 Handset

We are having an issue where audio is playing slow and distorted on Samsung Galaxy Note 8

This is from the Forum …and our question is at the bottom …

Now that the VST is released for my app (spacecraft granular synth) I have returned my attention to the Android version. The only thing that’s stopping me from releasing this right now is the dreaded CPU scaling that only happens on a subset of devices. I’m finding that a low-cost basic device such as Xiaomi Mi is able to run my app flawlessly, but a powerful flagship such as the Note 8 has audio glitches which are corelated to downward spikes in the CPU frequency (I’ve ruled out GUI/resolution as the culprit).

After lots of optimisation of the DSP, one of the last remaining possible fixes for this that I’m aware of is the Oboe ‘StabilizedCallback’, thanks to @donturner for pointing me in the right direction. After many failed attempts, I’ve finally managed to get this to work (I think) in JUCE.

Here’s my implementation (modifications to juce_android_Oboe.cpp). Note that this is just a prototype implementation with a standard pointer, I still need to figure out what happens with object destruction and whether or not smart pointers are needed (any suggestions welcome).

Declaration of a pointer to an AudioStreamCallback object in the OboeStream class:

oboe::AudioStreamCallback * asc;

Replace this line of code in the same class…

builder.setCallback (callback); 

…with…

if ( callback != NULL ) 
{ 
    asc = new oboe::StabilizedCallback(callback); 
    builder.setCallback (asc); 
} 
else 
{ 
    builder.setCallback (callback); 
} 

Jucer config:

  • Release build
  • Link-time optimisation enabled
  • Optimisation: Ofast
  • Oboe release v1.2.0

By adding the check for NULL I can finally run my app without crashing on start-up. The stabilized callback appears to be working; however, although as the cpu frequency profile and audio glitches appear to be improved, there are still sporadic drops in cpu freq and corresponding audio dropouts. Saying that, there is a huge number of settings (such as optimisation settings in jucer etc.) that I need to try before it can be conclusive.

So, I have a couple of questions going forward for @donturner.

Firstly, I notice that in StabilizedCallback.cpp there are some hardcoded parameters:

constexpr int32_t kLoadGenerationStepSizeNanos = 20000;
constexpr float kPercentageOfCallbackToUse = 0.8;

Can these parameters be thought of as tuning parameters for the stabilized callback? If I increase those numbers, does the stabilized callback become more ‘stabilisy’? If so, do you have any guidelines for tuning (max limits etc.)?

Secondly, in the scenario that the StabilizedCallback alone does not fix the issue, an additional modification could be for Oboe to use a larger buffer (latency is not so important in my app as it’s predominantly generative). Perhaps this, in combination with the StabilizedCallback, would help me find a sweet spot which would eliminate dropouts on my Galaxy Note 8. Questions relating to this:

  1. What is the proper way to increase the buffer size in Oboe? I’ve tried changing oboe::PerformanceMode::LowLatency to oboe::PerformanceMode::None or oboe::PerformanceMode::PowerSaving however, this results in very large audio dropouts (even with a barebones hello world JUCE app generating a sine wave)
  2. If buffer size modifications are possible, how could I control this within my app? (this might be more a question for the JUCE devs)

Reply


Hi there. I’m having exactly same issue with Android app - I’m getting glitched playback on Note 8, but everything goes smoothly on cheaper devices. I’m aware that the problem is related to CPU scaling. I’ve tried to modify juce_android_Oboe.cpp as @MarkWatt recommended but still the same. Is there anything else i’m missing? Are there any plans about integrating this implementation to official Juce release, or at least develop branch?

, in this article you are pointing bug related to optimization flags. Is it solved in latest Android Studio / NDK? Can I be sure that if i’m setting Ofast in Projucer it is taken into account by CMake? Any other advices from you?

Cheers

Unfortunately due to the sheer number and diversity of Android devices, getting optimal audio performance across them is mostly a matter of trial and error and testing with the actual devices that your app is going to run on. There are a few workarounds and improvements in the JUCE Oboe and OpenSL code that have been added thanks to the ROLI software team’s experiments with Android devices, but you will need to do your own testing with your app to figure out what works best for you. If you’ve got a working implementation of the oboe::StabilizedCallback and it’s helping with your app then feel free to submit a PR and we can get it added to JUCE.

2 Likes

Thank you so very much for your response. Do you think that if we resolved the glitchy audio on the Note 8 , it may then work for all devices with this exact issue. Its going to be quite difficult to have it tested on all devices !

It’s hard to say. I remember that the ROLI team were having some issues with the latest Samsung devices, so it could be a Samsung specific issue.

1 Like

Hopefully if we fix it for the Note 8 then at least the rest of the Samsung devices should be OK. The audio just appears to be working slowly, all other apps I tested work fine. Thanks again for your help

Hey, did you manage to fix this? I’m having a similiar problem with the note 10.

Hi. Yes, we have good performance after switching to oboe and implementing StabilizedCallback.
Follow these amazing instructions to modify juce_android_Oboe.cpp.

Dont forget to activate Oboe: in Projucer->Modules->juce_audio_devices there is macro JUCE_USE_ANDROID_OBOE. And in your Android exporter you could set the path to Oboe library

1 Like

Thanks. I’ve already done all that though. Still having issues. Have you tried the note 10?

Nope. Does the audio output differs with oboe and without it? Have you tried other devices?
You can report issue in oboe repository if its related to particular phone

Oboe uses yield to keep the CPU frequency high: https://github.com/google/oboe/blob/ec8de0feef6df0af28d8de1d85437e2b7620c92e/include/oboe/StabilizedCallback.h#L67

There was a presentation from ADC 2019 from Ableton where they found that wfe was more reliable on iOS ARM architectures. I would love it if someone could test this with a real world app to see whether it has an effect.

Ableton’s performance testing app is here: https://github.com/Ableton/AudioPerfLab

I’m still finding glitches when using note9 or note 10+ which use a buffer of 96 for some reason. On my note20 ultra, the buffer is 240 and no glitches. Don, am I right in thinking that there’s no way to actually change the audio buffer size on android phones?

Don, I just tried changing from PerformanceMode::LowLatency to PerformanceMode::None in juce_android_Oboe.cpp and it appears to have fixed the audio glitches in the note10+ (for the first time ever by the way!).

I noticed that with ::None, the buffer size has grown to 634 (from 96 with ::LowLatency) and then after about 2 minutes dropping to 516. I assume the lower buffer size is what is achieving the good audio performance.

Question: am I safe in setting to ::None, is the buffer size increase what you would expect to happen and would the same thing happen on other phones? (ok, technically three questions)

Note that only switching to stabilisedCallback was not sufficient.

@sonny0303 @robclouth

1 Like

Using Oboe with the various performance modes and StabilizedCallback are all important, but one other thing we found in extensive research into this area was that on Android, your audio thread is not locked to one core and jumps from CPU core to CPU core (you can see this if you use systrace). This seems to have big impact in terms of crackles etc.

You can set thread affinity to a given core by calling Thread::setCurrentThreadAffinityMask (1 << coreNumber); from somewhere in your audio thread (we call it the first time processBlock is called, then set a flag to not call it again). We found that locking to any core gave better results than letting Android move it from core to core, but for best results you want to set the affinity to one of the higher power CPU cores of the device.

Unfortunately there’s no reliable way to determine this in the Android APIs that we could find (there is a call getExclusiveCores() but it didn’t return useful information on most devices), but we went with floor (SystemStats::getNumPhysicalCpus() / 2) which seems to work reasonably well on the devices we tested on.

1 Like

One other thing to be aware of is that some older Samsung devices will ramp down the CPU speed as low as possible, unless you are touching the screen. I guess this is intended to save power while still being responsive in games, but it’s not much good if you are writing an audio app which doesn’t rely on touching the screen. I think they’ve addressed this in newer devices/firmwares, but some older devices may have this issue and we couldn’t find any way to work around it, it’s done at too low a level to “trick” it.

That’s really useful to know, I wasn’t aware of this. I may have to try this approach if beta testing comes back with bad results on other devices.

I have quite a few Samsung devices and the ONLY way I’ve found to eliminate crackling on all of them has been to use PerformanceMode::None. I’m not sure exactly what is going on here (@Don_Turner ?) but I have a feeling it’s increasing the buffer size somehow. I did notice that the buffer size is jumping around all over the place on every processBlock but it seems to work.

1 Like