Commit 2a66c7b8 by hschlueter Committed by Ian Baker

Move FrameProcessorChain OpenGL setup to factory method.

The encoder surface is no longer needed for the OpenGL setup and frame
processor initialization, as a placeholder surface is used instead. So
all of the setup can now be done in the factory method.

PiperOrigin-RevId: 438844450
parent f8f8b755
...@@ -260,7 +260,7 @@ public final class FrameProcessorChainPixelTest { ...@@ -260,7 +260,7 @@ public final class FrameProcessorChainPixelTest {
outputSize.getHeight(), outputSize.getHeight(),
PixelFormat.RGBA_8888, PixelFormat.RGBA_8888,
/* maxImages= */ 1); /* maxImages= */ 1);
frameProcessorChain.configure( frameProcessorChain.setOutputSurface(
outputImageReader.getSurface(), outputImageReader.getSurface(),
outputSize.getWidth(), outputSize.getWidth(),
outputSize.getHeight(), outputSize.getHeight(),
......
...@@ -28,10 +28,9 @@ import org.junit.Test; ...@@ -28,10 +28,9 @@ import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
/** /**
* Robolectric tests for {@link FrameProcessorChain}. * Tests for creating and configuring a {@link FrameProcessorChain}.
* *
* <p>See {@code FrameProcessorChainPixelTest} in the androidTest directory for instrumentation * <p>See {@link FrameProcessorChainPixelTest} for data processing tests.
* tests.
*/ */
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public final class FrameProcessorChainTest { public final class FrameProcessorChainTest {
......
...@@ -54,7 +54,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -54,7 +54,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
* and is processed on a background thread as it becomes available. All input frames should be * and is processed on a background thread as it becomes available. All input frames should be
* {@linkplain #registerInputFrame() registered} before they are rendered to the input surface. * {@linkplain #registerInputFrame() registered} before they are rendered to the input surface.
* {@link #getPendingFrameCount()} can be used to check whether there are frames that have not been * {@link #getPendingFrameCount()} can be used to check whether there are frames that have not been
* fully processed yet. Output is written to its {@linkplain #configure(Surface, int, int, * fully processed yet. Output is written to its {@linkplain #setOutputSurface(Surface, int, int,
* SurfaceView) output surface}. * SurfaceView) output surface}.
*/ */
/* package */ final class FrameProcessorChain { /* package */ final class FrameProcessorChain {
...@@ -73,7 +73,9 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -73,7 +73,9 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
* @param frameProcessors The {@link GlFrameProcessor GlFrameProcessors} to apply to each frame. * @param frameProcessors The {@link GlFrameProcessor GlFrameProcessors} to apply to each frame.
* @param enableExperimentalHdrEditing Whether to attempt to process the input as an HDR signal. * @param enableExperimentalHdrEditing Whether to attempt to process the input as an HDR signal.
* @return A new instance. * @return A new instance.
* @throws TransformationException If the {@code pixelWidthHeightRatio} isn't 1. * @throws TransformationException If the {@code pixelWidthHeightRatio} isn't 1, reading shader
* files fails, or an OpenGL error occurs while creating and configuring the OpenGL
* components.
*/ */
public static FrameProcessorChain create( public static FrameProcessorChain create(
Context context, Context context,
...@@ -93,42 +95,104 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -93,42 +95,104 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
TransformationException.ERROR_CODE_GL_INIT_FAILED); TransformationException.ERROR_CODE_GL_INIT_FAILED);
} }
ExecutorService singleThreadExecutorService = Util.newSingleThreadExecutor(THREAD_NAME);
ExternalCopyFrameProcessor externalCopyFrameProcessor = ExternalCopyFrameProcessor externalCopyFrameProcessor =
new ExternalCopyFrameProcessor(context, enableExperimentalHdrEditing); new ExternalCopyFrameProcessor(context, enableExperimentalHdrEditing);
try {
return singleThreadExecutorService
.submit(
() ->
createOpenGlObjectsAndFrameProcessorChain(
inputWidth,
inputHeight,
frameProcessors,
enableExperimentalHdrEditing,
singleThreadExecutorService,
externalCopyFrameProcessor))
.get();
} catch (ExecutionException e) {
throw TransformationException.createForFrameProcessorChain(
e, TransformationException.ERROR_CODE_GL_INIT_FAILED);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw TransformationException.createForFrameProcessorChain(
e, TransformationException.ERROR_CODE_GL_INIT_FAILED);
}
}
/**
* Creates the OpenGL textures, framebuffers, initializes the {@link GlFrameProcessor
* GlFrameProcessors} and returns a new {@code FrameProcessorChain}.
*
* <p>This method must by executed using the {@code singleThreadExecutorService}.
*/
private static FrameProcessorChain createOpenGlObjectsAndFrameProcessorChain(
int inputWidth,
int inputHeight,
List<GlFrameProcessor> frameProcessors,
boolean enableExperimentalHdrEditing,
ExecutorService singleThreadExecutorService,
ExternalCopyFrameProcessor externalCopyFrameProcessor)
throws IOException {
checkState(Thread.currentThread().getName().equals(THREAD_NAME));
EGLDisplay eglDisplay = GlUtil.createEglDisplay();
EGLContext eglContext =
enableExperimentalHdrEditing
? GlUtil.createEglContextEs3Rgba1010102(eglDisplay)
: GlUtil.createEglContext(eglDisplay);
if (GlUtil.isSurfacelessContextExtensionSupported()) {
GlUtil.focusEglSurface(
eglDisplay, eglContext, EGL14.EGL_NO_SURFACE, /* width= */ 1, /* height= */ 1);
} else if (enableExperimentalHdrEditing) {
// TODO(b/209404935): Don't assume BT.2020 PQ input/output.
GlUtil.focusPlaceholderEglSurfaceBt2020Pq(eglContext, eglDisplay);
} else {
GlUtil.focusPlaceholderEglSurface(eglContext, eglDisplay);
}
int inputExternalTexId = GlUtil.createExternalTexture();
externalCopyFrameProcessor.setInputSize(inputWidth, inputHeight); externalCopyFrameProcessor.setInputSize(inputWidth, inputHeight);
externalCopyFrameProcessor.initialize(inputExternalTexId);
int[] framebuffers = new int[frameProcessors.size()];
Size inputSize = externalCopyFrameProcessor.getOutputSize(); Size inputSize = externalCopyFrameProcessor.getOutputSize();
for (int i = 0; i < frameProcessors.size(); i++) { for (int i = 0; i < frameProcessors.size(); i++) {
int inputTexId = GlUtil.createTexture(inputSize.getWidth(), inputSize.getHeight());
framebuffers[i] = GlUtil.createFboForTexture(inputTexId);
frameProcessors.get(i).setInputSize(inputSize.getWidth(), inputSize.getHeight()); frameProcessors.get(i).setInputSize(inputSize.getWidth(), inputSize.getHeight());
frameProcessors.get(i).initialize(inputTexId);
inputSize = frameProcessors.get(i).getOutputSize(); inputSize = frameProcessors.get(i).getOutputSize();
} }
return new FrameProcessorChain( return new FrameProcessorChain(
externalCopyFrameProcessor, frameProcessors, enableExperimentalHdrEditing); eglDisplay,
eglContext,
singleThreadExecutorService,
inputExternalTexId,
externalCopyFrameProcessor,
framebuffers,
ImmutableList.copyOf(frameProcessors),
enableExperimentalHdrEditing);
} }
private static final String THREAD_NAME = "Transformer:FrameProcessorChain"; private static final String THREAD_NAME = "Transformer:FrameProcessorChain";
private final boolean enableExperimentalHdrEditing; private final boolean enableExperimentalHdrEditing;
private @MonotonicNonNull EGLDisplay eglDisplay; private final EGLDisplay eglDisplay;
private @MonotonicNonNull EGLContext eglContext; private final EGLContext eglContext;
/** Some OpenGL commands may block, so all OpenGL commands are run on a background thread. */ /** Some OpenGL commands may block, so all OpenGL commands are run on a background thread. */
private final ExecutorService singleThreadExecutorService; private final ExecutorService singleThreadExecutorService;
/** Futures corresponding to the executor service's pending tasks. */ /** Futures corresponding to the executor service's pending tasks. */
private final ConcurrentLinkedQueue<Future<?>> futures; private final ConcurrentLinkedQueue<Future<?>> futures;
/** Number of frames {@linkplain #registerInputFrame() registered} but not fully processed. */ /** Number of frames {@linkplain #registerInputFrame() registered} but not fully processed. */
private final AtomicInteger pendingFrameCount; private final AtomicInteger pendingFrameCount;
/** Prevents further frame processing tasks from being scheduled after {@link #release()}. */
private volatile boolean releaseRequested;
private boolean inputStreamEnded;
/** Wraps the {@link #inputSurfaceTexture}. */ /** Wraps the {@link #inputSurfaceTexture}. */
private @MonotonicNonNull Surface inputSurface; private final Surface inputSurface;
/** Associated with an OpenGL external texture. */ /** Associated with an OpenGL external texture. */
private @MonotonicNonNull SurfaceTexture inputSurfaceTexture; private final SurfaceTexture inputSurfaceTexture;
/**
* Identifier of the external texture the {@link ExternalCopyFrameProcessor} reads its input from.
*/
private int inputExternalTexId;
/** Transformation matrix associated with the {@link #inputSurfaceTexture}. */ /** Transformation matrix associated with the {@link #inputSurfaceTexture}. */
private final float[] textureTransformMatrix; private final float[] textureTransformMatrix;
...@@ -158,19 +222,33 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -158,19 +222,33 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
*/ */
private @MonotonicNonNull EGLSurface debugPreviewEglSurface; private @MonotonicNonNull EGLSurface debugPreviewEglSurface;
private boolean inputStreamEnded;
/** Prevents further frame processing tasks from being scheduled after {@link #release()}. */
private volatile boolean releaseRequested;
private FrameProcessorChain( private FrameProcessorChain(
EGLDisplay eglDisplay,
EGLContext eglContext,
ExecutorService singleThreadExecutorService,
int inputExternalTexId,
ExternalCopyFrameProcessor externalCopyFrameProcessor, ExternalCopyFrameProcessor externalCopyFrameProcessor,
List<GlFrameProcessor> frameProcessors, int[] framebuffers,
ImmutableList<GlFrameProcessor> frameProcessors,
boolean enableExperimentalHdrEditing) { boolean enableExperimentalHdrEditing) {
this.eglDisplay = eglDisplay;
this.eglContext = eglContext;
this.singleThreadExecutorService = singleThreadExecutorService;
this.externalCopyFrameProcessor = externalCopyFrameProcessor; this.externalCopyFrameProcessor = externalCopyFrameProcessor;
this.frameProcessors = ImmutableList.copyOf(frameProcessors); this.framebuffers = framebuffers;
this.frameProcessors = frameProcessors;
this.enableExperimentalHdrEditing = enableExperimentalHdrEditing; this.enableExperimentalHdrEditing = enableExperimentalHdrEditing;
singleThreadExecutorService = Util.newSingleThreadExecutor(THREAD_NAME);
futures = new ConcurrentLinkedQueue<>(); futures = new ConcurrentLinkedQueue<>();
pendingFrameCount = new AtomicInteger(); pendingFrameCount = new AtomicInteger();
inputSurfaceTexture = new SurfaceTexture(inputExternalTexId);
inputSurface = new Surface(inputSurfaceTexture);
textureTransformMatrix = new float[16]; textureTransformMatrix = new float[16];
framebuffers = new int[frameProcessors.size()];
outputSize = outputSize =
frameProcessors.isEmpty() frameProcessors.isEmpty()
? externalCopyFrameProcessor.getOutputSize() ? externalCopyFrameProcessor.getOutputSize()
...@@ -185,26 +263,20 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -185,26 +263,20 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
} }
/** /**
* Configures the {@code FrameProcessorChain} to process frames to the specified output targets. * Sets the output {@link Surface}.
* *
* <p>This method may only be called once and may override the {@linkplain * <p>This method may override the output size of the final {@link GlFrameProcessor}.
* GlFrameProcessor#setInputSize(int, int) output size} of the final {@link GlFrameProcessor}.
* *
* @param outputSurface The output {@link Surface}. * @param outputSurface The output {@link Surface}.
* @param outputWidth The output width, in pixels. * @param outputWidth The output width, in pixels.
* @param outputHeight The output height, in pixels. * @param outputHeight The output height, in pixels.
* @param debugSurfaceView Optional debug {@link SurfaceView} to show output. * @param debugSurfaceView Optional debug {@link SurfaceView} to show output.
* @throws IllegalStateException If the {@code FrameProcessorChain} has already been configured.
* @throws TransformationException If reading shader files fails, or an OpenGL error occurs while
* creating and configuring the OpenGL components.
*/ */
public void configure( public void setOutputSurface(
Surface outputSurface, Surface outputSurface,
int outputWidth, int outputWidth,
int outputHeight, int outputHeight,
@Nullable SurfaceView debugSurfaceView) @Nullable SurfaceView debugSurfaceView) {
throws TransformationException {
checkState(inputSurface == null, "The FrameProcessorChain has already been configured.");
// TODO(b/218488308): Don't override output size for encoder fallback. Instead allow the final // TODO(b/218488308): Don't override output size for encoder fallback. Instead allow the final
// GlFrameProcessor to be re-configured or append another GlFrameProcessor. // GlFrameProcessor to be re-configured or append another GlFrameProcessor.
outputSize = new Size(outputWidth, outputHeight); outputSize = new Size(outputWidth, outputHeight);
...@@ -214,21 +286,10 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -214,21 +286,10 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
debugPreviewHeight = debugSurfaceView.getHeight(); debugPreviewHeight = debugSurfaceView.getHeight();
} }
try { futures.add(
// Wait for task to finish to be able to use inputExternalTexId to create the SurfaceTexture. singleThreadExecutorService.submit(
singleThreadExecutorService () -> createOpenGlSurfaces(outputSurface, debugSurfaceView)));
.submit(this::createOpenGlObjectsAndInitializeFrameProcessors)
.get();
} catch (ExecutionException e) {
throw TransformationException.createForFrameProcessorChain(
e, TransformationException.ERROR_CODE_GL_INIT_FAILED);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw TransformationException.createForFrameProcessorChain(
e, TransformationException.ERROR_CODE_GL_INIT_FAILED);
}
inputSurfaceTexture = new SurfaceTexture(inputExternalTexId);
inputSurfaceTexture.setOnFrameAvailableListener( inputSurfaceTexture.setOnFrameAvailableListener(
surfaceTexture -> { surfaceTexture -> {
if (releaseRequested) { if (releaseRequested) {
...@@ -244,21 +305,10 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -244,21 +305,10 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
} }
} }
}); });
inputSurface = new Surface(inputSurfaceTexture);
futures.add(
singleThreadExecutorService.submit(
() -> createOpenGlSurfaces(outputSurface, debugSurfaceView)));
} }
/** /** Returns the input {@link Surface}. */
* Returns the input {@link Surface}.
*
* <p>The {@code FrameProcessorChain} must be {@linkplain #configure(Surface, int, int,
* SurfaceView) configured}.
*/
public Surface getInputSurface() { public Surface getInputSurface() {
checkStateNotNull(inputSurface, "The FrameProcessorChain must be configured.");
return inputSurface; return inputSurface;
} }
...@@ -347,9 +397,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -347,9 +397,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
/** /**
* Creates the OpenGL surfaces. * Creates the OpenGL surfaces.
* *
* <p>This method should only be called after {@link * <p>This method should only be called on the {@linkplain #THREAD_NAME background thread}.
* #createOpenGlObjectsAndInitializeFrameProcessors()} and must be called on the background
* thread.
*/ */
private void createOpenGlSurfaces(Surface outputSurface, @Nullable SurfaceView debugSurfaceView) { private void createOpenGlSurfaces(Surface outputSurface, @Nullable SurfaceView debugSurfaceView) {
checkState(Thread.currentThread().getName().equals(THREAD_NAME)); checkState(Thread.currentThread().getName().equals(THREAD_NAME));
...@@ -372,56 +420,14 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -372,56 +420,14 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
} }
/** /**
* Creates the OpenGL textures and framebuffers, and initializes the {@link GlFrameProcessor
* GlFrameProcessors}.
*
* <p>This method should only be called on the background thread.
*/
private Void createOpenGlObjectsAndInitializeFrameProcessors() throws IOException {
checkState(Thread.currentThread().getName().equals(THREAD_NAME));
eglDisplay = GlUtil.createEglDisplay();
eglContext =
enableExperimentalHdrEditing
? GlUtil.createEglContextEs3Rgba1010102(eglDisplay)
: GlUtil.createEglContext(eglDisplay);
if (GlUtil.isSurfacelessContextExtensionSupported()) {
GlUtil.focusEglSurface(
eglDisplay, eglContext, EGL14.EGL_NO_SURFACE, /* width= */ 1, /* height= */ 1);
} else if (enableExperimentalHdrEditing) {
// TODO(b/209404935): Don't assume BT.2020 PQ input/output.
GlUtil.focusPlaceholderEglSurfaceBt2020Pq(eglContext, eglDisplay);
} else {
GlUtil.focusPlaceholderEglSurface(eglContext, eglDisplay);
}
inputExternalTexId = GlUtil.createExternalTexture();
externalCopyFrameProcessor.initialize(inputExternalTexId);
Size intermediateSize = externalCopyFrameProcessor.getOutputSize();
for (int i = 0; i < frameProcessors.size(); i++) {
int inputTexId =
GlUtil.createTexture(intermediateSize.getWidth(), intermediateSize.getHeight());
framebuffers[i] = GlUtil.createFboForTexture(inputTexId);
frameProcessors.get(i).initialize(inputTexId);
intermediateSize = frameProcessors.get(i).getOutputSize();
}
// Return something because only Callables not Runnables can throw checked exceptions.
return null;
}
/**
* Processes an input frame. * Processes an input frame.
* *
* <p>This method should only be called on the background thread. * <p>This method should only be called on the {@linkplain #THREAD_NAME background thread}.
*/ */
@RequiresNonNull("inputSurfaceTexture") @RequiresNonNull("inputSurfaceTexture")
private void processFrame() { private void processFrame() {
checkState(Thread.currentThread().getName().equals(THREAD_NAME)); checkState(Thread.currentThread().getName().equals(THREAD_NAME));
checkStateNotNull(eglSurface); checkStateNotNull(eglSurface, "No output surface set.");
checkStateNotNull(eglContext);
checkStateNotNull(eglDisplay);
if (frameProcessors.isEmpty()) { if (frameProcessors.isEmpty()) {
GlUtil.focusEglSurface( GlUtil.focusEglSurface(
......
...@@ -116,7 +116,7 @@ import org.checkerframework.dataflow.qual.Pure; ...@@ -116,7 +116,7 @@ import org.checkerframework.dataflow.qual.Pure;
requestedEncoderFormat, requestedEncoderFormat,
encoderSupportedFormat)); encoderSupportedFormat));
frameProcessorChain.configure( frameProcessorChain.setOutputSurface(
/* outputSurface= */ encoder.getInputSurface(), /* outputSurface= */ encoder.getInputSurface(),
/* outputWidth= */ encoderSupportedFormat.width, /* outputWidth= */ encoderSupportedFormat.width,
/* outputHeight= */ encoderSupportedFormat.height, /* outputHeight= */ encoderSupportedFormat.height,
......
...@@ -114,13 +114,19 @@ public class DefaultEncoderFactoryTest { ...@@ -114,13 +114,19 @@ public class DefaultEncoderFactoryTest {
@Test @Test
public void createForVideoEncoding_withNoSupportedEncoder_throws() { public void createForVideoEncoding_withNoSupportedEncoder_throws() {
Format requestedVideoFormat = createVideoFormat(MimeTypes.VIDEO_H264, 1920, 1080, 30); Format requestedVideoFormat = createVideoFormat(MimeTypes.VIDEO_H264, 1920, 1080, 30);
assertThrows(
TransformationException.class, TransformationException exception =
() -> assertThrows(
new DefaultEncoderFactory() TransformationException.class,
.createForVideoEncoding( () ->
requestedVideoFormat, new DefaultEncoderFactory()
/* allowedMimeTypes= */ ImmutableList.of(MimeTypes.VIDEO_H265))); .createForVideoEncoding(
requestedVideoFormat,
/* allowedMimeTypes= */ ImmutableList.of(MimeTypes.VIDEO_H265)));
assertThat(exception).hasCauseThat().isInstanceOf(IllegalArgumentException.class);
assertThat(exception.errorCode)
.isEqualTo(TransformationException.ERROR_CODE_OUTPUT_FORMAT_UNSUPPORTED);
} }
@Test @Test
......
...@@ -30,7 +30,6 @@ import static org.mockito.Mockito.times; ...@@ -30,7 +30,6 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import android.content.Context; import android.content.Context;
import android.media.MediaCodecInfo;
import android.media.MediaCrypto; import android.media.MediaCrypto;
import android.media.MediaFormat; import android.media.MediaFormat;
import android.os.Handler; import android.os.Handler;
...@@ -406,26 +405,6 @@ public final class TransformerEndToEndTest { ...@@ -406,26 +405,6 @@ public final class TransformerEndToEndTest {
} }
@Test @Test
public void startTransformation_withVideoEncoderFormatUnsupported_completesWithError()
throws Exception {
Transformer transformer =
createTransformerBuilder(/* enableFallback= */ false)
.setTransformationRequest(
new TransformationRequest.Builder()
.setVideoMimeType(MimeTypes.VIDEO_H263) // unsupported encoder MIME type
.build())
.build();
MediaItem mediaItem = MediaItem.fromUri(URI_PREFIX + FILE_VIDEO_ONLY);
transformer.startTransformation(mediaItem, outputPath);
TransformationException exception = TransformerTestRunner.runUntilError(transformer);
assertThat(exception).hasCauseThat().isInstanceOf(IllegalArgumentException.class);
assertThat(exception.errorCode)
.isEqualTo(TransformationException.ERROR_CODE_OUTPUT_FORMAT_UNSUPPORTED);
}
@Test
public void startTransformation_withIoError_completesWithError() throws Exception { public void startTransformation_withIoError_completesWithError() throws Exception {
Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build(); Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build();
MediaItem mediaItem = MediaItem.fromUri("asset:///non-existing-path.mp4"); MediaItem mediaItem = MediaItem.fromUri("asset:///non-existing-path.mp4");
...@@ -802,11 +781,6 @@ public final class TransformerEndToEndTest { ...@@ -802,11 +781,6 @@ public final class TransformerEndToEndTest {
/* colorFormats= */ ImmutableList.of(), /* colorFormats= */ ImmutableList.of(),
/* isDecoder= */ true); /* isDecoder= */ true);
addCodec( addCodec(
MimeTypes.VIDEO_H263,
throwingCodecConfig,
ImmutableList.of(MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible),
/* isDecoder= */ false);
addCodec(
MimeTypes.AUDIO_AMR_NB, MimeTypes.AUDIO_AMR_NB,
throwingCodecConfig, throwingCodecConfig,
/* colorFormats= */ ImmutableList.of(), /* colorFormats= */ ImmutableList.of(),
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment