Interesting stuff happening to android latency


#1

maybe you’ve seen it already: there was a really interesting, deep session about android latency on Google IO recently

http://www.youtube.com/watch?v=d3kfEeMZ65c
(skip to 28’ for the good stuff on recommended android buffer sizes/sample rates)

some takeaways:
-use the exact right buffer sizes/sample rates for the device. The video gives the numbers for the Nexus devices at 28’
-there are new apis to query for the right buffer sizes/sample rates. Requires API level 17 though
-avoid mutexes in audio callbacks and use ringbuffers instead to sync with the UI. Seems fairly obvious, but apparently as simple as a sprintf() in the callback requires a mutex
-use the fastmixer that bypasses the slow Android resampler/mixer/effects path (not sure how to do this and/or whether this requires a certain API level. Maybe OpenSL does it already by default?)

There is also a website with more info and source code:
http://code.google.com/p/high-performance-audio/


#2

thanks for this. they always require the latest api don’t they. down here im still trying to support API 11 :slight_smile:

On the subject of latency, i though i’d post some code i’ve been using for SHORT sounds. stuff like button clicks and so forth, you dont need to mix them or buffer them if they are short enough. This has been my main latency problem, whereas music playback, i can put up with a short start latency, so long as it doesnt drop out.

To use this class you just give it a PCM buffer (in memory) and a length, and it plays it. It uses OpenSLES as simply as possible to avoid overhead. As you know, OpenSLES isn’t perfect anyhow.

Juce uses openSLES dynamically, so if you use this you have to manually link with the OpenSLES library.

in Android.mk add this:

/**
 *
 * Portions Copyright (c) 2013 Voidware Ltd.  All Rights Reserved.
 *
 * This file contains Original Code and/or Modifications of Original Code as
 * defined in and that are subject to the Voidware Public Source Licence version
 * 1.0 (the 'Licence'). You may not use this file except in compliance with the
 * Licence or with expressly written permission from Voidware.  Please obtain a
 * copy of the Licence at http://www.voidware.com/legal/vpsl1.txt and read it
 * before using this file.
 * 
 * The Original Code and all software distributed under the Licence are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS
 * OR IMPLIED, AND VOIDWARE HEREBY DISCLAIMS ALL SUCH WARRANTIES, INCLUDING
 * WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
 * PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 *
 * Please see the Licence for the specific language governing rights and 
 * limitations under the Licence.
 */

#ifndef __quicksound_h__
#define __quicksound_h__

#ifdef JUCE_ANDROID
#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>
#endif

class QuickSound
{
    // Play short sounds without (much) delay.

public:

    QuickSound() { _init(); }

    ~QuickSound() { _purge(); }

    bool isOpen() const { return _isOpen; }

    bool open(int sampleRate, int inchans, int outchans)
    {
        bool res = isOpen();
#ifdef JUCE_ANDROID
        if (!res)
        {
            // open audio device
            _sampleRate = sampleRate;
            _inChans = inchans;
            _outChans = outchans;
            
             res = _createEngine();

             if (res)
                 res = _openPlayDevice();
            
             _isOpen = res;
        }
#endif
        return res;
    }

    bool playPCMData(short *buffer, size_t sampleCount)
    {
        bool res = isOpen();
#ifdef JUCE_ANDROID
        if (res)
        {
            if (_audioLevel > 0)
            {
                SLresult result;
                (*_bqPlayerPlay)->SetPlayState(_bqPlayerPlay, SL_PLAYSTATE_STOPPED);

                (*_bqPlayerBufferQueue)->Clear(_bqPlayerBufferQueue);


                result =(*_bqPlayerBufferQueue)->Enqueue(_bqPlayerBufferQueue, 
                                                         buffer, 
                                                         sampleCount*sizeof(short));
                res = result == SL_RESULT_SUCCESS;

                if (res)
                    result = (*_bqPlayerPlay)->SetPlayState(_bqPlayerPlay, SL_PLAYSTATE_PLAYING);

                res = result == SL_RESULT_SUCCESS;
            }
        }
#endif // ANDROID
        return res;
    }

