Don't re-buffer when AudioTrack underruns occur.

This commit is contained in:
Oliver Woodman 2015-11-17 16:27:53 +00:00
parent 80d699920c
commit a4f1e3ce53
5 changed files with 96 additions and 13 deletions

View File

@ -159,6 +159,12 @@ public class EventLogger implements DemoPlayer.Listener, DemoPlayer.InfoListener
printInternalError("audioTrackWriteError", e);
}
@Override
public void onAudioTrackUnderrun(long audioTrackBufferSizeMs, long elapsedSinceLastFeedMs) {
printInternalError("audioTrackUnderrun [" + audioTrackBufferSizeMs + ", "
+ elapsedSinceLastFeedMs + "]", null);
}
@Override
public void onCryptoError(CryptoException e) {
printInternalError("cryptoError", e);

View File

@ -105,6 +105,7 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
void onRendererInitializationError(Exception e);
void onAudioTrackInitializationError(AudioTrack.InitializationException e);
void onAudioTrackWriteError(AudioTrack.WriteException e);
void onAudioTrackUnderrun(long audioTrackBufferSizeMs, long elapsedSinceLastFeedMs);
void onDecoderInitializationError(DecoderInitializationException e);
void onCryptoError(CryptoException e);
void onLoadError(int sourceId, IOException e);
@ -481,6 +482,13 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
}
}
@Override
public void onAudioTrackUnderrun(long audioTrackBufferSizeMs, long elapsedSinceLastFeedMs) {
if (internalErrorListener != null) {
internalErrorListener.onAudioTrackUnderrun(audioTrackBufferSizeMs, elapsedSinceLastFeedMs);
}
}
@Override
public void onCryptoError(CryptoException e) {
if (internalErrorListener != null) {

View File

@ -26,6 +26,7 @@ import android.media.AudioManager;
import android.media.MediaCodec;
import android.media.audiofx.Virtualizer;
import android.os.Handler;
import android.os.SystemClock;
import java.nio.ByteBuffer;
@ -55,6 +56,14 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer implem
*/
void onAudioTrackWriteError(AudioTrack.WriteException e);
/**
* Invoked when an {@link AudioTrack} underrun occurs.
*
* @param audioTrackBufferSizeMs The size of the {@link AudioTrack}'s buffer, in milliseconds.
* @param elapsedSinceLastFeedMs The time since the {@link AudioTrack} was last fed data.
*/
void onAudioTrackUnderrun(long audioTrackBufferSizeMs, long elapsedSinceLastFeedMs);
}
/**
@ -77,6 +86,9 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer implem
private long currentPositionUs;
private boolean allowPositionDiscontinuity;
private boolean audioTrackHasData;
private long lastFeedElapsedRealtimeMs;
/**
* @param source The upstream source from which the renderer obtains samples.
*/
@ -272,8 +284,7 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer implem
@Override
protected boolean isReady() {
return audioTrack.hasPendingData()
|| (super.isReady() && getSourceState() == SOURCE_STATE_READY_READ_MAY_FAIL);
return audioTrack.hasPendingData() || super.isReady();
}
@Override
@ -321,8 +332,8 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer implem
return true;
}
// Initialize and start the audio track now.
if (!audioTrack.isInitialized()) {
// Initialize the AudioTrack now.
try {
if (audioSessionId != AudioTrack.SESSION_ID_NOT_SET) {
audioTrack.initialize(audioSessionId);
@ -330,20 +341,29 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer implem
audioSessionId = audioTrack.initialize();
onAudioSessionId(audioSessionId);
}
audioTrackHasData = false;
} catch (AudioTrack.InitializationException e) {
notifyAudioTrackInitializationError(e);
throw new ExoPlaybackException(e);
}
if (getState() == TrackRenderer.STATE_STARTED) {
audioTrack.play();
}
} else {
// Check for AudioTrack underrun.
boolean audioTrackHadData = audioTrackHasData;
audioTrackHasData = audioTrack.hasPendingData();
if (audioTrackHadData && !audioTrackHasData && getState() == TrackRenderer.STATE_STARTED) {
long elapsedSinceLastFeedMs = SystemClock.elapsedRealtime() - lastFeedElapsedRealtimeMs;
notifyAudioTrackUnderrun(audioTrack.getBufferSizeUs() / 1000, elapsedSinceLastFeedMs);
}
}
int handleBufferResult;
try {
handleBufferResult = audioTrack.handleBuffer(
buffer, bufferInfo.offset, bufferInfo.size, bufferInfo.presentationTimeUs);
lastFeedElapsedRealtimeMs = SystemClock.elapsedRealtime();
} catch (AudioTrack.WriteException e) {
notifyAudioTrackWriteError(e);
throw new ExoPlaybackException(e);
@ -405,4 +425,16 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer implem
}
}
private void notifyAudioTrackUnderrun(final long audioTrackBufferSizeMs,
final long elapsedSinceLastFeedMs) {
if (eventHandler != null && eventListener != null) {
eventHandler.post(new Runnable() {
@Override
public void run() {
eventListener.onAudioTrackUnderrun(audioTrackBufferSizeMs, elapsedSinceLastFeedMs);
}
});
}
}
}

