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) {
103 if (
VERSION.SDK_INT >= API_LEVELS.API_23) {
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 {
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
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)));
249 codec.configure(format, surface,
null, 0);
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();