mirror of
https://github.com/androidx/media.git
synced 2025-05-07 23:50:44 +08:00
Don't re-buffer when AudioTrack underruns occur.
This commit is contained in:
parent
80d699920c
commit
a4f1e3ce53
@ -159,6 +159,12 @@ public class EventLogger implements DemoPlayer.Listener, DemoPlayer.InfoListener
|
|||||||
printInternalError("audioTrackWriteError", e);
|
printInternalError("audioTrackWriteError", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAudioTrackUnderrun(long audioTrackBufferSizeMs, long elapsedSinceLastFeedMs) {
|
||||||
|
printInternalError("audioTrackUnderrun [" + audioTrackBufferSizeMs + ", "
|
||||||
|
+ elapsedSinceLastFeedMs + "]", null);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCryptoError(CryptoException e) {
|
public void onCryptoError(CryptoException e) {
|
||||||
printInternalError("cryptoError", e);
|
printInternalError("cryptoError", e);
|
||||||
|
@ -105,6 +105,7 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
|
|||||||
void onRendererInitializationError(Exception e);
|
void onRendererInitializationError(Exception e);
|
||||||
void onAudioTrackInitializationError(AudioTrack.InitializationException e);
|
void onAudioTrackInitializationError(AudioTrack.InitializationException e);
|
||||||
void onAudioTrackWriteError(AudioTrack.WriteException e);
|
void onAudioTrackWriteError(AudioTrack.WriteException e);
|
||||||
|
void onAudioTrackUnderrun(long audioTrackBufferSizeMs, long elapsedSinceLastFeedMs);
|
||||||
void onDecoderInitializationError(DecoderInitializationException e);
|
void onDecoderInitializationError(DecoderInitializationException e);
|
||||||
void onCryptoError(CryptoException e);
|
void onCryptoError(CryptoException e);
|
||||||
void onLoadError(int sourceId, IOException 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
|
@Override
|
||||||
public void onCryptoError(CryptoException e) {
|
public void onCryptoError(CryptoException e) {
|
||||||
if (internalErrorListener != null) {
|
if (internalErrorListener != null) {
|
||||||
|
@ -26,6 +26,7 @@ import android.media.AudioManager;
|
|||||||
import android.media.MediaCodec;
|
import android.media.MediaCodec;
|
||||||
import android.media.audiofx.Virtualizer;
|
import android.media.audiofx.Virtualizer;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
|
import android.os.SystemClock;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
@ -55,6 +56,14 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer implem
|
|||||||
*/
|
*/
|
||||||
void onAudioTrackWriteError(AudioTrack.WriteException e);
|
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 long currentPositionUs;
|
||||||
private boolean allowPositionDiscontinuity;
|
private boolean allowPositionDiscontinuity;
|
||||||
|
|
||||||
|
private boolean audioTrackHasData;
|
||||||
|
private long lastFeedElapsedRealtimeMs;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param source The upstream source from which the renderer obtains samples.
|
* @param source The upstream source from which the renderer obtains samples.
|
||||||
*/
|
*/
|
||||||
@ -272,8 +284,7 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer implem
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean isReady() {
|
protected boolean isReady() {
|
||||||
return audioTrack.hasPendingData()
|
return audioTrack.hasPendingData() || super.isReady();
|
||||||
|| (super.isReady() && getSourceState() == SOURCE_STATE_READY_READ_MAY_FAIL);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -321,8 +332,8 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer implem
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize and start the audio track now.
|
|
||||||
if (!audioTrack.isInitialized()) {
|
if (!audioTrack.isInitialized()) {
|
||||||
|
// Initialize the AudioTrack now.
|
||||||
try {
|
try {
|
||||||
if (audioSessionId != AudioTrack.SESSION_ID_NOT_SET) {
|
if (audioSessionId != AudioTrack.SESSION_ID_NOT_SET) {
|
||||||
audioTrack.initialize(audioSessionId);
|
audioTrack.initialize(audioSessionId);
|
||||||
@ -330,20 +341,29 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer implem
|
|||||||
audioSessionId = audioTrack.initialize();
|
audioSessionId = audioTrack.initialize();
|
||||||
onAudioSessionId(audioSessionId);
|
onAudioSessionId(audioSessionId);
|
||||||
}
|
}
|
||||||
|
audioTrackHasData = false;
|
||||||
} catch (AudioTrack.InitializationException e) {
|
} catch (AudioTrack.InitializationException e) {
|
||||||
notifyAudioTrackInitializationError(e);
|
notifyAudioTrackInitializationError(e);
|
||||||
throw new ExoPlaybackException(e);
|
throw new ExoPlaybackException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (getState() == TrackRenderer.STATE_STARTED) {
|
if (getState() == TrackRenderer.STATE_STARTED) {
|
||||||
audioTrack.play();
|
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;
|
int handleBufferResult;
|
||||||
try {
|
try {
|
||||||
handleBufferResult = audioTrack.handleBuffer(
|
handleBufferResult = audioTrack.handleBuffer(
|
||||||
buffer, bufferInfo.offset, bufferInfo.size, bufferInfo.presentationTimeUs);
|
buffer, bufferInfo.offset, bufferInfo.size, bufferInfo.presentationTimeUs);
|
||||||
|
lastFeedElapsedRealtimeMs = SystemClock.elapsedRealtime();
|
||||||
} catch (AudioTrack.WriteException e) {
|
} catch (AudioTrack.WriteException e) {
|
||||||
notifyAudioTrackWriteError(e);
|
notifyAudioTrackWriteError(e);
|
||||||
throw new ExoPlaybackException(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 frameSize;
|
||||||
private int minBufferSize;
|
private int minBufferSize;
|
||||||
private int bufferSize;
|
private int bufferSize;
|
||||||
|
private long bufferSizeUs;
|
||||||
|
|
||||||
private int nextPlayheadOffsetIndex;
|
private int nextPlayheadOffsetIndex;
|
||||||
private int playheadOffsetCount;
|
private int playheadOffsetCount;
|
||||||
@ -433,9 +434,25 @@ public final class AudioTrack {
|
|||||||
: multipliedBufferSize > maxAppBufferSize ? maxAppBufferSize
|
: multipliedBufferSize > maxAppBufferSize ? maxAppBufferSize
|
||||||
: multipliedBufferSize;
|
: 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() {
|
public void play() {
|
||||||
if (isInitialized()) {
|
if (isInitialized()) {
|
||||||
resumeSystemTimeUs = System.nanoTime() / 1000;
|
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() {
|
public void handleDiscontinuity() {
|
||||||
// Force resynchronization after a skipped buffer.
|
// Force resynchronization after a skipped buffer.
|
||||||
if (startMediaTimeState == START_IN_SYNC) {
|
if (startMediaTimeState == START_IN_SYNC) {
|
||||||
@ -584,14 +603,18 @@ public final class AudioTrack {
|
|||||||
return audioTrack.write(buffer, size, android.media.AudioTrack.WRITE_NON_BLOCKING);
|
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() {
|
public boolean hasPendingData() {
|
||||||
return isInitialized()
|
return isInitialized()
|
||||||
&& (bytesToFrames(submittedBytes) > audioTrackUtil.getPlaybackHeadPosition()
|
&& (bytesToFrames(submittedBytes) > audioTrackUtil.getPlaybackHeadPosition()
|
||||||
|| overrideHasPendingData());
|
|| overrideHasPendingData());
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Sets the playback volume. */
|
/**
|
||||||
|
* Sets the playback volume.
|
||||||
|
*/
|
||||||
public void setVolume(float volume) {
|
public void setVolume(float volume) {
|
||||||
if (this.volume != volume) {
|
if (this.volume != volume) {
|
||||||
this.volume = volume;
|
this.volume = volume;
|
||||||
@ -619,7 +642,9 @@ public final class AudioTrack {
|
|||||||
audioTrack.setStereoVolume(volume, volume);
|
audioTrack.setStereoVolume(volume, volume);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Pauses playback. */
|
/**
|
||||||
|
* Pauses playback.
|
||||||
|
*/
|
||||||
public void pause() {
|
public void pause() {
|
||||||
if (isInitialized()) {
|
if (isInitialized()) {
|
||||||
resetSyncParams();
|
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() {
|
public void release() {
|
||||||
reset();
|
reset();
|
||||||
releaseKeepSessionIdAudioTrack();
|
releaseKeepSessionIdAudioTrack();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Releases {@link #keepSessionIdAudioTrack} asynchronously, if it is non-{@code null}. */
|
/**
|
||||||
|
* Releases {@link #keepSessionIdAudioTrack} asynchronously, if it is non-{@code null}.
|
||||||
|
*/
|
||||||
private void releaseKeepSessionIdAudioTrack() {
|
private void releaseKeepSessionIdAudioTrack() {
|
||||||
if (keepSessionIdAudioTrack == null) {
|
if (keepSessionIdAudioTrack == null) {
|
||||||
return;
|
return;
|
||||||
@ -685,7 +714,9 @@ public final class AudioTrack {
|
|||||||
}.start();
|
}.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns whether {@link #getCurrentPositionUs} can return the current playback position. */
|
/**
|
||||||
|
* Returns whether {@link #getCurrentPositionUs} can return the current playback position.
|
||||||
|
*/
|
||||||
private boolean hasCurrentPositionUs() {
|
private boolean hasCurrentPositionUs() {
|
||||||
return isInitialized() && startMediaTimeState != START_NOT_SET;
|
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
|
// Compute the audio track latency, excluding the latency due to the buffer (leaving
|
||||||
// latency due to the mixer and audio hardware driver).
|
// latency due to the mixer and audio hardware driver).
|
||||||
latencyUs = (Integer) getLatencyMethod.invoke(audioTrack, (Object[]) null) * 1000L
|
latencyUs = (Integer) getLatencyMethod.invoke(audioTrack, (Object[]) null) * 1000L
|
||||||
- framesToDurationUs(bytesToFrames(bufferSize));
|
- bufferSizeUs;
|
||||||
// Sanity check that the latency is non-negative.
|
// Sanity check that the latency is non-negative.
|
||||||
latencyUs = Math.max(latencyUs, 0);
|
latencyUs = Math.max(latencyUs, 0);
|
||||||
// Sanity check that the latency isn't too large.
|
// Sanity check that the latency isn't too large.
|
||||||
|
@ -104,6 +104,12 @@ public final class LogcatLogger implements ExoPlayer.Listener,
|
|||||||
Log.e(tag, "Audio track write error", e);
|
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
|
@Override
|
||||||
public void onDroppedFrames(int count, long elapsed) {
|
public void onDroppedFrames(int count, long elapsed) {
|
||||||
Log.w(tag, "Dropped frames (" + count + ")");
|
Log.w(tag, "Dropped frames (" + count + ")");
|
||||||
|
Loading…
x
Reference in New Issue
Block a user