Add plumbing for reporting internal decoder errors

PiperOrigin-RevId: 357732695
This commit is contained in:
olly 2021-02-16 17:13:16 +00:00 committed by kim-vde
parent e009322edd
commit 1a34de5954
12 changed files with 188 additions and 26 deletions

View File

@ -9,6 +9,8 @@
* Audio:
* Fix `SimpleExoPlayer` reporting audio session ID as 0 in some cases
([#8585](https://github.com/google/ExoPlayer/issues/8585)).
* Analytics:
* Add `onAudioCodecError` and `onVideoCodecError` to `AnalyticsListener`.
* Library restructuring:
* `DebugTextViewHelper` moved from `ui` package to `util` package.
* Spherical UI components moved from `video.spherical` package to

View File

@ -2098,6 +2098,11 @@ public class SimpleExoPlayer extends BasePlayer
analyticsCollector.onVideoFrameProcessingOffset(totalProcessingOffsetUs, frameCount);
}
@Override
public void onVideoCodecError(Exception videoCodecError) {
analyticsCollector.onVideoCodecError(videoCodecError);
}
// AudioRendererEventListener implementation
@Override
@ -2156,6 +2161,11 @@ public class SimpleExoPlayer extends BasePlayer
analyticsCollector.onAudioSinkError(audioSinkError);
}
@Override
public void onAudioCodecError(Exception audioCodecError) {
analyticsCollector.onAudioCodecError(audioCodecError);
}
// TextOutput implementation
@Override

View File

