mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
Simplify output format propagation
PiperOrigin-RevId: 324805335
This commit is contained in:
parent
4d03d30890
commit
71fd335bcd
11 changed files with 139 additions and 230 deletions
|
|
@ -2188,11 +2188,9 @@ public class SimpleExoPlayer extends BasePlayer
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onVideoFrameProcessingOffset(
|
public void onVideoFrameProcessingOffset(long totalProcessingOffsetUs, int frameCount) {
|
||||||
long totalProcessingOffsetUs, int frameCount, Format format) {
|
|
||||||
for (VideoRendererEventListener videoDebugListener : videoDebugListeners) {
|
for (VideoRendererEventListener videoDebugListener : videoDebugListeners) {
|
||||||
videoDebugListener.onVideoFrameProcessingOffset(
|
videoDebugListener.onVideoFrameProcessingOffset(totalProcessingOffsetUs, frameCount);
|
||||||
totalProcessingOffsetUs, frameCount, format);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -319,11 +319,10 @@ public class AnalyticsCollector
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void onVideoFrameProcessingOffset(
|
public final void onVideoFrameProcessingOffset(long totalProcessingOffsetUs, int frameCount) {
|
||||||
long totalProcessingOffsetUs, int frameCount, Format format) {
|
|
||||||
EventTime eventTime = generatePlayingMediaPeriodEventTime();
|
EventTime eventTime = generatePlayingMediaPeriodEventTime();
|
||||||
for (AnalyticsListener listener : listeners) {
|
for (AnalyticsListener listener : listeners) {
|
||||||
listener.onVideoFrameProcessingOffset(eventTime, totalProcessingOffsetUs, frameCount, format);
|
listener.onVideoFrameProcessingOffset(eventTime, totalProcessingOffsetUs, frameCount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -591,10 +591,9 @@ public interface AnalyticsListener {
|
||||||
* @param totalProcessingOffsetUs The sum of the video frame processing offsets for frames
|
* @param totalProcessingOffsetUs The sum of the video frame processing offsets for frames
|
||||||
* rendered since the last call to this method.
|
* rendered since the last call to this method.
|
||||||
* @param frameCount The number to samples included in {@code totalProcessingOffsetUs}.
|
* @param frameCount The number to samples included in {@code totalProcessingOffsetUs}.
|
||||||
* @param format The video {@link Format} being rendered.
|
|
||||||
*/
|
*/
|
||||||
default void onVideoFrameProcessingOffset(
|
default void onVideoFrameProcessingOffset(
|
||||||
EventTime eventTime, long totalProcessingOffsetUs, int frameCount, Format format) {}
|
EventTime eventTime, long totalProcessingOffsetUs, int frameCount) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when a frame is rendered for the first time since setting the surface, or since the
|
* Called when a frame is rendered for the first time since setting the surface, or since the
|
||||||
|
|
|
||||||
|
|
@ -91,7 +91,6 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
|
||||||
private boolean codecNeedsDiscardChannelsWorkaround;
|
private boolean codecNeedsDiscardChannelsWorkaround;
|
||||||
private boolean codecNeedsEosBufferTimestampWorkaround;
|
private boolean codecNeedsEosBufferTimestampWorkaround;
|
||||||
@Nullable private Format codecPassthroughFormat;
|
@Nullable private Format codecPassthroughFormat;
|
||||||
@Nullable private Format inputFormat;
|
|
||||||
private long currentPositionUs;
|
private long currentPositionUs;
|
||||||
private boolean allowFirstBufferPositionDiscontinuity;
|
private boolean allowFirstBufferPositionDiscontinuity;
|
||||||
private boolean allowPositionDiscontinuity;
|
private boolean allowPositionDiscontinuity;
|
||||||
|
|
@ -379,29 +378,23 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
|
||||||
@Override
|
@Override
|
||||||
protected void onInputFormatChanged(FormatHolder formatHolder) throws ExoPlaybackException {
|
protected void onInputFormatChanged(FormatHolder formatHolder) throws ExoPlaybackException {
|
||||||
super.onInputFormatChanged(formatHolder);
|
super.onInputFormatChanged(formatHolder);
|
||||||
inputFormat = formatHolder.format;
|
eventDispatcher.inputFormatChanged(formatHolder.format);
|
||||||
eventDispatcher.inputFormatChanged(inputFormat);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onOutputFormatChanged(Format outputFormat) throws ExoPlaybackException {
|
protected void onOutputFormatChanged(Format format, @Nullable MediaFormat mediaFormat)
|
||||||
configureOutput(outputFormat);
|
throws ExoPlaybackException {
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void configureOutput(Format outputFormat) throws ExoPlaybackException {
|
|
||||||
Format audioSinkInputFormat;
|
Format audioSinkInputFormat;
|
||||||
@Nullable int[] channelMap = null;
|
@Nullable int[] channelMap = null;
|
||||||
if (codecPassthroughFormat != null) { // Raw codec passthrough
|
if (codecPassthroughFormat != null) { // Raw codec passthrough
|
||||||
audioSinkInputFormat = codecPassthroughFormat;
|
audioSinkInputFormat = codecPassthroughFormat;
|
||||||
} else if (getCodec() == null) { // Codec bypass passthrough
|
} else if (getCodec() == null) { // Codec bypass passthrough
|
||||||
audioSinkInputFormat = outputFormat;
|
audioSinkInputFormat = format;
|
||||||
} else {
|
} else {
|
||||||
MediaFormat mediaFormat = getCodec().getOutputFormat();
|
|
||||||
@C.PcmEncoding int pcmEncoding;
|
@C.PcmEncoding int pcmEncoding;
|
||||||
if (MimeTypes.AUDIO_RAW.equals(outputFormat.sampleMimeType)) {
|
if (MimeTypes.AUDIO_RAW.equals(format.sampleMimeType)) {
|
||||||
// For PCM streams, the encoder passes through int samples despite set to float mode.
|
// For PCM streams, the encoder passes through int samples despite set to float mode.
|
||||||
pcmEncoding = outputFormat.pcmEncoding;
|
pcmEncoding = format.pcmEncoding;
|
||||||
} else if (Util.SDK_INT >= 24 && mediaFormat.containsKey(MediaFormat.KEY_PCM_ENCODING)) {
|
} else if (Util.SDK_INT >= 24 && mediaFormat.containsKey(MediaFormat.KEY_PCM_ENCODING)) {
|
||||||
pcmEncoding = mediaFormat.getInteger(MediaFormat.KEY_PCM_ENCODING);
|
pcmEncoding = mediaFormat.getInteger(MediaFormat.KEY_PCM_ENCODING);
|
||||||
} else if (mediaFormat.containsKey(VIVO_BITS_PER_SAMPLE_KEY)) {
|
} else if (mediaFormat.containsKey(VIVO_BITS_PER_SAMPLE_KEY)) {
|
||||||
|
|
@ -409,22 +402,25 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
|
||||||
} else {
|
} else {
|
||||||
// If the format is anything other than PCM then we assume that the audio decoder will
|
// If the format is anything other than PCM then we assume that the audio decoder will
|
||||||
// output 16-bit PCM.
|
// output 16-bit PCM.
|
||||||
pcmEncoding = C.ENCODING_PCM_16BIT;
|
pcmEncoding =
|
||||||
|
MimeTypes.AUDIO_RAW.equals(format.sampleMimeType)
|
||||||
|
? format.pcmEncoding
|
||||||
|
: C.ENCODING_PCM_16BIT;
|
||||||
}
|
}
|
||||||
audioSinkInputFormat =
|
audioSinkInputFormat =
|
||||||
new Format.Builder()
|
new Format.Builder()
|
||||||
.setSampleMimeType(MimeTypes.AUDIO_RAW)
|
.setSampleMimeType(MimeTypes.AUDIO_RAW)
|
||||||
.setPcmEncoding(pcmEncoding)
|
.setPcmEncoding(pcmEncoding)
|
||||||
.setEncoderDelay(outputFormat.encoderDelay)
|
.setEncoderDelay(format.encoderDelay)
|
||||||
.setEncoderPadding(outputFormat.encoderPadding)
|
.setEncoderPadding(format.encoderPadding)
|
||||||
.setChannelCount(mediaFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT))
|
.setChannelCount(mediaFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT))
|
||||||
.setSampleRate(mediaFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE))
|
.setSampleRate(mediaFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE))
|
||||||
.build();
|
.build();
|
||||||
if (codecNeedsDiscardChannelsWorkaround
|
if (codecNeedsDiscardChannelsWorkaround
|
||||||
&& audioSinkInputFormat.channelCount == 6
|
&& audioSinkInputFormat.channelCount == 6
|
||||||
&& outputFormat.channelCount < 6) {
|
&& format.channelCount < 6) {
|
||||||
channelMap = new int[outputFormat.channelCount];
|
channelMap = new int[format.channelCount];
|
||||||
for (int i = 0; i < outputFormat.channelCount; i++) {
|
for (int i = 0; i < format.channelCount; i++) {
|
||||||
channelMap[i] = i;
|
channelMap[i] = i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -432,7 +428,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
|
||||||
try {
|
try {
|
||||||
audioSink.configure(audioSinkInputFormat, /* specifiedBufferSize= */ 0, channelMap);
|
audioSink.configure(audioSinkInputFormat, /* specifiedBufferSize= */ 0, channelMap);
|
||||||
} catch (AudioSink.ConfigurationException e) {
|
} catch (AudioSink.ConfigurationException e) {
|
||||||
throw createRendererException(e, outputFormat);
|
throw createRendererException(e, format);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -621,8 +617,8 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
|
||||||
try {
|
try {
|
||||||
audioSink.playToEndOfStream();
|
audioSink.playToEndOfStream();
|
||||||
} catch (AudioSink.WriteException e) {
|
} catch (AudioSink.WriteException e) {
|
||||||
Format outputFormat = getCurrentOutputFormat();
|
@Nullable Format outputFormat = getOutputFormat();
|
||||||
throw createRendererException(e, outputFormat != null ? outputFormat : inputFormat);
|
throw createRendererException(e, outputFormat != null ? outputFormat : getInputFormat());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -364,7 +364,9 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
||||||
private float operatingRate;
|
private float operatingRate;
|
||||||
@Nullable private MediaCodec codec;
|
@Nullable private MediaCodec codec;
|
||||||
@Nullable private MediaCodecAdapter codecAdapter;
|
@Nullable private MediaCodecAdapter codecAdapter;
|
||||||
@Nullable private Format codecFormat;
|
@Nullable private Format codecInputFormat;
|
||||||
|
@Nullable private MediaFormat codecOutputMediaFormat;
|
||||||
|
private boolean codecOutputMediaFormatChanged;
|
||||||
private float codecOperatingRate;
|
private float codecOperatingRate;
|
||||||
@Nullable private ArrayDeque<MediaCodecInfo> availableCodecInfos;
|
@Nullable private ArrayDeque<MediaCodecInfo> availableCodecInfos;
|
||||||
@Nullable private DecoderInitializationException preferredDecoderInitializationException;
|
@Nullable private DecoderInitializationException preferredDecoderInitializationException;
|
||||||
|
|
@ -409,7 +411,6 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
||||||
protected DecoderCounters decoderCounters;
|
protected DecoderCounters decoderCounters;
|
||||||
private long outputStreamOffsetUs;
|
private long outputStreamOffsetUs;
|
||||||
private int pendingOutputStreamOffsetCount;
|
private int pendingOutputStreamOffsetCount;
|
||||||
private boolean receivedOutputMediaFormatChange;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param trackType The track type that the renderer handles. One of the {@code C.TRACK_TYPE_*}
|
* @param trackType The track type that the renderer handles. One of the {@code C.TRACK_TYPE_*}
|
||||||
|
|
@ -613,35 +614,40 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
||||||
*
|
*
|
||||||
* @param exception The exception.
|
* @param exception The exception.
|
||||||
*/
|
*/
|
||||||
protected void setPendingPlaybackException(ExoPlaybackException exception) {
|
protected final void setPendingPlaybackException(ExoPlaybackException exception) {
|
||||||
pendingPlaybackException = exception;
|
pendingPlaybackException = exception;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Polls the pending output format queue for a given buffer timestamp. If a format is present, it
|
* Updates the output formats for the specified output buffer timestamp, calling {@link
|
||||||
* is removed and returned. Otherwise returns {@code null}. Subclasses should only call this
|
* #onOutputFormatChanged} if a change has occurred.
|
||||||
* method if they are taking over responsibility for output format propagation (e.g., when using
|
*
|
||||||
* video tunneling).
|
* <p>Subclasses should only call this method if operating in a mode where buffers are not
|
||||||
|
* dequeued from the decoder, for example when using video tunneling).
|
||||||
*
|
*
|
||||||
* @throws ExoPlaybackException Thrown if an error occurs as a result of the output format change.
|
* @throws ExoPlaybackException Thrown if an error occurs as a result of the output format change.
|
||||||
*/
|
*/
|
||||||
protected final void updateOutputFormatForTime(long presentationTimeUs)
|
protected final void updateOutputFormatForTime(long presentationTimeUs)
|
||||||
throws ExoPlaybackException {
|
throws ExoPlaybackException {
|
||||||
|
boolean outputFormatChanged = false;
|
||||||
@Nullable Format format = formatQueue.pollFloor(presentationTimeUs);
|
@Nullable Format format = formatQueue.pollFloor(presentationTimeUs);
|
||||||
if (format != null) {
|
if (format != null) {
|
||||||
outputFormat = format;
|
outputFormat = format;
|
||||||
onOutputFormatChanged(outputFormat);
|
outputFormatChanged = true;
|
||||||
} else if (receivedOutputMediaFormatChange && outputFormat != null) {
|
}
|
||||||
// No Format change with the MediaFormat change, so we need to update based on the existing
|
if (outputFormatChanged || (codecOutputMediaFormatChanged && outputFormat != null)) {
|
||||||
// Format.
|
onOutputFormatChanged(outputFormat, codecOutputMediaFormat);
|
||||||
configureOutput(outputFormat);
|
codecOutputMediaFormatChanged = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
receivedOutputMediaFormatChange = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
protected final Format getCurrentOutputFormat() {
|
protected Format getInputFormat() {
|
||||||
|
return inputFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
protected final Format getOutputFormat() {
|
||||||
return outputFormat;
|
return outputFormat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -650,6 +656,11 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
||||||
return codec;
|
return codec;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
protected final MediaFormat getCodecOutputMediaFormat() {
|
||||||
|
return codecOutputMediaFormat;
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
protected final MediaCodecInfo getCodecInfo() {
|
protected final MediaCodecInfo getCodecInfo() {
|
||||||
return codecInfo;
|
return codecInfo;
|
||||||
|
|
@ -905,11 +916,14 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
||||||
protected void resetCodecStateForRelease() {
|
protected void resetCodecStateForRelease() {
|
||||||
resetCodecStateForFlush();
|
resetCodecStateForFlush();
|
||||||
|
|
||||||
|
pendingPlaybackException = null;
|
||||||
|
c2Mp3TimestampTracker = null;
|
||||||
availableCodecInfos = null;
|
availableCodecInfos = null;
|
||||||
codecInfo = null;
|
codecInfo = null;
|
||||||
codecFormat = null;
|
codecInputFormat = null;
|
||||||
|
codecOutputMediaFormat = null;
|
||||||
|
codecOutputMediaFormatChanged = false;
|
||||||
codecHasOutputMediaFormat = false;
|
codecHasOutputMediaFormat = false;
|
||||||
pendingPlaybackException = null;
|
|
||||||
codecOperatingRate = CODEC_OPERATING_RATE_UNSET;
|
codecOperatingRate = CODEC_OPERATING_RATE_UNSET;
|
||||||
codecAdaptationWorkaroundMode = ADAPTATION_WORKAROUND_MODE_NEVER;
|
codecAdaptationWorkaroundMode = ADAPTATION_WORKAROUND_MODE_NEVER;
|
||||||
codecNeedsReconfigureWorkaround = false;
|
codecNeedsReconfigureWorkaround = false;
|
||||||
|
|
@ -920,7 +934,6 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
||||||
codecNeedsEosOutputExceptionWorkaround = false;
|
codecNeedsEosOutputExceptionWorkaround = false;
|
||||||
codecNeedsMonoChannelCountWorkaround = false;
|
codecNeedsMonoChannelCountWorkaround = false;
|
||||||
codecNeedsEosPropagation = false;
|
codecNeedsEosPropagation = false;
|
||||||
c2Mp3TimestampTracker = null;
|
|
||||||
codecReconfigured = false;
|
codecReconfigured = false;
|
||||||
codecReconfigurationState = RECONFIGURATION_STATE_NONE;
|
codecReconfigurationState = RECONFIGURATION_STATE_NONE;
|
||||||
resetCodecBuffers();
|
resetCodecBuffers();
|
||||||
|
|
@ -1110,16 +1123,17 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
||||||
this.codecAdapter = codecAdapter;
|
this.codecAdapter = codecAdapter;
|
||||||
this.codecInfo = codecInfo;
|
this.codecInfo = codecInfo;
|
||||||
this.codecOperatingRate = codecOperatingRate;
|
this.codecOperatingRate = codecOperatingRate;
|
||||||
codecFormat = inputFormat;
|
codecInputFormat = inputFormat;
|
||||||
codecAdaptationWorkaroundMode = codecAdaptationWorkaroundMode(codecName);
|
codecAdaptationWorkaroundMode = codecAdaptationWorkaroundMode(codecName);
|
||||||
codecNeedsReconfigureWorkaround = codecNeedsReconfigureWorkaround(codecName);
|
codecNeedsReconfigureWorkaround = codecNeedsReconfigureWorkaround(codecName);
|
||||||
codecNeedsDiscardToSpsWorkaround = codecNeedsDiscardToSpsWorkaround(codecName, codecFormat);
|
codecNeedsDiscardToSpsWorkaround =
|
||||||
|
codecNeedsDiscardToSpsWorkaround(codecName, codecInputFormat);
|
||||||
codecNeedsFlushWorkaround = codecNeedsFlushWorkaround(codecName);
|
codecNeedsFlushWorkaround = codecNeedsFlushWorkaround(codecName);
|
||||||
codecNeedsSosFlushWorkaround = codecNeedsSosFlushWorkaround(codecName);
|
codecNeedsSosFlushWorkaround = codecNeedsSosFlushWorkaround(codecName);
|
||||||
codecNeedsEosFlushWorkaround = codecNeedsEosFlushWorkaround(codecName);
|
codecNeedsEosFlushWorkaround = codecNeedsEosFlushWorkaround(codecName);
|
||||||
codecNeedsEosOutputExceptionWorkaround = codecNeedsEosOutputExceptionWorkaround(codecName);
|
codecNeedsEosOutputExceptionWorkaround = codecNeedsEosOutputExceptionWorkaround(codecName);
|
||||||
codecNeedsMonoChannelCountWorkaround =
|
codecNeedsMonoChannelCountWorkaround =
|
||||||
codecNeedsMonoChannelCountWorkaround(codecName, codecFormat);
|
codecNeedsMonoChannelCountWorkaround(codecName, codecInputFormat);
|
||||||
codecNeedsEosPropagation =
|
codecNeedsEosPropagation =
|
||||||
codecNeedsEosPropagationWorkaround(codecInfo) || getCodecNeedsEosPropagation();
|
codecNeedsEosPropagationWorkaround(codecInfo) || getCodecNeedsEosPropagation();
|
||||||
if ("c2.android.mp3.decoder".equals(codecInfo.name)) {
|
if ("c2.android.mp3.decoder".equals(codecInfo.name)) {
|
||||||
|
|
@ -1234,8 +1248,8 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
||||||
// For adaptive reconfiguration, decoders expect all reconfiguration data to be supplied at
|
// For adaptive reconfiguration, decoders expect all reconfiguration data to be supplied at
|
||||||
// the start of the buffer that also contains the first frame in the new format.
|
// the start of the buffer that also contains the first frame in the new format.
|
||||||
if (codecReconfigurationState == RECONFIGURATION_STATE_WRITE_PENDING) {
|
if (codecReconfigurationState == RECONFIGURATION_STATE_WRITE_PENDING) {
|
||||||
for (int i = 0; i < codecFormat.initializationData.size(); i++) {
|
for (int i = 0; i < codecInputFormat.initializationData.size(); i++) {
|
||||||
byte[] data = codecFormat.initializationData.get(i);
|
byte[] data = codecInputFormat.initializationData.get(i);
|
||||||
buffer.data.put(data);
|
buffer.data.put(data);
|
||||||
}
|
}
|
||||||
codecReconfigurationState = RECONFIGURATION_STATE_QUEUE_PENDING;
|
codecReconfigurationState = RECONFIGURATION_STATE_QUEUE_PENDING;
|
||||||
|
|
@ -1270,7 +1284,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
||||||
if (codecReconfigurationState == RECONFIGURATION_STATE_QUEUE_PENDING) {
|
if (codecReconfigurationState == RECONFIGURATION_STATE_QUEUE_PENDING) {
|
||||||
// We received a new format immediately before the end of the stream. We need to clear
|
// We received a new format immediately before the end of the stream. We need to clear
|
||||||
// the corresponding reconfiguration data from the current buffer, but re-write it into
|
// the corresponding reconfiguration data from the current buffer, but re-write it into
|
||||||
// a subsequent buffer if there are any (e.g. if the user seeks backwards).
|
// a subsequent buffer if there are any (for example, if the user seeks backwards).
|
||||||
buffer.clear();
|
buffer.clear();
|
||||||
codecReconfigurationState = RECONFIGURATION_STATE_WRITE_PENDING;
|
codecReconfigurationState = RECONFIGURATION_STATE_WRITE_PENDING;
|
||||||
}
|
}
|
||||||
|
|
@ -1393,6 +1407,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
||||||
* @param formatHolder A {@link FormatHolder} that holds the new {@link Format}.
|
* @param formatHolder A {@link FormatHolder} that holds the new {@link Format}.
|
||||||
* @throws ExoPlaybackException If an error occurs re-initializing the {@link MediaCodec}.
|
* @throws ExoPlaybackException If an error occurs re-initializing the {@link MediaCodec}.
|
||||||
*/
|
*/
|
||||||
|
@CallSuper
|
||||||
protected void onInputFormatChanged(FormatHolder formatHolder) throws ExoPlaybackException {
|
protected void onInputFormatChanged(FormatHolder formatHolder) throws ExoPlaybackException {
|
||||||
waitingForFirstSampleInFormat = true;
|
waitingForFirstSampleInFormat = true;
|
||||||
Format newFormat = Assertions.checkNotNull(formatHolder.format);
|
Format newFormat = Assertions.checkNotNull(formatHolder.format);
|
||||||
|
|
@ -1426,12 +1441,12 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (canKeepCodec(codec, codecInfo, codecFormat, newFormat)) {
|
switch (canKeepCodec(codec, codecInfo, codecInputFormat, newFormat)) {
|
||||||
case KEEP_CODEC_RESULT_NO:
|
case KEEP_CODEC_RESULT_NO:
|
||||||
drainAndReinitializeCodec();
|
drainAndReinitializeCodec();
|
||||||
break;
|
break;
|
||||||
case KEEP_CODEC_RESULT_YES_WITH_FLUSH:
|
case KEEP_CODEC_RESULT_YES_WITH_FLUSH:
|
||||||
codecFormat = newFormat;
|
codecInputFormat = newFormat;
|
||||||
updateCodecOperatingRate();
|
updateCodecOperatingRate();
|
||||||
if (sourceDrmSession != codecDrmSession) {
|
if (sourceDrmSession != codecDrmSession) {
|
||||||
drainAndUpdateCodecDrmSession();
|
drainAndUpdateCodecDrmSession();
|
||||||
|
|
@ -1448,9 +1463,9 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
||||||
codecNeedsAdaptationWorkaroundBuffer =
|
codecNeedsAdaptationWorkaroundBuffer =
|
||||||
codecAdaptationWorkaroundMode == ADAPTATION_WORKAROUND_MODE_ALWAYS
|
codecAdaptationWorkaroundMode == ADAPTATION_WORKAROUND_MODE_ALWAYS
|
||||||
|| (codecAdaptationWorkaroundMode == ADAPTATION_WORKAROUND_MODE_SAME_RESOLUTION
|
|| (codecAdaptationWorkaroundMode == ADAPTATION_WORKAROUND_MODE_SAME_RESOLUTION
|
||||||
&& newFormat.width == codecFormat.width
|
&& newFormat.width == codecInputFormat.width
|
||||||
&& newFormat.height == codecFormat.height);
|
&& newFormat.height == codecInputFormat.height);
|
||||||
codecFormat = newFormat;
|
codecInputFormat = newFormat;
|
||||||
updateCodecOperatingRate();
|
updateCodecOperatingRate();
|
||||||
if (sourceDrmSession != codecDrmSession) {
|
if (sourceDrmSession != codecDrmSession) {
|
||||||
drainAndUpdateCodecDrmSession();
|
drainAndUpdateCodecDrmSession();
|
||||||
|
|
@ -1458,7 +1473,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case KEEP_CODEC_RESULT_YES_WITHOUT_RECONFIGURATION:
|
case KEEP_CODEC_RESULT_YES_WITHOUT_RECONFIGURATION:
|
||||||
codecFormat = newFormat;
|
codecInputFormat = newFormat;
|
||||||
updateCodecOperatingRate();
|
updateCodecOperatingRate();
|
||||||
if (sourceDrmSession != codecDrmSession) {
|
if (sourceDrmSession != codecDrmSession) {
|
||||||
drainAndUpdateCodecDrmSession();
|
drainAndUpdateCodecDrmSession();
|
||||||
|
|
@ -1470,40 +1485,18 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the output {@link MediaFormat} of the {@link MediaCodec} changes.
|
* Called when one of the output formats changes.
|
||||||
*
|
*
|
||||||
* <p>The default implementation is a no-op.
|
* <p>The default implementation is a no-op.
|
||||||
*
|
*
|
||||||
* @param codec The {@link MediaCodec} instance.
|
* @param format The input {@link Format} to which future output now corresponds. If the renderer
|
||||||
* @param outputMediaFormat The new output {@link MediaFormat}.
|
* is in bypass mode, this is also the output format.
|
||||||
* @throws ExoPlaybackException Thrown if an error occurs handling the new output media format.
|
* @param mediaFormat The codec output {@link MediaFormat}, or {@code null} if the renderer is in
|
||||||
*/
|
* bypass mode.
|
||||||
protected void onOutputMediaFormatChanged(MediaCodec codec, MediaFormat outputMediaFormat)
|
|
||||||
throws ExoPlaybackException {
|
|
||||||
// Do nothing.
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when the output {@link Format} changes.
|
|
||||||
*
|
|
||||||
* <p>The default implementation is a no-op.
|
|
||||||
*
|
|
||||||
* @param outputFormat The new output {@link Format}.
|
|
||||||
* @throws ExoPlaybackException Thrown if an error occurs handling the new output format.
|
|
||||||
*/
|
|
||||||
protected void onOutputFormatChanged(Format outputFormat) throws ExoPlaybackException {
|
|
||||||
// Do nothing.
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Configures the renderer output based on a {@link Format}.
|
|
||||||
*
|
|
||||||
* <p>The default implementation is a no-op.
|
|
||||||
*
|
|
||||||
* @param outputFormat The format to configure the output with.
|
|
||||||
* @throws ExoPlaybackException Thrown if an error occurs configuring the output.
|
* @throws ExoPlaybackException Thrown if an error occurs configuring the output.
|
||||||
*/
|
*/
|
||||||
protected void configureOutput(Format outputFormat) throws ExoPlaybackException {
|
protected void onOutputFormatChanged(Format format, @Nullable MediaFormat mediaFormat)
|
||||||
|
throws ExoPlaybackException {
|
||||||
// Do nothing.
|
// Do nothing.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1633,7 +1626,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
float newCodecOperatingRate =
|
float newCodecOperatingRate =
|
||||||
getCodecOperatingRateV23(operatingRate, codecFormat, getStreamFormats());
|
getCodecOperatingRateV23(operatingRate, codecInputFormat, getStreamFormats());
|
||||||
if (codecOperatingRate == newCodecOperatingRate) {
|
if (codecOperatingRate == newCodecOperatingRate) {
|
||||||
// No change.
|
// No change.
|
||||||
} else if (newCodecOperatingRate == CODEC_OPERATING_RATE_UNSET) {
|
} else if (newCodecOperatingRate == CODEC_OPERATING_RATE_UNSET) {
|
||||||
|
|
@ -1721,8 +1714,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
||||||
|
|
||||||
if (outputIndex < 0) {
|
if (outputIndex < 0) {
|
||||||
if (outputIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED /* (-2) */) {
|
if (outputIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED /* (-2) */) {
|
||||||
processOutputMediaFormat();
|
processOutputMediaFormatChanged();
|
||||||
receivedOutputMediaFormatChange = true;
|
|
||||||
return true;
|
return true;
|
||||||
} else if (outputIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED /* (-3) */) {
|
} else if (outputIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED /* (-3) */) {
|
||||||
processOutputBuffersChanged();
|
processOutputBuffersChanged();
|
||||||
|
|
@ -1750,6 +1742,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
||||||
|
|
||||||
this.outputIndex = outputIndex;
|
this.outputIndex = outputIndex;
|
||||||
outputBuffer = getOutputBuffer(outputIndex);
|
outputBuffer = getOutputBuffer(outputIndex);
|
||||||
|
|
||||||
// The dequeued buffer is a media buffer. Do some initial setup.
|
// The dequeued buffer is a media buffer. Do some initial setup.
|
||||||
// It will be processed by calling processOutputBuffer (possibly multiple times).
|
// It will be processed by calling processOutputBuffer (possibly multiple times).
|
||||||
if (outputBuffer != null) {
|
if (outputBuffer != null) {
|
||||||
|
|
@ -1815,8 +1808,8 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Processes a new output {@link MediaFormat}. */
|
/** Processes a change in the decoder output {@link MediaFormat}. */
|
||||||
private void processOutputMediaFormat() throws ExoPlaybackException {
|
private void processOutputMediaFormatChanged() {
|
||||||
codecHasOutputMediaFormat = true;
|
codecHasOutputMediaFormat = true;
|
||||||
MediaFormat mediaFormat = codecAdapter.getOutputFormat();
|
MediaFormat mediaFormat = codecAdapter.getOutputFormat();
|
||||||
if (codecAdaptationWorkaroundMode != ADAPTATION_WORKAROUND_MODE_NEVER
|
if (codecAdaptationWorkaroundMode != ADAPTATION_WORKAROUND_MODE_NEVER
|
||||||
|
|
@ -1830,7 +1823,8 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
||||||
if (codecNeedsMonoChannelCountWorkaround) {
|
if (codecNeedsMonoChannelCountWorkaround) {
|
||||||
mediaFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
|
mediaFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
|
||||||
}
|
}
|
||||||
onOutputMediaFormatChanged(codec, mediaFormat);
|
codecOutputMediaFormat = mediaFormat;
|
||||||
|
codecOutputMediaFormatChanged = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -1874,7 +1868,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
||||||
* by the source.
|
* by the source.
|
||||||
* @param isLastBuffer Whether the buffer is the last sample of the current stream.
|
* @param isLastBuffer Whether the buffer is the last sample of the current stream.
|
||||||
* @param format The {@link Format} associated with the buffer.
|
* @param format The {@link Format} associated with the buffer.
|
||||||
* @return Whether the output buffer was fully processed (e.g. rendered or skipped).
|
* @return Whether the output buffer was fully processed (for example, rendered or skipped).
|
||||||
* @throws ExoPlaybackException If an error occurs processing the output buffer.
|
* @throws ExoPlaybackException If an error occurs processing the output buffer.
|
||||||
*/
|
*/
|
||||||
protected abstract boolean processOutputBuffer(
|
protected abstract boolean processOutputBuffer(
|
||||||
|
|
@ -2121,7 +2115,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
||||||
if (!batchBuffer.isEmpty() && waitingForFirstSampleInFormat) {
|
if (!batchBuffer.isEmpty() && waitingForFirstSampleInFormat) {
|
||||||
// This is the first buffer in a new format, the output format must be updated.
|
// This is the first buffer in a new format, the output format must be updated.
|
||||||
outputFormat = Assertions.checkNotNull(inputFormat);
|
outputFormat = Assertions.checkNotNull(inputFormat);
|
||||||
onOutputFormatChanged(outputFormat);
|
onOutputFormatChanged(outputFormat, /* mediaFormat= */ null);
|
||||||
waitingForFirstSampleInFormat = false;
|
waitingForFirstSampleInFormat = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -153,9 +153,6 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||||
private long totalVideoFrameProcessingOffsetUs;
|
private long totalVideoFrameProcessingOffsetUs;
|
||||||
private int videoFrameProcessingOffsetCount;
|
private int videoFrameProcessingOffsetCount;
|
||||||
|
|
||||||
@Nullable private MediaFormat currentMediaFormat;
|
|
||||||
private int mediaFormatWidth;
|
|
||||||
private int mediaFormatHeight;
|
|
||||||
private int currentWidth;
|
private int currentWidth;
|
||||||
private int currentHeight;
|
private int currentHeight;
|
||||||
private int currentUnappliedRotationDegrees;
|
private int currentUnappliedRotationDegrees;
|
||||||
|
|
@ -262,8 +259,6 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||||
currentHeight = Format.NO_VALUE;
|
currentHeight = Format.NO_VALUE;
|
||||||
currentPixelWidthHeightRatio = Format.NO_VALUE;
|
currentPixelWidthHeightRatio = Format.NO_VALUE;
|
||||||
scalingMode = VIDEO_SCALING_MODE_DEFAULT;
|
scalingMode = VIDEO_SCALING_MODE_DEFAULT;
|
||||||
mediaFormatWidth = Format.NO_VALUE;
|
|
||||||
mediaFormatHeight = Format.NO_VALUE;
|
|
||||||
clearReportedVideoSize();
|
clearReportedVideoSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -449,7 +444,6 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onDisabled() {
|
protected void onDisabled() {
|
||||||
currentMediaFormat = null;
|
|
||||||
clearReportedVideoSize();
|
clearReportedVideoSize();
|
||||||
clearRenderedFirstFrame();
|
clearRenderedFirstFrame();
|
||||||
frameReleaseTimeHelper.disable();
|
frameReleaseTimeHelper.disable();
|
||||||
|
|
@ -668,51 +662,37 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onOutputMediaFormatChanged(MediaCodec codec, MediaFormat outputMediaFormat) {
|
protected void onOutputFormatChanged(Format format, @Nullable MediaFormat mediaFormat) {
|
||||||
currentMediaFormat = outputMediaFormat;
|
@Nullable MediaCodec codec = getCodec();
|
||||||
boolean hasCrop =
|
if (codec != null) {
|
||||||
outputMediaFormat.containsKey(KEY_CROP_RIGHT)
|
// Must be applied each time the output format changes.
|
||||||
&& outputMediaFormat.containsKey(KEY_CROP_LEFT)
|
|
||||||
&& outputMediaFormat.containsKey(KEY_CROP_BOTTOM)
|
|
||||||
&& outputMediaFormat.containsKey(KEY_CROP_TOP);
|
|
||||||
mediaFormatWidth =
|
|
||||||
hasCrop
|
|
||||||
? outputMediaFormat.getInteger(KEY_CROP_RIGHT)
|
|
||||||
- outputMediaFormat.getInteger(KEY_CROP_LEFT)
|
|
||||||
+ 1
|
|
||||||
: outputMediaFormat.getInteger(MediaFormat.KEY_WIDTH);
|
|
||||||
mediaFormatHeight =
|
|
||||||
hasCrop
|
|
||||||
? outputMediaFormat.getInteger(KEY_CROP_BOTTOM)
|
|
||||||
- outputMediaFormat.getInteger(KEY_CROP_TOP)
|
|
||||||
+ 1
|
|
||||||
: outputMediaFormat.getInteger(MediaFormat.KEY_HEIGHT);
|
|
||||||
|
|
||||||
// Must be applied each time the output MediaFormat changes.
|
|
||||||
codec.setVideoScalingMode(scalingMode);
|
codec.setVideoScalingMode(scalingMode);
|
||||||
maybeNotifyVideoFrameProcessingOffset();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onOutputFormatChanged(Format outputFormat) {
|
|
||||||
configureOutput(outputFormat);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void configureOutput(Format outputFormat) {
|
|
||||||
if (tunneling) {
|
if (tunneling) {
|
||||||
currentWidth = outputFormat.width;
|
currentWidth = format.width;
|
||||||
currentHeight = outputFormat.height;
|
currentHeight = format.height;
|
||||||
} else {
|
} else {
|
||||||
currentWidth = mediaFormatWidth;
|
Assertions.checkNotNull(mediaFormat);
|
||||||
currentHeight = mediaFormatHeight;
|
boolean hasCrop =
|
||||||
|
mediaFormat.containsKey(KEY_CROP_RIGHT)
|
||||||
|
&& mediaFormat.containsKey(KEY_CROP_LEFT)
|
||||||
|
&& mediaFormat.containsKey(KEY_CROP_BOTTOM)
|
||||||
|
&& mediaFormat.containsKey(KEY_CROP_TOP);
|
||||||
|
currentWidth =
|
||||||
|
hasCrop
|
||||||
|
? mediaFormat.getInteger(KEY_CROP_RIGHT) - mediaFormat.getInteger(KEY_CROP_LEFT) + 1
|
||||||
|
: mediaFormat.getInteger(MediaFormat.KEY_WIDTH);
|
||||||
|
currentHeight =
|
||||||
|
hasCrop
|
||||||
|
? mediaFormat.getInteger(KEY_CROP_BOTTOM) - mediaFormat.getInteger(KEY_CROP_TOP) + 1
|
||||||
|
: mediaFormat.getInteger(MediaFormat.KEY_HEIGHT);
|
||||||
}
|
}
|
||||||
currentPixelWidthHeightRatio = outputFormat.pixelWidthHeightRatio;
|
currentPixelWidthHeightRatio = format.pixelWidthHeightRatio;
|
||||||
if (Util.SDK_INT >= 21) {
|
if (Util.SDK_INT >= 21) {
|
||||||
// On API level 21 and above the decoder applies the rotation when rendering to the surface.
|
// On API level 21 and above the decoder applies the rotation when rendering to the surface.
|
||||||
// Hence currentUnappliedRotation should always be 0. For 90 and 270 degree rotations, we need
|
// Hence currentUnappliedRotation should always be 0. For 90 and 270 degree rotations, we need
|
||||||
// to flip the width, height and pixel aspect ratio to reflect the rotation that was applied.
|
// to flip the width, height and pixel aspect ratio to reflect the rotation that was applied.
|
||||||
if (outputFormat.rotationDegrees == 90 || outputFormat.rotationDegrees == 270) {
|
if (format.rotationDegrees == 90 || format.rotationDegrees == 270) {
|
||||||
int rotatedHeight = currentWidth;
|
int rotatedHeight = currentWidth;
|
||||||
currentWidth = currentHeight;
|
currentWidth = currentHeight;
|
||||||
currentHeight = rotatedHeight;
|
currentHeight = rotatedHeight;
|
||||||
|
|
@ -720,9 +700,9 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// On API level 20 and below the decoder does not apply the rotation.
|
// On API level 20 and below the decoder does not apply the rotation.
|
||||||
currentUnappliedRotationDegrees = outputFormat.rotationDegrees;
|
currentUnappliedRotationDegrees = format.rotationDegrees;
|
||||||
}
|
}
|
||||||
currentFrameRate = outputFormat.frameRate;
|
currentFrameRate = format.frameRate;
|
||||||
updateSurfaceFrameRate(/* isNewSurface= */ false);
|
updateSurfaceFrameRate(/* isNewSurface= */ false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -811,7 +791,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||||
|| (isStarted && shouldForceRenderOutputBuffer(earlyUs, elapsedSinceLastRenderUs)));
|
|| (isStarted && shouldForceRenderOutputBuffer(earlyUs, elapsedSinceLastRenderUs)));
|
||||||
if (forceRenderOutputBuffer) {
|
if (forceRenderOutputBuffer) {
|
||||||
long releaseTimeNs = System.nanoTime();
|
long releaseTimeNs = System.nanoTime();
|
||||||
notifyFrameMetadataListener(presentationTimeUs, releaseTimeNs, format, currentMediaFormat);
|
notifyFrameMetadataListener(presentationTimeUs, releaseTimeNs, format);
|
||||||
if (Util.SDK_INT >= 21) {
|
if (Util.SDK_INT >= 21) {
|
||||||
renderOutputBufferV21(codec, bufferIndex, presentationTimeUs, releaseTimeNs);
|
renderOutputBufferV21(codec, bufferIndex, presentationTimeUs, releaseTimeNs);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -857,8 +837,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||||
if (Util.SDK_INT >= 21) {
|
if (Util.SDK_INT >= 21) {
|
||||||
// Let the underlying framework time the release.
|
// Let the underlying framework time the release.
|
||||||
if (earlyUs < 50000) {
|
if (earlyUs < 50000) {
|
||||||
notifyFrameMetadataListener(
|
notifyFrameMetadataListener(presentationTimeUs, adjustedReleaseTimeNs, format);
|
||||||
presentationTimeUs, adjustedReleaseTimeNs, format, currentMediaFormat);
|
|
||||||
renderOutputBufferV21(codec, bufferIndex, presentationTimeUs, adjustedReleaseTimeNs);
|
renderOutputBufferV21(codec, bufferIndex, presentationTimeUs, adjustedReleaseTimeNs);
|
||||||
updateVideoFrameProcessingOffsetCounters(earlyUs);
|
updateVideoFrameProcessingOffsetCounters(earlyUs);
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -877,8 +856,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
notifyFrameMetadataListener(
|
notifyFrameMetadataListener(presentationTimeUs, adjustedReleaseTimeNs, format);
|
||||||
presentationTimeUs, adjustedReleaseTimeNs, format, currentMediaFormat);
|
|
||||||
renderOutputBuffer(codec, bufferIndex, presentationTimeUs);
|
renderOutputBuffer(codec, bufferIndex, presentationTimeUs);
|
||||||
updateVideoFrameProcessingOffsetCounters(earlyUs);
|
updateVideoFrameProcessingOffsetCounters(earlyUs);
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -890,10 +868,10 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void notifyFrameMetadataListener(
|
private void notifyFrameMetadataListener(
|
||||||
long presentationTimeUs, long releaseTimeNs, Format format, MediaFormat mediaFormat) {
|
long presentationTimeUs, long releaseTimeNs, Format format) {
|
||||||
if (frameMetadataListener != null) {
|
if (frameMetadataListener != null) {
|
||||||
frameMetadataListener.onVideoFrameAboutToBeRendered(
|
frameMetadataListener.onVideoFrameAboutToBeRendered(
|
||||||
presentationTimeUs, releaseTimeNs, format, mediaFormat);
|
presentationTimeUs, releaseTimeNs, format, getCodecOutputMediaFormat());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1230,10 +1208,9 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void maybeNotifyVideoFrameProcessingOffset() {
|
private void maybeNotifyVideoFrameProcessingOffset() {
|
||||||
@Nullable Format outputFormat = getCurrentOutputFormat();
|
if (videoFrameProcessingOffsetCount != 0) {
|
||||||
if (outputFormat != null && videoFrameProcessingOffsetCount != 0) {
|
|
||||||
eventDispatcher.reportVideoFrameProcessingOffset(
|
eventDispatcher.reportVideoFrameProcessingOffset(
|
||||||
totalVideoFrameProcessingOffsetUs, videoFrameProcessingOffsetCount, outputFormat);
|
totalVideoFrameProcessingOffsetUs, videoFrameProcessingOffsetCount);
|
||||||
totalVideoFrameProcessingOffsetUs = 0;
|
totalVideoFrameProcessingOffsetUs = 0;
|
||||||
videoFrameProcessingOffsetCount = 0;
|
videoFrameProcessingOffsetCount = 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -88,10 +88,8 @@ public interface VideoRendererEventListener {
|
||||||
* @param totalProcessingOffsetUs The sum of all video frame processing offset samples for the
|
* @param totalProcessingOffsetUs The sum of all video frame processing offset samples for the
|
||||||
* video frames processed by the renderer in microseconds.
|
* video frames processed by the renderer in microseconds.
|
||||||
* @param frameCount The number of samples included in the {@code totalProcessingOffsetUs}.
|
* @param frameCount The number of samples included in the {@code totalProcessingOffsetUs}.
|
||||||
* @param format The {@link Format} that is currently output.
|
|
||||||
*/
|
*/
|
||||||
default void onVideoFrameProcessingOffset(
|
default void onVideoFrameProcessingOffset(long totalProcessingOffsetUs, int frameCount) {}
|
||||||
long totalProcessingOffsetUs, int frameCount, Format format) {}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called before a frame is rendered for the first time since setting the surface, and each time
|
* Called before a frame is rendered for the first time since setting the surface, and each time
|
||||||
|
|
@ -182,13 +180,12 @@ public interface VideoRendererEventListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Invokes {@link VideoRendererEventListener#onVideoFrameProcessingOffset}. */
|
/** Invokes {@link VideoRendererEventListener#onVideoFrameProcessingOffset}. */
|
||||||
public void reportVideoFrameProcessingOffset(
|
public void reportVideoFrameProcessingOffset(long totalProcessingOffsetUs, int frameCount) {
|
||||||
long totalProcessingOffsetUs, int frameCount, Format format) {
|
|
||||||
if (handler != null) {
|
if (handler != null) {
|
||||||
handler.post(
|
handler.post(
|
||||||
() ->
|
() ->
|
||||||
castNonNull(listener)
|
castNonNull(listener)
|
||||||
.onVideoFrameProcessingOffset(totalProcessingOffsetUs, frameCount, format));
|
.onVideoFrameProcessingOffset(totalProcessingOffsetUs, frameCount));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1955,7 +1955,7 @@ public final class AnalyticsCollectorTest {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onVideoFrameProcessingOffset(
|
public void onVideoFrameProcessingOffset(
|
||||||
EventTime eventTime, long totalProcessingOffsetUs, int frameCount, Format format) {
|
EventTime eventTime, long totalProcessingOffsetUs, int frameCount) {
|
||||||
reportedEvents.add(new ReportedEvent(EVENT_VIDEO_FRAME_PROCESSING_OFFSET, eventTime));
|
reportedEvents.add(new ReportedEvent(EVENT_VIDEO_FRAME_PROCESSING_OFFSET, eventTime));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,9 @@ import static org.mockito.ArgumentMatchers.anyLong;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import android.media.MediaFormat;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
|
|
@ -216,15 +218,16 @@ public class MediaCodecAudioRendererTest {
|
||||||
/* eventHandler= */ null,
|
/* eventHandler= */ null,
|
||||||
/* eventListener= */ null) {
|
/* eventListener= */ null) {
|
||||||
@Override
|
@Override
|
||||||
protected void onOutputFormatChanged(Format outputFormat) throws ExoPlaybackException {
|
protected void onOutputFormatChanged(Format format, @Nullable MediaFormat mediaFormat)
|
||||||
super.onOutputFormatChanged(outputFormat);
|
throws ExoPlaybackException {
|
||||||
if (!outputFormat.equals(AUDIO_AAC)) {
|
super.onOutputFormatChanged(format, mediaFormat);
|
||||||
|
if (!format.equals(AUDIO_AAC)) {
|
||||||
setPendingPlaybackException(
|
setPendingPlaybackException(
|
||||||
ExoPlaybackException.createForRenderer(
|
ExoPlaybackException.createForRenderer(
|
||||||
new AudioSink.ConfigurationException("Test"),
|
new AudioSink.ConfigurationException("Test"),
|
||||||
"rendererName",
|
"rendererName",
|
||||||
/* rendererIndex= */ 0,
|
/* rendererIndex= */ 0,
|
||||||
outputFormat,
|
format,
|
||||||
FORMAT_HANDLED));
|
FORMAT_HANDLED));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -254,8 +257,11 @@ public class MediaCodecAudioRendererTest {
|
||||||
exceptionThrowingRenderer.render(/* positionUs= */ 0, SystemClock.elapsedRealtime() * 1000);
|
exceptionThrowingRenderer.render(/* positionUs= */ 0, SystemClock.elapsedRealtime() * 1000);
|
||||||
exceptionThrowingRenderer.render(/* positionUs= */ 250, SystemClock.elapsedRealtime() * 1000);
|
exceptionThrowingRenderer.render(/* positionUs= */ 250, SystemClock.elapsedRealtime() * 1000);
|
||||||
|
|
||||||
|
MediaFormat mediaFormat = new MediaFormat();
|
||||||
|
mediaFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 2);
|
||||||
|
mediaFormat.setInteger(MediaFormat.KEY_SAMPLE_RATE, 32_000);
|
||||||
// Simulating the exception being thrown when not traceable back to render.
|
// Simulating the exception being thrown when not traceable back to render.
|
||||||
exceptionThrowingRenderer.onOutputFormatChanged(changedFormat);
|
exceptionThrowingRenderer.onOutputFormatChanged(changedFormat, mediaFormat);
|
||||||
|
|
||||||
assertThrows(
|
assertThrows(
|
||||||
ExoPlaybackException.class,
|
ExoPlaybackException.class,
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ import static org.mockito.Mockito.verify;
|
||||||
import static org.robolectric.Shadows.shadowOf;
|
import static org.robolectric.Shadows.shadowOf;
|
||||||
|
|
||||||
import android.graphics.SurfaceTexture;
|
import android.graphics.SurfaceTexture;
|
||||||
|
import android.media.MediaFormat;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
|
|
@ -37,7 +38,6 @@ import androidx.annotation.Nullable;
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
|
||||||
import com.google.android.exoplayer2.Format;
|
import com.google.android.exoplayer2.Format;
|
||||||
import com.google.android.exoplayer2.Renderer;
|
import com.google.android.exoplayer2.Renderer;
|
||||||
import com.google.android.exoplayer2.RendererCapabilities;
|
import com.google.android.exoplayer2.RendererCapabilities;
|
||||||
|
|
@ -113,9 +113,9 @@ public class MediaCodecVideoRendererTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onOutputFormatChanged(Format outputFormat) {
|
protected void onOutputFormatChanged(Format format, @Nullable MediaFormat mediaFormat) {
|
||||||
super.onOutputFormatChanged(outputFormat);
|
super.onOutputFormatChanged(format, mediaFormat);
|
||||||
currentOutputFormat = outputFormat;
|
currentOutputFormat = format;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -458,59 +458,4 @@ public class MediaCodecVideoRendererTest {
|
||||||
shadowLooper.idle();
|
shadowLooper.idle();
|
||||||
verify(eventListener, times(2)).onRenderedFirstFrame(any());
|
verify(eventListener, times(2)).onRenderedFirstFrame(any());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void onVideoFrameProcessingOffset_isCalledAfterOutputFormatChanges()
|
|
||||||
throws ExoPlaybackException {
|
|
||||||
Format mp4Uhd = VIDEO_H264.buildUpon().setWidth(3840).setHeight(2160).build();
|
|
||||||
FakeSampleStream fakeSampleStream =
|
|
||||||
new FakeSampleStream(
|
|
||||||
/* mediaSourceEventDispatcher= */ null,
|
|
||||||
DrmSessionManager.DUMMY,
|
|
||||||
new DrmSessionEventListener.EventDispatcher(),
|
|
||||||
/* initialFormat= */ mp4Uhd,
|
|
||||||
ImmutableList.of(
|
|
||||||
oneByteSample(/* timeUs= */ 0, C.BUFFER_FLAG_KEY_FRAME),
|
|
||||||
format(VIDEO_H264),
|
|
||||||
oneByteSample(/* timeUs= */ 50, C.BUFFER_FLAG_KEY_FRAME),
|
|
||||||
oneByteSample(/* timeUs= */ 100),
|
|
||||||
format(mp4Uhd),
|
|
||||||
oneByteSample(/* timeUs= */ 150, C.BUFFER_FLAG_KEY_FRAME),
|
|
||||||
oneByteSample(/* timeUs= */ 200),
|
|
||||||
oneByteSample(/* timeUs= */ 250),
|
|
||||||
format(VIDEO_H264),
|
|
||||||
oneByteSample(/* timeUs= */ 300, C.BUFFER_FLAG_KEY_FRAME),
|
|
||||||
FakeSampleStreamItem.END_OF_STREAM_ITEM));
|
|
||||||
|
|
||||||
mediaCodecVideoRenderer.enable(
|
|
||||||
RendererConfiguration.DEFAULT,
|
|
||||||
new Format[] {mp4Uhd},
|
|
||||||
fakeSampleStream,
|
|
||||||
/* positionUs= */ 0,
|
|
||||||
/* joining= */ false,
|
|
||||||
/* mayRenderStartOfStream= */ true,
|
|
||||||
/* offsetUs */ 0);
|
|
||||||
|
|
||||||
mediaCodecVideoRenderer.setCurrentStreamFinal();
|
|
||||||
mediaCodecVideoRenderer.start();
|
|
||||||
|
|
||||||
int positionUs = 10;
|
|
||||||
do {
|
|
||||||
mediaCodecVideoRenderer.render(positionUs, SystemClock.elapsedRealtime() * 1000);
|
|
||||||
positionUs += 10;
|
|
||||||
} while (!mediaCodecVideoRenderer.isEnded());
|
|
||||||
mediaCodecVideoRenderer.stop();
|
|
||||||
shadowOf(testMainLooper).idle();
|
|
||||||
|
|
||||||
InOrder orderVerifier = inOrder(eventListener);
|
|
||||||
orderVerifier.verify(eventListener).onVideoFrameProcessingOffset(anyLong(), eq(1), eq(mp4Uhd));
|
|
||||||
orderVerifier
|
|
||||||
.verify(eventListener)
|
|
||||||
.onVideoFrameProcessingOffset(anyLong(), eq(2), eq(VIDEO_H264));
|
|
||||||
orderVerifier.verify(eventListener).onVideoFrameProcessingOffset(anyLong(), eq(3), eq(mp4Uhd));
|
|
||||||
orderVerifier
|
|
||||||
.verify(eventListener)
|
|
||||||
.onVideoFrameProcessingOffset(anyLong(), eq(1), eq(VIDEO_H264));
|
|
||||||
orderVerifier.verifyNoMoreInteractions();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -67,9 +67,7 @@ public class FakeVideoRenderer extends FakeRenderer {
|
||||||
super.onStopped();
|
super.onStopped();
|
||||||
eventDispatcher.droppedFrames(/* droppedFrameCount= */ 0, /* elapsedMs= */ 0);
|
eventDispatcher.droppedFrames(/* droppedFrameCount= */ 0, /* elapsedMs= */ 0);
|
||||||
eventDispatcher.reportVideoFrameProcessingOffset(
|
eventDispatcher.reportVideoFrameProcessingOffset(
|
||||||
/* totalProcessingOffsetUs= */ 400000,
|
/* totalProcessingOffsetUs= */ 400000, /* frameCount= */ 10);
|
||||||
/* frameCount= */ 10,
|
|
||||||
Assertions.checkNotNull(format));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue