Commit a4f1e3ce by Oliver Woodman

Don't re-buffer when AudioTrack underruns occur.

parent 80d69992
......@@ -160,6 +160,12 @@ public class EventLogger implements DemoPlayer.Listener, DemoPlayer.InfoListener
}
@Override
public void onAudioTrackUnderrun(long audioTrackBufferSizeMs, long elapsedSinceLastFeedMs) {
printInternalError("audioTrackUnderrun [" + audioTrackBufferSizeMs + ", "
+ elapsedSinceLastFeedMs + "]", null);
}
@Override
public void onCryptoError(CryptoException e) {
printInternalError("cryptoError", e);
}
......
......@@ -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);
......@@ -482,6 +483,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) {
internalErrorListener.onCryptoError(e);
......
......@@ -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);
}
});
}
}
}
......@@ -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.
......
......@@ -105,6 +105,12 @@ public final class LogcatLogger implements ExoPlayer.Listener,
}
@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 + ")");
}
......
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