Move VideoSinkProvider.initialize to VideoSink
PiperOrigin-RevId: 627290721
This commit is contained in:
parent
a43ffa8898
commit
e3caed1441
@ -64,8 +64,8 @@ import java.util.ArrayList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.CopyOnWriteArraySet;
|
import java.util.concurrent.CopyOnWriteArraySet;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import org.checkerframework.checker.initialization.qual.Initialized;
|
|
||||||
import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
|
import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
|
||||||
|
import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
|
||||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
|
|
||||||
/** Handles composition of video sinks. */
|
/** Handles composition of video sinks. */
|
||||||
@ -197,18 +197,17 @@ public final class CompositingVideoSinkProvider
|
|||||||
private static final Executor NO_OP_EXECUTOR = runnable -> {};
|
private static final Executor NO_OP_EXECUTOR = runnable -> {};
|
||||||
|
|
||||||
private final Context context;
|
private final Context context;
|
||||||
|
private final VideoSinkImpl videoSinkImpl;
|
||||||
private final PreviewingVideoGraph.Factory previewingVideoGraphFactory;
|
private final PreviewingVideoGraph.Factory previewingVideoGraphFactory;
|
||||||
private final CopyOnWriteArraySet<CompositingVideoSinkProvider.Listener> listeners;
|
private final CopyOnWriteArraySet<CompositingVideoSinkProvider.Listener> listeners;
|
||||||
|
|
||||||
private Clock clock;
|
private Clock clock;
|
||||||
private List<Effect> videoEffects;
|
|
||||||
private @MonotonicNonNull VideoFrameReleaseControl videoFrameReleaseControl;
|
private @MonotonicNonNull VideoFrameReleaseControl videoFrameReleaseControl;
|
||||||
private @MonotonicNonNull VideoFrameRenderControl videoFrameRenderControl;
|
private @MonotonicNonNull VideoFrameRenderControl videoFrameRenderControl;
|
||||||
private @MonotonicNonNull Format outputFormat;
|
private @MonotonicNonNull Format outputFormat;
|
||||||
private @MonotonicNonNull VideoFrameMetadataListener videoFrameMetadataListener;
|
private @MonotonicNonNull VideoFrameMetadataListener videoFrameMetadataListener;
|
||||||
private @MonotonicNonNull HandlerWrapper handler;
|
private @MonotonicNonNull HandlerWrapper handler;
|
||||||
private @MonotonicNonNull PreviewingVideoGraph videoGraph;
|
private @MonotonicNonNull PreviewingVideoGraph videoGraph;
|
||||||
private @MonotonicNonNull VideoSinkImpl videoSinkImpl;
|
|
||||||
@Nullable private Pair<Surface, Size> currentSurfaceAndSize;
|
@Nullable private Pair<Surface, Size> currentSurfaceAndSize;
|
||||||
private int pendingFlushCount;
|
private int pendingFlushCount;
|
||||||
private @State int state;
|
private @State int state;
|
||||||
@ -216,12 +215,13 @@ public final class CompositingVideoSinkProvider
|
|||||||
|
|
||||||
private CompositingVideoSinkProvider(Builder builder) {
|
private CompositingVideoSinkProvider(Builder builder) {
|
||||||
context = builder.context;
|
context = builder.context;
|
||||||
videoEffects = ImmutableList.of();
|
videoSinkImpl = new VideoSinkImpl(context);
|
||||||
previewingVideoGraphFactory = checkStateNotNull(builder.previewingVideoGraphFactory);
|
previewingVideoGraphFactory = checkStateNotNull(builder.previewingVideoGraphFactory);
|
||||||
listeners = new CopyOnWriteArraySet<>();
|
listeners = new CopyOnWriteArraySet<>();
|
||||||
clock = Clock.DEFAULT;
|
clock = Clock.DEFAULT;
|
||||||
state = STATE_CREATED;
|
state = STATE_CREATED;
|
||||||
playbackSpeed = 1f;
|
playbackSpeed = 1f;
|
||||||
|
addListener(videoSinkImpl);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -267,78 +267,22 @@ public final class CompositingVideoSinkProvider
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setVideoEffects(List<Effect> videoEffects) {
|
public void setVideoEffects(List<Effect> videoEffects) {
|
||||||
this.videoEffects = videoEffects;
|
videoSinkImpl.setVideoEffects(videoEffects);
|
||||||
if (isInitialized()) {
|
|
||||||
checkStateNotNull(videoSinkImpl).setVideoEffects(videoEffects);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setPendingVideoEffects(List<Effect> videoEffects) {
|
public void setPendingVideoEffects(List<Effect> videoEffects) {
|
||||||
this.videoEffects = videoEffects;
|
videoSinkImpl.setPendingVideoEffects(videoEffects);
|
||||||
if (isInitialized()) {
|
|
||||||
checkStateNotNull(videoSinkImpl).setPendingVideoEffects(videoEffects);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void initialize(Format sourceFormat) throws VideoSink.VideoSinkException {
|
|
||||||
checkState(state == STATE_CREATED);
|
|
||||||
checkState(videoFrameRenderControl != null && videoFrameReleaseControl != null);
|
|
||||||
|
|
||||||
// Lazily initialize the handler here so it's initialized on the playback looper.
|
|
||||||
handler = clock.createHandler(checkStateNotNull(Looper.myLooper()), /* callback= */ null);
|
|
||||||
|
|
||||||
ColorInfo inputColorInfo = getAdjustedInputColorInfo(sourceFormat.colorInfo);
|
|
||||||
ColorInfo outputColorInfo = inputColorInfo;
|
|
||||||
if (inputColorInfo.colorTransfer == C.COLOR_TRANSFER_HLG && Util.SDK_INT < 34) {
|
|
||||||
// PQ SurfaceView output is supported from API 33, but HLG output is supported from API 34.
|
|
||||||
// Therefore, convert HLG to PQ below API 34, so that HLG input can be displayed properly on
|
|
||||||
// API 33.
|
|
||||||
outputColorInfo =
|
|
||||||
inputColorInfo.buildUpon().setColorTransfer(C.COLOR_TRANSFER_ST2084).build();
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
@SuppressWarnings("nullness:assignment")
|
|
||||||
VideoGraph.@Initialized Listener thisRef = this;
|
|
||||||
videoGraph =
|
|
||||||
previewingVideoGraphFactory.create(
|
|
||||||
context,
|
|
||||||
outputColorInfo,
|
|
||||||
DebugViewProvider.NONE,
|
|
||||||
/* listener= */ thisRef,
|
|
||||||
/* listenerExecutor= */ handler::post,
|
|
||||||
/* compositionEffects= */ ImmutableList.of(),
|
|
||||||
/* initialTimestampOffsetUs= */ 0);
|
|
||||||
if (currentSurfaceAndSize != null) {
|
|
||||||
Surface surface = currentSurfaceAndSize.first;
|
|
||||||
Size size = currentSurfaceAndSize.second;
|
|
||||||
maybeSetOutputSurfaceInfo(surface, size.getWidth(), size.getHeight());
|
|
||||||
}
|
|
||||||
videoSinkImpl = new VideoSinkImpl(context, videoGraph);
|
|
||||||
} catch (VideoFrameProcessingException e) {
|
|
||||||
throw new VideoSink.VideoSinkException(e, sourceFormat);
|
|
||||||
}
|
|
||||||
if (!videoEffects.isEmpty()) {
|
|
||||||
videoSinkImpl.setVideoEffects(videoEffects);
|
|
||||||
}
|
|
||||||
addListener(videoSinkImpl);
|
|
||||||
state = STATE_INITIALIZED;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isInitialized() {
|
|
||||||
return state == STATE_INITIALIZED;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public VideoSink getSink() {
|
public VideoSink getSink() {
|
||||||
return checkStateNotNull(videoSinkImpl);
|
return videoSinkImpl;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setStreamOffsetUs(long streamOffsetUs) {
|
public void setStreamOffsetUs(long streamOffsetUs) {
|
||||||
checkStateNotNull(videoSinkImpl).setStreamOffsetUs(streamOffsetUs);
|
videoSinkImpl.setStreamOffsetUs(streamOffsetUs);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -485,6 +429,50 @@ public final class CompositingVideoSinkProvider
|
|||||||
|
|
||||||
// Internal methods
|
// Internal methods
|
||||||
|
|
||||||
|
private VideoFrameProcessor initialize(Format sourceFormat) throws VideoSink.VideoSinkException {
|
||||||
|
checkState(state == STATE_CREATED);
|
||||||
|
checkState(videoFrameRenderControl != null && videoFrameReleaseControl != null);
|
||||||
|
|
||||||
|
// Lazily initialize the handler here so it's initialized on the playback looper.
|
||||||
|
handler = clock.createHandler(checkStateNotNull(Looper.myLooper()), /* callback= */ null);
|
||||||
|
|
||||||
|
ColorInfo inputColorInfo = getAdjustedInputColorInfo(sourceFormat.colorInfo);
|
||||||
|
ColorInfo outputColorInfo = inputColorInfo;
|
||||||
|
if (inputColorInfo.colorTransfer == C.COLOR_TRANSFER_HLG && Util.SDK_INT < 34) {
|
||||||
|
// PQ SurfaceView output is supported from API 33, but HLG output is supported from API 34.
|
||||||
|
// Therefore, convert HLG to PQ below API 34, so that HLG input can be displayed properly on
|
||||||
|
// API 33.
|
||||||
|
outputColorInfo =
|
||||||
|
inputColorInfo.buildUpon().setColorTransfer(C.COLOR_TRANSFER_ST2084).build();
|
||||||
|
}
|
||||||
|
int videoGraphInputId;
|
||||||
|
try {
|
||||||
|
videoGraph =
|
||||||
|
previewingVideoGraphFactory.create(
|
||||||
|
context,
|
||||||
|
outputColorInfo,
|
||||||
|
DebugViewProvider.NONE,
|
||||||
|
/* listener= */ this,
|
||||||
|
/* listenerExecutor= */ handler::post,
|
||||||
|
/* compositionEffects= */ ImmutableList.of(),
|
||||||
|
/* initialTimestampOffsetUs= */ 0);
|
||||||
|
if (currentSurfaceAndSize != null) {
|
||||||
|
Surface surface = currentSurfaceAndSize.first;
|
||||||
|
Size size = currentSurfaceAndSize.second;
|
||||||
|
maybeSetOutputSurfaceInfo(surface, size.getWidth(), size.getHeight());
|
||||||
|
}
|
||||||
|
videoGraphInputId = videoGraph.registerInput();
|
||||||
|
} catch (VideoFrameProcessingException e) {
|
||||||
|
throw new VideoSink.VideoSinkException(e, sourceFormat);
|
||||||
|
}
|
||||||
|
state = STATE_INITIALIZED;
|
||||||
|
return videoGraph.getProcessor(videoGraphInputId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isInitialized() {
|
||||||
|
return state == STATE_INITIALIZED;
|
||||||
|
}
|
||||||
|
|
||||||
private void maybeSetOutputSurfaceInfo(@Nullable Surface surface, int width, int height) {
|
private void maybeSetOutputSurfaceInfo(@Nullable Surface surface, int width, int height) {
|
||||||
if (videoGraph != null) {
|
if (videoGraph != null) {
|
||||||
// Update the surface on the video graph and the video frame release control together.
|
// Update the surface on the video graph and the video frame release control together.
|
||||||
@ -551,11 +539,11 @@ public final class CompositingVideoSinkProvider
|
|||||||
/** Receives input from an ExoPlayer renderer and forwards it to the video graph. */
|
/** Receives input from an ExoPlayer renderer and forwards it to the video graph. */
|
||||||
private final class VideoSinkImpl implements VideoSink, CompositingVideoSinkProvider.Listener {
|
private final class VideoSinkImpl implements VideoSink, CompositingVideoSinkProvider.Listener {
|
||||||
private final Context context;
|
private final Context context;
|
||||||
private final VideoFrameProcessor videoFrameProcessor;
|
|
||||||
private final int videoFrameProcessorMaxPendingFrameCount;
|
private final int videoFrameProcessorMaxPendingFrameCount;
|
||||||
private final ArrayList<Effect> videoEffects;
|
private final ArrayList<Effect> videoEffects;
|
||||||
@Nullable private Effect rotationEffect;
|
@Nullable private Effect rotationEffect;
|
||||||
|
|
||||||
|
private @MonotonicNonNull VideoFrameProcessor videoFrameProcessor;
|
||||||
@Nullable private Format inputFormat;
|
@Nullable private Format inputFormat;
|
||||||
private @InputType int inputType;
|
private @InputType int inputType;
|
||||||
private long inputStreamOffsetUs;
|
private long inputStreamOffsetUs;
|
||||||
@ -575,8 +563,7 @@ public final class CompositingVideoSinkProvider
|
|||||||
private Executor listenerExecutor;
|
private Executor listenerExecutor;
|
||||||
|
|
||||||
/** Creates a new instance. */
|
/** Creates a new instance. */
|
||||||
public VideoSinkImpl(Context context, PreviewingVideoGraph videoGraph)
|
public VideoSinkImpl(Context context) {
|
||||||
throws VideoFrameProcessingException {
|
|
||||||
this.context = context;
|
this.context = context;
|
||||||
// TODO b/226330223 - Investigate increasing frame count when frame dropping is
|
// TODO b/226330223 - Investigate increasing frame count when frame dropping is
|
||||||
// allowed.
|
// allowed.
|
||||||
@ -584,8 +571,6 @@ public final class CompositingVideoSinkProvider
|
|||||||
// reduces decoder timeouts, and consider restoring.
|
// reduces decoder timeouts, and consider restoring.
|
||||||
videoFrameProcessorMaxPendingFrameCount =
|
videoFrameProcessorMaxPendingFrameCount =
|
||||||
Util.getMaxPendingFramesCountForMediaCodecDecoders(context);
|
Util.getMaxPendingFramesCountForMediaCodecDecoders(context);
|
||||||
int videoGraphInputId = videoGraph.registerInput();
|
|
||||||
videoFrameProcessor = videoGraph.getProcessor(videoGraphInputId);
|
|
||||||
|
|
||||||
videoEffects = new ArrayList<>();
|
videoEffects = new ArrayList<>();
|
||||||
finalBufferPresentationTimeUs = C.TIME_UNSET;
|
finalBufferPresentationTimeUs = C.TIME_UNSET;
|
||||||
@ -596,9 +581,23 @@ public final class CompositingVideoSinkProvider
|
|||||||
|
|
||||||
// VideoSink impl
|
// VideoSink impl
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize(Format sourceFormat) throws VideoSinkException {
|
||||||
|
checkState(!isInitialized());
|
||||||
|
videoFrameProcessor = CompositingVideoSinkProvider.this.initialize(sourceFormat);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@EnsuresNonNullIf(result = true, expression = "videoFrameProcessor")
|
||||||
|
public boolean isInitialized() {
|
||||||
|
return videoFrameProcessor != null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void flush() {
|
public void flush() {
|
||||||
|
if (isInitialized()) {
|
||||||
videoFrameProcessor.flush();
|
videoFrameProcessor.flush();
|
||||||
|
}
|
||||||
hasRegisteredFirstInputStream = false;
|
hasRegisteredFirstInputStream = false;
|
||||||
finalBufferPresentationTimeUs = C.TIME_UNSET;
|
finalBufferPresentationTimeUs = C.TIME_UNSET;
|
||||||
lastBufferPresentationTimeUs = C.TIME_UNSET;
|
lastBufferPresentationTimeUs = C.TIME_UNSET;
|
||||||
@ -611,17 +610,19 @@ public final class CompositingVideoSinkProvider
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isReady() {
|
public boolean isReady() {
|
||||||
return CompositingVideoSinkProvider.this.isReady();
|
return isInitialized() && CompositingVideoSinkProvider.this.isReady();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isEnded() {
|
public boolean isEnded() {
|
||||||
return finalBufferPresentationTimeUs != C.TIME_UNSET
|
return isInitialized()
|
||||||
|
&& finalBufferPresentationTimeUs != C.TIME_UNSET
|
||||||
&& CompositingVideoSinkProvider.this.hasReleasedFrame(finalBufferPresentationTimeUs);
|
&& CompositingVideoSinkProvider.this.hasReleasedFrame(finalBufferPresentationTimeUs);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void registerInputStream(@InputType int inputType, Format format) {
|
public void registerInputStream(@InputType int inputType, Format format) {
|
||||||
|
checkState(isInitialized());
|
||||||
switch (inputType) {
|
switch (inputType) {
|
||||||
case INPUT_TYPE_SURFACE:
|
case INPUT_TYPE_SURFACE:
|
||||||
case INPUT_TYPE_BITMAP:
|
case INPUT_TYPE_BITMAP:
|
||||||
@ -674,11 +675,13 @@ public final class CompositingVideoSinkProvider
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Surface getInputSurface() {
|
public Surface getInputSurface() {
|
||||||
return videoFrameProcessor.getInputSurface();
|
checkState(isInitialized());
|
||||||
|
return checkStateNotNull(videoFrameProcessor).getInputSurface();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long registerInputFrame(long framePresentationTimeUs, boolean isLastFrame) {
|
public long registerInputFrame(long framePresentationTimeUs, boolean isLastFrame) {
|
||||||
|
checkState(isInitialized());
|
||||||
checkState(videoFrameProcessorMaxPendingFrameCount != C.LENGTH_UNSET);
|
checkState(videoFrameProcessorMaxPendingFrameCount != C.LENGTH_UNSET);
|
||||||
|
|
||||||
// An input stream is fully decoded, wait until all of its frames are released before queueing
|
// An input stream is fully decoded, wait until all of its frames are released before queueing
|
||||||
@ -693,11 +696,11 @@ public final class CompositingVideoSinkProvider
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (videoFrameProcessor.getPendingInputFrameCount()
|
if (checkStateNotNull(videoFrameProcessor).getPendingInputFrameCount()
|
||||||
>= videoFrameProcessorMaxPendingFrameCount) {
|
>= videoFrameProcessorMaxPendingFrameCount) {
|
||||||
return C.TIME_UNSET;
|
return C.TIME_UNSET;
|
||||||
}
|
}
|
||||||
if (!videoFrameProcessor.registerInputFrame()) {
|
if (!checkStateNotNull(videoFrameProcessor).registerInputFrame()) {
|
||||||
return C.TIME_UNSET;
|
return C.TIME_UNSET;
|
||||||
}
|
}
|
||||||
// The sink takes in frames with monotonically increasing, non-offset frame
|
// The sink takes in frames with monotonically increasing, non-offset frame
|
||||||
@ -717,6 +720,8 @@ public final class CompositingVideoSinkProvider
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean queueBitmap(Bitmap inputBitmap, TimestampIterator timestampIterator) {
|
public boolean queueBitmap(Bitmap inputBitmap, TimestampIterator timestampIterator) {
|
||||||
|
checkState(isInitialized());
|
||||||
|
|
||||||
if (!maybeRegisterPendingInputStream()) {
|
if (!maybeRegisterPendingInputStream()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -744,6 +749,7 @@ public final class CompositingVideoSinkProvider
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void render(long positionUs, long elapsedRealtimeUs) throws VideoSinkException {
|
public void render(long positionUs, long elapsedRealtimeUs) throws VideoSinkException {
|
||||||
|
checkState(isInitialized());
|
||||||
try {
|
try {
|
||||||
CompositingVideoSinkProvider.this.render(positionUs, elapsedRealtimeUs);
|
CompositingVideoSinkProvider.this.render(positionUs, elapsedRealtimeUs);
|
||||||
} catch (ExoPlaybackException e) {
|
} catch (ExoPlaybackException e) {
|
||||||
@ -820,7 +826,8 @@ public final class CompositingVideoSinkProvider
|
|||||||
}
|
}
|
||||||
effects.addAll(videoEffects);
|
effects.addAll(videoEffects);
|
||||||
Format inputFormat = checkNotNull(this.inputFormat);
|
Format inputFormat = checkNotNull(this.inputFormat);
|
||||||
videoFrameProcessor.registerInputStream(
|
checkStateNotNull(videoFrameProcessor)
|
||||||
|
.registerInputStream(
|
||||||
inputType,
|
inputType,
|
||||||
effects,
|
effects,
|
||||||
new FrameInfo.Builder(
|
new FrameInfo.Builder(
|
||||||
|
@ -22,6 +22,7 @@ import static androidx.media3.common.util.Assertions.checkStateNotNull;
|
|||||||
import static androidx.media3.exoplayer.DecoderReuseEvaluation.DISCARD_REASON_MAX_INPUT_SIZE_EXCEEDED;
|
import static androidx.media3.exoplayer.DecoderReuseEvaluation.DISCARD_REASON_MAX_INPUT_SIZE_EXCEEDED;
|
||||||
import static androidx.media3.exoplayer.DecoderReuseEvaluation.DISCARD_REASON_VIDEO_MAX_RESOLUTION_EXCEEDED;
|
import static androidx.media3.exoplayer.DecoderReuseEvaluation.DISCARD_REASON_VIDEO_MAX_RESOLUTION_EXCEEDED;
|
||||||
import static androidx.media3.exoplayer.DecoderReuseEvaluation.REUSE_RESULT_NO;
|
import static androidx.media3.exoplayer.DecoderReuseEvaluation.REUSE_RESULT_NO;
|
||||||
|
import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
|
||||||
import static java.lang.Math.max;
|
import static java.lang.Math.max;
|
||||||
import static java.lang.Math.min;
|
import static java.lang.Math.min;
|
||||||
|
|
||||||
@ -77,7 +78,6 @@ import androidx.media3.exoplayer.mediacodec.MediaCodecUtil;
|
|||||||
import androidx.media3.exoplayer.mediacodec.MediaCodecUtil.DecoderQueryException;
|
import androidx.media3.exoplayer.mediacodec.MediaCodecUtil.DecoderQueryException;
|
||||||
import androidx.media3.exoplayer.video.VideoRendererEventListener.EventDispatcher;
|
import androidx.media3.exoplayer.video.VideoRendererEventListener.EventDispatcher;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.util.concurrent.MoreExecutors;
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import org.checkerframework.checker.initialization.qual.Initialized;
|
import org.checkerframework.checker.initialization.qual.Initialized;
|
||||||
@ -367,12 +367,11 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer
|
|||||||
* @param assumedMinimumCodecOperatingRate A codec operating rate that all codecs instantiated by
|
* @param assumedMinimumCodecOperatingRate A codec operating rate that all codecs instantiated by
|
||||||
* this renderer are assumed to meet implicitly (i.e. without the operating rate being set
|
* this renderer are assumed to meet implicitly (i.e. without the operating rate being set
|
||||||
* explicitly using {@link MediaFormat#KEY_OPERATING_RATE}).
|
* explicitly using {@link MediaFormat#KEY_OPERATING_RATE}).
|
||||||
* @param videoSinkProvider The {@link VideoSinkProvider} that will used be used for applying
|
* @param videoSinkProvider The {@link VideoSinkProvider} that will be used for applying video
|
||||||
* video effects also providing the {@linkplain
|
* effects also providing the {@linkplain VideoSinkProvider#getVideoFrameReleaseControl()
|
||||||
* VideoSinkProvider#getVideoFrameReleaseControl() VideoFrameReleaseControl} for releasing
|
* VideoFrameReleaseControl} for releasing video frames. If {@code null}, the {@link
|
||||||
* video frames. If {@code null}, the {@link CompositingVideoSinkProvider} with its default
|
* CompositingVideoSinkProvider} with its default configuration will be used, and the renderer
|
||||||
* configuration will be used, and the renderer will drive releasing of video frames by
|
* will drive releasing of video frames by itself.
|
||||||
* itself.
|
|
||||||
*/
|
*/
|
||||||
public MediaCodecVideoRenderer(
|
public MediaCodecVideoRenderer(
|
||||||
Context context,
|
Context context,
|
||||||
@ -733,7 +732,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer
|
|||||||
@Override
|
@Override
|
||||||
protected void onRelease() {
|
protected void onRelease() {
|
||||||
super.onRelease();
|
super.onRelease();
|
||||||
if (ownsVideoSinkProvider && videoSinkProvider.isInitialized()) {
|
if (ownsVideoSinkProvider && videoSink != null) {
|
||||||
videoSinkProvider.release();
|
videoSinkProvider.release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -777,7 +776,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer
|
|||||||
outputResolution = (Size) checkNotNull(message);
|
outputResolution = (Size) checkNotNull(message);
|
||||||
// TODO: b/292111083 Set the surface on the videoSinkProvider before it's initialized
|
// TODO: b/292111083 Set the surface on the videoSinkProvider before it's initialized
|
||||||
// otherwise the first frames are missed until a new video output resolution arrives.
|
// otherwise the first frames are missed until a new video output resolution arrives.
|
||||||
if (videoSinkProvider.isInitialized()
|
if (videoSink != null
|
||||||
&& checkNotNull(outputResolution).getWidth() != 0
|
&& checkNotNull(outputResolution).getWidth() != 0
|
||||||
&& checkNotNull(outputResolution).getHeight() != 0
|
&& checkNotNull(outputResolution).getHeight() != 0
|
||||||
&& displaySurface != null) {
|
&& displaySurface != null) {
|
||||||
@ -820,10 +819,10 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer
|
|||||||
|
|
||||||
@State int state = getState();
|
@State int state = getState();
|
||||||
@Nullable MediaCodecAdapter codec = getCodec();
|
@Nullable MediaCodecAdapter codec = getCodec();
|
||||||
// The video sink provider is initialized just before the first codec is ever created, so
|
// The video sink is instantiated just before the first codec is ever created, so the sink can
|
||||||
// the provider can be initialized only when the codec is non-null. Therefore, we don't have
|
// be non-null only when the codec is non-null. Therefore, we don't have to check if the sink
|
||||||
// to check if the provider is initialized but the codec is not.
|
// is non-null but the codec is null.
|
||||||
if (codec != null && !videoSinkProvider.isInitialized()) {
|
if (codec != null && videoSink == null) {
|
||||||
if (Util.SDK_INT >= 23 && displaySurface != null && !codecNeedsSetOutputSurfaceWorkaround) {
|
if (Util.SDK_INT >= 23 && displaySurface != null && !codecNeedsSetOutputSurfaceWorkaround) {
|
||||||
setOutputSurfaceV23(codec, displaySurface);
|
setOutputSurfaceV23(codec, displaySurface);
|
||||||
} else {
|
} else {
|
||||||
@ -842,13 +841,13 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer
|
|||||||
videoFrameReleaseControl.join(/* renderNextFrameImmediately= */ true);
|
videoFrameReleaseControl.join(/* renderNextFrameImmediately= */ true);
|
||||||
}
|
}
|
||||||
// When effects previewing is enabled, set display surface and an unknown size.
|
// When effects previewing is enabled, set display surface and an unknown size.
|
||||||
if (videoSinkProvider.isInitialized()) {
|
if (videoSink != null) {
|
||||||
videoSinkProvider.setOutputSurfaceInfo(displaySurface, Size.UNKNOWN);
|
videoSinkProvider.setOutputSurfaceInfo(displaySurface, Size.UNKNOWN);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// The display surface has been removed.
|
// The display surface has been removed.
|
||||||
reportedVideoSize = null;
|
reportedVideoSize = null;
|
||||||
if (videoSinkProvider.isInitialized()) {
|
if (videoSink != null) {
|
||||||
videoSinkProvider.clearOutputSurfaceInfo();
|
videoSinkProvider.clearOutputSurfaceInfo();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1059,24 +1058,26 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer
|
|||||||
@CallSuper
|
@CallSuper
|
||||||
@Override
|
@Override
|
||||||
protected void onReadyToInitializeCodec(Format format) throws ExoPlaybackException {
|
protected void onReadyToInitializeCodec(Format format) throws ExoPlaybackException {
|
||||||
// We only enable effects preview on the first time a codec is initialized and if effects are
|
// If the video sink provider is created by the renderer, we only enable effects preview (by
|
||||||
|
// using the video sink) on the first time a codec is initialized and if effects are
|
||||||
// already set. We do not enable effects mid-playback. For effects to be enabled after
|
// already set. We do not enable effects mid-playback. For effects to be enabled after
|
||||||
// playback has started, the renderer needs to be reset first.
|
// playback has started, the renderer needs to be reset first.
|
||||||
if (hasEffects && !hasInitializedPlayback && !videoSinkProvider.isInitialized()) {
|
boolean enableEffectsForOwnSinkProvider =
|
||||||
|
ownsVideoSinkProvider && hasEffects && !hasInitializedPlayback;
|
||||||
|
// We always use the video sink if the video sink provider is passed to the renderer.
|
||||||
|
boolean useVideoSink = enableEffectsForOwnSinkProvider || !ownsVideoSinkProvider;
|
||||||
|
if (useVideoSink && videoSink == null) {
|
||||||
try {
|
try {
|
||||||
videoSinkProvider.initialize(format);
|
videoSinkProvider.setStreamOffsetUs(getOutputStreamOffsetUs());
|
||||||
if (displaySurface != null && outputResolution != null) {
|
videoSink = videoSinkProvider.getSink();
|
||||||
videoSinkProvider.setOutputSurfaceInfo(displaySurface, outputResolution);
|
if (!videoSink.isInitialized()) {
|
||||||
}
|
try {
|
||||||
|
videoSink.initialize(format);
|
||||||
} catch (VideoSink.VideoSinkException e) {
|
} catch (VideoSink.VideoSinkException e) {
|
||||||
throw createRendererException(
|
throw createRendererException(
|
||||||
e, format, PlaybackException.ERROR_CODE_VIDEO_FRAME_PROCESSOR_INIT_FAILED);
|
e, format, PlaybackException.ERROR_CODE_VIDEO_FRAME_PROCESSOR_INIT_FAILED);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (videoSink == null && videoSinkProvider.isInitialized()) {
|
|
||||||
videoSinkProvider.setStreamOffsetUs(getOutputStreamOffsetUs());
|
|
||||||
videoSink = videoSinkProvider.getSink();
|
|
||||||
videoSink.setListener(
|
videoSink.setListener(
|
||||||
new VideoSink.Listener() {
|
new VideoSink.Listener() {
|
||||||
@Override
|
@Override
|
||||||
@ -1110,7 +1111,18 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer
|
|||||||
},
|
},
|
||||||
// Pass a direct executor since the callback handling involves posting on the app looper
|
// Pass a direct executor since the callback handling involves posting on the app looper
|
||||||
// again, so there's no need to do two hops.
|
// again, so there's no need to do two hops.
|
||||||
MoreExecutors.directExecutor());
|
directExecutor());
|
||||||
|
if (enableEffectsForOwnSinkProvider) {
|
||||||
|
if (displaySurface != null && outputResolution != null) {
|
||||||
|
videoSinkProvider.setOutputSurfaceInfo(displaySurface, outputResolution);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Set videoSink back to null so that, if the try block fails and the renderer retries the
|
||||||
|
// codec initialization, the try block is re-executed.
|
||||||
|
videoSink = null;
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
hasInitializedPlayback = true;
|
hasInitializedPlayback = true;
|
||||||
}
|
}
|
||||||
@ -1335,7 +1347,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer
|
|||||||
|
|
||||||
// We are not rendering on a surface, the renderer will wait until a surface is set.
|
// We are not rendering on a surface, the renderer will wait until a surface is set.
|
||||||
// Opportunistically render to VideoFrameProcessor if effects are enabled.
|
// Opportunistically render to VideoFrameProcessor if effects are enabled.
|
||||||
if (displaySurface == placeholderSurface && !videoSinkProvider.isInitialized()) {
|
if (displaySurface == placeholderSurface && videoSink == null) {
|
||||||
// Skip frames in sync with playback, so we'll be at the right frame if the mode changes.
|
// Skip frames in sync with playback, so we'll be at the right frame if the mode changes.
|
||||||
if (videoFrameReleaseInfo.getEarlyUs() < 30_000) {
|
if (videoFrameReleaseInfo.getEarlyUs() < 30_000) {
|
||||||
skipOutputBuffer(codec, bufferIndex, presentationTimeUs);
|
skipOutputBuffer(codec, bufferIndex, presentationTimeUs);
|
||||||
@ -1464,7 +1476,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer
|
|||||||
super.onProcessedStreamChange();
|
super.onProcessedStreamChange();
|
||||||
videoFrameReleaseControl.onProcessedStreamChange();
|
videoFrameReleaseControl.onProcessedStreamChange();
|
||||||
maybeSetupTunnelingForFirstFrame();
|
maybeSetupTunnelingForFirstFrame();
|
||||||
if (videoSinkProvider.isInitialized()) {
|
if (videoSink != null) {
|
||||||
videoSinkProvider.setStreamOffsetUs(getOutputStreamOffsetUs());
|
videoSinkProvider.setStreamOffsetUs(getOutputStreamOffsetUs());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -108,6 +108,17 @@ public interface VideoSink {
|
|||||||
*/
|
*/
|
||||||
void setListener(Listener listener, Executor executor);
|
void setListener(Listener listener, Executor executor);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the video sink.
|
||||||
|
*
|
||||||
|
* @param sourceFormat The format of the compressed video.
|
||||||
|
* @throws VideoSink.VideoSinkException If initializing the sink failed.
|
||||||
|
*/
|
||||||
|
void initialize(Format sourceFormat) throws VideoSinkException;
|
||||||
|
|
||||||
|
/** Returns whether the video sink is {@linkplain #initialize(Format) initialized}. */
|
||||||
|
boolean isInitialized();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flushes the video sink.
|
* Flushes the video sink.
|
||||||
*
|
*
|
||||||
@ -115,20 +126,28 @@ public interface VideoSink {
|
|||||||
*/
|
*/
|
||||||
void flush();
|
void flush();
|
||||||
|
|
||||||
/** Whether the video sink is able to immediately render media from the current position. */
|
/**
|
||||||
|
* Returns whether the video sink is able to immediately render media from the current position.
|
||||||
|
*/
|
||||||
boolean isReady();
|
boolean isReady();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether all queued video frames have been rendered, including the frame marked as last buffer.
|
* Returns whether all queued video frames have been rendered, including the frame marked as last
|
||||||
|
* buffer.
|
||||||
*/
|
*/
|
||||||
boolean isEnded();
|
boolean isEnded();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether frames could be dropped from the sink's {@linkplain #getInputSurface() input surface}.
|
* Returns whether frames could be dropped from the sink's {@linkplain #getInputSurface() input
|
||||||
|
* surface}.
|
||||||
*/
|
*/
|
||||||
boolean isFrameDropAllowedOnInput();
|
boolean isFrameDropAllowedOnInput();
|
||||||
|
|
||||||
/** Returns the input {@link Surface} where the video sink consumes input frames from. */
|
/**
|
||||||
|
* Returns the input {@link Surface} where the video sink consumes input frames from.
|
||||||
|
*
|
||||||
|
* <p>Must be called after the sink is {@linkplain #initialize(Format) initialized}.
|
||||||
|
*/
|
||||||
Surface getInputSurface();
|
Surface getInputSurface();
|
||||||
|
|
||||||
/** Sets the playback speed. */
|
/** Sets the playback speed. */
|
||||||
@ -137,6 +156,8 @@ public interface VideoSink {
|
|||||||
/**
|
/**
|
||||||
* Informs the video sink that a new input stream will be queued.
|
* Informs the video sink that a new input stream will be queued.
|
||||||
*
|
*
|
||||||
|
* <p>Must be called after the sink is {@linkplain #initialize(Format) initialized}.
|
||||||
|
*
|
||||||
* @param inputType The {@link InputType} of the stream.
|
* @param inputType The {@link InputType} of the stream.
|
||||||
* @param format The {@link Format} of the stream.
|
* @param format The {@link Format} of the stream.
|
||||||
*/
|
*/
|
||||||
@ -146,9 +167,11 @@ public interface VideoSink {
|
|||||||
* Informs the video sink that a frame will be queued to its {@linkplain #getInputSurface() input
|
* Informs the video sink that a frame will be queued to its {@linkplain #getInputSurface() input
|
||||||
* surface}.
|
* surface}.
|
||||||
*
|
*
|
||||||
|
* <p>Must be called after the sink is {@linkplain #initialize(Format) initialized}.
|
||||||
|
*
|
||||||
* @param framePresentationTimeUs The frame's presentation time, in microseconds.
|
* @param framePresentationTimeUs The frame's presentation time, in microseconds.
|
||||||
* @param isLastFrame Whether this is the last frame of the video stream.
|
* @param isLastFrame Whether this is the last frame of the video stream.
|
||||||
* @return a release timestamp, in nanoseconds, that should be associated when releasing this
|
* @return A release timestamp, in nanoseconds, that should be associated when releasing this
|
||||||
* frame, or {@link C#TIME_UNSET} if the sink was not able to register the frame and the
|
* frame, or {@link C#TIME_UNSET} if the sink was not able to register the frame and the
|
||||||
* caller must try again later.
|
* caller must try again later.
|
||||||
*/
|
*/
|
||||||
@ -157,6 +180,8 @@ public interface VideoSink {
|
|||||||
/**
|
/**
|
||||||
* Provides an input {@link Bitmap} to the video sink.
|
* Provides an input {@link Bitmap} to the video sink.
|
||||||
*
|
*
|
||||||
|
* <p>Must be called after the sink is {@linkplain #initialize(Format) initialized}.
|
||||||
|
*
|
||||||
* @param inputBitmap The {@link Bitmap} queued to the video sink.
|
* @param inputBitmap The {@link Bitmap} queued to the video sink.
|
||||||
* @param timestampIterator The times within the current stream that the bitmap should be shown
|
* @param timestampIterator The times within the current stream that the bitmap should be shown
|
||||||
* at. The timestamps should be monotonically increasing.
|
* at. The timestamps should be monotonically increasing.
|
||||||
@ -168,6 +193,8 @@ public interface VideoSink {
|
|||||||
/**
|
/**
|
||||||
* Incrementally renders processed video frames.
|
* Incrementally renders processed video frames.
|
||||||
*
|
*
|
||||||
|
* <p>Must be called after the sink is {@linkplain #initialize(Format) initialized}.
|
||||||
|
*
|
||||||
* @param positionUs The current playback position, in microseconds.
|
* @param positionUs The current playback position, in microseconds.
|
||||||
* @param elapsedRealtimeUs {@link android.os.SystemClock#elapsedRealtime()} in microseconds,
|
* @param elapsedRealtimeUs {@link android.os.SystemClock#elapsedRealtime()} in microseconds,
|
||||||
* taken approximately at the time the playback position was {@code positionUs}.
|
* taken approximately at the time the playback position was {@code positionUs}.
|
||||||
|
@ -33,7 +33,8 @@ public interface VideoSinkProvider {
|
|||||||
* Sets the {@link VideoFrameReleaseControl} that will be used for releasing of video frames
|
* Sets the {@link VideoFrameReleaseControl} that will be used for releasing of video frames
|
||||||
* during rendering.
|
* during rendering.
|
||||||
*
|
*
|
||||||
* <p>Must be called before the sink provider is {@linkplain #initialize(Format) initialized}.
|
* <p>Must be called before the first {@linkplain #getSink() sink} is {@linkplain
|
||||||
|
* VideoSink#initialize(Format) initialized}.
|
||||||
*/
|
*/
|
||||||
void setVideoFrameReleaseControl(VideoFrameReleaseControl videoFrameReleaseControl);
|
void setVideoFrameReleaseControl(VideoFrameReleaseControl videoFrameReleaseControl);
|
||||||
|
|
||||||
@ -49,7 +50,8 @@ public interface VideoSinkProvider {
|
|||||||
/**
|
/**
|
||||||
* Sets the {@link Clock} that the provider should use internally.
|
* Sets the {@link Clock} that the provider should use internally.
|
||||||
*
|
*
|
||||||
* <p>Must be called before the sink provider is {@linkplain #initialize(Format) initialized}.
|
* <p>Must be called before the first {@linkplain #getSink() sink} is {@linkplain
|
||||||
|
* VideoSink#initialize(Format) initialized}.
|
||||||
*/
|
*/
|
||||||
void setClock(Clock clock);
|
void setClock(Clock clock);
|
||||||
|
|
||||||
@ -62,29 +64,12 @@ public interface VideoSinkProvider {
|
|||||||
*/
|
*/
|
||||||
void setPendingVideoEffects(List<Effect> videoEffects);
|
void setPendingVideoEffects(List<Effect> videoEffects);
|
||||||
|
|
||||||
/**
|
/** Returns a {@link VideoSink} to forward video frames for processing. */
|
||||||
* Initializes the provider for video frame processing. Can be called up to one time.
|
|
||||||
*
|
|
||||||
* @param sourceFormat The format of the compressed video.
|
|
||||||
* @throws VideoSink.VideoSinkException If enabling the provider failed.
|
|
||||||
*/
|
|
||||||
void initialize(Format sourceFormat) throws VideoSink.VideoSinkException;
|
|
||||||
|
|
||||||
/** Returns whether this provider is initialized for frame processing. */
|
|
||||||
boolean isInitialized();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a {@link VideoSink} to forward video frames for processing.
|
|
||||||
*
|
|
||||||
* <p>Must be called after the sink provider is {@linkplain #initialize(Format) initialized}.
|
|
||||||
*/
|
|
||||||
VideoSink getSink();
|
VideoSink getSink();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the offset, in microseconds, that is added to the video frames presentation timestamps
|
* Sets the offset, in microseconds, that is added to the video frames presentation timestamps
|
||||||
* from the player.
|
* from the player.
|
||||||
*
|
|
||||||
* <p>Must be called after the sink provider is {@linkplain #initialize(Format) initialized}.
|
|
||||||
*/
|
*/
|
||||||
void setStreamOffsetUs(long streamOffsetUs);
|
void setStreamOffsetUs(long streamOffsetUs);
|
||||||
|
|
||||||
|
@ -51,50 +51,33 @@ public final class CompositingVideoSinkProviderTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void initialize() throws VideoSink.VideoSinkException {
|
public void initializeSink_withoutReleaseControl_throws() {
|
||||||
CompositingVideoSinkProvider provider = createCompositingVideoSinkProvider();
|
CompositingVideoSinkProvider provider =
|
||||||
|
new CompositingVideoSinkProvider.Builder(ApplicationProvider.getApplicationContext())
|
||||||
provider.initialize(new Format.Builder().build());
|
.setPreviewingVideoGraphFactory(new TestPreviewingVideoGraphFactory())
|
||||||
|
.build();
|
||||||
assertThat(provider.isInitialized()).isTrue();
|
VideoSink sink = provider.getSink();
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void initialize_calledTwice_throws() throws VideoSink.VideoSinkException {
|
|
||||||
CompositingVideoSinkProvider provider = createCompositingVideoSinkProvider();
|
|
||||||
provider.initialize(new Format.Builder().build());
|
|
||||||
|
|
||||||
assertThrows(
|
assertThrows(
|
||||||
IllegalStateException.class, () -> provider.initialize(new Format.Builder().build()));
|
IllegalStateException.class,
|
||||||
|
() -> sink.initialize(new Format.Builder().setWidth(640).setHeight(480).build()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void isInitialized_afterRelease_returnsFalse() throws VideoSink.VideoSinkException {
|
public void initializeSink_calledTwice_throws() throws VideoSink.VideoSinkException {
|
||||||
CompositingVideoSinkProvider provider = createCompositingVideoSinkProvider();
|
CompositingVideoSinkProvider provider = createCompositingVideoSinkProvider();
|
||||||
provider.initialize(new Format.Builder().build());
|
VideoSink sink = provider.getSink();
|
||||||
|
sink.initialize(new Format.Builder().build());
|
||||||
|
|
||||||
provider.release();
|
assertThrows(IllegalStateException.class, () -> sink.initialize(new Format.Builder().build()));
|
||||||
|
|
||||||
assertThat(provider.isInitialized()).isFalse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void initialize_afterRelease_throws() throws VideoSink.VideoSinkException {
|
|
||||||
CompositingVideoSinkProvider provider = createCompositingVideoSinkProvider();
|
|
||||||
Format format = new Format.Builder().build();
|
|
||||||
|
|
||||||
provider.initialize(format);
|
|
||||||
provider.release();
|
|
||||||
|
|
||||||
assertThrows(IllegalStateException.class, () -> provider.initialize(format));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void setOutputStreamOffsetUs_frameReleaseTimesAreAdjusted()
|
public void setOutputStreamOffsetUs_frameReleaseTimesAreAdjusted()
|
||||||
throws VideoSink.VideoSinkException {
|
throws VideoSink.VideoSinkException {
|
||||||
CompositingVideoSinkProvider provider = createCompositingVideoSinkProvider();
|
CompositingVideoSinkProvider provider = createCompositingVideoSinkProvider();
|
||||||
provider.initialize(new Format.Builder().build());
|
|
||||||
VideoSink videoSink = provider.getSink();
|
VideoSink videoSink = provider.getSink();
|
||||||
|
videoSink.initialize(new Format.Builder().build());
|
||||||
videoSink.registerInputStream(
|
videoSink.registerInputStream(
|
||||||
VideoSink.INPUT_TYPE_SURFACE, new Format.Builder().setWidth(640).setHeight(480).build());
|
VideoSink.INPUT_TYPE_SURFACE, new Format.Builder().setWidth(640).setHeight(480).build());
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user