From 1613c9c7a8ee7c719a04b7156d8fbf925527af75 Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Tue, 6 Jan 2015 20:13:50 +0000 Subject: [PATCH] Refine logic for determining AudioTrack size. - Target 4x the minimum specified by the framework. - Impose a minimum duration (250ms). - Impose a maximum duration (750ms, or the minimum specified by the framework if that's larger). I've removed the ability to specify the multiplication factor, since the underlying implementation is getting more complicated, and we should really be able to figure this out internally. --- .../Ac3PassthroughAudioTrackRenderer.java | 9 +-- .../MediaCodecAudioTrackRenderer.java | 68 +------------------ .../android/exoplayer/audio/AudioTrack.java | 53 ++++++++------- 3 files changed, 32 insertions(+), 98 deletions(-) diff --git a/library/src/main/java/com/google/android/exoplayer/Ac3PassthroughAudioTrackRenderer.java b/library/src/main/java/com/google/android/exoplayer/Ac3PassthroughAudioTrackRenderer.java index 319e206ddf..549b9dcd55 100644 --- a/library/src/main/java/com/google/android/exoplayer/Ac3PassthroughAudioTrackRenderer.java +++ b/library/src/main/java/com/google/android/exoplayer/Ac3PassthroughAudioTrackRenderer.java @@ -73,9 +73,6 @@ public final class Ac3PassthroughAudioTrackRenderer extends TrackRenderer { /** Default buffer size for AC-3 packets from the sample source */ private static final int DEFAULT_BUFFER_SIZE = 16384 * 2; - /** Multiplication factor for the audio track's buffer size. */ - private static final int MIN_BUFFER_MULTIPLICATION_FACTOR = 3; - private final Handler eventHandler; private final EventListener eventListener; @@ -103,15 +100,15 @@ public final class Ac3PassthroughAudioTrackRenderer extends TrackRenderer { * null if delivery of events is not required. * @param eventListener A listener of events. May be null if delivery of events is not required. */ - public Ac3PassthroughAudioTrackRenderer( - SampleSource source, Handler eventHandler, EventListener eventListener) { + public Ac3PassthroughAudioTrackRenderer(SampleSource source, Handler eventHandler, + EventListener eventListener) { this.source = Assertions.checkNotNull(source); this.eventHandler = eventHandler; this.eventListener = eventListener; sampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_NORMAL); sampleHolder.data = ByteBuffer.allocateDirect(DEFAULT_BUFFER_SIZE); formatHolder = new MediaFormatHolder(); - audioTrack = new AudioTrack(MIN_BUFFER_MULTIPLICATION_FACTOR); + audioTrack = new AudioTrack(); shouldReadInputBuffer = true; } diff --git a/library/src/main/java/com/google/android/exoplayer/MediaCodecAudioTrackRenderer.java b/library/src/main/java/com/google/android/exoplayer/MediaCodecAudioTrackRenderer.java index b12cb6cb2a..a6ff3b0a44 100644 --- a/library/src/main/java/com/google/android/exoplayer/MediaCodecAudioTrackRenderer.java +++ b/library/src/main/java/com/google/android/exoplayer/MediaCodecAudioTrackRenderer.java @@ -17,7 +17,6 @@ package com.google.android.exoplayer; import com.google.android.exoplayer.audio.AudioTrack; import com.google.android.exoplayer.drm.DrmSessionManager; -import com.google.android.exoplayer.util.Assertions; import com.google.android.exoplayer.util.MimeTypes; import android.annotation.TargetApi; @@ -64,10 +63,9 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer { public static final int MSG_SET_VOLUME = 1; private final EventListener eventListener; - private final AudioTrack audioTrack; - private int audioSessionId; + private int audioSessionId; private long currentPositionUs; /** @@ -118,72 +116,10 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer { */ public MediaCodecAudioTrackRenderer(SampleSource source, DrmSessionManager drmSessionManager, boolean playClearSamplesWithoutKeys, Handler eventHandler, EventListener eventListener) { - this(source, drmSessionManager, playClearSamplesWithoutKeys, eventHandler, eventListener, - new AudioTrack()); - } - - /** - * @param source The upstream source from which the renderer obtains samples. - * @param minBufferMultiplicationFactor When instantiating an underlying - * {@link android.media.AudioTrack}, the size of the track is calculated as this value - * multiplied by the minimum buffer size obtained from - * {@link android.media.AudioTrack#getMinBufferSize(int, int, int)}. The multiplication - * factor must be greater than or equal to 1. - * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be - * null if delivery of events is not required. - * @param eventListener A listener of events. May be null if delivery of events is not required. - */ - public MediaCodecAudioTrackRenderer(SampleSource source, float minBufferMultiplicationFactor, - Handler eventHandler, EventListener eventListener) { - this(source, null, true, minBufferMultiplicationFactor, eventHandler, eventListener); - } - - /** - * @param source The upstream source from which the renderer obtains samples. - * @param drmSessionManager For use with encrypted content. May be null if support for encrypted - * content is not required. - * @param playClearSamplesWithoutKeys Encrypted media may contain clear (un-encrypted) regions. - * For example a media file may start with a short clear region so as to allow playback to - * begin in parallel with key acquisision. This parameter specifies whether the renderer is - * permitted to play clear regions of encrypted media files before {@code drmSessionManager} - * has obtained the keys necessary to decrypt encrypted regions of the media. - * @param minBufferMultiplicationFactor When instantiating an underlying - * {@link android.media.AudioTrack}, the size of the track is calculated as this value - * multiplied by the minimum buffer size obtained from - * {@link android.media.AudioTrack#getMinBufferSize(int, int, int)}. The multiplication - * factor must be greater than or equal to 1. - * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be - * null if delivery of events is not required. - * @param eventListener A listener of events. May be null if delivery of events is not required. - */ - public MediaCodecAudioTrackRenderer(SampleSource source, DrmSessionManager drmSessionManager, - boolean playClearSamplesWithoutKeys, float minBufferMultiplicationFactor, - Handler eventHandler, EventListener eventListener) { - this(source, drmSessionManager, playClearSamplesWithoutKeys, eventHandler, eventListener, - new AudioTrack(minBufferMultiplicationFactor)); - } - - /** - * @param source The upstream source from which the renderer obtains samples. - * @param drmSessionManager For use with encrypted content. May be null if support for encrypted - * content is not required. - * @param playClearSamplesWithoutKeys Encrypted media may contain clear (un-encrypted) regions. - * For example a media file may start with a short clear region so as to allow playback to - * begin in parallel with key acquisision. This parameter specifies whether the renderer is - * permitted to play clear regions of encrypted media files before {@code drmSessionManager} - * has obtained the keys necessary to decrypt encrypted regions of the media. - * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be - * null if delivery of events is not required. - * @param eventListener A listener of events. May be null if delivery of events is not required. - * @param audioTrack Used for playing back decoded audio samples. - */ - public MediaCodecAudioTrackRenderer(SampleSource source, DrmSessionManager drmSessionManager, - boolean playClearSamplesWithoutKeys, Handler eventHandler, EventListener eventListener, - AudioTrack audioTrack) { super(source, drmSessionManager, playClearSamplesWithoutKeys, eventHandler, eventListener); this.eventListener = eventListener; - this.audioTrack = Assertions.checkNotNull(audioTrack); this.audioSessionId = AudioTrack.SESSION_ID_NOT_SET; + this.audioTrack = new AudioTrack(); } @Override diff --git a/library/src/main/java/com/google/android/exoplayer/audio/AudioTrack.java b/library/src/main/java/com/google/android/exoplayer/audio/AudioTrack.java index ccc3943b28..d331ce9b7c 100644 --- a/library/src/main/java/com/google/android/exoplayer/audio/AudioTrack.java +++ b/library/src/main/java/com/google/android/exoplayer/audio/AudioTrack.java @@ -16,7 +16,6 @@ package com.google.android.exoplayer.audio; import com.google.android.exoplayer.C; -import com.google.android.exoplayer.util.Assertions; import com.google.android.exoplayer.util.Util; import android.annotation.SuppressLint; @@ -89,12 +88,19 @@ public final class AudioTrack { /** Represents an unset {@link android.media.AudioTrack} session identifier. */ public static final int SESSION_ID_NOT_SET = 0; - /** The default multiplication factor used when determining the size of the track's buffer. */ - public static final float DEFAULT_MIN_BUFFER_MULTIPLICATION_FACTOR = 4; - /** Returned by {@link #getCurrentPositionUs} when the position is not set. */ public static final long CURRENT_POSITION_NOT_SET = Long.MIN_VALUE; + /** A minimum length for the {@link android.media.AudioTrack} buffer, in microseconds. */ + private static final long MIN_BUFFER_DURATION_US = 250000; + /** A maximum length for the {@link android.media.AudioTrack} buffer, in microseconds. */ + private static final long MAX_BUFFER_DURATION_US = 750000; + /** + * A multiplication factor to apply to the minimum buffer size requested by the underlying + * {@link android.media.AudioTrack}. + */ + private static final int BUFFER_MULTIPLICATION_FACTOR = 4; + private static final String TAG = "AudioTrack"; /** @@ -126,7 +132,6 @@ public final class AudioTrack { private final ConditionVariable releasingConditionVariable; private final AudioTimestampCompat audioTimestampCompat; private final long[] playheadOffsets; - private final float minBufferMultiplicationFactor; private android.media.AudioTrack audioTrack; private int sampleRate; @@ -162,15 +167,7 @@ public final class AudioTrack { /** Bitrate measured in kilobits per second, if {@link #isAc3} is true. */ private int ac3Bitrate; - /** Constructs an audio track using the default minimum buffer size multiplier. */ public AudioTrack() { - this(DEFAULT_MIN_BUFFER_MULTIPLICATION_FACTOR); - } - - /** Constructs an audio track using the specified minimum buffer size multiplier. */ - public AudioTrack(float minBufferMultiplicationFactor) { - Assertions.checkArgument(minBufferMultiplicationFactor >= 1); - this.minBufferMultiplicationFactor = minBufferMultiplicationFactor; releasingConditionVariable = new ConditionVariable(true); if (Util.SDK_INT >= 19) { audioTimestampCompat = new AudioTimestampCompatV19(); @@ -297,11 +294,11 @@ public final class AudioTrack { * * @param format Specifies the channel count and sample rate to play back. * @param encoding The format in which audio is represented. - * @param bufferSize The total size of the playback buffer in bytes. Specify 0 to use a buffer - * size based on the minimum for format. + * @param specifiedBufferSize A specific size for the playback buffer in bytes, or 0 to use a + * size inferred from the format. */ @SuppressLint("InlinedApi") - public void reconfigure(MediaFormat format, int encoding, int bufferSize) { + public void reconfigure(MediaFormat format, int encoding, int specifiedBufferSize) { int channelCount = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT); int channelConfig; switch (channelCount) { @@ -333,16 +330,25 @@ public final class AudioTrack { reset(); - minBufferSize = android.media.AudioTrack.getMinBufferSize(sampleRate, channelConfig, encoding); - this.encoding = encoding; - this.bufferSize = - bufferSize == 0 ? (int) (minBufferMultiplicationFactor * minBufferSize) : bufferSize; this.sampleRate = sampleRate; this.channelConfig = channelConfig; this.isAc3 = isAc3; ac3Bitrate = UNKNOWN_AC3_BITRATE; // Calculated on receiving the first buffer if isAc3 is true. frameSize = 2 * channelCount; // 2 bytes per 16 bit sample * number of channels. + minBufferSize = android.media.AudioTrack.getMinBufferSize(sampleRate, channelConfig, encoding); + + if (specifiedBufferSize != 0) { + bufferSize = specifiedBufferSize; + } else { + int multipliedBufferSize = minBufferSize * BUFFER_MULTIPLICATION_FACTOR; + int minAppBufferSize = (int) durationUsToFrames(MIN_BUFFER_DURATION_US) * frameSize; + int maxAppBufferSize = (int) Math.max(minBufferSize, + durationUsToFrames(MAX_BUFFER_DURATION_US) * frameSize); + bufferSize = multipliedBufferSize < minAppBufferSize ? minAppBufferSize + : multipliedBufferSize > maxAppBufferSize ? maxAppBufferSize + : multipliedBufferSize; + } } /** Starts/resumes playing audio if the audio track has been initialized. */ @@ -434,7 +440,7 @@ public final class AudioTrack { int bytesWritten = 0; if (Util.SDK_INT < 21) { // Work out how many bytes we can write without the risk of blocking. - int bytesPending = (int) (submittedBytes - framesToBytes(getPlaybackPositionFrames())); + int bytesPending = (int) (submittedBytes - (getPlaybackPositionFrames() * frameSize)); int bytesToWrite = bufferSize - bytesPending; if (bytesToWrite > 0) { bytesToWrite = Math.min(temporaryBufferSize, bytesToWrite); @@ -651,11 +657,6 @@ public final class AudioTrack { return framesToDurationUs(getPlaybackPositionFrames()); } - private long framesToBytes(long frameCount) { - // This method is unused on SDK >= 21. - return frameCount * frameSize; - } - private long bytesToFrames(long byteCount) { if (isAc3) { return