Android crackling sound

I have a JUCE Project that loads .wav files to an edit and plays the edit in a loop. On Android, it sounds bad on the test device (Samsung Galaxy A33). Playback is very messy and sound crackling occurs. I guess there are some adjustments to be made related to device manager. The only thing I am currently doing in that direction is setting defaultNumInputChannelsToOpen to 0 as I only need output.

Output is attached for emulator and device run. Output also contains some info about the files being played. Actually, playback sounds better in the emulator.

emulator_run.txt (14.1 KB)
device_run.txt (52.9 KB)

There’s a bit of a discussion around Android in this thread: Default number of input channels - #15 by leehu

Can you take a read through and see if any of the ideas help your situation?
Particularly reducing the number of CPUs to use and the Android specific code in te::DeviceManager discussed in this post: Default number of input channels - #11 by dave96

JUCE 7.0.6 should contain a fix for Android Oboe audio loop that allocated heap ([Bug]: Android: HeapBlock::calloc() is called on every Oboe audio callback · Issue #1206 · juce-framework/JUCE · GitHub). If you are using master releases then it’s worth trying 7.0.6 (it’s 5 months ago so if you are using a newer revision in JUCE develop branch then it is irrelevant).

@atsushieno Thanks for the suggestion. I am on develop with JUCE and it still sounds messy.

@dave96 I tried commenting out the #JUCE_ANDROID code related to “steadyLoadContext” in “tracktion_DeviceManager.h/.cpp” and also set “getNumberOfCPUsToUseForAudio()” to return 1. Unfortunately, the crackling still occurs, tested on a real device (Samsung A33).

I made a very simple Juce GUI app to test this. It just plays back the guitar demo sound from “tracktion_engine/examples/common” on one track.

Everything relevant happens in “MainComponent.h” (apart from the changes in “tracktion_DeviceManager.h/.cpp”):

#pragma once
#include <JuceHeader.h>

/**
* PlaybackDemoAudio.h and PlaybackDemoAudio.cpp contain demo audio data for playback.
* Both files are taken from tracktion_engine/examples/common and put into project folder "Source".
*/
#include "PlaybackDemoAudio.h" //tracktion_engine/examples/common

/*
* Setup namespaces
*/
namespace te = tracktion;
using namespace tracktion::literals;
using namespace std::literals;

/**
* Customize engine behavior by overriding functions from te::EngineBehaviour
*/
class CustomEngineBehaviour: public te::EngineBehaviour
{

public:
    bool autoInitialiseDeviceManager() override
    {
        DBG("Custom behaviour: do not auto-initialise device manager");
        return false;
    }

     te::EngineBehaviour::ClipDefaults getClipDefaults() override
     {
         auto clip_defaults = te::EngineBehaviour::getClipDefaults();
         clip_defaults.useProxyFile = false;
         DBG("Custom behaviour: clips use no proxy file");
         return clip_defaults;
     }

    int getNumberOfCPUsToUseForAudio() override
    {
        DBG("Custom behaviour: Set number of CPUs to 1");
        return 1;
    }
};

class MainComponent  : public juce::Component
{
public:

    MainComponent(){
        setSize (600, 400);
        doPlaybackTest(); //Start testing audio playback here
    };
    ~MainComponent() override;
    void paint (juce::Graphics&) override;
    void resized() override;

    /**
     * Code below tests playing audio file as clip inside an edit
     */
    std::unique_ptr<te::Engine> engine;
    std::unique_ptr<te::Edit> edit;
    //=================================================================================================
    void doPlaybackTest()
    {
        //Setup engine with custom behavior and 0 input channels
        engine = std::make_unique<te::Engine>(ProjectInfo::projectName, nullptr, std::make_unique<CustomEngineBehaviour>());
        engine->getDeviceManager().initialise(0);
        
        //Setup edit for editing
        edit = std::make_unique<te::Edit>(*engine, te::createEmptyEdit(*engine), te::Edit::EditRole::forEditing, nullptr, 0);
        
        //Setup file for playback
        auto f = juce::File::createTempFile (".ogg");
        f.replaceWithData (PlaybackDemoAudio::guitar_loop_ogg, PlaybackDemoAudio::guitar_loop_oggSize);
        te::AudioFile audioFile(*engine, f);
        
        //Setup 1 track
        edit->ensureNumberOfAudioTracks(1);
        te::AudioTrack* track= te::getAudioTracks(*edit)[0];
        
        //Insert clip to track
        juce::ReferenceCountedObjectPtr<te::WaveAudioClip> newClip = track->insertWaveClip (f.getFileNameWithoutExtension(), f,
                                                          { { {}, te::TimeDuration::fromSeconds (audioFile.getLength()) }, {} }, false);
        
        //Setup transport
        te::TransportControl& transport = edit->getTransport();
        transport.looping = false;
        transport.setPosition (0s);
        
        //Start playback
        transport.play(false);
    }
    
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainComponent)
};

Output is attached. “run_with_changes.txt” contains the output of the code presented above. “run_no_changes.txt” contains the output for a version of the project that doesn’t override
getNumberOfCPUsToUseForAudio() and doesn’t comment out the #JUCE_ANDROID code in “tracktion_DeviceManager.h/.cpp”. Both have the sound problem.

run_with_changes.txt (43.9 KB)
run_no_changes.txt (51.2 KB)

Have you tried a vanilla JUCE app like the JUCE demo? Does that also have dropouts?

If not, have you tried profiling the Android app to see where the time is being spent?

@dave96 JUCE AudioPlaybackDemo.h sounds good.

I changed the code above to loop around the clip and tried profiling on Android. Screenshots are attached. First one shows results without overriding getNumberOfCPUsToUseForAudio() and commenting out the #JUCE_ANDROID code. Second image shows results after applying the changes.


So does anyone has an idea of what could go wrong for the crackling to appear?
I feel like this very basic tracktion usage example provided above should work as it just plays a demo sound. Might there be something trivial wrong with the settings I am using in the example? Or is it more probable that the problem is rooted in the engine itself?

What’s the “AudioTrack” thread above? Is that the audio thread provided by the Android OS?
I’m not sure I fully understand the profile though, it looks like the Engine based app is only using 12% CPU on average?

Does the audio thread ever peak above the buffer size? It looks pretty even to me.

I achieved decent playback performance with TE on Android by switching to OpenSLES and building in release mode.

Oboe should be the way to go but maybe this helps someone.