    // vaguely a percentage level
    void        setVolume(int level)
    {
        if (level > 100) level = 100;
        else if (level < 0) level = 0;
        
        if (level == _audioLevel) return;

        _audioLevel = level;

#ifdef JUCE_ANDROID
        
        // attenuation * -50
        int millibel = (_audioLevel - 100)* 50;
        if (_bqPlayerVolume)
            (*_bqPlayerVolume)->SetVolumeLevel(_bqPlayerVolume, millibel);
#endif
    }

    
protected:

#ifdef JUCE_ANDROID
    bool _openPlayDevice()
    {
        SLresult result;
        bool res = true;

        SLuint32 sr = 0;
        if (_outChans > 0)
        {
            switch(_sampleRate)
            {
            case 8000:
                sr = SL_SAMPLINGRATE_8;
                break;
            case 11025:
                sr = SL_SAMPLINGRATE_11_025;
                break;
            case 16000:
                sr = SL_SAMPLINGRATE_16;
                break;
            case 22050:
                sr = SL_SAMPLINGRATE_22_05;
                break;
            case 24000:
                sr = SL_SAMPLINGRATE_24;
                break;
            case 32000:
                sr = SL_SAMPLINGRATE_32;
                break;
            case 44100:
                sr = SL_SAMPLINGRATE_44_1;
                break;
            case 48000:
                sr = SL_SAMPLINGRATE_48;
                break;
            case 64000:
                sr = SL_SAMPLINGRATE_64;
                break;
            case 88200:
                sr = SL_SAMPLINGRATE_88_2;
                break;
            case 96000:
                sr = SL_SAMPLINGRATE_96;
                break;
            case 192000:
                sr = SL_SAMPLINGRATE_192;
                break;
            }

            if (!sr) return false;

            // configure audio source
            SLDataLocator_AndroidSimpleBufferQueue loc_bufq = 
                {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2};
            
            const SLInterfaceID ids[] = {SL_IID_VOLUME};
            const SLboolean req[] = {SL_BOOLEAN_FALSE};
            
            result = (*_engineEngine)->CreateOutputMix(_engineEngine,
                                                       &_outputMixObject,
                                                       1, ids, req);
            
            res = result == SL_RESULT_SUCCESS;
            
            if (res)
            {

                // realize the output mix
                result = (*_outputMixObject)->Realize(_outputMixObject,
                                                      SL_BOOLEAN_FALSE);
            
                int speakers;
                if(_outChans > 1) 
                    speakers = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
                else speakers = SL_SPEAKER_FRONT_CENTER;
                
                // select 16 bit PCM integeger little endian
                SLDataFormat_PCM format_pcm = {SL_DATAFORMAT_PCM,
                                               _outChans,
                                               sr,
                                               SL_PCMSAMPLEFORMAT_FIXED_16,
                                               SL_PCMSAMPLEFORMAT_FIXED_16,
                                               speakers, SL_BYTEORDER_LITTLEENDIAN};

                SLDataSource audioSrc = {&loc_bufq, &format_pcm};

                // configure audio sink
                SLDataLocator_OutputMix loc_outmix =
                    {SL_DATALOCATOR_OUTPUTMIX, _outputMixObject};

                SLDataSink audioSnk = {&loc_outmix, NULL};

                // create audio player
                const SLInterfaceID ids1[] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE, SL_IID_VOLUME };
                const SLboolean req1[] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE };

                result = (*_engineEngine)->CreateAudioPlayer(_engineEngine,
                                                             &_bqPlayerObject,
                                                             &audioSrc, &audioSnk,
                                                             2, ids1, req1);
                res = result == SL_RESULT_SUCCESS;
            }

            if (res)
            {
            
                // realize the player
                result = (*_bqPlayerObject)->Realize(_bqPlayerObject,
                                                     SL_BOOLEAN_FALSE);
                res = result == SL_RESULT_SUCCESS;
            }

            if (res)
            {
                // get the play interface
                result = (*_bqPlayerObject)->GetInterface(_bqPlayerObject,
                                                          SL_IID_PLAY,
                                                          &_bqPlayerPlay);

                res = result == SL_RESULT_SUCCESS;
            }

