From 6f82f82abbb9346537f39569b93c41b65d03eca3 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Fri, 16 Jun 2017 19:08:15 -0700 Subject: [PATCH] Use low latency audio pathway on Lollipop and later --- .../binding/audio/AndroidAudioRenderer.java | 136 ++++++++++++++---- 1 file changed, 105 insertions(+), 31 deletions(-) diff --git a/app/src/main/java/com/limelight/binding/audio/AndroidAudioRenderer.java b/app/src/main/java/com/limelight/binding/audio/AndroidAudioRenderer.java index bc297ebe..7fa1d439 100644 --- a/app/src/main/java/com/limelight/binding/audio/AndroidAudioRenderer.java +++ b/app/src/main/java/com/limelight/binding/audio/AndroidAudioRenderer.java @@ -1,8 +1,10 @@ package com.limelight.binding.audio; +import android.media.AudioAttributes; import android.media.AudioFormat; import android.media.AudioManager; import android.media.AudioTrack; +import android.os.Build; import com.limelight.LimeLog; import com.limelight.nvstream.av.audio.AudioRenderer; @@ -12,10 +14,50 @@ public class AndroidAudioRenderer implements AudioRenderer { private AudioTrack track; + private AudioTrack createAudioTrack(int channelConfig, int bufferSize, boolean lowLatency) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + return new AudioTrack(AudioManager.STREAM_MUSIC, + 48000, + channelConfig, + AudioFormat.ENCODING_PCM_16BIT, + bufferSize, + AudioTrack.MODE_STREAM); + } + else { + AudioAttributes.Builder attributesBuilder = new AudioAttributes.Builder() + .setUsage(AudioAttributes.USAGE_GAME); + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { + // Use FLAG_LOW_LATENCY on L through N + if (lowLatency) { + attributesBuilder.setFlags(AudioAttributes.FLAG_LOW_LATENCY); + } + } + + AudioTrack.Builder trackBuilder = new AudioTrack.Builder() + .setAudioFormat(new AudioFormat.Builder() + .setEncoding(AudioFormat.ENCODING_PCM_16BIT) + .setSampleRate(48000) + .setChannelMask(channelConfig) + .build()) + .setAudioAttributes(attributesBuilder.build()) + .setTransferMode(AudioTrack.MODE_STREAM) + .setBufferSizeInBytes(bufferSize); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + // Use PERFORMANCE_MODE_LOW_LATENCY on O and later + if (lowLatency) { + trackBuilder.setPerformanceMode(AudioTrack.PERFORMANCE_MODE_LOW_LATENCY); + } + } + + return trackBuilder.build(); + } + } + @Override public int setup(int audioConfiguration) { int channelConfig; - int bufferSize; int bytesPerFrame; switch (audioConfiguration) @@ -38,44 +80,76 @@ public class AndroidAudioRenderer implements AudioRenderer { // do this on many devices and it lowers audio latency. // We'll try the small buffer size first and if it fails, // use the recommended larger buffer size. - try { - // Buffer two frames of audio if possible - bufferSize = bytesPerFrame * 2; - track = new AudioTrack(AudioManager.STREAM_MUSIC, - 48000, - channelConfig, - AudioFormat.ENCODING_PCM_16BIT, - bufferSize, - AudioTrack.MODE_STREAM); - track.play(); - } catch (Exception e) { - // Try to release the AudioTrack if we got far enough - try { - if (track != null) { - track.release(); - } - } catch (Exception ignored) {} + for (int i = 0; i < 4; i++) { + boolean lowLatency; + int bufferSize; - // Now try the larger buffer size - bufferSize = Math.max(AudioTrack.getMinBufferSize(48000, + // We will try: + // 1) Small buffer, low latency mode + // 2) Large buffer, low latency mode + // 3) Small buffer, standard mode + // 4) Large buffer, standard mode + + switch (i) { + case 0: + case 1: + lowLatency = true; + break; + case 2: + case 3: + lowLatency = false; + break; + default: + // Unreachable + throw new IllegalStateException(); + } + + switch (i) { + case 0: + case 2: + bufferSize = bytesPerFrame * 2; + break; + + case 1: + case 3: + // Try the larger buffer size + bufferSize = Math.max(AudioTrack.getMinBufferSize(48000, channelConfig, AudioFormat.ENCODING_PCM_16BIT), - bytesPerFrame * 2); + bytesPerFrame * 2); - // Round to next frame - bufferSize = (((bufferSize + (bytesPerFrame - 1)) / bytesPerFrame) * bytesPerFrame); + // Round to next frame + bufferSize = (((bufferSize + (bytesPerFrame - 1)) / bytesPerFrame) * bytesPerFrame); + break; + default: + // Unreachable + throw new IllegalStateException(); + } - track = new AudioTrack(AudioManager.STREAM_MUSIC, - 48000, - channelConfig, - AudioFormat.ENCODING_PCM_16BIT, - bufferSize, - AudioTrack.MODE_STREAM); - track.play(); + // Skip low latency options if hardware sample rate isn't 48000Hz + if (AudioTrack.getNativeOutputSampleRate(AudioManager.STREAM_MUSIC) != 48000 && lowLatency) { + continue; + } + + try { + track = createAudioTrack(channelConfig, bufferSize, lowLatency); + track.play(); + + // Successfully created working AudioTrack. We're done here. + LimeLog.info("Audio track configuration: "+bufferSize+" "+lowLatency); + break; + } catch (Exception e) { + // Try to release the AudioTrack if we got far enough + try { + if (track != null) { + track.release(); + track = null; + } + } catch (Exception ignored) {} + } } - LimeLog.info("Audio track buffer size: "+bufferSize); return 0; }