-
Notifications
You must be signed in to change notification settings - Fork 31
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
KUX-1021: RN_Multicast_STB - UDP / Multicast zapping time is over 5 s… (
#812) * KUX-1021: RN_Multicast_STB - UDP / Multicast zapping time is over 5 seconds
- Loading branch information
1 parent
61e3fc2
commit 3f01606
Showing
10 changed files
with
587 additions
and
12 deletions.
There are no files selected for viewing
216 changes: 216 additions & 0 deletions
216
playkit/src/main/java/com/kaltura/android/exoplayer2/audio/KMediaCodecAudioRenderer.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,216 @@ | ||
package com.kaltura.android.exoplayer2.audio; | ||
|
||
import static com.kaltura.android.exoplayer2.audio.DefaultAudioSink.DEFAULT_PLAYBACK_SPEED; | ||
import static java.lang.Math.max; | ||
import static java.lang.Math.min; | ||
|
||
import android.content.Context; | ||
import android.media.MediaCodec; | ||
import android.os.Handler; | ||
|
||
import androidx.annotation.Nullable; | ||
|
||
import com.kaltura.android.exoplayer2.ExoPlaybackException; | ||
import com.kaltura.android.exoplayer2.Format; | ||
import com.kaltura.android.exoplayer2.PlaybackParameters; | ||
import com.kaltura.android.exoplayer2.decoder.DecoderInputBuffer; | ||
import com.kaltura.android.exoplayer2.mediacodec.MediaCodecAdapter; | ||
import com.kaltura.android.exoplayer2.mediacodec.MediaCodecSelector; | ||
import com.kaltura.playkit.PKLog; | ||
|
||
import java.lang.reflect.Field; | ||
import java.nio.ByteBuffer; | ||
import java.util.Objects; | ||
|
||
public class KMediaCodecAudioRenderer extends MediaCodecAudioRenderer { | ||
|
||
private static final String ALLOW_FIRST_BUFFER_POSITION_DISCONTINUITY_FIELD_NAME = "allowFirstBufferPositionDiscontinuity"; | ||
|
||
private static final String CURRENT_POSITION_US_FIELD_NAME = "currentPositionUs"; | ||
|
||
private static final String DECRYPT_ONLY_CODEC_FORMAT_FIELD_NAME = "decryptOnlyCodecFormat"; | ||
|
||
private static final long DEFAULT_MAX_AUDIO_GAP_THRESHOLD = 3_000_000L; | ||
|
||
private static final boolean DEFAULT_USE_CONTINUOUS_SPEED_ADJUSTMENT = false; | ||
|
||
private static final float DEFAULT_MAX_SPEED_FACTOR = 4.0f; | ||
|
||
private static final float DEFAULT_SPEED_STEP = 3.0f; | ||
|
||
private static final long DEFAULT_MAX_AV_GAP = 600_000L; | ||
|
||
private final long maxAudioGapThreshold; | ||
|
||
private final boolean useContinuousSpeedAdjustment; | ||
|
||
private final float maxSpeedFactor; | ||
|
||
private final float speedStep; | ||
|
||
private final long maxAVGap; | ||
|
||
private static final PKLog log = PKLog.get("KMediaCodecAudioRenderer"); | ||
|
||
private boolean speedAdjustedAfterPositionReset = false; | ||
|
||
public KMediaCodecAudioRenderer(Context context, | ||
MediaCodecAdapter.Factory codecAdapterFactory, | ||
MediaCodecSelector mediaCodecSelector, | ||
boolean enableDecoderFallback, | ||
@Nullable Handler eventHandler, | ||
@Nullable AudioRendererEventListener eventListener, | ||
AudioSink audioSink) { | ||
this(context, | ||
codecAdapterFactory, | ||
mediaCodecSelector, | ||
enableDecoderFallback, | ||
eventHandler, | ||
eventListener, | ||
audioSink, | ||
DEFAULT_MAX_AUDIO_GAP_THRESHOLD, | ||
DEFAULT_MAX_SPEED_FACTOR, | ||
DEFAULT_SPEED_STEP, | ||
DEFAULT_MAX_AV_GAP, | ||
DEFAULT_USE_CONTINUOUS_SPEED_ADJUSTMENT); | ||
} | ||
|
||
public KMediaCodecAudioRenderer(Context context, | ||
MediaCodecAdapter.Factory codecAdapterFactory, | ||
MediaCodecSelector mediaCodecSelector, | ||
boolean enableDecoderFallback, | ||
@Nullable Handler eventHandler, | ||
@Nullable AudioRendererEventListener eventListener, | ||
AudioSink audioSink, | ||
long maxAudioGapThreshold, | ||
float maxSpeedFactor, | ||
float speedStep, | ||
long maxAVGap, | ||
boolean useContinuousSpeedAdjustment) { | ||
super(context, codecAdapterFactory, mediaCodecSelector, enableDecoderFallback, eventHandler, eventListener, audioSink); | ||
this.maxAudioGapThreshold = maxAudioGapThreshold; | ||
this.maxSpeedFactor = maxSpeedFactor; | ||
this.speedStep = speedStep; | ||
this.maxAVGap = maxAVGap; | ||
this.useContinuousSpeedAdjustment = useContinuousSpeedAdjustment; | ||
log.d("KMediaCodecAudioRenderer", "getSpeedGap()=" + getMaxAVGap() + | ||
", getSpeedFactor()=" + getMaxSpeedFactor() + | ||
", getSpeedStep()=" + getSpeedStep() + | ||
", continuousSpeedAdjustment=" + getContinuousSpeedAdjustment()); | ||
} | ||
|
||
protected long getMaxAudioGapThreshold() { | ||
return maxAudioGapThreshold; | ||
} | ||
|
||
protected float getMaxSpeedFactor() { | ||
return maxSpeedFactor; | ||
} | ||
|
||
protected float getSpeedStep() { | ||
return speedStep; | ||
} | ||
|
||
protected long getMaxAVGap() { | ||
return maxAVGap; | ||
} | ||
|
||
protected boolean getContinuousSpeedAdjustment() { | ||
return useContinuousSpeedAdjustment; | ||
} | ||
|
||
@Override | ||
protected void onPositionReset(long positionUs, boolean joining) throws ExoPlaybackException { | ||
super.onPositionReset(positionUs, joining); | ||
speedAdjustedAfterPositionReset = false; | ||
} | ||
|
||
@Override | ||
protected void onQueueInputBuffer(DecoderInputBuffer buffer) { | ||
try { | ||
Field allowFirstBufferPositionDiscontinuityField = Objects.requireNonNull( | ||
getClass().getSuperclass()) | ||
.getDeclaredField(ALLOW_FIRST_BUFFER_POSITION_DISCONTINUITY_FIELD_NAME); | ||
allowFirstBufferPositionDiscontinuityField.setAccessible(true); | ||
Field currentPositionUsField = Objects.requireNonNull( | ||
getClass().getSuperclass()) | ||
.getDeclaredField(CURRENT_POSITION_US_FIELD_NAME); | ||
currentPositionUsField.setAccessible(true); | ||
if (allowFirstBufferPositionDiscontinuityField.getBoolean(this) && !buffer.isDecodeOnly()) { | ||
log.d("KMediaCodecAudioRenderer", "A/V start buffers gap measured: " | ||
+ Math.abs(buffer.timeUs - currentPositionUsField.getLong(this)) + " uS"); | ||
if (Math.abs(buffer.timeUs - currentPositionUsField.getLong(this)) > getMaxAudioGapThreshold()) { | ||
currentPositionUsField.setLong(this, buffer.timeUs); | ||
} | ||
allowFirstBufferPositionDiscontinuityField.setBoolean(this, false); | ||
} | ||
} catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException | NullPointerException e) { | ||
log.e("KMediaCodecAudioRenderer", "Error subclassing audio renderer: " + e.getMessage()); | ||
// Fallback to superclass in case something goes wrong | ||
super.onQueueInputBuffer(buffer); | ||
} | ||
} | ||
|
||
@Override | ||
protected boolean processOutputBuffer(long positionUs, long elapsedRealtimeUs, @Nullable MediaCodecAdapter codec, @Nullable ByteBuffer buffer, int bufferIndex, int bufferFlags, int sampleCount, long bufferPresentationTimeUs, boolean isDecodeOnlyBuffer, boolean isLastBuffer, Format format) throws ExoPlaybackException { | ||
Format decryptOnlyCodecFormat = null; | ||
try { | ||
Field decryptOnlyCodeFormatField = Objects.requireNonNull( | ||
getClass().getSuperclass()).getDeclaredField(DECRYPT_ONLY_CODEC_FORMAT_FIELD_NAME); | ||
decryptOnlyCodeFormatField.setAccessible(true); | ||
decryptOnlyCodecFormat = (Format)decryptOnlyCodeFormatField.get(this); | ||
} catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException | NullPointerException e) { | ||
log.e("KMediaCodecAudioRenderer", "Error getting decryptOnlyCodecFormat: " + e.getMessage()); | ||
} | ||
|
||
if ((decryptOnlyCodecFormat != null | ||
&& (bufferFlags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) || isDecodeOnlyBuffer) { | ||
return super.processOutputBuffer( | ||
positionUs, | ||
elapsedRealtimeUs, | ||
codec, | ||
buffer, | ||
bufferIndex, | ||
bufferFlags, | ||
sampleCount, | ||
bufferPresentationTimeUs, | ||
isDecodeOnlyBuffer, | ||
isLastBuffer, | ||
format); | ||
} | ||
|
||
if (!speedAdjustedAfterPositionReset || getContinuousSpeedAdjustment()) { | ||
log.d("KMediaCodecAudioRenderer", "currentSpeed=" + getPlaybackParameters().speed + | ||
", bufferPresentationTimeUs=" + bufferPresentationTimeUs + | ||
", positionUs=" + positionUs); | ||
if (bufferPresentationTimeUs - positionUs > getMaxAVGap() | ||
&& getPlaybackParameters().speed < getMaxSpeedFactor()) { | ||
float newSpeed = getPlaybackParameters().speed + getSpeedStep(); | ||
newSpeed = min(newSpeed, getMaxSpeedFactor()); | ||
log.d("KMediaCodecAudioRenderer", "Setting speed to " + newSpeed); | ||
setPlaybackParameters(new PlaybackParameters(newSpeed)); | ||
} else if (getPlaybackParameters().speed != DEFAULT_PLAYBACK_SPEED) { | ||
float newSpeed = getPlaybackParameters().speed - getSpeedStep(); | ||
newSpeed = max(newSpeed, DEFAULT_PLAYBACK_SPEED); | ||
log.d("KMediaCodecAudioRenderer", "Setting speed to " + newSpeed); | ||
setPlaybackParameters(new PlaybackParameters(newSpeed)); | ||
if (newSpeed == DEFAULT_PLAYBACK_SPEED) { | ||
speedAdjustedAfterPositionReset = true; | ||
} | ||
} | ||
} | ||
|
||
return super.processOutputBuffer( | ||
positionUs, | ||
elapsedRealtimeUs, | ||
codec, | ||
buffer, | ||
bufferIndex, | ||
bufferFlags, | ||
sampleCount, | ||
bufferPresentationTimeUs, | ||
isDecodeOnlyBuffer, | ||
isLastBuffer, | ||
format); | ||
} | ||
} |
72 changes: 72 additions & 0 deletions
72
playkit/src/main/java/com/kaltura/android/exoplayer2/video/KMediaCodecVideoRenderer.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
package com.kaltura.android.exoplayer2.video; | ||
|
||
import android.content.Context; | ||
import android.os.Handler; | ||
import android.os.Looper; | ||
|
||
import androidx.annotation.Nullable; | ||
import androidx.annotation.RequiresApi; | ||
|
||
import com.kaltura.android.exoplayer2.ExoPlaybackException; | ||
import com.kaltura.android.exoplayer2.mediacodec.MediaCodecAdapter; | ||
import com.kaltura.android.exoplayer2.mediacodec.MediaCodecSelector; | ||
import com.kaltura.playkit.PKLog; | ||
|
||
public class KMediaCodecVideoRenderer extends MediaCodecVideoRenderer{ | ||
|
||
private boolean renderedFirstFrameAfterResetAfterReady = false; | ||
|
||
private boolean shouldNotifyRenderedFirstFrameAfterStarted = false; | ||
|
||
private static final PKLog log = PKLog.get("KMediaCodecVideoRenderer"); | ||
|
||
@Nullable private KVideoRendererFirstFrameWhenStartedEventListener rendererFirstFrameWhenStartedEventListener; | ||
|
||
public KMediaCodecVideoRenderer(Context context, | ||
MediaCodecAdapter.Factory codecAdapterFactory, | ||
MediaCodecSelector mediaCodecSelector, | ||
long allowedJoiningTimeMs, | ||
boolean enableDecoderFallback, | ||
@Nullable Handler eventHandler, | ||
@Nullable VideoRendererEventListener eventListener, | ||
int maxDroppedFramesToNotify, | ||
KVideoRendererFirstFrameWhenStartedEventListener rendererFirstFrameWhenStartedEventListener) { | ||
super(context, codecAdapterFactory, mediaCodecSelector, allowedJoiningTimeMs, enableDecoderFallback, eventHandler, eventListener, maxDroppedFramesToNotify); | ||
this.rendererFirstFrameWhenStartedEventListener = rendererFirstFrameWhenStartedEventListener; | ||
} | ||
|
||
@Override | ||
void maybeNotifyRenderedFirstFrame() { | ||
super.maybeNotifyRenderedFirstFrame(); | ||
if (this.shouldNotifyRenderedFirstFrameAfterStarted) { | ||
log.d("KMediaCodecVideoRenderer", "maybeNotifyRenderedFirstFrame"); | ||
this.shouldNotifyRenderedFirstFrameAfterStarted = false; | ||
new Handler(Looper.getMainLooper()).post(() -> { | ||
if (rendererFirstFrameWhenStartedEventListener != null) { | ||
log.d("KMediaCodecVideoRenderer", "onRenderedFirstFrameWhenStarted"); | ||
rendererFirstFrameWhenStartedEventListener.onRenderedFirstFrameWhenStarted(); | ||
} | ||
}); | ||
} | ||
} | ||
|
||
@Override | ||
protected void onPositionReset(long positionUs, boolean joining) throws ExoPlaybackException { | ||
log.d("KMediaCodecVideoRenderer", "onPositionReset() called with: positionUs = [" + positionUs + "], joining = [" + joining + "]"); | ||
super.onPositionReset(positionUs, joining); | ||
this.renderedFirstFrameAfterResetAfterReady = false; | ||
this.shouldNotifyRenderedFirstFrameAfterStarted = false; | ||
} | ||
|
||
@RequiresApi(21) | ||
@Override | ||
protected void renderOutputBufferV21(MediaCodecAdapter codec, int index, long presentationTimeUs, long releaseTimeNs) { | ||
if(getState() == STATE_STARTED) { | ||
if(!this.renderedFirstFrameAfterResetAfterReady) { | ||
this.renderedFirstFrameAfterResetAfterReady = true; | ||
this.shouldNotifyRenderedFirstFrameAfterStarted = true; | ||
} | ||
} | ||
super.renderOutputBufferV21(codec, index, presentationTimeUs, releaseTimeNs); | ||
} | ||
} |
6 changes: 6 additions & 0 deletions
6
...om/kaltura/android/exoplayer2/video/KVideoRendererFirstFrameWhenStartedEventListener.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package com.kaltura.android.exoplayer2.video; | ||
|
||
public interface KVideoRendererFirstFrameWhenStartedEventListener { | ||
default void onRenderedFirstFrameWhenStarted() { | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.