@ -309,6 +309,15 @@ public class AnalyticsCollector
listener -> listener.onAudioSinkError(eventTime, audioSinkError));
}
@Override
public final void onAudioCodecError(Exception audioCodecError) {
EventTime eventTime = generateReadingMediaPeriodEventTime();
sendEvent(
eventTime,
AnalyticsListener.EVENT_AUDIO_CODEC_ERROR,
listener -> listener.onAudioCodecError(eventTime, audioCodecError));
}
// Additional audio events.
/**
@ -456,6 +465,15 @@ public class AnalyticsCollector
listener.onVideoFrameProcessingOffset(eventTime, totalProcessingOffsetUs, frameCount));
}
@Override
public final void onVideoCodecError(Exception videoCodecError) {
EventTime eventTime = generateReadingMediaPeriodEventTime();
sendEvent(
eventTime,
AnalyticsListener.EVENT_VIDEO_CODEC_ERROR,
listener -> listener.onVideoCodecError(eventTime, videoCodecError));
}
// Additional video events.
/**

View File

@ -17,6 +17,8 @@ package com.google.android.exoplayer2.analytics;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import android.media.MediaCodec;
import android.media.MediaCodec.CodecException;
import android.os.Looper;
import android.util.SparseArray;
import android.view.Surface;
@ -35,6 +37,7 @@ import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.audio.AudioAttributes;
import com.google.android.exoplayer2.audio.AudioSink;
import com.google.android.exoplayer2.decoder.DecoderCounters;
import com.google.android.exoplayer2.decoder.DecoderException;
import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation;
import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.source.LoadEventInfo;
@ -198,6 +201,8 @@ public interface AnalyticsListener {
EVENT_DRM_KEYS_REMOVED,
EVENT_DRM_SESSION_RELEASED,
EVENT_PLAYER_RELEASED,
EVENT_AUDIO_CODEC_ERROR,
EVENT_VIDEO_CODEC_ERROR,
})
@interface EventFlags {}
/** {@link Player#getCurrentTimeline()} changed. */
@ -312,6 +317,10 @@ public interface AnalyticsListener {
int EVENT_DRM_SESSION_RELEASED = 1035;
/** The player was released. */
int EVENT_PLAYER_RELEASED = 1036;
/** The audio codec encountered an error. */
int EVENT_AUDIO_CODEC_ERROR = 1037;
/** The video codec encountered an error. */
int EVENT_VIDEO_CODEC_ERROR = 1038;
/** Time information of an event. */
final class EventTime {
@ -649,8 +658,14 @@ public interface AnalyticsListener {
EventTime eventTime, LoadEventInfo loadEventInfo, MediaLoadData mediaLoadData) {}
/**
* Called when a media source loading error occurred. These errors are just for informational
* purposes and the player may recover.
* Called when a media source loading error occurred.
*
* <p>This method being called does not indicate that playback has failed, or that it will fail.
* The player may be able to recover from the error. Hence applications should <em>not</em>
* implement this method to display a user visible error or initiate an application level retry.
* {@link Player.EventListener#onPlayerError} is the appropriate place to implement such behavior.
* This method is called to provide the application with an opportunity to log the error if it
* wishes to do so.
*
* @param eventTime The event time.
* @param loadEventInfo The {@link LoadEventInfo} defining the load event.
@ -829,8 +844,14 @@ public interface AnalyticsListener {
default void onSkipSilenceEnabledChanged(EventTime eventTime, boolean skipSilenceEnabled) {}
/**
* Called when {@link AudioSink} has encountered an error. These errors are just for informational
* purposes and the player may recover.
* Called when {@link AudioSink} has encountered an error.
*
* <p>This method being called does not indicate that playback has failed, or that it will fail.
* The player may be able to recover from the error. Hence applications should <em>not</em>
* implement this method to display a user visible error or initiate an application level retry.
* {@link Player.EventListener#onPlayerError} is the appropriate place to implement such behavior.
* This method is called to provide the application with an opportunity to log the error if it
* wishes to do so.
*
* @param eventTime The event time.
* @param audioSinkError Either a {@link AudioSink.InitializationException} or a {@link
@ -838,6 +859,22 @@ public interface AnalyticsListener {
*/
default void onAudioSinkError(EventTime eventTime, Exception audioSinkError) {}
/**
* Called when an audio decoder encounters an error.
*
* <p>This method being called does not indicate that playback has failed, or that it will fail.
* The player may be able to recover from the error. Hence applications should <em>not</em>
* implement this method to display a user visible error or initiate an application level retry.
* {@link Player.EventListener#onPlayerError} is the appropriate place to implement such behavior.
* This method is called to provide the application with an opportunity to log the error if it
* wishes to do so.
*
* @param eventTime The event time.
* @param audioCodecError The error. Typically a {@link CodecException} if the renderer uses
* {@link MediaCodec}, or a {@link DecoderException} if the renderer uses a software decoder.
*/
default void onAudioCodecError(EventTime eventTime, Exception audioCodecError) {}
/**
* Called when the volume changes.
*
@ -931,6 +968,22 @@ public interface AnalyticsListener {
default void onVideoFrameProcessingOffset(
EventTime eventTime, long totalProcessingOffsetUs, int frameCount) {}
/**
* Called when a video decoder encounters an error.
*
* <p>This method being called does not indicate that playback has failed, or that it will fail.
* The player may be able to recover from the error. Hence applications should <em>not</em>
* implement this method to display a user visible error or initiate an application level retry.
* {@link Player.EventListener#onPlayerError} is the appropriate place to implement such behavior.
* This method is called to provide the application with an opportunity to log the error if it
* wishes to do so.
*
* @param eventTime The event time.
* @param videoCodecError The error. Typically a {@link CodecException} if the renderer uses
* {@link MediaCodec}, or a {@link DecoderException} if the renderer uses a software decoder.
*/
default void onVideoCodecError(EventTime eventTime, Exception videoCodecError) {}
/**
* Called when a frame is rendered for the first time since setting the surface, or since the
* renderer was reset, or since the stream being rendered was changed.
@ -987,8 +1040,14 @@ public interface AnalyticsListener {
default void onDrmKeysLoaded(EventTime eventTime) {}
/**
* Called when a drm error occurs. These errors are just for informational purposes and the player
* may recover.
* Called when a drm error occurs.
*
* <p>This method being called does not indicate that playback has failed, or that it will fail.
* The player may be able to recover from the error. Hence applications should <em>not</em>
* implement this method to display a user visible error or initiate an application level retry.
* {@link Player.EventListener#onPlayerError} is the appropriate place to implement such behavior.
* This method is called to provide the application with an opportunity to log the error if it
* wishes to do so.
*
* @param eventTime The event time.
* @param error The error.

View File

@ -18,15 +18,17 @@ package com.google.android.exoplayer2.audio;
import static com.google.android.exoplayer2.util.Util.castNonNull;
import android.media.AudioTrack;
import android.media.MediaCodec;
import android.media.MediaCodec.CodecException;
import android.os.Handler;
import android.os.SystemClock;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Renderer;
import com.google.android.exoplayer2.decoder.DecoderCounters;
import com.google.android.exoplayer2.decoder.DecoderException;
import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation;
import com.google.android.exoplayer2.util.Assertions;
@ -113,6 +115,21 @@ public interface AudioRendererEventListener {
*/
default void onSkipSilenceEnabledChanged(boolean skipSilenceEnabled) {}
/**
* Called when an audio decoder encounters an error.
*
* <p>This method being called does not indicate that playback has failed, or that it will fail.
* The player may be able to recover from the error. Hence applications should <em>not</em>
* implement this method to display a user visible error or initiate an application level retry.
* {@link Player.EventListener#onPlayerError} is the appropriate place to implement such behavior.
* This method is called to provide the application with an opportunity to log the error if it
* wishes to do so.
*
* @param audioCodecError The error. Typically a {@link CodecException} if the renderer uses
* {@link MediaCodec}, or a {@link DecoderException} if the renderer uses a software decoder.
*/
default void onAudioCodecError(Exception audioCodecError) {}
/**
* Called when {@link AudioSink} has encountered an error.
*
@ -120,18 +137,14 @@ public interface AudioRendererEventListener {
* AudioTrack} errors.
*
* <p>This method being called does not indicate that playback has failed, or that it will fail.
* The player may be able to recover from the error (for example by recreating the AudioTrack,
* possibly with different settings) and continue. Hence applications should <em>not</em>
* implement this method to display a user visible error or initiate an application level retry
* ({@link Player.EventListener#onPlayerError} is the appropriate place to implement such
* behavior). This method is called to provide the application with an opportunity to log the
* error if it wishes to do so.
* The player may be able to recover from the error. Hence applications should <em>not</em>
* implement this method to display a user visible error or initiate an application level retry.
* {@link Player.EventListener#onPlayerError} is the appropriate place to implement such behavior.
* This method is called to provide the application with an opportunity to log the error if it
* wishes to do so.
*
* <p>Fatal errors that cannot be recovered will be reported wrapped in a {@link
* ExoPlaybackException} by {@link Player.EventListener#onPlayerError(ExoPlaybackException)}.
*
* @param audioSinkError Either an {@link AudioSink.InitializationException} or a {@link
* AudioSink.WriteException} describing the error.
* @param audioSinkError The error that occurred. Typically an {@link
* AudioSink.InitializationException} or a {@link AudioSink.WriteException}.
*/
default void onAudioSinkError(Exception audioSinkError) {}
@ -230,5 +243,12 @@ public interface AudioRendererEventListener {
handler.post(() -> castNonNull(listener).onAudioSinkError(audioSinkError));
}
}
/** Invokes {@link AudioRendererEventListener#onAudioCodecError(Exception)}. */
public void audioCodecError(Exception audioCodecError) {
if (handler != null) {
handler.post(() -> castNonNull(listener).onAudioCodecError(audioCodecError));
}
}
}
}

