Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AudioTrack samplerate inconsistencies

Using AudioTrack for playback, I sometimes need to resample audio that do not conform to the sample rates supported by AudioTrack. In doing so, I need to determine the maximum sample rate supported by AudioTrack, under the current device, under the current audio configuration.

Due to the allowable sample rates for AudioTrack being poorly documented, I decided to snoop around the source code for AudioTrack and found this staggering line:

private static final int SAMPLE_RATE_HZ_MAX = 96000;

it would seem that the AudioTrack instance is applying a hard limit of 96 KHz regardless of the actual playback capabilities of the device.

More confusing is in the AudioFormat class, in which I pass to the constructor (API 21) of AudioTrack, which contains this line:

if ((sampleRate <= 0) || (sampleRate > 192000)) {

in it's setSampleRate() method. Now that's a hard limit of 192 KHz. So, passing > 192 KHz into AudioFormat (or it's builder) will result in IllegalArgumentException from AudioFormat and passing a configured 192 KHz < x < 96 KHz sample rate AudioFormat into AudioTrack will also throw an IllegalArgumentException.


What I found, by far, most confusing is the method getNativeOutputSampleRate() in AudioTrack which actually does return the correct output sampling rate (well, not much surprise given it's ran directly from the native layer, but so inconsistent).

And just to top it off, the method setPlaybackRate() which claims:

The valid sample rate range is from 1 Hz to twice the value returned by getNativeOutputSampleRate(int).

And indeed, I did try it, and it works? Consider the following snippet:

int nativeRate = AudioTrack.getNativeOutputSampleRate(AudioManager.STREAM_MUSIC);

android.util.Log.i("UI", "Native stream rate: " + nativeRate + " Hz");

// Build audio attributes

AudioAttributes.Builder attribBuilder = new AudioAttributes.Builder();

attribBuilder.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC);
attribBuilder.setUsage(AudioAttributes.USAGE_MEDIA);

AudioAttributes attrib = attribBuilder.build();

// Build audio format

AudioFormat.Builder afBuilder = new AudioFormat.Builder();

afBuilder.setChannelMask(AudioFormat.CHANNEL_OUT_STEREO);
afBuilder.setEncoding(AudioFormat.ENCODING_PCM_16BIT);
afBuilder.setSampleRate(nativeRate);

try{
    AudioTrack trackTest = new AudioTrack(attrib, afBuilder.build(), nativeRate, AudioTrack.MODE_STREAM, 0);

    android.util.Log.i("UI", "Track created successfully (direct)");
}catch(Exception ex){
    android.util.Log.w("UI", "Failed to create AudioTrack at native rate!");

    // Use a random supported samplerate to get pass constructor
    afBuilder.setSampleRate(48000);

    try{
        AudioTrack trackTest = new AudioTrack(attrib, afBuilder.build(), nativeRate, AudioTrack.MODE_STREAM, 0);

        trackTest.setPlaybackRate(nativeRate);

        android.util.Log.i("UI", "Track created successfully (indirect)");
    }catch(Exception e){
        android.util.Log.w("UI", "Failed to create AudioTrack at 48 KHz");
    }
}

following the program flow, when the native sampling rate is < 96 KHz, the code prints out:

Native stream rate: 48000 Hz
Track created successfully (direct)

but, when I hook up an external DAC with playback capabilities up to 192 KHz, I get:

Native stream rate: 192000 Hz
Failed to create AudioTrack at native rate!
Track created successfully (indirect)

What is with these inconsistencies? And is, setPlaybackRate() identical to the sample rate passed into the constructor?

like image 471
initramfs Avatar asked May 01 '15 07:05

initramfs


1 Answers

Currently most Android phones on the market support only one sampling rate. I believe certain Samsungs play at 48kHz and almost all others play at 44.1kHz. These values are dictated by the hardware, and although there is a facility to change the native rate, it's function is to secondarily to future proof, but primarily to 2. resample ALL audio at runtime in SOFTWARE. This is an expensive task, and also somewhat destructive. The reason why there is a hard limit at 192kHz (= 2 * 96kHz) is likely because sending more than twice the maximum frequency (96kHz) is a massive waste of resources as you may as well just throw away every second sample to effectively downsample by a factor of 2 until you are in that range.

Specifying a non-native sampling rate is best avoided. It will be resampled in software and is at best a waste of resources and at worst a source of lag.

Or hear it from a Google engineer

like image 173
Andrew Gallasch Avatar answered Nov 08 '22 04:11

Andrew Gallasch