5package dev.flutter.scenarios;
7import static io.flutter.Build.API_LEVELS;
9import android.content.res.AssetFileDescriptor;
11import android.graphics.ImageFormat;
12import android.graphics.LinearGradient;
14import android.graphics.Shader.TileMode;
15import android.hardware.HardwareBuffer;
17import android.media.ImageReader;
18import android.media.ImageWriter;
20import android.media.MediaExtractor;
21import android.media.MediaFormat;
29import android.view.SurfaceHolder;
32import android.widget.FrameLayout;
33import android.widget.FrameLayout.LayoutParams;
34import androidx.annotation.NonNull;
35import androidx.annotation.Nullable;
36import androidx.annotation.RequiresApi;
37import androidx.core.util.Supplier;
38import io.flutter.view.TextureRegistry;
39import java.io.IOException;
40import java.nio.ByteBuffer;
42import java.util.Objects;
43import java.util.concurrent.CountDownLatch;
46 static final String
TAG =
"Scenarios";
47 private static final int SURFACE_WIDTH = 192;
48 private static final int SURFACE_HEIGHT = 256;
50 private SurfaceRenderer flutterRenderer;
53 private final CountDownLatch firstFrameLatch =
new CountDownLatch(1);
55 private long textureId = 0;
56 private TextureRegistry.SurfaceProducer surfaceProducer;
59 protected void onCreate(@Nullable Bundle savedInstanceState) {
60 super.onCreate(savedInstanceState);
62 String surfaceRenderer = getIntent().getStringExtra(
"surface_renderer");
63 assert surfaceRenderer !=
null;
64 flutterRenderer = selectSurfaceRenderer(surfaceRenderer);
67 SurfaceView surfaceView =
new SurfaceView(
getContext());
68 surfaceView.setZOrderMediaOverlay(
true);
69 surfaceView.setMinimumWidth(SURFACE_WIDTH);
70 surfaceView.setMinimumHeight(SURFACE_HEIGHT);
72 FrameLayout frameLayout =
new FrameLayout(
getContext());
76 ViewGroup.LayoutParams.WRAP_CONTENT,
77 ViewGroup.LayoutParams.WRAP_CONTENT,
78 Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL));
82 new ViewGroup.LayoutParams(
83 ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
85 SurfaceHolder surfaceHolder = surfaceView.getHolder();
86 surfaceHolder.setFixedSize(SURFACE_WIDTH, SURFACE_HEIGHT);
91 super.waitUntilFlutterRendered();
94 firstFrameLatch.await();
95 }
catch (InterruptedException
e) {
96 throw new RuntimeException(
e);
100 private SurfaceRenderer selectSurfaceRenderer(String surfaceRenderer) {
101 switch (surfaceRenderer) {
106 return new ImageSurfaceRenderer(selectSurfaceRenderer(
"media"));
108 throw new RuntimeException(
"ImageSurfaceRenderer not supported");
111 return new MediaSurfaceRenderer(this::createMediaExtractor);
114 return new CanvasSurfaceRenderer();
118 private MediaExtractor createMediaExtractor() {
123 MediaExtractor extractor =
new MediaExtractor();
124 try (AssetFileDescriptor afd = getAssets().openFd(
"sample.mp4")) {
125 extractor.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
128 }
catch (IOException
e) {
130 throw new RuntimeException(
e);
136 flutterRenderer.destroy();
137 surfaceProducer.release();
144 Objects.requireNonNull(
getFlutterEngine()).getRenderer().createSurfaceProducer();
145 surfaceProducer.setSize(SURFACE_WIDTH, SURFACE_HEIGHT);
146 flutterRenderer.attach(surfaceProducer.getSurface(), firstFrameLatch);
147 flutterRenderer.repaint();
148 textureId = surfaceProducer.id();
150 super.onFlutterUiDisplayed();
155 super.getScenarioParams(
args);
156 args.put(
"texture_id", textureId);
157 args.put(
"texture_width", SURFACE_WIDTH);
158 args.put(
"texture_height", SURFACE_HEIGHT);
161 private interface SurfaceRenderer {
162 void attach(Surface
surface, CountDownLatch onFirstFrame);
170 private static class CanvasSurfaceRenderer
implements SurfaceRenderer {
172 private CountDownLatch onFirstFrame;
174 protected CanvasSurfaceRenderer() {}
177 public void attach(Surface
surface, CountDownLatch onFirstFrame) {
179 this.onFirstFrame = onFirstFrame;
183 public void repaint() {
185 VERSION.SDK_INT >= API_LEVELS.API_23
188 Paint
paint =
new Paint();
205 canvas.drawPaint(
paint);
206 surface.unlockCanvasAndPost(canvas);
208 if (onFirstFrame !=
null) {
209 onFirstFrame.countDown();
219 private static class MediaSurfaceRenderer
implements SurfaceRenderer {
220 private final Supplier<MediaExtractor> extractorSupplier;
221 private CountDownLatch onFirstFrame;
224 private MediaExtractor extractor;
225 private MediaFormat
format;
226 private Thread decodeThread;
228 protected MediaSurfaceRenderer(Supplier<MediaExtractor> extractorSupplier) {
229 this.extractorSupplier = extractorSupplier;
233 public void attach(Surface
surface, CountDownLatch onFirstFrame) {
235 this.onFirstFrame = onFirstFrame;
237 extractor = extractorSupplier.get();
238 format = extractor.getTrackFormat(0);
240 decodeThread =
new Thread(this::decodeThreadMain);
241 decodeThread.start();
244 private void decodeThreadMain() {
247 MediaCodec.createDecoderByType(
248 Objects.requireNonNull(
format.getString(MediaFormat.KEY_MIME)));
253 extractor.selectTrack(0);
255 MediaCodec.BufferInfo bufferInfo =
new MediaCodec.BufferInfo();
256 boolean seenEOS =
false;
257 long startTimeNs = System.nanoTime();
264 int inputBufferIndex = codec.dequeueInputBuffer(-1);
265 ByteBuffer inputBuffer = codec.getInputBuffer(inputBufferIndex);
266 assert inputBuffer !=
null;
267 int sampleSize = extractor.readSampleData(inputBuffer, 0);
268 if (sampleSize >= 0) {
269 long presentationTimeUs = extractor.getSampleTime();
270 codec.queueInputBuffer(inputBufferIndex, 0, sampleSize, presentationTimeUs, 0);
273 codec.queueInputBuffer(
274 inputBufferIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
281 int outputBufferIndex = codec.dequeueOutputBuffer(bufferInfo, 10000);
282 boolean lastBuffer = (bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
283 if (outputBufferIndex >= 0) {
284 if (bufferInfo.size > 0) {
285 if (onFirstFrame !=
null) {
286 onFirstFrame.countDown();
289 Log.w(
TAG,
"Presenting frame " + frameCount);
292 codec.releaseOutputBuffer(
293 outputBufferIndex, startTimeNs + (bufferInfo.presentationTimeUs * 1000));
306 }
catch (IOException
e) {
308 throw new RuntimeException(
e);
313 public void repaint() {}
319 }
catch (InterruptedException
e) {
321 throw new RuntimeException(
e);
330 @RequiresApi(API_LEVELS.API_23)
331 private static class ImageSurfaceRenderer implements SurfaceRenderer {
332 private final SurfaceRenderer inner;
333 private CountDownLatch onFirstFrame;
334 private ImageReader reader;
335 private ImageWriter writer;
337 private Handler handler;
338 private HandlerThread handlerThread;
340 private boolean canReadImage =
true;
341 private boolean canWriteImage =
true;
343 protected ImageSurfaceRenderer(SurfaceRenderer inner) {
348 public void attach(Surface
surface, CountDownLatch onFirstFrame) {
349 this.onFirstFrame = onFirstFrame;
350 if (
VERSION.SDK_INT >= API_LEVELS.API_29) {
354 writer = ImageWriter.newInstance(
surface, 3, ImageFormat.PRIVATE);
356 ImageReader.newInstance(
361 HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE);
364 writer = ImageWriter.newInstance(
surface, 3);
365 reader = ImageReader.newInstance(SURFACE_WIDTH, SURFACE_HEIGHT, writer.getFormat(), 2);
367 inner.attach(reader.getSurface(),
null);
369 handlerThread =
new HandlerThread(
"image reader/writer thread");
370 handlerThread.start();
372 handler =
new Handler(handlerThread.getLooper());
373 reader.setOnImageAvailableListener(this::onImageAvailable, handler);
374 writer.setOnImageReleasedListener(this::onImageReleased, handler);
377 private void onImageAvailable(ImageReader reader) {
378 Log.v(
TAG,
"Image available");
380 if (!canWriteImage) {
389 canReadImage =
false;
392 canWriteImage =
false;
393 writer.queueInputImage(
image);
394 }
catch (IllegalStateException
e) {
398 Log.i(
TAG,
"Surface disconnected from ImageWriter",
e);
402 Log.v(
TAG,
"Output image");
404 if (onFirstFrame !=
null) {
405 onFirstFrame.countDown();
410 private void tryAcquireImage() {
412 onImageAvailable(reader);
416 private void onImageReleased(ImageWriter imageWriter) {
417 Log.v(
TAG,
"Image released");
419 if (!canWriteImage) {
420 canWriteImage =
true;
424 handler.post(this::tryAcquireImage);
430 public void repaint() {
436 Log.i(
TAG,
"Destroying ImageSurfaceRenderer");
438 handler.post(this::destroyReaderWriter);
441 private void destroyReaderWriter() {
443 Log.i(
TAG,
"ImageWriter destroyed");
445 Log.i(
TAG,
"ImageReader destroyed");
446 handlerThread.quitSafely();
void waitUntilFlutterRendered()
void getScenarioParams(@NonNull Map< String, Object > args)
void onCreate(@Nullable Bundle savedInstanceState)
void onFlutterUiDisplayed()
FlutterEngine getFlutterEngine()
G_BEGIN_DECLS G_MODULE_EXPORT FlValue * args
uint32_t uint32_t * format
sk_sp< const SkImage > image
void Log(const char *format,...) SK_PRINTF_LIKE(1
SkTileMode TileMode(jint tm)
SK_API sk_sp< PrecompileShader > LinearGradient()