View File

@ -181,6 +181,7 @@ public final class AudioTrack {
private int frameSize;
private int minBufferSize;
private int bufferSize;
private long bufferSizeUs;
private int nextPlayheadOffsetIndex;
private int playheadOffsetCount;
@ -433,9 +434,25 @@ public final class AudioTrack {
: multipliedBufferSize > maxAppBufferSize ? maxAppBufferSize
: multipliedBufferSize;
}
bufferSizeUs = framesToDurationUs(bytesToFrames(bufferSize));
}
/** Starts/resumes playing audio if the audio track has been initialized. */
/**
* Returns the size of this {@link AudioTrack}'s buffer in microseconds, given its current
* configuration.
* <p>
* The duration returned from this method may change as a result of calling one of the
* {@link #reconfigure} methods.
*
* @return The size of the buffer in microseconds.
*/
public long getBufferSizeUs() {
return bufferSizeUs;
}
/**
* Starts or resumes playing audio if the audio track has been initialized.
*/
public void play() {
if (isInitialized()) {
resumeSystemTimeUs = System.nanoTime() / 1000;
@ -443,7 +460,9 @@ public final class AudioTrack {
}
}
/** Signals to the audio track that the next buffer is discontinuous with the previous buffer. */
/**
* Signals to the audio track that the next buffer is discontinuous with the previous buffer.
*/
public void handleDiscontinuity() {
// Force resynchronization after a skipped buffer.
if (startMediaTimeState == START_IN_SYNC) {
@ -584,14 +603,18 @@ public final class AudioTrack {
return audioTrack.write(buffer, size, android.media.AudioTrack.WRITE_NON_BLOCKING);
}
/** Returns whether the audio track has more data pending that will be played back. */
/**
* Returns whether the audio track has more data pending that will be played back.
*/
public boolean hasPendingData() {
return isInitialized()
&& (bytesToFrames(submittedBytes) > audioTrackUtil.getPlaybackHeadPosition()
|| overrideHasPendingData());
}
/** Sets the playback volume. */
/**
* Sets the playback volume.
*/
public void setVolume(float volume) {
if (this.volume != volume) {
this.volume = volume;
@ -619,7 +642,9 @@ public final class AudioTrack {
audioTrack.setStereoVolume(volume, volume);
}
/** Pauses playback. */
/**
* Pauses playback.
*/
public void pause() {
if (isInitialized()) {
resetSyncParams();
@ -662,13 +687,17 @@ public final class AudioTrack {
}
}
/** Releases all resources associated with this instance. */
/**
* Releases all resources associated with this instance.
*/
public void release() {
reset();
releaseKeepSessionIdAudioTrack();
}
/** Releases {@link #keepSessionIdAudioTrack} asynchronously, if it is non-{@code null}. */
/**
* Releases {@link #keepSessionIdAudioTrack} asynchronously, if it is non-{@code null}.
*/
private void releaseKeepSessionIdAudioTrack() {
if (keepSessionIdAudioTrack == null) {
return;
@ -685,7 +714,9 @@ public final class AudioTrack {
}.start();
}
/** Returns whether {@link #getCurrentPositionUs} can return the current playback position. */
/**
* Returns whether {@link #getCurrentPositionUs} can return the current playback position.
*/
private boolean hasCurrentPositionUs() {
return isInitialized() && startMediaTimeState != START_NOT_SET;
}
@ -757,7 +788,7 @@ public final class AudioTrack {
// Compute the audio track latency, excluding the latency due to the buffer (leaving
// latency due to the mixer and audio hardware driver).
latencyUs = (Integer) getLatencyMethod.invoke(audioTrack, (Object[]) null) * 1000L
- framesToDurationUs(bytesToFrames(bufferSize));
- bufferSizeUs;
// Sanity check that the latency is non-negative.
latencyUs = Math.max(latencyUs, 0);
// Sanity check that the latency isn't too large.

View File

@ -104,6 +104,12 @@ public final class LogcatLogger implements ExoPlayer.Listener,
Log.e(tag, "Audio track write error", e);
}
@Override
public void onAudioTrackUnderrun(long audioTrackBufferSizeMs, long elapsedSinceLastFeedMs) {
Log.e(tag, "Audio track underrun (" + audioTrackBufferSizeMs + ", " + elapsedSinceLastFeedMs
+ ")");
}
@Override
public void onDroppedFrames(int count, long elapsed) {
Log.w(tag, "Dropped frames (" + count + ")");