            if (res)
            {
                // get the buffer queue interface
                result = (*_bqPlayerObject)->GetInterface(_bqPlayerObject,
                                                          SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
                                                          &_bqPlayerBufferQueue);

                res = result == SL_RESULT_SUCCESS;
            }

            if (res)
            {
                // get the volume level interface
                result = (*_bqPlayerObject)->GetInterface(_bqPlayerObject,
                                                          SL_IID_VOLUME,
                                                          &_bqPlayerVolume);

                res = result == SL_RESULT_SUCCESS;
            }
            
            if (res)
            {
                // register callback on the buffer queue
                result = (*_bqPlayerBufferQueue)->RegisterCallback(_bqPlayerBufferQueue,
                                                                   _bqPlayerCallback, this);
                res = result == SL_RESULT_SUCCESS;
            }

            if (res)
            {
                // set the player's state to playing
                result = (*_bqPlayerPlay)->SetPlayState(_bqPlayerPlay,
                                                        SL_PLAYSTATE_PLAYING);

                res = result == SL_RESULT_SUCCESS;
            }
        }
        return res;
    }

    bool _createEngine()
    {
        SLresult result;

        // create engine
        result = slCreateEngine(&_engineObject, 0, NULL, 0, NULL, NULL);
        bool ok = (result == SL_RESULT_SUCCESS);

        if (ok)
        {
            // realize the engine 
            result = (*_engineObject)->Realize(_engineObject, SL_BOOLEAN_FALSE);
            ok = (result == SL_RESULT_SUCCESS);

            if (ok)
            {
                // get the engine interface, which is needed in order to create other objects
                result = (*_engineObject)->GetInterface(_engineObject,
                                                        SL_IID_ENGINE,
                                                        &_engineEngine);
                ok = result == SL_RESULT_SUCCESS;
            }
        }
        return ok;
    }

    // called every time a buffer finishes playing
    void playerCallback(SLAndroidSimpleBufferQueueItf bq)
    {
    }
#endif

    void close()
    {
#ifdef JUCE_ANDROID
        // invalidate all associated interfaces
        if (_bqPlayerObject)
        {
            (*_bqPlayerObject)->Destroy(_bqPlayerObject);
            _bqPlayerObject = 0;
            _bqPlayerPlay = 0;
            _bqPlayerBufferQueue = 0;
            _bqPlayerVolume = 0;
        }

        if (_outputMixObject)
        {
            (*_outputMixObject)->Destroy(_outputMixObject);
            _outputMixObject = NULL;
        }
        
        if (_engineObject)
        {
            (*_engineObject)->Destroy(_engineObject);
            _engineObject = NULL;
            _engineEngine = NULL;
        }

#endif
    }

private:

    void _init()
    {
        _isOpen = false;
        _audioLevel = 100;

#ifdef JUCE_ANDROID
        _engineObject = 0;
        _engineEngine = 0;
        _outputMixObject = 0;

        _bqPlayerObject = 0;
        _bqPlayerPlay = 0;
        _bqPlayerBufferQueue = 0;
        _bqPlayerVolume = 0;
#endif
    }

    void _purge()
    {
        close();
        _init();
    }

#ifdef JUCE_ANDROID
    static void _bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq,
                                  void *context)
    {
        QuickSound* qs = (QuickSound*)context;
        qs->playerCallback(bq);
    }

    // OpenSL ES interface objects
    SLObjectItf                         _engineObject;
    SLEngineItf                         _engineEngine;
    SLObjectItf                         _outputMixObject;


    // buffer queue player interfaces
    SLObjectItf                         _bqPlayerObject;
    SLPlayItf                           _bqPlayerPlay;
    SLAndroidSimpleBufferQueueItf       _bqPlayerBufferQueue;
    SLVolumeItf                         _bqPlayerVolume;
#endif

    int                                 _sampleRate;
    int                                 _inChans;
    int                                 _outChans;
    bool                                _isOpen;
    int                                 _audioLevel;
};


#endif // __quicksound_h__