/*
* Copyright (c) 2011, 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
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.
/**
* 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<Screen, TextureMapEntry> textures = new WeakHashMap<>(1);
private static Map<Object, PrismMediaFrameHandler> 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.
Params: - g – the Graphics context about to be rendered into
Returns: the current media texture valid for rendering into g
or null if called outside a render cycle
/**
* 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 <code>g</code>
* 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.
/**
* 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.
/**
* 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;
}
}