Commit 348b5802 by andrewlewis Committed by Oliver Woodman

Move underrun detection into AudioTrack.

This removes duplication from SimpleDecoderAudioRenderer and
MediaCodecAudioRenderer.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=139187254
parent 74383716
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
*/ */
package com.google.android.exoplayer2.audio; package com.google.android.exoplayer2.audio;
import android.annotation.SuppressLint;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.media.AudioFormat; import android.media.AudioFormat;
import android.media.AudioTimestamp; import android.media.AudioTimestamp;
...@@ -55,6 +56,24 @@ import java.nio.ByteBuffer; ...@@ -55,6 +56,24 @@ import java.nio.ByteBuffer;
public final class AudioTrack { public final class AudioTrack {
/** /**
* Listener for audio track events.
*/
public interface Listener {
/**
* Called when the audio track underruns.
*
* @param bufferSize The size of the track's buffer, in bytes.
* @param bufferSizeMs The size of the track's buffer, in milliseconds, if it is configured for
* PCM output. {@link C#TIME_UNSET} if it is configured for passthrough output, as the
* buffered media can have a variable bitrate so the duration may be unknown.
* @param elapsedSinceLastFeedMs The time since the track was last fed data, in milliseconds.
*/
void onUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs);
}
/**
* Thrown when a failure occurs initializing an {@link android.media.AudioTrack}. * Thrown when a failure occurs initializing an {@link android.media.AudioTrack}.
*/ */
public static final class InitializationException extends Exception { public static final class InitializationException extends Exception {
...@@ -152,6 +171,40 @@ public final class AudioTrack { ...@@ -152,6 +171,40 @@ public final class AudioTrack {
*/ */
private static final int BUFFER_MULTIPLICATION_FACTOR = 4; private static final int BUFFER_MULTIPLICATION_FACTOR = 4;
/**
* @see android.media.AudioTrack#PLAYSTATE_STOPPED
*/
private static final int PLAYSTATE_STOPPED = android.media.AudioTrack.PLAYSTATE_STOPPED;
/**
* @see android.media.AudioTrack#PLAYSTATE_PAUSED
*/
private static final int PLAYSTATE_PAUSED = android.media.AudioTrack.PLAYSTATE_PAUSED;
/**
* @see android.media.AudioTrack#PLAYSTATE_PLAYING
*/
private static final int PLAYSTATE_PLAYING = android.media.AudioTrack.PLAYSTATE_PLAYING;
/**
* @see android.media.AudioTrack#ERROR_BAD_VALUE
*/
private static final int ERROR_BAD_VALUE = android.media.AudioTrack.ERROR_BAD_VALUE;
/**
* @see android.media.AudioTrack#MODE_STATIC
*/
private static final int MODE_STATIC = android.media.AudioTrack.MODE_STATIC;
/**
* @see android.media.AudioTrack#MODE_STREAM
*/
private static final int MODE_STREAM = android.media.AudioTrack.MODE_STREAM;
/**
* @see android.media.AudioTrack#STATE_INITIALIZED
*/
private static final int STATE_INITIALIZED = android.media.AudioTrack.STATE_INITIALIZED;
/**
* @see android.media.AudioTrack#WRITE_NON_BLOCKING
*/
@SuppressLint("InlinedApi")
private static final int WRITE_NON_BLOCKING = android.media.AudioTrack.WRITE_NON_BLOCKING;
private static final String TAG = "AudioTrack"; private static final String TAG = "AudioTrack";
/** /**
...@@ -197,6 +250,7 @@ public final class AudioTrack { ...@@ -197,6 +250,7 @@ public final class AudioTrack {
private final AudioCapabilities audioCapabilities; private final AudioCapabilities audioCapabilities;
private final int streamType; private final int streamType;
private final Listener listener;
private final ConditionVariable releasingConditionVariable; private final ConditionVariable releasingConditionVariable;
private final long[] playheadOffsets; private final long[] playheadOffsets;
private final AudioTrackUtil audioTrackUtil; private final AudioTrackUtil audioTrackUtil;
...@@ -242,13 +296,18 @@ public final class AudioTrack { ...@@ -242,13 +296,18 @@ public final class AudioTrack {
private ByteBuffer resampledBuffer; private ByteBuffer resampledBuffer;
private boolean useResampledBuffer; private boolean useResampledBuffer;
private boolean hasData;
private long lastFeedElapsedRealtimeMs;
/** /**
* @param audioCapabilities The current audio capabilities. * @param audioCapabilities The current audio capabilities.
* @param streamType The type of audio stream for the underlying {@link android.media.AudioTrack}. * @param streamType The type of audio stream for the underlying {@link android.media.AudioTrack}.
* @param listener Listener for audio track events.
*/ */
public AudioTrack(AudioCapabilities audioCapabilities, int streamType) { public AudioTrack(AudioCapabilities audioCapabilities, int streamType, Listener listener) {
this.audioCapabilities = audioCapabilities; this.audioCapabilities = audioCapabilities;
this.streamType = streamType; this.streamType = streamType;
this.listener = listener;
releasingConditionVariable = new ConditionVariable(true); releasingConditionVariable = new ConditionVariable(true);
if (Util.SDK_INT >= 18) { if (Util.SDK_INT >= 18) {
try { try {
...@@ -305,7 +364,7 @@ public final class AudioTrack { ...@@ -305,7 +364,7 @@ public final class AudioTrack {
return CURRENT_POSITION_NOT_SET; return CURRENT_POSITION_NOT_SET;
} }
if (audioTrack.getPlayState() == android.media.AudioTrack.PLAYSTATE_PLAYING) { if (audioTrack.getPlayState() == PLAYSTATE_PLAYING) {
maybeSampleSyncParams(); maybeSampleSyncParams();
} }
...@@ -424,7 +483,7 @@ public final class AudioTrack { ...@@ -424,7 +483,7 @@ public final class AudioTrack {
} else { } else {
int minBufferSize = int minBufferSize =
android.media.AudioTrack.getMinBufferSize(sampleRate, channelConfig, targetEncoding); android.media.AudioTrack.getMinBufferSize(sampleRate, channelConfig, targetEncoding);
Assertions.checkState(minBufferSize != android.media.AudioTrack.ERROR_BAD_VALUE); Assertions.checkState(minBufferSize != ERROR_BAD_VALUE);
int multipliedBufferSize = minBufferSize * BUFFER_MULTIPLICATION_FACTOR; int multipliedBufferSize = minBufferSize * BUFFER_MULTIPLICATION_FACTOR;
int minAppBufferSize = (int) durationUsToFrames(MIN_BUFFER_DURATION_US) * pcmFrameSize; int minAppBufferSize = (int) durationUsToFrames(MIN_BUFFER_DURATION_US) * pcmFrameSize;
int maxAppBufferSize = (int) Math.max(minBufferSize, int maxAppBufferSize = (int) Math.max(minBufferSize,
...@@ -453,11 +512,11 @@ public final class AudioTrack { ...@@ -453,11 +512,11 @@ public final class AudioTrack {
if (sessionId == SESSION_ID_NOT_SET) { if (sessionId == SESSION_ID_NOT_SET) {
audioTrack = new android.media.AudioTrack(streamType, sampleRate, channelConfig, audioTrack = new android.media.AudioTrack(streamType, sampleRate, channelConfig,
targetEncoding, bufferSize, android.media.AudioTrack.MODE_STREAM); targetEncoding, bufferSize, MODE_STREAM);
} else { } else {
// Re-attach to the same audio session. // Re-attach to the same audio session.
audioTrack = new android.media.AudioTrack(streamType, sampleRate, channelConfig, audioTrack = new android.media.AudioTrack(streamType, sampleRate, channelConfig,
targetEncoding, bufferSize, android.media.AudioTrack.MODE_STREAM, sessionId); targetEncoding, bufferSize, MODE_STREAM, sessionId);
} }
checkAudioTrackInitialized(); checkAudioTrackInitialized();
...@@ -476,43 +535,18 @@ public final class AudioTrack { ...@@ -476,43 +535,18 @@ public final class AudioTrack {
@C.PcmEncoding int encoding = C.ENCODING_PCM_16BIT; @C.PcmEncoding int encoding = C.ENCODING_PCM_16BIT;
int bufferSize = 2; // Use a two byte buffer, as it is not actually used for playback. int bufferSize = 2; // Use a two byte buffer, as it is not actually used for playback.
keepSessionIdAudioTrack = new android.media.AudioTrack(streamType, sampleRate, keepSessionIdAudioTrack = new android.media.AudioTrack(streamType, sampleRate,
channelConfig, encoding, bufferSize, android.media.AudioTrack.MODE_STATIC, sessionId); channelConfig, encoding, bufferSize, MODE_STATIC, sessionId);
} }
} }
} }
audioTrackUtil.reconfigure(audioTrack, needsPassthroughWorkarounds()); audioTrackUtil.reconfigure(audioTrack, needsPassthroughWorkarounds());
setAudioTrackVolume(); setAudioTrackVolume();
hasData = false;
return sessionId; return sessionId;
} }
/** /**
* Returns the size of this {@link AudioTrack}'s buffer in bytes.
* <p>
* The value returned from this method may change as a result of calling one of the
* {@link #configure} methods.
*
* @return The size of the buffer in bytes.
*/
public int getBufferSize() {
return bufferSize;
}
/**
* Returns the size of the buffer in microseconds for PCM {@link AudioTrack}s, or
* {@link C#TIME_UNSET} for passthrough {@link AudioTrack}s.
* <p>
* The value returned from this method may change as a result of calling one of the
* {@link #configure} methods.
*
* @return The size of the buffer in microseconds for PCM {@link AudioTrack}s, or
* {@link C#TIME_UNSET} for passthrough {@link AudioTrack}s.
*/
public long getBufferSizeUs() {
return bufferSizeUs;
}
/**
* Starts or resumes playing audio if the audio track has been initialized. * Starts or resumes playing audio if the audio track has been initialized.
*/ */
public void play() { public void play() {
...@@ -553,6 +587,18 @@ public final class AudioTrack { ...@@ -553,6 +587,18 @@ public final class AudioTrack {
* @throws WriteException If an error occurs writing the audio data. * @throws WriteException If an error occurs writing the audio data.
*/ */
public int handleBuffer(ByteBuffer buffer, long presentationTimeUs) throws WriteException { public int handleBuffer(ByteBuffer buffer, long presentationTimeUs) throws WriteException {
boolean hadData = hasData;
hasData = hasPendingData();
if (hadData && !hasData && audioTrack.getPlayState() != PLAYSTATE_STOPPED) {
long elapsedSinceLastFeedMs = SystemClock.elapsedRealtime() - lastFeedElapsedRealtimeMs;
listener.onUnderrun(bufferSize, C.usToMs(bufferSizeUs), elapsedSinceLastFeedMs);
}
int result = writeBuffer(buffer, presentationTimeUs);
lastFeedElapsedRealtimeMs = SystemClock.elapsedRealtime();
return result;
}
private int writeBuffer(ByteBuffer buffer, long presentationTimeUs) throws WriteException {
boolean isNewSourceBuffer = currentSourceBuffer == null; boolean isNewSourceBuffer = currentSourceBuffer == null;
Assertions.checkState(isNewSourceBuffer || currentSourceBuffer == buffer); Assertions.checkState(isNewSourceBuffer || currentSourceBuffer == buffer);
currentSourceBuffer = buffer; currentSourceBuffer = buffer;
...@@ -560,14 +606,14 @@ public final class AudioTrack { ...@@ -560,14 +606,14 @@ public final class AudioTrack {
if (needsPassthroughWorkarounds()) { if (needsPassthroughWorkarounds()) {
// An AC-3 audio track continues to play data written while it is paused. Stop writing so its // An AC-3 audio track continues to play data written while it is paused. Stop writing so its
// buffer empties. See [Internal: b/18899620]. // buffer empties. See [Internal: b/18899620].
if (audioTrack.getPlayState() == android.media.AudioTrack.PLAYSTATE_PAUSED) { if (audioTrack.getPlayState() == PLAYSTATE_PAUSED) {
return 0; return 0;
} }
// A new AC-3 audio track's playback position continues to increase from the old track's // A new AC-3 audio track's playback position continues to increase from the old track's
// position for a short time after is has been released. Avoid writing data until the playback // position for a short time after is has been released. Avoid writing data until the playback
// head position actually returns to zero. // head position actually returns to zero.
if (audioTrack.getPlayState() == android.media.AudioTrack.PLAYSTATE_STOPPED if (audioTrack.getPlayState() == PLAYSTATE_STOPPED
&& audioTrackUtil.getPlaybackHeadPosition() != 0) { && audioTrackUtil.getPlaybackHeadPosition() != 0) {
return 0; return 0;
} }
...@@ -745,7 +791,7 @@ public final class AudioTrack { ...@@ -745,7 +791,7 @@ public final class AudioTrack {
latencyUs = 0; latencyUs = 0;
resetSyncParams(); resetSyncParams();
int playState = audioTrack.getPlayState(); int playState = audioTrack.getPlayState();
if (playState == android.media.AudioTrack.PLAYSTATE_PLAYING) { if (playState == PLAYSTATE_PLAYING) {
audioTrack.pause(); audioTrack.pause();
} }
// AudioTrack.release can take some time, so we call it on a background thread. // AudioTrack.release can take some time, so we call it on a background thread.
...@@ -894,7 +940,7 @@ public final class AudioTrack { ...@@ -894,7 +940,7 @@ public final class AudioTrack {
*/ */
private void checkAudioTrackInitialized() throws InitializationException { private void checkAudioTrackInitialized() throws InitializationException {
int state = audioTrack.getState(); int state = audioTrack.getState();
if (state == android.media.AudioTrack.STATE_INITIALIZED) { if (state == STATE_INITIALIZED) {
return; return;
} }
// The track is not successfully initialized. Release and null the track. // The track is not successfully initialized. Release and null the track.
...@@ -952,7 +998,7 @@ public final class AudioTrack { ...@@ -952,7 +998,7 @@ public final class AudioTrack {
*/ */
private boolean overrideHasPendingData() { private boolean overrideHasPendingData() {
return needsPassthroughWorkarounds() return needsPassthroughWorkarounds()
&& audioTrack.getPlayState() == android.media.AudioTrack.PLAYSTATE_PAUSED && audioTrack.getPlayState() == PLAYSTATE_PAUSED
&& audioTrack.getPlaybackHeadPosition() == 0; && audioTrack.getPlaybackHeadPosition() == 0;
} }
...@@ -1063,7 +1109,7 @@ public final class AudioTrack { ...@@ -1063,7 +1109,7 @@ public final class AudioTrack {
@TargetApi(21) @TargetApi(21)
private static int writeNonBlockingV21( private static int writeNonBlockingV21(
android.media.AudioTrack audioTrack, ByteBuffer buffer, int size) { android.media.AudioTrack audioTrack, ByteBuffer buffer, int size) {
return audioTrack.write(buffer, size, android.media.AudioTrack.WRITE_NON_BLOCKING); return audioTrack.write(buffer, size, WRITE_NON_BLOCKING);
} }
@TargetApi(21) @TargetApi(21)
...@@ -1156,7 +1202,7 @@ public final class AudioTrack { ...@@ -1156,7 +1202,7 @@ public final class AudioTrack {
} }
int state = audioTrack.getPlayState(); int state = audioTrack.getPlayState();
if (state == android.media.AudioTrack.PLAYSTATE_STOPPED) { if (state == PLAYSTATE_STOPPED) {
// The audio track hasn't been started. // The audio track hasn't been started.
return 0; return 0;
} }
...@@ -1166,7 +1212,7 @@ public final class AudioTrack { ...@@ -1166,7 +1212,7 @@ public final class AudioTrack {
// Work around an issue with passthrough/direct AudioTracks on platform API versions 21/22 // Work around an issue with passthrough/direct AudioTracks on platform API versions 21/22
// where the playback head position jumps back to zero on paused passthrough/direct audio // where the playback head position jumps back to zero on paused passthrough/direct audio
// tracks. See [Internal: b/19187573]. // tracks. See [Internal: b/19187573].
if (state == android.media.AudioTrack.PLAYSTATE_PAUSED && rawPlaybackHeadPosition == 0) { if (state == PLAYSTATE_PAUSED && rawPlaybackHeadPosition == 0) {
passthroughWorkaroundPauseOffset = lastRawPlaybackHeadPosition; passthroughWorkaroundPauseOffset = lastRawPlaybackHeadPosition;
} }
rawPlaybackHeadPosition += passthroughWorkaroundPauseOffset; rawPlaybackHeadPosition += passthroughWorkaroundPauseOffset;
......
...@@ -23,7 +23,6 @@ import android.media.MediaFormat; ...@@ -23,7 +23,6 @@ import android.media.MediaFormat;
import android.media.PlaybackParams; import android.media.PlaybackParams;
import android.media.audiofx.Virtualizer; import android.media.audiofx.Virtualizer;
import android.os.Handler; import android.os.Handler;
import android.os.SystemClock;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
...@@ -43,7 +42,8 @@ import java.nio.ByteBuffer; ...@@ -43,7 +42,8 @@ import java.nio.ByteBuffer;
* Decodes and renders audio using {@link MediaCodec} and {@link AudioTrack}. * Decodes and renders audio using {@link MediaCodec} and {@link AudioTrack}.
*/ */
@TargetApi(16) @TargetApi(16)
public class MediaCodecAudioRenderer extends MediaCodecRenderer implements MediaClock { public class MediaCodecAudioRenderer extends MediaCodecRenderer implements MediaClock,
AudioTrack.Listener {
private final EventDispatcher eventDispatcher; private final EventDispatcher eventDispatcher;
private final AudioTrack audioTrack; private final AudioTrack audioTrack;
...@@ -55,9 +55,6 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media ...@@ -55,9 +55,6 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
private long currentPositionUs; private long currentPositionUs;
private boolean allowPositionDiscontinuity; private boolean allowPositionDiscontinuity;
private boolean audioTrackHasData;
private long lastFeedElapsedRealtimeMs;
/** /**
* @param mediaCodecSelector A decoder selector. * @param mediaCodecSelector A decoder selector.
*/ */
...@@ -136,7 +133,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media ...@@ -136,7 +133,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
int streamType) { int streamType) {
super(C.TRACK_TYPE_AUDIO, mediaCodecSelector, drmSessionManager, playClearSamplesWithoutKeys); super(C.TRACK_TYPE_AUDIO, mediaCodecSelector, drmSessionManager, playClearSamplesWithoutKeys);
audioSessionId = AudioTrack.SESSION_ID_NOT_SET; audioSessionId = AudioTrack.SESSION_ID_NOT_SET;
audioTrack = new AudioTrack(audioCapabilities, streamType); audioTrack = new AudioTrack(audioCapabilities, streamType, this);
eventDispatcher = new EventDispatcher(eventHandler, eventListener); eventDispatcher = new EventDispatcher(eventHandler, eventListener);
} }
...@@ -341,29 +338,17 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media ...@@ -341,29 +338,17 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
} else { } else {
audioTrack.initialize(audioSessionId); audioTrack.initialize(audioSessionId);
} }
audioTrackHasData = false;
} catch (AudioTrack.InitializationException e) { } catch (AudioTrack.InitializationException e) {
throw ExoPlaybackException.createForRenderer(e, getIndex()); throw ExoPlaybackException.createForRenderer(e, getIndex());
} }
if (getState() == STATE_STARTED) { if (getState() == STATE_STARTED) {
audioTrack.play(); audioTrack.play();
} }
} else {
// Check for AudioTrack underrun.
boolean audioTrackHadData = audioTrackHasData;
audioTrackHasData = audioTrack.hasPendingData();
if (audioTrackHadData && !audioTrackHasData && getState() == STATE_STARTED) {
long elapsedSinceLastFeedMs = SystemClock.elapsedRealtime() - lastFeedElapsedRealtimeMs;
long bufferSizeMs = C.usToMs(audioTrack.getBufferSizeUs());
eventDispatcher.audioTrackUnderrun(audioTrack.getBufferSize(), bufferSizeMs,
elapsedSinceLastFeedMs);
}
} }
int handleBufferResult; int handleBufferResult;
try { try {
handleBufferResult = audioTrack.handleBuffer(buffer, bufferPresentationTimeUs); handleBufferResult = audioTrack.handleBuffer(buffer, bufferPresentationTimeUs);
lastFeedElapsedRealtimeMs = SystemClock.elapsedRealtime();
} catch (AudioTrack.WriteException e) { } catch (AudioTrack.WriteException e) {
throw ExoPlaybackException.createForRenderer(e, getIndex()); throw ExoPlaybackException.createForRenderer(e, getIndex());
} }
...@@ -408,4 +393,11 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media ...@@ -408,4 +393,11 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
} }
} }
// AudioTrack.Listener implementation.
@Override
public void onUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {
eventDispatcher.audioTrackUnderrun(bufferSize, bufferSizeMs, elapsedSinceLastFeedMs);
}
} }
...@@ -41,7 +41,8 @@ import com.google.android.exoplayer2.util.Util; ...@@ -41,7 +41,8 @@ import com.google.android.exoplayer2.util.Util;
/** /**
* Decodes and renders audio using a {@link SimpleDecoder}. * Decodes and renders audio using a {@link SimpleDecoder}.
*/ */
public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements MediaClock { public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements MediaClock,
AudioTrack.Listener {
private final boolean playClearSamplesWithoutKeys; private final boolean playClearSamplesWithoutKeys;
...@@ -67,9 +68,6 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements ...@@ -67,9 +68,6 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
private final AudioTrack audioTrack; private final AudioTrack audioTrack;
private int audioSessionId; private int audioSessionId;
private boolean audioTrackHasData;
private long lastFeedElapsedRealtimeMs;
public SimpleDecoderAudioRenderer() { public SimpleDecoderAudioRenderer() {
this(null, null); this(null, null);
} }
...@@ -122,7 +120,7 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements ...@@ -122,7 +120,7 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
this.playClearSamplesWithoutKeys = playClearSamplesWithoutKeys; this.playClearSamplesWithoutKeys = playClearSamplesWithoutKeys;
eventDispatcher = new EventDispatcher(eventHandler, eventListener); eventDispatcher = new EventDispatcher(eventHandler, eventListener);
audioSessionId = AudioTrack.SESSION_ID_NOT_SET; audioSessionId = AudioTrack.SESSION_ID_NOT_SET;
audioTrack = new AudioTrack(audioCapabilities, streamType); audioTrack = new AudioTrack(audioCapabilities, streamType, this);
formatHolder = new FormatHolder(); formatHolder = new FormatHolder();
} }
...@@ -245,24 +243,12 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements ...@@ -245,24 +243,12 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
} else { } else {
audioTrack.initialize(audioSessionId); audioTrack.initialize(audioSessionId);
} }
audioTrackHasData = false;
if (getState() == STATE_STARTED) { if (getState() == STATE_STARTED) {
audioTrack.play(); audioTrack.play();
} }
} else {
// Check for AudioTrack underrun.
boolean audioTrackHadData = audioTrackHasData;
audioTrackHasData = audioTrack.hasPendingData();
if (audioTrackHadData && !audioTrackHasData && getState() == STATE_STARTED) {
long elapsedSinceLastFeedMs = SystemClock.elapsedRealtime() - lastFeedElapsedRealtimeMs;
long bufferSizeMs = C.usToMs(audioTrack.getBufferSizeUs());
eventDispatcher.audioTrackUnderrun(audioTrack.getBufferSize(), bufferSizeMs,
elapsedSinceLastFeedMs);
}
} }
int handleBufferResult = audioTrack.handleBuffer(outputBuffer.data, outputBuffer.timeUs); int handleBufferResult = audioTrack.handleBuffer(outputBuffer.data, outputBuffer.timeUs);
lastFeedElapsedRealtimeMs = SystemClock.elapsedRealtime();
// If we are out of sync, allow currentPositionUs to jump backwards. // If we are out of sync, allow currentPositionUs to jump backwards.
if ((handleBufferResult & AudioTrack.RESULT_POSITION_DISCONTINUITY) != 0) { if ((handleBufferResult & AudioTrack.RESULT_POSITION_DISCONTINUITY) != 0) {
...@@ -493,4 +479,11 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements ...@@ -493,4 +479,11 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
} }
} }
// AudioTrack.Listener implementation.
@Override
public void onUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {
eventDispatcher.audioTrackUnderrun(bufferSize, bufferSizeMs, elapsedSinceLastFeedMs);
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment