Как определить задержку звука (AudioTrack) на Android?

у меня есть приложение, в котором я использую AudioTrack в потоковом режиме для воспроизведения динамически генерируемого звука. Приложение не должно мгновенно реагировать на входы, поэтому проблемы с задержкой не беспокоят меня для этой стороны программы.

проблема в том, что у меня есть анимация, которая должна быть как раз в синхронизации с аудио-и кажется, что разные устройства имеют разное количество времени между тем, когда AudioTrack прекращает блокирование write() позвоните и попросите больше данных, и когда этот звук слышен из динамика.

мое текущее решение получает меня большую часть пути туда-я считаю количество кадров, которые я передал в AudioTrack до сих пор, и сравнить его с getPlaybackHeadPosition(). Это выглядит в основном так:

long currentTimeInFrames = 0;
while(playingAudio) {
  currentTimeInFrames += numberOfFramesToWrite;
  long delayInFrames = (currentTimeInFrames - audioTrack.getPlaybackHeadPosition());
  audioTrack.write(frameBuffer,0,sampleSize);
  doAnimationAfterDelay(delayInFrames);
}

однако, есть еще некоторая задержка, что getPlaybackHeadPosition() не похоже, что это зависит от устройства.

есть ли способ опросить систему для задержки AudioTrack?

4 ответов


рассмотрим задержку драйвера. Есть скрытая функция AudioManager.getOutputLatency (int), чтобы получить это.

назовем это так:

AudioManager am = (AudioManager)getSystemService(Context.AUDIO_SERVICE);
try{
   Method m = am.getClass().getMethod("getOutputLatency", int.class);
   latency = (Integer)m.invoke(am, AudioManager.STREAM_MUSIC);
}catch(Exception e){
}

Я получаю около 45 - 50 МС на различных устройствах. Использовать результат в своих расчетах.


уровень API 19 добавляет метод в AudioTrack под названием getTimeStamp(). Из документации:

опрос для метки времени по требованию.

Если вам нужно отслеживать метки времени во время начального прогрева или после маршрутизации или изменения режима, вы должны периодически запрашивать новую метку времени, пока сообщенные метки времени не покажут, что позиция кадра продвигается, или пока не станет ясно, что метки времени недоступны для этого маршрут.

указать AudioTimestamp объект в качестве параметра функции, и он заполнит самую последнюю" представленную "позицию кадра вместе с его "оценочной" меткой времени в наносекундах. Значение наносекунды соответствует значению миллисекунды, возвращаемому SystemClock.uptimeMillis().

затем вы можете определить задержку, выяснив, когда вы написали этот конкретный кадр в AudioTrack и когда getTimestamp() думает, что это фактически представлен. Я нашел этот метод более точным, чем другие методы, упомянутые выше.

вы должны быть осторожны, хотя. В документации говорится getTimeStamp() не поддерживается на всех платформах и всех маршрутах. Вы можете определить, был ли вызов успешным, проверив boolean возвращаемое значение. Я обнаружил, что с устройствами, которые я тестировал, функция возвращает false до тех пор, пока аудио не начнет представлять, а затем последующие вызовы возвращают true. Я только протестировал с AudioTrack в STREAM_MUSIC режим. Ваш пробег может отличаться.


вы должны учитывать размер буфера, который вы передали в создание AudioTrack.

final int minBufSize = AudioTrack.getMinBufferSize(Application.PLAYRATE,
AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT);

out=new AudioTrack(AudioManager.STREAM_MUSIC, Application.PLAYRATE, 
AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT, minBufSize, 
AudioTrack.MODE_STREAM);

extraLatencyFrames = minBufSize/4;

хорошо, это ключ. Сначала вам нужно расширить класс Audiotrack, а затем использовать getNativeFrameCount для аппроксимации латентности, связанной с родной стороной вещей.

class MyAudioTrack extends AudioTrack
{
    public MyAudioTrack(int streamType, int sampleRateInHz, int channelConfig,
            int audioFormat, int bufferSizeInBytes, int mode)
            throws IllegalArgumentException {
        super(streamType, sampleRateInHz, channelConfig, audioFormat,
                bufferSizeInBytes, mode);
        System.out.println("Native framecount "+getNativeFrameCount());
    }   
    public int getFrameCount()
    {
        return getNativeFrameCount();
    }
}