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?
setPlaybackRate()
, it clearly bypasses the (inconsistent) checks you mentioned and directly set the playback in the native side. Also, it might be interesting to look at the history of [core/jni/android_media_AudioTrack.cpp][1] [1]: android.googlesource.com/platform/frameworks/base/+/88e209d%5E! – Inexpressive