/* * Copyright (c) 2011, 2015, Oracle and/or its affiliates. All rights reserved. * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. * * * * * * * * * * * * * * * * * * * * */ package com.sun.javafx.media; import java.nio.ByteBuffer; import java.util.Map; import java.util.WeakHashMap; import com.sun.glass.ui.Screen; import com.sun.javafx.tk.RenderJob; import com.sun.javafx.tk.Toolkit; import com.sun.media.jfxmedia.control.VideoDataBuffer; import com.sun.media.jfxmedia.control.VideoFormat; import com.sun.prism.Graphics; import com.sun.prism.GraphicsPipeline; import com.sun.prism.MediaFrame; import com.sun.prism.PixelFormat; import com.sun.prism.ResourceFactoryListener; import com.sun.prism.Texture; /** * Prism-specific subclass for handling media frames. The frames uploaded * into per-Screen textures, updated on every frame. * */ public class PrismMediaFrameHandler implements ResourceFactoryListener { private final Map textures = new WeakHashMap<>(1); private static Map handlers; public synchronized static PrismMediaFrameHandler getHandler(Object provider) { if (provider == null) { throw new IllegalArgumentException("provider must be non-null"); } if (handlers == null) { handlers = new WeakHashMap<>(1); } PrismMediaFrameHandler ret = handlers.get(provider); if (ret == null) { ret = new PrismMediaFrameHandler(provider); handlers.put(provider, ret); } return ret; } private boolean registeredWithFactory = false; private PrismMediaFrameHandler(Object provider) { } /* NOTE: The following methods will only ever happen on one thread, so thread * safety should not be a concern here. */ /** * This should only ever be called during a render cycle. Any other time it * will return null. Note that a returned texture should be unlocked when * the caller no longer needs it. * * @param g the Graphics context about to be rendered into * @return the current media texture valid for rendering into g * or null if called outside a render cycle */ public Texture getTexture(Graphics g, VideoDataBuffer currentFrame) { Screen screen = g.getAssociatedScreen(); TextureMapEntry tme = textures.get(screen); if (null == currentFrame) { // null frame, remove the existing texture if (textures.containsKey(screen)) { textures.remove(screen); } return null; } if (null == tme) { // we need to create a new texture for this graphics context tme = new TextureMapEntry(); textures.put(screen, tme); } if (tme.texture != null) { tme.texture.lock(); if (tme.texture.isSurfaceLost()) { tme.texture = null; } } // check if it needs to be updated if (null == tme.texture || tme.lastFrameTime != currentFrame.getTimestamp()) { updateTexture(g, currentFrame, tme); } return tme.texture; } private void updateTexture(Graphics g, VideoDataBuffer vdb, TextureMapEntry tme) { Screen screen = g.getAssociatedScreen(); // reset texture if the encoded size changes if (tme.texture != null && (tme.encodedWidth != vdb.getEncodedWidth() || tme.encodedHeight != vdb.getEncodedHeight())) { tme.texture.dispose(); tme.texture = null; } PrismFrameBuffer prismBuffer = new PrismFrameBuffer(vdb); if (tme.texture == null) { if (!registeredWithFactory) { // make sure we've registered with the resource factory so we know // when to purge old textures GraphicsPipeline.getDefaultResourceFactory().addFactoryListener(this); registeredWithFactory = true; } tme.texture = GraphicsPipeline.getPipeline(). getResourceFactory(screen). createTexture(prismBuffer); tme.encodedWidth = vdb.getEncodedWidth(); tme.encodedHeight = vdb.getEncodedHeight(); } // upload frame data, check for null in case createTexture fails if (tme.texture != null) { tme.texture.update(prismBuffer, false); } tme.lastFrameTime = vdb.getTimestamp(); } private void releaseData() { for (TextureMapEntry tme : textures.values()) { if (tme != null && tme.texture != null) { tme.texture.dispose(); } } textures.clear(); } private final RenderJob releaseRenderJob = new RenderJob(() -> { releaseData(); }); /** * Call this when you no longer need to render movie frames, for example * when playback stops. */ public void releaseTextures() { Toolkit tk = Toolkit.getToolkit(); tk.addRenderJob(releaseRenderJob); } public void factoryReset() { releaseData(); } public void factoryReleased() { releaseData(); } /** * Bridge class to avoid having to import JFXMedia into a bunch of prism * code. */ private class PrismFrameBuffer implements MediaFrame { private final PixelFormat videoFormat; private final VideoDataBuffer master; public PrismFrameBuffer(VideoDataBuffer sourceBuffer) { if (null == sourceBuffer) { throw new NullPointerException(); } master = sourceBuffer; switch (master.getFormat()) { case BGRA_PRE: videoFormat = PixelFormat.INT_ARGB_PRE; break; case YCbCr_420p: videoFormat = PixelFormat.MULTI_YCbCr_420; break; case YCbCr_422: videoFormat = PixelFormat.BYTE_APPLE_422; break; // ARGB isn't supported in prism, there's no corresponding PixelFormat case ARGB: default: throw new IllegalArgumentException("Unsupported video format "+master.getFormat()); } } @Override public ByteBuffer getBufferForPlane(int plane) { return master.getBufferForPlane(plane); } @Override public void holdFrame() { master.holdFrame(); } @Override public void releaseFrame() { master.releaseFrame(); } @Override public PixelFormat getPixelFormat() { return videoFormat; } @Override public int getWidth() { return master.getWidth(); } @Override public int getHeight() { return master.getHeight(); } @Override public int getEncodedWidth() { return master.getEncodedWidth(); } @Override public int getEncodedHeight() { return master.getEncodedHeight(); } @Override public int planeCount() { return master.getPlaneCount(); } @Override public int[] planeStrides() { return master.getPlaneStrides(); } @Override public int strideForPlane(int planeIndex) { return master.getStrideForPlane(planeIndex); } @Override public MediaFrame convertToFormat(PixelFormat fmt) { if (fmt == getPixelFormat()) { return this; } // This method only supports conversion to INT_ARGB_PRE currently if (fmt != PixelFormat.INT_ARGB_PRE) { return null; } VideoDataBuffer newVDB = master.convertToFormat(VideoFormat.BGRA_PRE); if (null == newVDB) { return null; } return new PrismFrameBuffer(newVDB); } } private static class TextureMapEntry { public double lastFrameTime = -1; // used to determine if we need to update public Texture texture; public int encodedWidth; public int encodedHeight; } }