View File

@ -300,6 +300,7 @@ public abstract class DecoderAudioRenderer<
while (feedInputBuffer()) {}
TraceUtil.endSection();
} catch (DecoderException e) {
eventDispatcher.audioCodecError(e);
throw createRendererException(e, inputFormat);
} catch (AudioSink.ConfigurationException e) {
throw createRendererException(e, e.format);
@ -618,7 +619,10 @@ public abstract class DecoderAudioRenderer<
eventDispatcher.decoderInitialized(decoder.getName(), codecInitializedTimestamp,
codecInitializedTimestamp - codecInitializingTimestamp);
decoderCounters.decoderInitCount++;
} catch (DecoderException | OutOfMemoryError e) {
} catch (DecoderException e) {
eventDispatcher.audioCodecError(e);
throw createRendererException(e, inputFormat);
} catch (OutOfMemoryError e) {
throw createRendererException(e, inputFormat);
}
}

View File

@ -414,6 +414,11 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
eventDispatcher.decoderReleased(name);
}
@Override
protected void onCodecError(Exception codecError) {
eventDispatcher.audioCodecError(codecError);
}
@Override
@Nullable
protected DecoderReuseEvaluation onInputFormatChanged(FormatHolder formatHolder)

View File

@ -848,6 +848,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
decoderCounters.ensureUpdated();
} catch (IllegalStateException e) {
if (isMediaCodecException(e)) {
onCodecError(e);
boolean isRecoverable =
enableRecoverableCodecExceptionRetries
&& Util.SDK_INT >= 21
@ -1406,6 +1407,17 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
// Do nothing.
}
/**
* Called when a codec error has occurred.
*
* <p>The default implementation is a no-op.
*
* @param codecError The error.
*/
protected void onCodecError(Exception codecError) {
// Do nothing.
}
/**
* Called when a new {@link Format} is read from the upstream {@link MediaPeriod}.
*

View File

@ -92,11 +92,11 @@ public interface MediaSourceEventListener {
* <em>not</em> be called in addition to this method.
*
* <p>This method being called does not indicate that playback has failed, or that it will fail.
* The player may be able to recover from the error and continue. Hence applications should
* <em>not</em> implement this method to display a user visible error or initiate an application
* level retry ({@link Player.EventListener#onPlayerError} is the appropriate place to implement
* such behavior). This method is called to provide the application with an opportunity to log the
* error if it wishes to do so.
* The player may be able to recover from the error. Hence applications should <em>not</em>
* implement this method to display a user visible error or initiate an application level retry.
* {@link Player.EventListener#onPlayerError} is the appropriate place to implement such behavior.
* This method is called to provide the application with an opportunity to log the error if it
* wishes to do so.
*
* @param windowIndex The window index in the timeline of the media source this load belongs to.
* @param mediaPeriodId The {@link MediaPeriodId} this load belongs to. Null if the load does not

View File

@ -206,6 +206,7 @@ public abstract class DecoderVideoRenderer extends BaseRenderer {
while (feedInputBuffer()) {}
TraceUtil.endSection();
} catch (DecoderException e) {
eventDispatcher.videoCodecError(e);
throw createRendererException(e, inputFormat);
}
decoderCounters.ensureUpdated();
@ -707,7 +708,10 @@ public abstract class DecoderVideoRenderer extends BaseRenderer {
decoderInitializedTimestamp,
decoderInitializedTimestamp - decoderInitializingTimestamp);
decoderCounters.decoderInitCount++;
} catch (DecoderException | OutOfMemoryError e) {
} catch (DecoderException e) {
eventDispatcher.videoCodecError(e);
throw createRendererException(e, inputFormat);
} catch (OutOfMemoryError e) {
throw createRendererException(e, inputFormat);
}
}

View File

@ -690,6 +690,11 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
eventDispatcher.decoderReleased(name);
}
@Override
protected void onCodecError(Exception codecError) {
eventDispatcher.videoCodecError(codecError);
}
@Override
@Nullable
protected DecoderReuseEvaluation onInputFormatChanged(FormatHolder formatHolder)

View File

@ -17,14 +17,18 @@ package com.google.android.exoplayer2.video;
import static com.google.android.exoplayer2.util.Util.castNonNull;
import android.media.MediaCodec;
import android.media.MediaCodec.CodecException;
import android.os.Handler;
import android.os.SystemClock;
import android.view.Surface;
import android.view.TextureView;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Renderer;
import com.google.android.exoplayer2.decoder.DecoderCounters;
import com.google.android.exoplayer2.decoder.DecoderException;
import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation;
import com.google.android.exoplayer2.util.Assertions;
@ -147,8 +151,21 @@ public interface VideoRendererEventListener {
default void onVideoDisabled(DecoderCounters counters) {}
/**
* Dispatches events to a {@link VideoRendererEventListener}.
* Called when a video decoder encounters an error.
*
* <p>This method being called does not indicate that playback has failed, or that it will fail.
* The player may be able to recover from the error. Hence applications should <em>not</em>
* implement this method to display a user visible error or initiate an application level retry.
* {@link Player.EventListener#onPlayerError} is the appropriate place to implement such behavior.
* This method is called to provide the application with an opportunity to log the error if it
* wishes to do so.
*
* @param videoCodecError The error. Typically a {@link CodecException} if the renderer uses
* {@link MediaCodec}, or a {@link DecoderException} if the renderer uses a software decoder.
*/
default void onVideoCodecError(Exception videoCodecError) {}
/** Dispatches events to a {@link VideoRendererEventListener}. */
final class EventDispatcher {
@Nullable private final Handler handler;
@ -254,6 +271,12 @@ public interface VideoRendererEventListener {
}
}
/** Invokes {@link VideoRendererEventListener#onVideoCodecError(Exception)}. */
public void videoCodecError(Exception videoCodecError) {
if (handler != null) {
handler.post(() -> castNonNull(listener).onVideoCodecError(videoCodecError));
}
}
}
}