Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
SDK
/
exoplayer
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Settings
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
c47e6220
authored
Nov 13, 2020
by
olly
Committed by
Ian Baker
Nov 16, 2020
Browse files
Options
_('Browse Files')
Download
Email Patches
Plain Diff
Report reasons for not being able to reuse decoders
PiperOrigin-RevId: 342344090
parent
3ef609fa
Hide whitespace changes
Inline
Side-by-side
Showing
21 changed files
with
708 additions
and
206 deletions
RELEASENOTES.md
extensions/av1/src/main/java/com/google/android/exoplayer2/ext/av1/Libgav1VideoRenderer.java
extensions/ffmpeg/src/main/java/com/google/android/exoplayer2/ext/ffmpeg/FfmpegVideoRenderer.java
extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java
library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java
library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java
library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsListener.java
library/core/src/main/java/com/google/android/exoplayer2/audio/AudioRendererEventListener.java
library/core/src/main/java/com/google/android/exoplayer2/audio/DecoderAudioRenderer.java
library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java
library/core/src/main/java/com/google/android/exoplayer2/decoder/DecoderReuseEvaluation.java
library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecInfo.java
library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java
library/core/src/main/java/com/google/android/exoplayer2/util/EventLogger.java
library/core/src/main/java/com/google/android/exoplayer2/video/DecoderVideoRenderer.java
library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java
library/core/src/main/java/com/google/android/exoplayer2/video/VideoRendererEventListener.java
library/core/src/test/java/com/google/android/exoplayer2/mediacodec/MediaCodecInfoTest.java
playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DebugRenderersFactory.java
testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAudioRenderer.java
testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeVideoRenderer.java
RELEASENOTES.md
View file @
c47e6220
...
...
@@ -48,6 +48,12 @@
*
DRM:
*
Fix playback failure when switching from PlayReady protected content to
Widevine or Clearkey protected content in a playlist.
*
Analytics:
*
Pass a
`DecoderReuseEvaluation`
to
`AnalyticsListener`
's
`onVideoInputFormatChanged`
and
`onAudioInputFormatChanged`
methods. The
`DecoderReuseEvaluation`
indicates whether it was possible to re-use an
existing decoder instance for the new format, and if not then the
reasons why.
*
IMA extension:
*
Upgrade IMA SDK dependency to 3.21.0, and release the
`AdsLoader`
(
[
#7344
](
https://github.com/google/ExoPlayer/issues/7344
)
).
...
...
extensions/av1/src/main/java/com/google/android/exoplayer2/ext/av1/Libgav1VideoRenderer.java
View file @
c47e6220
...
...
@@ -15,12 +15,15 @@
*/
package
com
.
google
.
android
.
exoplayer2
.
ext
.
av1
;
import
static
com
.
google
.
android
.
exoplayer2
.
decoder
.
DecoderReuseEvaluation
.
REUSE_RESULT_YES_WITHOUT_RECONFIGURATION
;
import
android.os.Handler
;
import
android.view.Surface
;
import
androidx.annotation.Nullable
;
import
com.google.android.exoplayer2.C
;
import
com.google.android.exoplayer2.Format
;
import
com.google.android.exoplayer2.RendererCapabilities
;
import
com.google.android.exoplayer2.decoder.DecoderReuseEvaluation
;
import
com.google.android.exoplayer2.drm.ExoMediaCrypto
;
import
com.google.android.exoplayer2.util.MimeTypes
;
import
com.google.android.exoplayer2.util.TraceUtil
;
...
...
@@ -164,7 +167,13 @@ public class Libgav1VideoRenderer extends DecoderVideoRenderer {
}
@Override
protected
boolean
canKeepCodec
(
Format
oldFormat
,
Format
newFormat
)
{
return
true
;
protected
DecoderReuseEvaluation
canReuseDecoder
(
String
decoderName
,
Format
oldFormat
,
Format
newFormat
)
{
return
new
DecoderReuseEvaluation
(
decoderName
,
oldFormat
,
newFormat
,
REUSE_RESULT_YES_WITHOUT_RECONFIGURATION
,
/* discardReasons= */
0
);
}
}
extensions/ffmpeg/src/main/java/com/google/android/exoplayer2/ext/ffmpeg/FfmpegVideoRenderer.java
View file @
c47e6220
...
...
@@ -15,6 +15,10 @@
*/
package
com
.
google
.
android
.
exoplayer2
.
ext
.
ffmpeg
;
import
static
com
.
google
.
android
.
exoplayer2
.
decoder
.
DecoderReuseEvaluation
.
DISCARD_REASON_MIME_TYPE_CHANGED
;
import
static
com
.
google
.
android
.
exoplayer2
.
decoder
.
DecoderReuseEvaluation
.
REUSE_RESULT_NO
;
import
static
com
.
google
.
android
.
exoplayer2
.
decoder
.
DecoderReuseEvaluation
.
REUSE_RESULT_YES_WITHOUT_RECONFIGURATION
;
import
android.os.Handler
;
import
android.view.Surface
;
import
androidx.annotation.Nullable
;
...
...
@@ -22,6 +26,7 @@ import com.google.android.exoplayer2.C;
import
com.google.android.exoplayer2.Format
;
import
com.google.android.exoplayer2.RendererCapabilities
;
import
com.google.android.exoplayer2.decoder.Decoder
;
import
com.google.android.exoplayer2.decoder.DecoderReuseEvaluation
;
import
com.google.android.exoplayer2.drm.ExoMediaCrypto
;
import
com.google.android.exoplayer2.util.TraceUtil
;
import
com.google.android.exoplayer2.util.Util
;
...
...
@@ -116,7 +121,15 @@ public final class FfmpegVideoRenderer extends DecoderVideoRenderer {
}
@Override
protected
boolean
canKeepCodec
(
Format
oldFormat
,
Format
newFormat
)
{
return
Util
.
areEqual
(
oldFormat
.
sampleMimeType
,
newFormat
.
sampleMimeType
);
protected
DecoderReuseEvaluation
canReuseDecoder
(
String
decoderName
,
Format
oldFormat
,
Format
newFormat
)
{
boolean
sameMimeType
=
Util
.
areEqual
(
oldFormat
.
sampleMimeType
,
newFormat
.
sampleMimeType
);
// TODO: Ability to reuse the decoder may be MIME type dependent.
return
new
DecoderReuseEvaluation
(
decoderName
,
oldFormat
,
newFormat
,
sameMimeType
?
REUSE_RESULT_YES_WITHOUT_RECONFIGURATION
:
REUSE_RESULT_NO
,
sameMimeType
?
0
:
DISCARD_REASON_MIME_TYPE_CHANGED
);
}
}
extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java
View file @
c47e6220
...
...
@@ -15,6 +15,7 @@
*/
package
com
.
google
.
android
.
exoplayer2
.
ext
.
vp9
;
import
static
com
.
google
.
android
.
exoplayer2
.
decoder
.
DecoderReuseEvaluation
.
REUSE_RESULT_YES_WITHOUT_RECONFIGURATION
;
import
static
java
.
lang
.
Runtime
.
getRuntime
;
import
android.os.Handler
;
...
...
@@ -23,6 +24,7 @@ import androidx.annotation.Nullable;
import
com.google.android.exoplayer2.C
;
import
com.google.android.exoplayer2.Format
;
import
com.google.android.exoplayer2.RendererCapabilities
;
import
com.google.android.exoplayer2.decoder.DecoderReuseEvaluation
;
import
com.google.android.exoplayer2.drm.ExoMediaCrypto
;
import
com.google.android.exoplayer2.util.MimeTypes
;
import
com.google.android.exoplayer2.util.TraceUtil
;
...
...
@@ -169,7 +171,13 @@ public class LibvpxVideoRenderer extends DecoderVideoRenderer {
}
@Override
protected
boolean
canKeepCodec
(
Format
oldFormat
,
Format
newFormat
)
{
return
true
;
protected
DecoderReuseEvaluation
canReuseDecoder
(
String
decoderName
,
Format
oldFormat
,
Format
newFormat
)
{
return
new
DecoderReuseEvaluation
(
decoderName
,
oldFormat
,
newFormat
,
REUSE_RESULT_YES_WITHOUT_RECONFIGURATION
,
/* discardReasons= */
0
);
}
}
library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java
View file @
c47e6220
...
...
@@ -36,6 +36,7 @@ import com.google.android.exoplayer2.audio.AudioListener;
import
com.google.android.exoplayer2.audio.AudioRendererEventListener
;
import
com.google.android.exoplayer2.audio.AuxEffectInfo
;
import
com.google.android.exoplayer2.decoder.DecoderCounters
;
import
com.google.android.exoplayer2.decoder.DecoderReuseEvaluation
;
import
com.google.android.exoplayer2.device.DeviceInfo
;
import
com.google.android.exoplayer2.device.DeviceListener
;
import
com.google.android.exoplayer2.extractor.DefaultExtractorsFactory
;
...
...
@@ -2242,10 +2243,11 @@ public class SimpleExoPlayer extends BasePlayer
}
@Override
public
void
onVideoInputFormatChanged
(
Format
format
)
{
public
void
onVideoInputFormatChanged
(
Format
format
,
@Nullable
DecoderReuseEvaluation
decoderReuseEvaluation
)
{
videoFormat
=
format
;
for
(
VideoRendererEventListener
videoDebugListener
:
videoDebugListeners
)
{
videoDebugListener
.
onVideoInputFormatChanged
(
format
);
videoDebugListener
.
onVideoInputFormatChanged
(
format
,
decoderReuseEvaluation
);
}
}
...
...
@@ -2337,10 +2339,11 @@ public class SimpleExoPlayer extends BasePlayer
}
@Override
public
void
onAudioInputFormatChanged
(
Format
format
)
{
public
void
onAudioInputFormatChanged
(
Format
format
,
@Nullable
DecoderReuseEvaluation
decoderReuseEvaluation
)
{
audioFormat
=
format
;
for
(
AudioRendererEventListener
audioDebugListener
:
audioDebugListeners
)
{
audioDebugListener
.
onAudioInputFormatChanged
(
format
);
audioDebugListener
.
onAudioInputFormatChanged
(
format
,
decoderReuseEvaluation
);
}
}
...
...
library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java
View file @
c47e6220
...
...
@@ -34,6 +34,7 @@ import com.google.android.exoplayer2.audio.AudioAttributes;
import
com.google.android.exoplayer2.audio.AudioListener
;
import
com.google.android.exoplayer2.audio.AudioRendererEventListener
;
import
com.google.android.exoplayer2.decoder.DecoderCounters
;
import
com.google.android.exoplayer2.decoder.DecoderReuseEvaluation
;
import
com.google.android.exoplayer2.drm.DrmSessionEventListener
;
import
com.google.android.exoplayer2.metadata.Metadata
;
import
com.google.android.exoplayer2.metadata.MetadataOutput
;
...
...
@@ -195,11 +196,12 @@ public class AnalyticsCollector
@SuppressWarnings
(
"deprecation"
)
@Override
public
final
void
onAudioInputFormatChanged
(
Format
format
)
{
public
final
void
onAudioInputFormatChanged
(
Format
format
,
@Nullable
DecoderReuseEvaluation
decoderReuseEvaluation
)
{
EventTime
eventTime
=
generateReadingMediaPeriodEventTime
();
listeners
.
sendEvent
(
listener
->
{
listener
.
onAudioInputFormatChanged
(
eventTime
,
format
);
listener
.
onAudioInputFormatChanged
(
eventTime
,
format
,
decoderReuseEvaluation
);
listener
.
onDecoderInputFormatChanged
(
eventTime
,
C
.
TRACK_TYPE_AUDIO
,
format
);
});
}
...
...
@@ -298,11 +300,12 @@ public class AnalyticsCollector
@SuppressWarnings
(
"deprecation"
)
@Override
public
final
void
onVideoInputFormatChanged
(
Format
format
)
{
public
final
void
onVideoInputFormatChanged
(
Format
format
,
@Nullable
DecoderReuseEvaluation
decoderReuseEvaluation
)
{
EventTime
eventTime
=
generateReadingMediaPeriodEventTime
();
listeners
.
sendEvent
(
listener
->
{
listener
.
onVideoInputFormatChanged
(
eventTime
,
format
);
listener
.
onVideoInputFormatChanged
(
eventTime
,
format
,
decoderReuseEvaluation
);
listener
.
onDecoderInputFormatChanged
(
eventTime
,
C
.
TRACK_TYPE_VIDEO
,
format
);
});
}
...
...
library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsListener.java
View file @
c47e6220
...
...
@@ -30,6 +30,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.DecoderReuseEvaluation
;
import
com.google.android.exoplayer2.metadata.Metadata
;
import
com.google.android.exoplayer2.source.LoadEventInfo
;
import
com.google.android.exoplayer2.source.MediaLoadData
;
...
...
@@ -452,8 +453,8 @@ public interface AnalyticsListener {
EventTime
eventTime
,
int
trackType
,
String
decoderName
,
long
initializationDurationMs
)
{}
/**
* @deprecated Use {@link #onAudioInputFormatChanged
} and {@link #onVideoInputFormatChanged
}
* instead.
* @deprecated Use {@link #onAudioInputFormatChanged
(EventTime, Format, DecoderReuseEvaluation)
}
*
and {@link #onVideoInputFormatChanged(EventTime, Format, DecoderReuseEvaluation)}.
instead.
*/
@Deprecated
default
void
onDecoderInputFormatChanged
(
EventTime
eventTime
,
int
trackType
,
Format
format
)
{}
...
...
@@ -483,12 +484,25 @@ public interface AnalyticsListener {
EventTime
eventTime
,
String
decoderName
,
long
initializationDurationMs
)
{}
/**
* @deprecated Use {@link #onAudioInputFormatChanged(EventTime, Format, DecoderReuseEvaluation)}.
*/
@Deprecated
default
void
onAudioInputFormatChanged
(
EventTime
eventTime
,
Format
format
)
{}
/**
* Called when the format of the media being consumed by an audio renderer changes.
*
* @param eventTime The event time.
* @param format The new format.
* @param decoderReuseEvaluation The result of the evaluation to determine whether an existing
* decoder instance can be reused for the new format, or {@code null} if the renderer did not
* have a decoder.
*/
default
void
onAudioInputFormatChanged
(
EventTime
eventTime
,
Format
format
)
{}
@SuppressWarnings
(
"deprecation"
)
default
void
onAudioInputFormatChanged
(
EventTime
eventTime
,
Format
format
,
@Nullable
DecoderReuseEvaluation
decoderReuseEvaluation
)
{
onAudioInputFormatChanged
(
eventTime
,
format
);
}
/**
* Called when the audio position has increased for the first time since the last pause or
...
...
@@ -590,12 +604,25 @@ public interface AnalyticsListener {
EventTime
eventTime
,
String
decoderName
,
long
initializationDurationMs
)
{}
/**
* @deprecated Use {@link #onVideoInputFormatChanged(EventTime, Format, DecoderReuseEvaluation)}.
*/
@Deprecated
default
void
onVideoInputFormatChanged
(
EventTime
eventTime
,
Format
format
)
{}
/**
* Called when the format of the media being consumed by a video renderer changes.
*
* @param eventTime The event time.
* @param format The new format.
* @param decoderReuseEvaluation The result of the evaluation to determine whether an existing
* decoder instance can be reused for the new format, or {@code null} if the renderer did not
* have a decoder.
*/
default
void
onVideoInputFormatChanged
(
EventTime
eventTime
,
Format
format
)
{}
@SuppressWarnings
(
"deprecation"
)
default
void
onVideoInputFormatChanged
(
EventTime
eventTime
,
Format
format
,
@Nullable
DecoderReuseEvaluation
decoderReuseEvaluation
)
{
onVideoInputFormatChanged
(
eventTime
,
format
);
}
/**
* Called after video frames have been dropped.
...
...
library/core/src/main/java/com/google/android/exoplayer2/audio/AudioRendererEventListener.java
View file @
c47e6220
...
...
@@ -27,6 +27,7 @@ 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.DecoderReuseEvaluation
;
import
com.google.android.exoplayer2.util.Assertions
;
/**
...
...
@@ -61,12 +62,23 @@ public interface AudioRendererEventListener {
default
void
onAudioDecoderInitialized
(
String
decoderName
,
long
initializedTimestampMs
,
long
initializationDurationMs
)
{}
/** @deprecated Use {@link #onAudioInputFormatChanged(Format, DecoderReuseEvaluation)}. */
@Deprecated
default
void
onAudioInputFormatChanged
(
Format
format
)
{}
/**
* Called when the format of the media being consumed by the renderer changes.
*
* @param format The new format.
* @param decoderReuseEvaluation The result of the evaluation to determine whether an existing
* decoder instance can be reused for the new format, or {@code null} if the renderer did not
* have a decoder.
*/
default
void
onAudioInputFormatChanged
(
Format
format
)
{}
@SuppressWarnings
(
"deprecation"
)
default
void
onAudioInputFormatChanged
(
Format
format
,
@Nullable
DecoderReuseEvaluation
decoderReuseEvaluation
)
{
onAudioInputFormatChanged
(
format
);
}
/**
* Called when the audio position has increased for the first time since the last pause or
...
...
@@ -167,9 +179,11 @@ public interface AudioRendererEventListener {
}
/** Invokes {@link AudioRendererEventListener#onAudioInputFormatChanged(Format)}. */
public
void
inputFormatChanged
(
Format
format
)
{
public
void
inputFormatChanged
(
Format
format
,
@Nullable
DecoderReuseEvaluation
decoderReuseEvaluation
)
{
if
(
handler
!=
null
)
{
handler
.
post
(()
->
castNonNull
(
listener
).
onAudioInputFormatChanged
(
format
));
handler
.
post
(
()
->
castNonNull
(
listener
).
onAudioInputFormatChanged
(
format
,
decoderReuseEvaluation
));
}
}
...
...
library/core/src/main/java/com/google/android/exoplayer2/audio/DecoderAudioRenderer.java
View file @
c47e6220
...
...
@@ -15,6 +15,9 @@
*/
package
com
.
google
.
android
.
exoplayer2
.
audio
;
import
static
com
.
google
.
android
.
exoplayer2
.
decoder
.
DecoderReuseEvaluation
.
DISCARD_REASON_DRM_SESSION_CHANGED
;
import
static
com
.
google
.
android
.
exoplayer2
.
decoder
.
DecoderReuseEvaluation
.
DISCARD_REASON_REUSE_NOT_IMPLEMENTED
;
import
static
com
.
google
.
android
.
exoplayer2
.
decoder
.
DecoderReuseEvaluation
.
REUSE_RESULT_NO
;
import
static
java
.
lang
.
Math
.
max
;
import
android.media.audiofx.Virtualizer
;
...
...
@@ -38,6 +41,7 @@ import com.google.android.exoplayer2.decoder.Decoder;
import
com.google.android.exoplayer2.decoder.DecoderCounters
;
import
com.google.android.exoplayer2.decoder.DecoderException
;
import
com.google.android.exoplayer2.decoder.DecoderInputBuffer
;
import
com.google.android.exoplayer2.decoder.DecoderReuseEvaluation
;
import
com.google.android.exoplayer2.decoder.SimpleOutputBuffer
;
import
com.google.android.exoplayer2.drm.DrmSession
;
import
com.google.android.exoplayer2.drm.DrmSession.DrmSessionException
;
...
...
@@ -347,14 +351,19 @@ public abstract class DecoderAudioRenderer<
protected
abstract
Format
getOutputFormat
(
T
decoder
);
/**
*
Returns whether the existing decoder can be kept for a new format
.
*
Evaluates whether the existing decoder can be reused for a new {@link Format}
.
*
* <p>The default implementation does not allow decoder reuse.
*
* @param decoderName The name of the decoder.
* @param oldFormat The previous format.
* @param newFormat The new format.
* @return
Whether the existing decoder can be kept
.
* @return
The result of the evaluation
.
*/
protected
boolean
canKeepCodec
(
Format
oldFormat
,
Format
newFormat
)
{
return
false
;
protected
DecoderReuseEvaluation
canReuseDecoder
(
String
decoderName
,
Format
oldFormat
,
Format
newFormat
)
{
return
new
DecoderReuseEvaluation
(
decoderName
,
oldFormat
,
newFormat
,
REUSE_RESULT_NO
,
DISCARD_REASON_REUSE_NOT_IMPLEMENTED
);
}
private
boolean
drainOutputBuffer
()
...
...
@@ -655,10 +664,29 @@ public abstract class DecoderAudioRenderer<
setSourceDrmSession
(
formatHolder
.
drmSession
);
Format
oldFormat
=
inputFormat
;
inputFormat
=
newFormat
;
encoderDelay
=
newFormat
.
encoderDelay
;
encoderPadding
=
newFormat
.
encoderPadding
;
if
(
decoder
==
null
)
{
maybeInitDecoder
();
}
else
if
(
sourceDrmSession
!=
decoderDrmSession
||
!
canKeepCodec
(
oldFormat
,
inputFormat
))
{
eventDispatcher
.
inputFormatChanged
(
inputFormat
,
/* decoderReuseEvaluation= */
null
);
return
;
}
DecoderReuseEvaluation
evaluation
;
if
(
sourceDrmSession
!=
decoderDrmSession
)
{
evaluation
=
new
DecoderReuseEvaluation
(
decoder
.
getName
(),
oldFormat
,
newFormat
,
REUSE_RESULT_NO
,
DISCARD_REASON_DRM_SESSION_CHANGED
);
}
else
{
evaluation
=
canReuseDecoder
(
decoder
.
getName
(),
oldFormat
,
newFormat
);
}
if
(
evaluation
.
result
==
REUSE_RESULT_NO
)
{
if
(
decoderReceivedBuffers
)
{
// Signal end of stream and wait for any final output buffers before re-initialization.
decoderReinitializationState
=
REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM
;
...
...
@@ -669,10 +697,7 @@ public abstract class DecoderAudioRenderer<
audioTrackNeedsConfigure
=
true
;
}
}
encoderDelay
=
inputFormat
.
encoderDelay
;
encoderPadding
=
inputFormat
.
encoderPadding
;
eventDispatcher
.
inputFormatChanged
(
inputFormat
);
eventDispatcher
.
inputFormatChanged
(
inputFormat
,
evaluation
);
}
private
void
onQueueInputBuffer
(
DecoderInputBuffer
buffer
)
{
...
...
library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java
View file @
c47e6220
...
...
@@ -15,7 +15,8 @@
*/
package
com
.
google
.
android
.
exoplayer2
.
audio
;
import
static
com
.
google
.
android
.
exoplayer2
.
mediacodec
.
MediaCodecInfo
.
KEEP_CODEC_RESULT_NO
;
import
static
com
.
google
.
android
.
exoplayer2
.
decoder
.
DecoderReuseEvaluation
.
DISCARD_REASON_MAX_INPUT_SIZE_EXCEEDED
;
import
static
com
.
google
.
android
.
exoplayer2
.
decoder
.
DecoderReuseEvaluation
.
REUSE_RESULT_NO
;
import
static
com
.
google
.
android
.
exoplayer2
.
util
.
Assertions
.
checkNotNull
;
import
static
java
.
lang
.
Math
.
max
;
...
...
@@ -41,9 +42,10 @@ import com.google.android.exoplayer2.audio.AudioRendererEventListener.EventDispa
import
com.google.android.exoplayer2.audio.AudioSink.InitializationException
;
import
com.google.android.exoplayer2.audio.AudioSink.WriteException
;
import
com.google.android.exoplayer2.decoder.DecoderInputBuffer
;
import
com.google.android.exoplayer2.decoder.DecoderReuseEvaluation
;
import
com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DecoderDiscardReasons
;
import
com.google.android.exoplayer2.mediacodec.MediaCodecAdapter
;
import
com.google.android.exoplayer2.mediacodec.MediaCodecInfo
;
import
com.google.android.exoplayer2.mediacodec.MediaCodecInfo.KeepCodecResult
;
import
com.google.android.exoplayer2.mediacodec.MediaCodecRenderer
;
import
com.google.android.exoplayer2.mediacodec.MediaCodecSelector
;
import
com.google.android.exoplayer2.mediacodec.MediaCodecUtil
;
...
...
@@ -328,13 +330,21 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
}
@Override
@KeepCodecResult
protected
int
canKeepCodec
(
MediaCodecAdapter
codec
,
MediaCodecInfo
codecInfo
,
Format
oldFormat
,
Format
newFormat
)
{
protected
DecoderReuseEvaluation
canReuseCodec
(
MediaCodecInfo
codecInfo
,
Format
oldFormat
,
Format
newFormat
)
{
DecoderReuseEvaluation
evaluation
=
codecInfo
.
canReuseCodec
(
oldFormat
,
newFormat
);
@DecoderDiscardReasons
int
discardReasons
=
evaluation
.
discardReasons
;
if
(
getCodecMaxInputSize
(
codecInfo
,
newFormat
)
>
codecMaxInputSize
)
{
return
KEEP_CODEC_RESULT_NO
;
discardReasons
|=
DISCARD_REASON_MAX_INPUT_SIZE_EXCEEDED
;
}
return
codecInfo
.
canKeepCodec
(
oldFormat
,
newFormat
);
return
new
DecoderReuseEvaluation
(
codecInfo
.
name
,
oldFormat
,
newFormat
,
discardReasons
!=
0
?
REUSE_RESULT_NO
:
evaluation
.
result
,
discardReasons
);
}
@Override
...
...
@@ -370,9 +380,12 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
}
@Override
protected
void
onInputFormatChanged
(
FormatHolder
formatHolder
)
throws
ExoPlaybackException
{
super
.
onInputFormatChanged
(
formatHolder
);
eventDispatcher
.
inputFormatChanged
(
formatHolder
.
format
);
@Nullable
protected
DecoderReuseEvaluation
onInputFormatChanged
(
FormatHolder
formatHolder
)
throws
ExoPlaybackException
{
@Nullable
DecoderReuseEvaluation
evaluation
=
super
.
onInputFormatChanged
(
formatHolder
);
eventDispatcher
.
inputFormatChanged
(
formatHolder
.
format
,
evaluation
);
return
evaluation
;
}
@Override
...
...
@@ -664,7 +677,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
return
maxInputSize
;
}
for
(
Format
streamFormat
:
streamFormats
)
{
if
(
codecInfo
.
can
KeepCodec
(
format
,
streamFormat
)
!=
KEEP_CODEC
_RESULT_NO
)
{
if
(
codecInfo
.
can
ReuseCodec
(
format
,
streamFormat
).
result
!=
REUSE
_RESULT_NO
)
{
maxInputSize
=
max
(
maxInputSize
,
getCodecMaxInputSize
(
codecInfo
,
streamFormat
));
}
}
...
...
library/core/src/main/java/com/google/android/exoplayer2/decoder/DecoderReuseEvaluation.java
0 → 100644
View file @
c47e6220
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
google
.
android
.
exoplayer2
.
decoder
;
import
static
com
.
google
.
android
.
exoplayer2
.
util
.
Assertions
.
checkArgument
;
import
static
com
.
google
.
android
.
exoplayer2
.
util
.
Assertions
.
checkNotEmpty
;
import
static
com
.
google
.
android
.
exoplayer2
.
util
.
Assertions
.
checkNotNull
;
import
androidx.annotation.IntDef
;
import
androidx.annotation.Nullable
;
import
com.google.android.exoplayer2.Format
;
import
com.google.android.exoplayer2.video.ColorInfo
;
import
java.lang.annotation.Documented
;
import
java.lang.annotation.Retention
;
import
java.lang.annotation.RetentionPolicy
;
/**
* The result of an evaluation to determine whether a decoder can be reused for a new input format.
*/
public
final
class
DecoderReuseEvaluation
{
/** Possible outcomes of the evaluation. */
@Documented
@Retention
(
RetentionPolicy
.
SOURCE
)
@IntDef
({
REUSE_RESULT_NO
,
REUSE_RESULT_YES_WITH_FLUSH
,
REUSE_RESULT_YES_WITH_RECONFIGURATION
,
REUSE_RESULT_YES_WITHOUT_RECONFIGURATION
})
public
@interface
DecoderReuseResult
{}
/** The decoder cannot be reused. */
public
static
final
int
REUSE_RESULT_NO
=
0
;
/** The decoder can be reused, but must be flushed. */
public
static
final
int
REUSE_RESULT_YES_WITH_FLUSH
=
1
;
/**
* The decoder can be reused. It does not need to be flushed, but must be reconfigured by
* prefixing the next input buffer with the new format's configuration data.
*/
public
static
final
int
REUSE_RESULT_YES_WITH_RECONFIGURATION
=
2
;
/** The decoder can be kept. It does not need to be flushed and no reconfiguration is required. */
public
static
final
int
REUSE_RESULT_YES_WITHOUT_RECONFIGURATION
=
3
;
/** Possible reasons why reuse is not possible. */
@Documented
@Retention
(
RetentionPolicy
.
SOURCE
)
@IntDef
(
flag
=
true
,
value
=
{
DISCARD_REASON_REUSE_NOT_IMPLEMENTED
,
DISCARD_REASON_WORKAROUND
,
DISCARD_REASON_APP_OVERRIDE
,
DISCARD_REASON_MIME_TYPE_CHANGED
,
DISCARD_REASON_OPERATING_RATE_CHANGED
,
DISCARD_REASON_INITIALIZATION_DATA_CHANGED
,
DISCARD_REASON_DRM_SESSION_CHANGED
,
DISCARD_REASON_MAX_INPUT_SIZE_EXCEEDED
,
DISCARD_REASON_VIDEO_MAX_RESOLUTION_EXCEEDED
,
DISCARD_REASON_VIDEO_RESOLUTION_CHANGED
,
DISCARD_REASON_VIDEO_ROTATION_CHANGED
,
DISCARD_REASON_VIDEO_COLOR_INFO_CHANGED
,
DISCARD_REASON_AUDIO_CHANNEL_COUNT_CHANGED
,
DISCARD_REASON_AUDIO_SAMPLE_RATE_CHANGED
,
DISCARD_REASON_AUDIO_ENCODING_CHANGED
})
public
@interface
DecoderDiscardReasons
{}
/** Decoder reuse is not implemented. */
public
static
final
int
DISCARD_REASON_REUSE_NOT_IMPLEMENTED
=
1
<<
0
;
/** Decoder reuse is disabled by a workaround. */
public
static
final
int
DISCARD_REASON_WORKAROUND
=
1
<<
1
;
/** Decoder reuse is disabled by overriding behavior in application code. */
public
static
final
int
DISCARD_REASON_APP_OVERRIDE
=
1
<<
2
;
/** The sample MIME type is changing. */
public
static
final
int
DISCARD_REASON_MIME_TYPE_CHANGED
=
1
<<
3
;
/** The codec's operating rate is changing. */
public
static
final
int
DISCARD_REASON_OPERATING_RATE_CHANGED
=
1
<<
4
;
/** The format initialization data is changing. */
public
static
final
int
DISCARD_REASON_INITIALIZATION_DATA_CHANGED
=
1
<<
5
;
/** The new format may exceed the decoder's configured maximum sample size, in bytes. */
public
static
final
int
DISCARD_REASON_MAX_INPUT_SIZE_EXCEEDED
=
1
<<
6
;
/** The DRM session is changing. */
public
static
final
int
DISCARD_REASON_DRM_SESSION_CHANGED
=
1
<<
7
;
/** The new format may exceed the decoder's configured maximum resolution. */
public
static
final
int
DISCARD_REASON_VIDEO_MAX_RESOLUTION_EXCEEDED
=
1
<<
8
;
/** The video resolution is changing. */
public
static
final
int
DISCARD_REASON_VIDEO_RESOLUTION_CHANGED
=
1
<<
9
;
/** The video rotation is changing. */
public
static
final
int
DISCARD_REASON_VIDEO_ROTATION_CHANGED
=
1
<<
10
;
/** The video {@link ColorInfo} is changing. */
public
static
final
int
DISCARD_REASON_VIDEO_COLOR_INFO_CHANGED
=
1
<<
11
;
/** The audio channel count is changing. */
public
static
final
int
DISCARD_REASON_AUDIO_CHANNEL_COUNT_CHANGED
=
1
<<
12
;
/** The audio sample rate is changing. */
public
static
final
int
DISCARD_REASON_AUDIO_SAMPLE_RATE_CHANGED
=
1
<<
13
;
/** The audio encoding is changing. */
public
static
final
int
DISCARD_REASON_AUDIO_ENCODING_CHANGED
=
1
<<
14
;
/** The name of the decoder. */
public
final
String
decoderName
;
/** The {@link Format} for which the decoder was previously configured. */
public
final
Format
oldFormat
;
/** The new {@link Format} being evaluated. */
public
final
Format
newFormat
;
/** The {@link DecoderReuseResult result} of the evaluation. */
@DecoderReuseResult
public
final
int
result
;
/**
* {@link DecoderDiscardReasons Reasons} why the decoder cannot be reused. Always {@code 0} if
* reuse is possible. May also be {code 0} if reuse is not possible for an unspecified reason.
*/
@DecoderDiscardReasons
public
final
int
discardReasons
;
/**
* @param decoderName The name of the decoder.
* @param oldFormat The {@link Format} for which the decoder was previously configured.
* @param newFormat The new {@link Format} being evaluated.
* @param result The {@link DecoderReuseResult result} of the evaluation.
* @param discardReasons One or more {@link DecoderDiscardReasons reasons} why the decoder cannot
* be reused, or {@code 0} if reuse is possible.
*/
public
DecoderReuseEvaluation
(
String
decoderName
,
Format
oldFormat
,
Format
newFormat
,
@DecoderReuseResult
int
result
,
@DecoderDiscardReasons
int
discardReasons
)
{
checkArgument
(
result
==
REUSE_RESULT_NO
||
discardReasons
==
0
);
this
.
decoderName
=
checkNotEmpty
(
decoderName
);
this
.
oldFormat
=
checkNotNull
(
oldFormat
);
this
.
newFormat
=
checkNotNull
(
newFormat
);
this
.
result
=
result
;
this
.
discardReasons
=
discardReasons
;
}
@Override
public
boolean
equals
(
@Nullable
Object
obj
)
{
if
(
this
==
obj
)
{
return
true
;
}
if
(
obj
==
null
||
getClass
()
!=
obj
.
getClass
())
{
return
false
;
}
DecoderReuseEvaluation
other
=
(
DecoderReuseEvaluation
)
obj
;
return
result
==
other
.
result
&&
discardReasons
==
other
.
discardReasons
&&
decoderName
.
equals
(
other
.
decoderName
)
&&
oldFormat
.
equals
(
other
.
oldFormat
)
&&
newFormat
.
equals
(
other
.
newFormat
);
}
@Override
public
int
hashCode
()
{
int
hashCode
=
17
;
hashCode
=
31
*
hashCode
+
result
;
hashCode
=
31
*
hashCode
+
discardReasons
;
hashCode
=
31
*
hashCode
+
decoderName
.
hashCode
();
hashCode
=
31
*
hashCode
+
oldFormat
.
hashCode
();
hashCode
=
31
*
hashCode
+
newFormat
.
hashCode
();
return
hashCode
;
}
}
library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecInfo.java
View file @
c47e6220
...
...
@@ -15,6 +15,20 @@
*/
package
com
.
google
.
android
.
exoplayer2
.
mediacodec
;
import
static
com
.
google
.
android
.
exoplayer2
.
decoder
.
DecoderReuseEvaluation
.
DISCARD_REASON_AUDIO_CHANNEL_COUNT_CHANGED
;
import
static
com
.
google
.
android
.
exoplayer2
.
decoder
.
DecoderReuseEvaluation
.
DISCARD_REASON_AUDIO_ENCODING_CHANGED
;
import
static
com
.
google
.
android
.
exoplayer2
.
decoder
.
DecoderReuseEvaluation
.
DISCARD_REASON_AUDIO_SAMPLE_RATE_CHANGED
;
import
static
com
.
google
.
android
.
exoplayer2
.
decoder
.
DecoderReuseEvaluation
.
DISCARD_REASON_INITIALIZATION_DATA_CHANGED
;
import
static
com
.
google
.
android
.
exoplayer2
.
decoder
.
DecoderReuseEvaluation
.
DISCARD_REASON_MIME_TYPE_CHANGED
;
import
static
com
.
google
.
android
.
exoplayer2
.
decoder
.
DecoderReuseEvaluation
.
DISCARD_REASON_VIDEO_COLOR_INFO_CHANGED
;
import
static
com
.
google
.
android
.
exoplayer2
.
decoder
.
DecoderReuseEvaluation
.
DISCARD_REASON_VIDEO_RESOLUTION_CHANGED
;
import
static
com
.
google
.
android
.
exoplayer2
.
decoder
.
DecoderReuseEvaluation
.
DISCARD_REASON_VIDEO_ROTATION_CHANGED
;
import
static
com
.
google
.
android
.
exoplayer2
.
decoder
.
DecoderReuseEvaluation
.
DISCARD_REASON_WORKAROUND
;
import
static
com
.
google
.
android
.
exoplayer2
.
decoder
.
DecoderReuseEvaluation
.
REUSE_RESULT_NO
;
import
static
com
.
google
.
android
.
exoplayer2
.
decoder
.
DecoderReuseEvaluation
.
REUSE_RESULT_YES_WITHOUT_RECONFIGURATION
;
import
static
com
.
google
.
android
.
exoplayer2
.
decoder
.
DecoderReuseEvaluation
.
REUSE_RESULT_YES_WITH_FLUSH
;
import
static
com
.
google
.
android
.
exoplayer2
.
decoder
.
DecoderReuseEvaluation
.
REUSE_RESULT_YES_WITH_RECONFIGURATION
;
import
android.graphics.Point
;
import
android.media.MediaCodec
;
import
android.media.MediaCodecInfo.AudioCapabilities
;
...
...
@@ -22,18 +36,17 @@ import android.media.MediaCodecInfo.CodecCapabilities;
import
android.media.MediaCodecInfo.CodecProfileLevel
;
import
android.media.MediaCodecInfo.VideoCapabilities
;
import
android.util.Pair
;
import
androidx.annotation.IntDef
;
import
androidx.annotation.Nullable
;
import
androidx.annotation.RequiresApi
;
import
androidx.annotation.VisibleForTesting
;
import
com.google.android.exoplayer2.Format
;
import
com.google.android.exoplayer2.decoder.DecoderReuseEvaluation
;
import
com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DecoderDiscardReasons
;
import
com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DecoderReuseResult
;
import
com.google.android.exoplayer2.util.Assertions
;
import
com.google.android.exoplayer2.util.Log
;
import
com.google.android.exoplayer2.util.MimeTypes
;
import
com.google.android.exoplayer2.util.Util
;
import
java.lang.annotation.Documented
;
import
java.lang.annotation.Retention
;
import
java.lang.annotation.RetentionPolicy
;
/** Information about a {@link MediaCodec} for a given mime type. */
@SuppressWarnings
(
"InlinedApi"
)
...
...
@@ -47,28 +60,6 @@ public final class MediaCodecInfo {
*/
public
static
final
int
MAX_SUPPORTED_INSTANCES_UNKNOWN
=
-
1
;
/** The possible return values for {@link #canKeepCodec(Format, Format)}. */
@Documented
@Retention
(
RetentionPolicy
.
SOURCE
)
@IntDef
({
KEEP_CODEC_RESULT_NO
,
KEEP_CODEC_RESULT_YES_WITH_FLUSH
,
KEEP_CODEC_RESULT_YES_WITH_RECONFIGURATION
,
KEEP_CODEC_RESULT_YES_WITHOUT_RECONFIGURATION
})
public
@interface
KeepCodecResult
{}
/** The codec cannot be kept. */
public
static
final
int
KEEP_CODEC_RESULT_NO
=
0
;
/** The codec can be kept, but must be flushed. */
public
static
final
int
KEEP_CODEC_RESULT_YES_WITH_FLUSH
=
1
;
/**
* The codec can be kept. It does not need to be flushed, but must be reconfigured by prefixing
* the next input buffer with the new format's configuration data.
*/
public
static
final
int
KEEP_CODEC_RESULT_YES_WITH_RECONFIGURATION
=
2
;
/** The codec can be kept. It does not need to be flushed and no reconfiguration is required. */
public
static
final
int
KEEP_CODEC_RESULT_YES_WITHOUT_RECONFIGURATION
=
3
;
/**
* The name of the decoder.
*
...
...
@@ -361,7 +352,7 @@ public final class MediaCodecInfo {
* @param isNewFormatComplete Whether {@code newFormat} is populated with format-specific
* metadata.
* @return Whether it is possible to adapt the decoder seamlessly.
* @deprecated Use {@link #can
Keep
Codec}.
* @deprecated Use {@link #can
Reuse
Codec}.
*/
@Deprecated
public
boolean
isSeamlessAdaptationSupported
(
...
...
@@ -369,49 +360,68 @@ public final class MediaCodecInfo {
if
(!
isNewFormatComplete
&&
oldFormat
.
colorInfo
!=
null
&&
newFormat
.
colorInfo
==
null
)
{
newFormat
=
newFormat
.
buildUpon
().
setColorInfo
(
oldFormat
.
colorInfo
).
build
();
}
@
KeepCodecResult
int
keepCodecResult
=
canKeepCodec
(
oldFormat
,
newFormat
)
;
return
keepCodecResult
==
KEEP_CODEC
_RESULT_YES_WITH_RECONFIGURATION
||
keepCodecResult
==
KEEP_CODEC
_RESULT_YES_WITHOUT_RECONFIGURATION
;
@
DecoderReuseResult
int
reuseResult
=
canReuseCodec
(
oldFormat
,
newFormat
).
result
;
return
reuseResult
==
REUSE
_RESULT_YES_WITH_RECONFIGURATION
||
reuseResult
==
REUSE
_RESULT_YES_WITHOUT_RECONFIGURATION
;
}
/**
*
Returns the extent to which it's possible to adapt an instance of this decoder that's currently
*
decoding
{@code oldFormat} to decode {@code newFormat} instead.
*
Evaluates whether it's possible to reuse an instance of this decoder that's currently decoding
* {@code oldFormat} to decode {@code newFormat} instead.
*
* <p>For adaptation to succeed, the codec must also be configured with maximum values that are
* compatible with the new format.
*
* @param oldFormat The format being decoded.
* @param newFormat The new format.
* @return The
extent to which it's possible to adapt an instance of the decoder
.
* @return The
result of the evaluation
.
*/
@KeepCodecResult
public
int
canKeepCodec
(
Format
oldFormat
,
Format
newFormat
)
{
public
DecoderReuseEvaluation
canReuseCodec
(
Format
oldFormat
,
Format
newFormat
)
{
@DecoderDiscardReasons
int
discardReasons
=
0
;
if
(!
Util
.
areEqual
(
oldFormat
.
sampleMimeType
,
newFormat
.
sampleMimeType
))
{
return
KEEP_CODEC_RESULT_NO
;
discardReasons
|=
DISCARD_REASON_MIME_TYPE_CHANGED
;
}
if
(
isVideo
)
{
if
(
oldFormat
.
rotationDegrees
==
newFormat
.
rotationDegrees
&&
(
adaptive
||
(
oldFormat
.
width
==
newFormat
.
width
&&
oldFormat
.
height
==
newFormat
.
height
))
&&
Util
.
areEqual
(
oldFormat
.
colorInfo
,
newFormat
.
colorInfo
))
{
if
(
oldFormat
.
initializationDataEquals
(
newFormat
))
{
return
KEEP_CODEC_RESULT_YES_WITHOUT_RECONFIGURATION
;
}
else
if
(!
needsAdaptationReconfigureWorkaround
(
name
))
{
return
KEEP_CODEC_RESULT_YES_WITH_RECONFIGURATION
;
}
if
(
oldFormat
.
rotationDegrees
!=
newFormat
.
rotationDegrees
)
{
discardReasons
|=
DISCARD_REASON_VIDEO_ROTATION_CHANGED
;
}
if
(!
adaptive
&&
(
oldFormat
.
width
!=
newFormat
.
width
||
oldFormat
.
height
!=
newFormat
.
height
))
{
discardReasons
|=
DISCARD_REASON_VIDEO_RESOLUTION_CHANGED
;
}
if
(!
Util
.
areEqual
(
oldFormat
.
colorInfo
,
newFormat
.
colorInfo
))
{
discardReasons
|=
DISCARD_REASON_VIDEO_COLOR_INFO_CHANGED
;
}
if
(
needsAdaptationReconfigureWorkaround
(
name
)
&&
!
oldFormat
.
initializationDataEquals
(
newFormat
))
{
discardReasons
|=
DISCARD_REASON_WORKAROUND
;
}
if
(
discardReasons
==
0
)
{
return
new
DecoderReuseEvaluation
(
name
,
oldFormat
,
newFormat
,
oldFormat
.
initializationDataEquals
(
newFormat
)
?
REUSE_RESULT_YES_WITHOUT_RECONFIGURATION
:
REUSE_RESULT_YES_WITH_RECONFIGURATION
,
/* discardReasons= */
0
);
}
}
else
{
if
(
oldFormat
.
channelCount
!=
newFormat
.
channelCount
||
oldFormat
.
sampleRate
!=
newFormat
.
sampleRate
||
oldFormat
.
pcmEncoding
!=
newFormat
.
pcmEncoding
)
{
return
KEEP_CODEC_RESULT_NO
;
if
(
oldFormat
.
channelCount
!=
newFormat
.
channelCount
)
{
discardReasons
|=
DISCARD_REASON_AUDIO_CHANNEL_COUNT_CHANGED
;
}
if
(
oldFormat
.
sampleRate
!=
newFormat
.
sampleRate
)
{
discardReasons
|=
DISCARD_REASON_AUDIO_SAMPLE_RATE_CHANGED
;
}
if
(
oldFormat
.
pcmEncoding
!=
newFormat
.
pcmEncoding
)
{
discardReasons
|=
DISCARD_REASON_AUDIO_ENCODING_CHANGED
;
}
// Check whether we're adapting between two xHE-AAC formats, for which adaptation is possible
// without reconfiguration or flushing.
if
(
MimeTypes
.
AUDIO_AAC
.
equals
(
mimeType
))
{
if
(
discardReasons
==
0
&&
MimeTypes
.
AUDIO_AAC
.
equals
(
mimeType
))
{
@Nullable
Pair
<
Integer
,
Integer
>
oldCodecProfileLevel
=
MediaCodecUtil
.
getCodecProfileAndLevel
(
oldFormat
);
...
...
@@ -423,18 +433,30 @@ public final class MediaCodecInfo {
int
newProfile
=
newCodecProfileLevel
.
first
;
if
(
oldProfile
==
CodecProfileLevel
.
AACObjectXHE
&&
newProfile
==
CodecProfileLevel
.
AACObjectXHE
)
{
return
KEEP_CODEC_RESULT_YES_WITHOUT_RECONFIGURATION
;
return
new
DecoderReuseEvaluation
(
name
,
oldFormat
,
newFormat
,
REUSE_RESULT_YES_WITHOUT_RECONFIGURATION
,
/* discardReasons= */
0
);
}
}
}
if
(
oldFormat
.
initializationDataEquals
(
newFormat
)
&&
!
needsAdaptationFlushWorkaround
(
mimeType
))
{
return
KEEP_CODEC_RESULT_YES_WITH_FLUSH
;
if
(!
oldFormat
.
initializationDataEquals
(
newFormat
))
{
discardReasons
|=
DISCARD_REASON_INITIALIZATION_DATA_CHANGED
;
}
if
(
needsAdaptationFlushWorkaround
(
mimeType
))
{
discardReasons
|=
DISCARD_REASON_WORKAROUND
;
}
if
(
discardReasons
==
0
)
{
return
new
DecoderReuseEvaluation
(
name
,
oldFormat
,
newFormat
,
REUSE_RESULT_YES_WITH_FLUSH
,
/* discardReasons= */
0
);
}
}
return
KEEP_CODEC_RESULT_NO
;
return
new
DecoderReuseEvaluation
(
name
,
oldFormat
,
newFormat
,
REUSE_RESULT_NO
,
discardReasons
)
;
}
/**
...
...
library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java
View file @
c47e6220
...
...
@@ -15,10 +15,14 @@
*/
package
com
.
google
.
android
.
exoplayer2
.
mediacodec
;
import
static
com
.
google
.
android
.
exoplayer2
.
mediacodec
.
MediaCodecInfo
.
KEEP_CODEC_RESULT_NO
;
import
static
com
.
google
.
android
.
exoplayer2
.
mediacodec
.
MediaCodecInfo
.
KEEP_CODEC_RESULT_YES_WITHOUT_RECONFIGURATION
;
import
static
com
.
google
.
android
.
exoplayer2
.
mediacodec
.
MediaCodecInfo
.
KEEP_CODEC_RESULT_YES_WITH_FLUSH
;
import
static
com
.
google
.
android
.
exoplayer2
.
mediacodec
.
MediaCodecInfo
.
KEEP_CODEC_RESULT_YES_WITH_RECONFIGURATION
;
import
static
com
.
google
.
android
.
exoplayer2
.
decoder
.
DecoderReuseEvaluation
.
DISCARD_REASON_DRM_SESSION_CHANGED
;
import
static
com
.
google
.
android
.
exoplayer2
.
decoder
.
DecoderReuseEvaluation
.
DISCARD_REASON_OPERATING_RATE_CHANGED
;
import
static
com
.
google
.
android
.
exoplayer2
.
decoder
.
DecoderReuseEvaluation
.
DISCARD_REASON_REUSE_NOT_IMPLEMENTED
;
import
static
com
.
google
.
android
.
exoplayer2
.
decoder
.
DecoderReuseEvaluation
.
DISCARD_REASON_WORKAROUND
;
import
static
com
.
google
.
android
.
exoplayer2
.
decoder
.
DecoderReuseEvaluation
.
REUSE_RESULT_NO
;
import
static
com
.
google
.
android
.
exoplayer2
.
decoder
.
DecoderReuseEvaluation
.
REUSE_RESULT_YES_WITHOUT_RECONFIGURATION
;
import
static
com
.
google
.
android
.
exoplayer2
.
decoder
.
DecoderReuseEvaluation
.
REUSE_RESULT_YES_WITH_FLUSH
;
import
static
com
.
google
.
android
.
exoplayer2
.
decoder
.
DecoderReuseEvaluation
.
REUSE_RESULT_YES_WITH_RECONFIGURATION
;
import
static
com
.
google
.
android
.
exoplayer2
.
util
.
Assertions
.
checkNotNull
;
import
static
com
.
google
.
android
.
exoplayer2
.
util
.
Assertions
.
checkState
;
import
static
java
.
lang
.
Math
.
max
;
...
...
@@ -44,11 +48,12 @@ import com.google.android.exoplayer2.Format;
import
com.google.android.exoplayer2.FormatHolder
;
import
com.google.android.exoplayer2.decoder.DecoderCounters
;
import
com.google.android.exoplayer2.decoder.DecoderInputBuffer
;
import
com.google.android.exoplayer2.decoder.DecoderReuseEvaluation
;
import
com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DecoderDiscardReasons
;
import
com.google.android.exoplayer2.drm.DrmSession
;
import
com.google.android.exoplayer2.drm.DrmSession.DrmSessionException
;
import
com.google.android.exoplayer2.drm.ExoMediaCrypto
;
import
com.google.android.exoplayer2.drm.FrameworkMediaCrypto
;
import
com.google.android.exoplayer2.mediacodec.MediaCodecInfo.KeepCodecResult
;
import
com.google.android.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryException
;
import
com.google.android.exoplayer2.source.MediaPeriod
;
import
com.google.android.exoplayer2.source.SampleStream
;
...
...
@@ -710,7 +715,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
if
(
codec
!=
null
&&
codecDrainAction
!=
DRAIN_ACTION_REINITIALIZE
&&
getState
()
!=
STATE_DISABLED
)
{
update
OperatingRateOrReinitializeCodec
(
codecInputFormat
);
update
CodecOperatingRate
(
codecInputFormat
);
}
}
...
...
@@ -1375,9 +1380,13 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
*
* @param formatHolder A {@link FormatHolder} that holds the new {@link Format}.
* @throws ExoPlaybackException If an error occurs re-initializing the {@link MediaCodec}.
* @return The result of the evaluation to determine whether the existing decoder instance can be
* reused for the new format, or {@code null} if the renderer did not have a decoder.
*/
@CallSuper
protected
void
onInputFormatChanged
(
FormatHolder
formatHolder
)
throws
ExoPlaybackException
{
@Nullable
protected
DecoderReuseEvaluation
onInputFormatChanged
(
FormatHolder
formatHolder
)
throws
ExoPlaybackException
{
waitingForFirstSampleInFormat
=
true
;
Format
newFormat
=
checkNotNull
(
formatHolder
.
format
);
setSourceDrmSession
(
formatHolder
.
drmSession
);
...
...
@@ -1385,7 +1394,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
if
(
bypassEnabled
)
{
bypassDrainAndReinitialize
=
true
;
return
;
// Need to drain batch buffer first.
return
null
;
// Need to drain batch buffer first.
}
if
(
codec
==
null
)
{
...
...
@@ -1393,66 +1402,86 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
availableCodecInfos
=
null
;
}
maybeInitCodecOrBypass
();
return
;
return
null
;
}
// We have an existing codec that we may need to reconfigure, re-initialize, or release to
// switch to bypass. If the existing codec instance is kept then its operating rate and DRM
// session may need to be updated.
MediaCodecAdapter
oldCodec
=
codec
;
Format
oldFormat
=
codecInputFormat
;
if
(
drmNeedsCodecReinitialization
(
codecInfo
,
newFormat
,
codecDrmSession
,
sourceDrmSession
))
{
drainAndReinitializeCodec
();
return
;
return
new
DecoderReuseEvaluation
(
codecInfo
.
name
,
oldFormat
,
newFormat
,
REUSE_RESULT_NO
,
DISCARD_REASON_DRM_SESSION_CHANGED
);
}
boolean
drainAndUpdateCodecDrmSession
=
sourceDrmSession
!=
codecDrmSession
;
Assertions
.
checkState
(!
drainAndUpdateCodecDrmSession
||
Util
.
SDK_INT
>=
23
);
switch
(
canKeepCodec
(
codec
,
codecInfo
,
codecInputFormat
,
newFormat
))
{
case
KEEP_CODEC_RESULT_NO:
DecoderReuseEvaluation
evaluation
=
canReuseCodec
(
codecInfo
,
oldFormat
,
newFormat
);
@DecoderDiscardReasons
int
overridingDiscardReasons
=
0
;
switch
(
evaluation
.
result
)
{
case
REUSE_RESULT_NO:
drainAndReinitializeCodec
();
break
;
case
KEEP_CODEC
_RESULT_YES_WITH_FLUSH:
if
(
updateOperatingRateOrReinitializeCodec
(
newFormat
))
{
// Codec re-initialization triggered.
case
REUSE
_RESULT_YES_WITH_FLUSH:
if
(
!
updateCodecOperatingRate
(
newFormat
))
{
overridingDiscardReasons
|=
DISCARD_REASON_OPERATING_RATE_CHANGED
;
}
else
{
codecInputFormat
=
newFormat
;
if
(
drainAndUpdateCodecDrmSession
)
{
drainAndUpdateCodecDrmSessionV23
();
}
else
{
drainAndFlushCodec
();
if
(!
drainAndUpdateCodecDrmSessionV23
())
{
overridingDiscardReasons
|=
DISCARD_REASON_WORKAROUND
;
}
}
else
if
(!
drainAndFlushCodec
())
{
overridingDiscardReasons
|=
DISCARD_REASON_WORKAROUND
;
}
}
break
;
case
KEEP_CODEC
_RESULT_YES_WITH_RECONFIGURATION:
if
(
updateOperatingRateOrReinitializeCodec
(
newFormat
))
{
// Codec re-initialization triggered.
case
REUSE
_RESULT_YES_WITH_RECONFIGURATION:
if
(
!
updateCodecOperatingRate
(
newFormat
))
{
overridingDiscardReasons
|=
DISCARD_REASON_OPERATING_RATE_CHANGED
;
}
else
{
codecReconfigured
=
true
;
codecReconfigurationState
=
RECONFIGURATION_STATE_WRITE_PENDING
;
codecNeedsAdaptationWorkaroundBuffer
=
codecAdaptationWorkaroundMode
==
ADAPTATION_WORKAROUND_MODE_ALWAYS
||
(
codecAdaptationWorkaroundMode
==
ADAPTATION_WORKAROUND_MODE_SAME_RESOLUTION
&&
newFormat
.
width
==
codecInput
Format
.
width
&&
newFormat
.
height
==
codecInput
Format
.
height
);
&&
newFormat
.
width
==
old
Format
.
width
&&
newFormat
.
height
==
old
Format
.
height
);
codecInputFormat
=
newFormat
;
if
(
drainAndUpdateCodecDrmSession
)
{
drainAndUpdateCodecDrmSessionV23
()
;
if
(
drainAndUpdateCodecDrmSession
&&
!
drainAndUpdateCodecDrmSessionV23
()
)
{
overridingDiscardReasons
|=
DISCARD_REASON_WORKAROUND
;
}
}
break
;
case
KEEP_CODEC
_RESULT_YES_WITHOUT_RECONFIGURATION:
if
(
updateOperatingRateOrReinitializeCodec
(
newFormat
))
{
// Codec re-initialization triggered.
case
REUSE
_RESULT_YES_WITHOUT_RECONFIGURATION:
if
(
!
updateCodecOperatingRate
(
newFormat
))
{
overridingDiscardReasons
|=
DISCARD_REASON_OPERATING_RATE_CHANGED
;
}
else
{
codecInputFormat
=
newFormat
;
if
(
drainAndUpdateCodecDrmSession
)
{
drainAndUpdateCodecDrmSessionV23
()
;
if
(
drainAndUpdateCodecDrmSession
&&
!
drainAndUpdateCodecDrmSessionV23
()
)
{
overridingDiscardReasons
|=
DISCARD_REASON_WORKAROUND
;
}
}
break
;
default
:
throw
new
IllegalStateException
();
// Never happens.
}
if
(
evaluation
.
result
!=
REUSE_RESULT_NO
&&
(
codec
!=
oldCodec
||
codecDrainAction
==
DRAIN_ACTION_REINITIALIZE
))
{
// Initial evaluation indicated reuse was possible, but codec re-initialization was triggered.
// The reasons are indicated by overridingDiscardReasons.
return
new
DecoderReuseEvaluation
(
codecInfo
.
name
,
oldFormat
,
newFormat
,
REUSE_RESULT_NO
,
overridingDiscardReasons
);
}
return
evaluation
;
}
/**
...
...
@@ -1544,21 +1573,24 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
}
/**
*
Determin
es whether the existing {@link MediaCodec} can be kept for a new {@link Format}, and if
*
Evaluat
es whether the existing {@link MediaCodec} can be kept for a new {@link Format}, and if
* it can whether it requires reconfiguration.
*
* <p>The default implementation
returns {@link MediaCodecInfo#KEEP_CODEC_RESULT_NO}
.
* <p>The default implementation
does not allow decoder reuse
.
*
* @param codec The existing {@link MediaCodecAdapter} instance.
* @param codecInfo A {@link MediaCodecInfo} describing the decoder.
* @param oldFormat The {@link Format} for which the existing instance is configured.
* @param newFormat The new {@link Format}.
* @return
Whether the instance can be kept, and if it can whether it requires reconfigur
ation.
* @return
The result of the evalu
ation.
*/
@KeepCodecResult
protected
int
canKeepCodec
(
MediaCodecAdapter
codec
,
MediaCodecInfo
codecInfo
,
Format
oldFormat
,
Format
newFormat
)
{
return
KEEP_CODEC_RESULT_NO
;
protected
DecoderReuseEvaluation
canReuseCodec
(
MediaCodecInfo
codecInfo
,
Format
oldFormat
,
Format
newFormat
)
{
return
new
DecoderReuseEvaluation
(
codecInfo
.
name
,
oldFormat
,
newFormat
,
REUSE_RESULT_NO
,
DISCARD_REASON_REUSE_NOT_IMPLEMENTED
);
}
@Override
...
...
@@ -1608,25 +1640,23 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
*
* @param format The {@link Format} for which the operating rate should be configured.
* @throws ExoPlaybackException If an error occurs releasing or initializing a codec.
* @return Whether codec release and re-initialization was triggered, rather than the existing
* codec being updated.
* @return False if codec release and re-initialization was triggered. True in all other cases.
*/
private
boolean
updateOperatingRateOrReinitializeCodec
(
Format
format
)
throws
ExoPlaybackException
{
private
boolean
updateCodecOperatingRate
(
Format
format
)
throws
ExoPlaybackException
{
if
(
Util
.
SDK_INT
<
23
)
{
return
fals
e
;
return
tru
e
;
}
float
newCodecOperatingRate
=
getCodecOperatingRateV23
(
playbackSpeed
,
format
,
getStreamFormats
());
if
(
codecOperatingRate
==
newCodecOperatingRate
)
{
// No change.
return
fals
e
;
return
tru
e
;
}
else
if
(
newCodecOperatingRate
==
CODEC_OPERATING_RATE_UNSET
)
{
// The only way to clear the operating rate is to instantiate a new codec instance. See
// [Internal ref: b/111543954].
drainAndReinitializeCodec
();
return
tru
e
;
return
fals
e
;
}
else
if
(
codecOperatingRate
!=
CODEC_OPERATING_RATE_UNSET
||
newCodecOperatingRate
>
assumedMinimumCodecOperatingRate
)
{
// We need to set the operating rate, either because we've set it previously or because it's
...
...
@@ -1635,36 +1665,48 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
codecParameters
.
putFloat
(
MediaFormat
.
KEY_OPERATING_RATE
,
newCodecOperatingRate
);
codec
.
setParameters
(
codecParameters
);
codecOperatingRate
=
newCodecOperatingRate
;
return
fals
e
;
return
tru
e
;
}
return
fals
e
;
return
tru
e
;
}
/** Starts draining the codec for flush. */
private
void
drainAndFlushCodec
()
{
/**
* Starts draining the codec for a flush, or to release and re-initialize the codec if flushing
* will not be possible. If no buffers have been queued to the codec then this method is a no-op.
*
* @return False if codec release and re-initialization was triggered due to the need to apply a
* flush workaround. True in all other cases.
*/
private
boolean
drainAndFlushCodec
()
{
if
(
codecReceivedBuffers
)
{
codecDrainState
=
DRAIN_STATE_SIGNAL_END_OF_STREAM
;
if
(
codecNeedsFlushWorkaround
||
codecNeedsEosFlushWorkaround
)
{
codecDrainAction
=
DRAIN_ACTION_REINITIALIZE
;
return
false
;
}
else
{
codecDrainAction
=
DRAIN_ACTION_FLUSH
;
}
}
return
true
;
}
/**
* Starts draining the codec to update its DRM session. The update may occur immediately if no
* buffers have been queued to the codec.
* Starts draining the codec to flush it and update its DRM session, or to release and
* re-initialize the codec if flushing will not be possible. If no buffers have been queued to the
* codec then this method updates the DRM session immediately without flushing the codec.
*
* @throws ExoPlaybackException If an error occurs updating the codec's DRM session.
* @return False if codec release and re-initialization was triggered due to the need to apply a
* flush workaround. True in all other cases.
*/
@TargetApi
(
23
)
// Only called when SDK_INT >= 23, but lint isn't clever enough to know.
private
void
drainAndUpdateCodecDrmSessionV23
()
throws
ExoPlaybackException
{
private
boolean
drainAndUpdateCodecDrmSessionV23
()
throws
ExoPlaybackException
{
if
(
codecReceivedBuffers
)
{
codecDrainState
=
DRAIN_STATE_SIGNAL_END_OF_STREAM
;
if
(
codecNeedsFlushWorkaround
||
codecNeedsEosFlushWorkaround
)
{
codecDrainAction
=
DRAIN_ACTION_REINITIALIZE
;
return
false
;
}
else
{
codecDrainAction
=
DRAIN_ACTION_FLUSH_AND_UPDATE_DRM_SESSION
;
}
...
...
@@ -1672,6 +1714,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
// Nothing has been queued to the decoder, so we can do the update immediately.
updateDrmSessionV23
();
}
return
true
;
}
/**
...
...
library/core/src/main/java/com/google/android/exoplayer2/util/EventLogger.java
View file @
c47e6220
...
...
@@ -34,6 +34,7 @@ import com.google.android.exoplayer2.Timeline;
import
com.google.android.exoplayer2.analytics.AnalyticsListener
;
import
com.google.android.exoplayer2.audio.AudioAttributes
;
import
com.google.android.exoplayer2.decoder.DecoderCounters
;
import
com.google.android.exoplayer2.decoder.DecoderReuseEvaluation
;
import
com.google.android.exoplayer2.metadata.Metadata
;
import
com.google.android.exoplayer2.source.LoadEventInfo
;
import
com.google.android.exoplayer2.source.MediaLoadData
;
...
...
@@ -328,7 +329,8 @@ public class EventLogger implements AnalyticsListener {
}
@Override
public
void
onAudioInputFormatChanged
(
EventTime
eventTime
,
Format
format
)
{
public
void
onAudioInputFormatChanged
(
EventTime
eventTime
,
Format
format
,
@Nullable
DecoderReuseEvaluation
decoderReuseEvaluation
)
{
logd
(
eventTime
,
"audioInputFormat"
,
Format
.
toLogString
(
format
));
}
...
...
@@ -393,7 +395,8 @@ public class EventLogger implements AnalyticsListener {
}
@Override
public
void
onVideoInputFormatChanged
(
EventTime
eventTime
,
Format
format
)
{
public
void
onVideoInputFormatChanged
(
EventTime
eventTime
,
Format
format
,
@Nullable
DecoderReuseEvaluation
decoderReuseEvaluation
)
{
logd
(
eventTime
,
"videoInputFormat"
,
Format
.
toLogString
(
format
));
}
...
...
library/core/src/main/java/com/google/android/exoplayer2/video/DecoderVideoRenderer.java
View file @
c47e6220
...
...
@@ -15,6 +15,9 @@
*/
package
com
.
google
.
android
.
exoplayer2
.
video
;
import
static
com
.
google
.
android
.
exoplayer2
.
decoder
.
DecoderReuseEvaluation
.
DISCARD_REASON_DRM_SESSION_CHANGED
;
import
static
com
.
google
.
android
.
exoplayer2
.
decoder
.
DecoderReuseEvaluation
.
DISCARD_REASON_REUSE_NOT_IMPLEMENTED
;
import
static
com
.
google
.
android
.
exoplayer2
.
decoder
.
DecoderReuseEvaluation
.
REUSE_RESULT_NO
;
import
static
java
.
lang
.
Math
.
max
;
import
android.os.Handler
;
...
...
@@ -34,6 +37,7 @@ import com.google.android.exoplayer2.decoder.Decoder;
import
com.google.android.exoplayer2.decoder.DecoderCounters
;
import
com.google.android.exoplayer2.decoder.DecoderException
;
import
com.google.android.exoplayer2.decoder.DecoderInputBuffer
;
import
com.google.android.exoplayer2.decoder.DecoderReuseEvaluation
;
import
com.google.android.exoplayer2.drm.DrmSession
;
import
com.google.android.exoplayer2.drm.DrmSession.DrmSessionException
;
import
com.google.android.exoplayer2.drm.ExoMediaCrypto
;
...
...
@@ -97,9 +101,12 @@ public abstract class DecoderVideoRenderer extends BaseRenderer {
private
Format
inputFormat
;
private
Format
outputFormat
;
@Nullable
private
Decoder
<
VideoDecoderInputBuffer
,
?
extends
VideoDecoderOutputBuffer
,
?
extends
DecoderException
>
decoder
;
private
VideoDecoderInputBuffer
inputBuffer
;
private
VideoDecoderOutputBuffer
outputBuffer
;
@Nullable
private
Surface
surface
;
...
...
@@ -366,7 +373,24 @@ public abstract class DecoderVideoRenderer extends BaseRenderer {
if
(
decoder
==
null
)
{
maybeInitDecoder
();
}
else
if
(
sourceDrmSession
!=
decoderDrmSession
||
!
canKeepCodec
(
oldFormat
,
inputFormat
))
{
eventDispatcher
.
inputFormatChanged
(
inputFormat
,
/* decoderReuseEvaluation= */
null
);
return
;
}
DecoderReuseEvaluation
evaluation
;
if
(
sourceDrmSession
!=
decoderDrmSession
)
{
evaluation
=
new
DecoderReuseEvaluation
(
decoder
.
getName
(),
oldFormat
,
newFormat
,
REUSE_RESULT_NO
,
DISCARD_REASON_DRM_SESSION_CHANGED
);
}
else
{
evaluation
=
canReuseDecoder
(
decoder
.
getName
(),
oldFormat
,
newFormat
);
}
if
(
evaluation
.
result
==
REUSE_RESULT_NO
)
{
if
(
decoderReceivedBuffers
)
{
// Signal end of stream and wait for any final output buffers before re-initialization.
decoderReinitializationState
=
REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM
;
...
...
@@ -376,8 +400,7 @@ public abstract class DecoderVideoRenderer extends BaseRenderer {
maybeInitDecoder
();
}
}
eventDispatcher
.
inputFormatChanged
(
inputFormat
);
eventDispatcher
.
inputFormatChanged
(
inputFormat
,
evaluation
);
}
/**
...
...
@@ -626,14 +649,18 @@ public abstract class DecoderVideoRenderer extends BaseRenderer {
protected
abstract
void
setDecoderOutputMode
(
@C
.
VideoOutputMode
int
outputMode
);
/**
* Returns whether the existing decoder can be kept for a new format.
* Evaluates whether the existing decoder can be reused for a new {@link Format}.
*
* <p>The default implementation does not allow decoder reuse.
*
* @param oldFormat The previous format.
* @param newFormat The new format.
* @return
Whether the existing decoder can be kept
.
* @return
The result of the evaluation
.
*/
protected
boolean
canKeepCodec
(
Format
oldFormat
,
Format
newFormat
)
{
return
false
;
protected
DecoderReuseEvaluation
canReuseDecoder
(
String
decoderName
,
Format
oldFormat
,
Format
newFormat
)
{
return
new
DecoderReuseEvaluation
(
decoderName
,
oldFormat
,
newFormat
,
REUSE_RESULT_NO
,
DISCARD_REASON_REUSE_NOT_IMPLEMENTED
);
}
// Internal methods.
...
...
library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java
View file @
c47e6220
...
...
@@ -15,7 +15,9 @@
*/
package
com
.
google
.
android
.
exoplayer2
.
video
;
import
static
com
.
google
.
android
.
exoplayer2
.
mediacodec
.
MediaCodecInfo
.
KEEP_CODEC_RESULT_NO
;
import
static
com
.
google
.
android
.
exoplayer2
.
decoder
.
DecoderReuseEvaluation
.
DISCARD_REASON_MAX_INPUT_SIZE_EXCEEDED
;
import
static
com
.
google
.
android
.
exoplayer2
.
decoder
.
DecoderReuseEvaluation
.
DISCARD_REASON_VIDEO_MAX_RESOLUTION_EXCEEDED
;
import
static
com
.
google
.
android
.
exoplayer2
.
decoder
.
DecoderReuseEvaluation
.
REUSE_RESULT_NO
;
import
static
java
.
lang
.
Math
.
max
;
import
static
java
.
lang
.
Math
.
min
;
...
...
@@ -46,11 +48,12 @@ import com.google.android.exoplayer2.PlayerMessage.Target;
import
com.google.android.exoplayer2.RendererCapabilities
;
import
com.google.android.exoplayer2.decoder.DecoderCounters
;
import
com.google.android.exoplayer2.decoder.DecoderInputBuffer
;
import
com.google.android.exoplayer2.decoder.DecoderReuseEvaluation
;
import
com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DecoderDiscardReasons
;
import
com.google.android.exoplayer2.drm.DrmInitData
;
import
com.google.android.exoplayer2.mediacodec.MediaCodecAdapter
;
import
com.google.android.exoplayer2.mediacodec.MediaCodecDecoderException
;
import
com.google.android.exoplayer2.mediacodec.MediaCodecInfo
;
import
com.google.android.exoplayer2.mediacodec.MediaCodecInfo.KeepCodecResult
;
import
com.google.android.exoplayer2.mediacodec.MediaCodecRenderer
;
import
com.google.android.exoplayer2.mediacodec.MediaCodecSelector
;
import
com.google.android.exoplayer2.mediacodec.MediaCodecUtil
;
...
...
@@ -566,15 +569,24 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
}
@Override
@KeepCodecResult
protected
int
canKeepCodec
(
MediaCodecAdapter
codec
,
MediaCodecInfo
codecInfo
,
Format
oldFormat
,
Format
newFormat
)
{
if
(
newFormat
.
width
>
codecMaxValues
.
width
||
newFormat
.
height
>
codecMaxValues
.
height
||
getMaxInputSize
(
codecInfo
,
newFormat
)
>
codecMaxValues
.
inputSize
)
{
return
KEEP_CODEC_RESULT_NO
;
protected
DecoderReuseEvaluation
canReuseCodec
(
MediaCodecInfo
codecInfo
,
Format
oldFormat
,
Format
newFormat
)
{
DecoderReuseEvaluation
evaluation
=
codecInfo
.
canReuseCodec
(
oldFormat
,
newFormat
);
@DecoderDiscardReasons
int
discardReasons
=
evaluation
.
discardReasons
;
if
(
newFormat
.
width
>
codecMaxValues
.
width
||
newFormat
.
height
>
codecMaxValues
.
height
)
{
discardReasons
|=
DISCARD_REASON_VIDEO_MAX_RESOLUTION_EXCEEDED
;
}
if
(
getMaxInputSize
(
codecInfo
,
newFormat
)
>
codecMaxValues
.
inputSize
)
{
discardReasons
|=
DISCARD_REASON_MAX_INPUT_SIZE_EXCEEDED
;
}
return
codecInfo
.
canKeepCodec
(
oldFormat
,
newFormat
);
return
new
DecoderReuseEvaluation
(
codecInfo
.
name
,
oldFormat
,
newFormat
,
discardReasons
!=
0
?
REUSE_RESULT_NO
:
evaluation
.
result
,
discardReasons
);
}
@CallSuper
...
...
@@ -621,9 +633,12 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
}
@Override
protected
void
onInputFormatChanged
(
FormatHolder
formatHolder
)
throws
ExoPlaybackException
{
super
.
onInputFormatChanged
(
formatHolder
);
eventDispatcher
.
inputFormatChanged
(
formatHolder
.
format
);
@Nullable
protected
DecoderReuseEvaluation
onInputFormatChanged
(
FormatHolder
formatHolder
)
throws
ExoPlaybackException
{
@Nullable
DecoderReuseEvaluation
evaluation
=
super
.
onInputFormatChanged
(
formatHolder
);
eventDispatcher
.
inputFormatChanged
(
formatHolder
.
format
,
evaluation
);
return
evaluation
;
}
/**
...
...
@@ -1333,7 +1348,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
// from format to avoid codec re-use being ruled out for only this reason.
streamFormat
=
streamFormat
.
buildUpon
().
setColorInfo
(
format
.
colorInfo
).
build
();
}
if
(
codecInfo
.
can
KeepCodec
(
format
,
streamFormat
)
!=
KEEP_CODEC
_RESULT_NO
)
{
if
(
codecInfo
.
can
ReuseCodec
(
format
,
streamFormat
).
result
!=
REUSE
_RESULT_NO
)
{
haveUnknownDimensions
|=
(
streamFormat
.
width
==
Format
.
NO_VALUE
||
streamFormat
.
height
==
Format
.
NO_VALUE
);
maxWidth
=
max
(
maxWidth
,
streamFormat
.
width
);
...
...
library/core/src/main/java/com/google/android/exoplayer2/video/VideoRendererEventListener.java
View file @
c47e6220
...
...
@@ -25,6 +25,7 @@ import androidx.annotation.Nullable;
import
com.google.android.exoplayer2.Format
;
import
com.google.android.exoplayer2.Renderer
;
import
com.google.android.exoplayer2.decoder.DecoderCounters
;
import
com.google.android.exoplayer2.decoder.DecoderReuseEvaluation
;
import
com.google.android.exoplayer2.util.Assertions
;
/**
...
...
@@ -52,12 +53,23 @@ public interface VideoRendererEventListener {
default
void
onVideoDecoderInitialized
(
String
decoderName
,
long
initializedTimestampMs
,
long
initializationDurationMs
)
{}
/** @deprecated Use {@link #onVideoInputFormatChanged(Format, DecoderReuseEvaluation)}. */
@Deprecated
default
void
onVideoInputFormatChanged
(
Format
format
)
{}
/**
* Called when the format of the media being consumed by the renderer changes.
*
* @param format The new format.
* @param decoderReuseEvaluation The result of the evaluation to determine whether an existing
* decoder instance can be reused for the new format, or {@code null} if the renderer did not
* have a decoder.
*/
default
void
onVideoInputFormatChanged
(
Format
format
)
{}
@SuppressWarnings
(
"deprecation"
)
default
void
onVideoInputFormatChanged
(
Format
format
,
@Nullable
DecoderReuseEvaluation
decoderReuseEvaluation
)
{
onVideoInputFormatChanged
(
format
);
}
/**
* Called to report the number of frames dropped by the renderer. Dropped frames are reported
...
...
@@ -172,10 +184,15 @@ public interface VideoRendererEventListener {
}
}
/** Invokes {@link VideoRendererEventListener#onVideoInputFormatChanged(Format)}. */
public
void
inputFormatChanged
(
Format
format
)
{
/**
* Invokes {@link VideoRendererEventListener#onVideoInputFormatChanged(Format,
* DecoderReuseEvaluation)}.
*/
public
void
inputFormatChanged
(
Format
format
,
@Nullable
DecoderReuseEvaluation
decoderReuseEvaluation
)
{
if
(
handler
!=
null
)
{
handler
.
post
(()
->
castNonNull
(
listener
).
onVideoInputFormatChanged
(
format
));
handler
.
post
(
()
->
castNonNull
(
listener
).
onVideoInputFormatChanged
(
format
,
decoderReuseEvaluation
));
}
}
...
...
library/core/src/test/java/com/google/android/exoplayer2/mediacodec/MediaCodecInfoTest.java
View file @
c47e6220
...
...
@@ -15,9 +15,15 @@
*/
package
com
.
google
.
android
.
exoplayer2
.
mediacodec
;
import
static
com
.
google
.
android
.
exoplayer2
.
mediacodec
.
MediaCodecInfo
.
KEEP_CODEC_RESULT_NO
;
import
static
com
.
google
.
android
.
exoplayer2
.
mediacodec
.
MediaCodecInfo
.
KEEP_CODEC_RESULT_YES_WITH_FLUSH
;
import
static
com
.
google
.
android
.
exoplayer2
.
mediacodec
.
MediaCodecInfo
.
KEEP_CODEC_RESULT_YES_WITH_RECONFIGURATION
;
import
static
com
.
google
.
android
.
exoplayer2
.
decoder
.
DecoderReuseEvaluation
.
DISCARD_REASON_AUDIO_CHANNEL_COUNT_CHANGED
;
import
static
com
.
google
.
android
.
exoplayer2
.
decoder
.
DecoderReuseEvaluation
.
DISCARD_REASON_INITIALIZATION_DATA_CHANGED
;
import
static
com
.
google
.
android
.
exoplayer2
.
decoder
.
DecoderReuseEvaluation
.
DISCARD_REASON_MIME_TYPE_CHANGED
;
import
static
com
.
google
.
android
.
exoplayer2
.
decoder
.
DecoderReuseEvaluation
.
DISCARD_REASON_VIDEO_COLOR_INFO_CHANGED
;
import
static
com
.
google
.
android
.
exoplayer2
.
decoder
.
DecoderReuseEvaluation
.
DISCARD_REASON_VIDEO_RESOLUTION_CHANGED
;
import
static
com
.
google
.
android
.
exoplayer2
.
decoder
.
DecoderReuseEvaluation
.
DISCARD_REASON_VIDEO_ROTATION_CHANGED
;
import
static
com
.
google
.
android
.
exoplayer2
.
decoder
.
DecoderReuseEvaluation
.
REUSE_RESULT_NO
;
import
static
com
.
google
.
android
.
exoplayer2
.
decoder
.
DecoderReuseEvaluation
.
REUSE_RESULT_YES_WITH_FLUSH
;
import
static
com
.
google
.
android
.
exoplayer2
.
decoder
.
DecoderReuseEvaluation
.
REUSE_RESULT_YES_WITH_RECONFIGURATION
;
import
static
com
.
google
.
android
.
exoplayer2
.
util
.
MimeTypes
.
AUDIO_AAC
;
import
static
com
.
google
.
android
.
exoplayer2
.
util
.
MimeTypes
.
VIDEO_AV1
;
import
static
com
.
google
.
android
.
exoplayer2
.
util
.
MimeTypes
.
VIDEO_H264
;
...
...
@@ -26,6 +32,7 @@ import static com.google.common.truth.Truth.assertThat;
import
androidx.test.ext.junit.runners.AndroidJUnit4
;
import
com.google.android.exoplayer2.C
;
import
com.google.android.exoplayer2.Format
;
import
com.google.android.exoplayer2.decoder.DecoderReuseEvaluation
;
import
com.google.android.exoplayer2.video.ColorInfo
;
import
com.google.common.collect.ImmutableList
;
import
org.junit.Test
;
...
...
@@ -71,7 +78,14 @@ public final class MediaCodecInfoTest {
MediaCodecInfo
codecInfo
=
buildH264CodecInfo
(
/* adaptive= */
true
);
Format
hdAv1Format
=
FORMAT_H264_HD
.
buildUpon
().
setSampleMimeType
(
VIDEO_AV1
).
build
();
assertThat
(
codecInfo
.
canKeepCodec
(
FORMAT_H264_HD
,
hdAv1Format
)).
isEqualTo
(
KEEP_CODEC_RESULT_NO
);
assertThat
(
codecInfo
.
canReuseCodec
(
FORMAT_H264_HD
,
hdAv1Format
))
.
isEqualTo
(
new
DecoderReuseEvaluation
(
codecInfo
.
name
,
FORMAT_H264_HD
,
hdAv1Format
,
REUSE_RESULT_NO
,
DISCARD_REASON_MIME_TYPE_CHANGED
));
}
@Test
...
...
@@ -79,24 +93,42 @@ public final class MediaCodecInfoTest {
MediaCodecInfo
codecInfo
=
buildH264CodecInfo
(
/* adaptive= */
true
);
Format
hdRotatedFormat
=
FORMAT_H264_HD
.
buildUpon
().
setRotationDegrees
(
90
).
build
();
assertThat
(
codecInfo
.
canKeepCodec
(
FORMAT_H264_HD
,
hdRotatedFormat
))
.
isEqualTo
(
KEEP_CODEC_RESULT_NO
);
assertThat
(
codecInfo
.
canReuseCodec
(
FORMAT_H264_HD
,
hdRotatedFormat
))
.
isEqualTo
(
new
DecoderReuseEvaluation
(
codecInfo
.
name
,
FORMAT_H264_HD
,
hdRotatedFormat
,
REUSE_RESULT_NO
,
DISCARD_REASON_VIDEO_ROTATION_CHANGED
));
}
@Test
public
void
canKeepCodec_withResolutionChange_adaptiveCodec_returnsYesWithReconfiguration
()
{
MediaCodecInfo
codecInfo
=
buildH264CodecInfo
(
/* adaptive= */
true
);
assertThat
(
codecInfo
.
canKeepCodec
(
FORMAT_H264_HD
,
FORMAT_H264_4K
))
.
isEqualTo
(
KEEP_CODEC_RESULT_YES_WITH_RECONFIGURATION
);
assertThat
(
codecInfo
.
canReuseCodec
(
FORMAT_H264_HD
,
FORMAT_H264_4K
))
.
isEqualTo
(
new
DecoderReuseEvaluation
(
codecInfo
.
name
,
FORMAT_H264_HD
,
FORMAT_H264_4K
,
REUSE_RESULT_YES_WITH_RECONFIGURATION
,
/* discardReasons= */
0
));
}
@Test
public
void
canKeepCodec_withResolutionChange_nonAdaptiveCodec_returnsNo
()
{
MediaCodecInfo
codecInfo
=
buildH264CodecInfo
(
/* adaptive= */
false
);
assertThat
(
codecInfo
.
canKeepCodec
(
FORMAT_H264_HD
,
FORMAT_H264_4K
))
.
isEqualTo
(
KEEP_CODEC_RESULT_NO
);
assertThat
(
codecInfo
.
canReuseCodec
(
FORMAT_H264_HD
,
FORMAT_H264_4K
))
.
isEqualTo
(
new
DecoderReuseEvaluation
(
codecInfo
.
name
,
FORMAT_H264_HD
,
FORMAT_H264_4K
,
REUSE_RESULT_NO
,
DISCARD_REASON_VIDEO_RESOLUTION_CHANGED
));
}
@Test
...
...
@@ -105,8 +137,14 @@ public final class MediaCodecInfoTest {
Format
hdVariantFormat
=
FORMAT_H264_HD
.
buildUpon
().
setInitializationData
(
ImmutableList
.
of
(
new
byte
[]
{
0
})).
build
();
assertThat
(
codecInfo
.
canKeepCodec
(
FORMAT_H264_HD
,
hdVariantFormat
))
.
isEqualTo
(
KEEP_CODEC_RESULT_YES_WITH_RECONFIGURATION
);
assertThat
(
codecInfo
.
canReuseCodec
(
FORMAT_H264_HD
,
hdVariantFormat
))
.
isEqualTo
(
new
DecoderReuseEvaluation
(
codecInfo
.
name
,
FORMAT_H264_HD
,
hdVariantFormat
,
REUSE_RESULT_YES_WITH_RECONFIGURATION
,
/* discardReasons= */
0
));
}
@Test
...
...
@@ -115,8 +153,14 @@ public final class MediaCodecInfoTest {
Format
hdrVariantFormat
=
FORMAT_H264_4K
.
buildUpon
().
setColorInfo
(
buildColorInfo
(
C
.
COLOR_SPACE_BT601
)).
build
();
assertThat
(
codecInfo
.
canKeepCodec
(
hdrVariantFormat
,
FORMAT_H264_4K
))
.
isEqualTo
(
KEEP_CODEC_RESULT_NO
);
assertThat
(
codecInfo
.
canReuseCodec
(
hdrVariantFormat
,
FORMAT_H264_4K
))
.
isEqualTo
(
new
DecoderReuseEvaluation
(
codecInfo
.
name
,
hdrVariantFormat
,
FORMAT_H264_4K
,
REUSE_RESULT_NO
,
DISCARD_REASON_VIDEO_COLOR_INFO_CHANGED
));
}
@Test
...
...
@@ -125,8 +169,14 @@ public final class MediaCodecInfoTest {
Format
hdrVariantFormat
=
FORMAT_H264_4K
.
buildUpon
().
setColorInfo
(
buildColorInfo
(
C
.
COLOR_SPACE_BT601
)).
build
();
assertThat
(
codecInfo
.
canKeepCodec
(
FORMAT_H264_4K
,
hdrVariantFormat
))
.
isEqualTo
(
KEEP_CODEC_RESULT_NO
);
assertThat
(
codecInfo
.
canReuseCodec
(
FORMAT_H264_4K
,
hdrVariantFormat
))
.
isEqualTo
(
new
DecoderReuseEvaluation
(
codecInfo
.
name
,
FORMAT_H264_4K
,
hdrVariantFormat
,
REUSE_RESULT_NO
,
DISCARD_REASON_VIDEO_COLOR_INFO_CHANGED
));
}
@Test
...
...
@@ -137,18 +187,28 @@ public final class MediaCodecInfoTest {
FORMAT_H264_4K
.
buildUpon
().
setColorInfo
(
buildColorInfo
(
C
.
COLOR_SPACE_BT601
)).
build
();
Format
hdrVariantFormat2
=
FORMAT_H264_4K
.
buildUpon
().
setColorInfo
(
buildColorInfo
(
C
.
COLOR_SPACE_BT709
)).
build
();
assertThat
(
codecInfo
.
canKeepCodec
(
hdrVariantFormat1
,
hdrVariantFormat2
))
.
isEqualTo
(
KEEP_CODEC_RESULT_NO
);
assertThat
(
codecInfo
.
canKeepCodec
(
hdrVariantFormat1
,
hdrVariantFormat2
))
.
isEqualTo
(
KEEP_CODEC_RESULT_NO
);
assertThat
(
codecInfo
.
canReuseCodec
(
hdrVariantFormat1
,
hdrVariantFormat2
))
.
isEqualTo
(
new
DecoderReuseEvaluation
(
codecInfo
.
name
,
hdrVariantFormat1
,
hdrVariantFormat2
,
REUSE_RESULT_NO
,
DISCARD_REASON_VIDEO_COLOR_INFO_CHANGED
));
}
@Test
public
void
canKeepCodec_audioWithDifferentChannelCounts_returnsNo
()
{
MediaCodecInfo
codecInfo
=
buildAacCodecInfo
();
assertThat
(
codecInfo
.
canKeepCodec
(
FORMAT_AAC_STEREO
,
FORMAT_AAC_SURROUND
))
.
isEqualTo
(
KEEP_CODEC_RESULT_NO
);
assertThat
(
codecInfo
.
canReuseCodec
(
FORMAT_AAC_STEREO
,
FORMAT_AAC_SURROUND
))
.
isEqualTo
(
new
DecoderReuseEvaluation
(
codecInfo
.
name
,
FORMAT_AAC_STEREO
,
FORMAT_AAC_SURROUND
,
REUSE_RESULT_NO
,
DISCARD_REASON_AUDIO_CHANNEL_COUNT_CHANGED
));
}
@Test
...
...
@@ -156,8 +216,14 @@ public final class MediaCodecInfoTest {
MediaCodecInfo
codecInfo
=
buildAacCodecInfo
();
Format
stereoVariantFormat
=
FORMAT_AAC_STEREO
.
buildUpon
().
setAverageBitrate
(
100
).
build
();
assertThat
(
codecInfo
.
canKeepCodec
(
FORMAT_AAC_STEREO
,
stereoVariantFormat
))
.
isEqualTo
(
KEEP_CODEC_RESULT_YES_WITH_FLUSH
);
assertThat
(
codecInfo
.
canReuseCodec
(
FORMAT_AAC_STEREO
,
stereoVariantFormat
))
.
isEqualTo
(
new
DecoderReuseEvaluation
(
codecInfo
.
name
,
FORMAT_AAC_STEREO
,
stereoVariantFormat
,
REUSE_RESULT_YES_WITH_FLUSH
,
/* discardReasons= */
0
));
}
@Test
...
...
@@ -169,8 +235,14 @@ public final class MediaCodecInfoTest {
.
buildUpon
()
.
setInitializationData
(
ImmutableList
.
of
(
new
byte
[]
{
0
}))
.
build
();
assertThat
(
codecInfo
.
canKeepCodec
(
FORMAT_AAC_STEREO
,
stereoVariantFormat
))
.
isEqualTo
(
KEEP_CODEC_RESULT_NO
);
assertThat
(
codecInfo
.
canReuseCodec
(
FORMAT_AAC_STEREO
,
stereoVariantFormat
))
.
isEqualTo
(
new
DecoderReuseEvaluation
(
codecInfo
.
name
,
FORMAT_AAC_STEREO
,
stereoVariantFormat
,
REUSE_RESULT_NO
,
DISCARD_REASON_INITIALIZATION_DATA_CHANGED
));
}
@Test
...
...
playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DebugRenderersFactory.java
View file @
c47e6220
...
...
@@ -30,6 +30,7 @@ import com.google.android.exoplayer2.Format;
import
com.google.android.exoplayer2.FormatHolder
;
import
com.google.android.exoplayer2.Renderer
;
import
com.google.android.exoplayer2.decoder.DecoderInputBuffer
;
import
com.google.android.exoplayer2.decoder.DecoderReuseEvaluation
;
import
com.google.android.exoplayer2.mediacodec.MediaCodecAdapter
;
import
com.google.android.exoplayer2.mediacodec.MediaCodecInfo
;
import
com.google.android.exoplayer2.mediacodec.MediaCodecSelector
;
...
...
@@ -165,12 +166,15 @@ import java.util.ArrayList;
}
@Override
protected
void
onInputFormatChanged
(
FormatHolder
formatHolder
)
throws
ExoPlaybackException
{
super
.
onInputFormatChanged
(
formatHolder
);
@Nullable
protected
DecoderReuseEvaluation
onInputFormatChanged
(
FormatHolder
formatHolder
)
throws
ExoPlaybackException
{
@Nullable
DecoderReuseEvaluation
evaluation
=
super
.
onInputFormatChanged
(
formatHolder
);
// Ensure timestamps of buffers queued after this format change are never inserted into the
// queue of expected output timestamps before those of buffers that have already been queued.
minimumInsertIndex
=
startIndex
+
queueSize
;
inputFormatChanged
=
true
;
return
evaluation
;
}
@Override
...
...
testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAudioRenderer.java
View file @
c47e6220
...
...
@@ -55,7 +55,7 @@ public class FakeAudioRenderer extends FakeRenderer {
@Override
protected
void
onFormatChanged
(
Format
format
)
{
eventDispatcher
.
inputFormatChanged
(
format
);
eventDispatcher
.
inputFormatChanged
(
format
,
/* decoderReuseEvaluation= */
null
);
eventDispatcher
.
decoderInitialized
(
/* decoderName= */
"fake.audio.decoder"
,
/* initializedTimestampMs= */
SystemClock
.
elapsedRealtime
(),
...
...
testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeVideoRenderer.java
View file @
c47e6220
...
...
@@ -85,7 +85,7 @@ public class FakeVideoRenderer extends FakeRenderer {
@Override
protected
void
onFormatChanged
(
Format
format
)
{
eventDispatcher
.
inputFormatChanged
(
format
);
eventDispatcher
.
inputFormatChanged
(
format
,
/* decoderReuseEvaluation= */
null
);
eventDispatcher
.
decoderInitialized
(
/* decoderName= */
"fake.video.decoder"
,
/* initializedTimestampMs= */
SystemClock
.
elapsedRealtime
(),
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment