/* * To change this template, choose Tools | Templates * and open the template in the editor. */ package webcamstudio.mixers; import static java.awt.AlphaComposite.SRC_OVER; import static java.awt.AlphaComposite.getInstance; import java.awt.BasicStroke; import static java.awt.BasicStroke.CAP_BUTT; import static java.awt.BasicStroke.JOIN_MITER; import java.awt.Graphics2D; import java.awt.Image; import static java.awt.RenderingHints.KEY_ALPHA_INTERPOLATION; import static java.awt.RenderingHints.KEY_COLOR_RENDERING; import static java.awt.RenderingHints.KEY_DITHERING; import static java.awt.RenderingHints.KEY_FRACTIONALMETRICS; import static java.awt.RenderingHints.KEY_INTERPOLATION; import static java.awt.RenderingHints.KEY_RENDERING; import static java.awt.RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED; import static java.awt.RenderingHints.VALUE_COLOR_RENDER_SPEED; import static java.awt.RenderingHints.VALUE_DITHER_DISABLE; import static java.awt.RenderingHints.VALUE_FRACTIONALMETRICS_OFF; import static java.awt.RenderingHints.VALUE_INTERPOLATION_BILINEAR; import static java.awt.RenderingHints.VALUE_RENDER_SPEED; import java.awt.geom.RoundRectangle2D; import java.awt.image.BufferedImage; import static java.lang.Short.MAX_VALUE; import static java.lang.Short.MIN_VALUE; import static java.lang.System.currentTimeMillis; import static java.nio.ByteBuffer.wrap; import java.nio.ShortBuffer; import java.util.ArrayList; import java.util.Collection; import java.util.TreeMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import static java.util.concurrent.Executors.newCachedThreadPool; import java.util.concurrent.Future; import static java.util.concurrent.TimeUnit.SECONDS; import java.util.logging.Level; import java.util.logging.Logger; import webcamstudio.streams.SourceDesktop; import webcamstudio.streams.Stream; import static webcamstudio.util.Tools.sleep; /** * * @author patrick (modified by karl) */ public class PreviewFrameBuilder implements Runnable { private static final ArrayList<Stream> preStreams = new ArrayList<>();// add private private static int fps = 0; private static int sRate = 0; public static synchronized void register(Stream s) { if (!preStreams.contains(s)) { preStreams.add(s); // System.out.println("Register Preview Stream Size: "+preStreams.size()); if (s instanceof SourceDesktop) { sRate = s.getRate(); } s.setRate(PreviewMixer.getInstance().getRate()); } } public static synchronized void unregister(Stream s) { preStreams.remove(s); // System.out.println("UnRegister Preview Stream Size: "+preStreams.size()); if (s instanceof SourceDesktop) { s.setRate(sRate); } else { s.setRate(MasterMixer.getInstance().getRate()); } } private Image imageF; private int imageX, imageY, imageW, imageH; private boolean stopMe = false; private long mark = currentTimeMillis(); private FrameBuffer frameBuffer = null; private final TreeMap<Integer, Frame> orderedFrames = new TreeMap<>(); public PreviewFrameBuilder(int w, int h, int r) { frameBuffer = new FrameBuffer(w, h, r); } public void stop() { stopMe = true; } private void mixImages(Collection<Frame> frames, Frame targetFrame) { for (Frame f : frames) { orderedFrames.put(f.getZOrder(), f); } BufferedImage image = targetFrame.getImage(); if (image != null) { Graphics2D g = image.createGraphics(); g.setRenderingHint(KEY_INTERPOLATION, VALUE_INTERPOLATION_BILINEAR); g.setRenderingHint(KEY_ALPHA_INTERPOLATION, VALUE_ALPHA_INTERPOLATION_SPEED); g.setRenderingHint(KEY_RENDERING, VALUE_RENDER_SPEED); g.setRenderingHint(KEY_FRACTIONALMETRICS, VALUE_FRACTIONALMETRICS_OFF); g.setRenderingHint(KEY_DITHERING, VALUE_DITHER_DISABLE); g.setRenderingHint(KEY_COLOR_RENDERING, VALUE_COLOR_RENDER_SPEED); g.clearRect(0, 0, image.getWidth(), image.getHeight()); final float dash1[] = {10.0f}; final BasicStroke dashed = new BasicStroke(5.0f, CAP_BUTT, JOIN_MITER, 10.0f, dash1, 0.0f); g.setStroke(dashed); g.draw(new RoundRectangle2D.Double(0, 0, image.getWidth()/2, image.getHeight(), 10, 10)); g.draw(new RoundRectangle2D.Double(image.getWidth()/2, 0, image.getWidth()/2, image.getHeight(), 10, 10)); g.draw(new RoundRectangle2D.Double(0, 0, image.getWidth(), image.getHeight()/2, 10, 10)); for (Frame f : orderedFrames.values()) { imageF = f.getImage(); imageX = f.getX(); imageY = f.getY(); imageW = f.getWidth(); imageH = f.getHeight(); g.setComposite(getInstance(SRC_OVER, f.getOpacity() / 100F)); g.drawImage(imageF, imageX, imageY, imageW, imageH, null); } g.dispose(); } orderedFrames.clear(); } private void mixAudio(Collection<Frame> frames, Frame targetFrame) { byte[] audioData = targetFrame.getAudioData(); ShortBuffer outputBuffer = wrap(audioData).asShortBuffer(); for (int i = 0; i < audioData.length; i++) { audioData[i] = 0; } for (Frame f : frames) { byte[] data = f.getAudioData(); if (data != null) { ShortBuffer buffer = wrap(data).asShortBuffer(); outputBuffer.rewind(); while (buffer.hasRemaining()) { float mix = buffer.get() * f.getVolume(); outputBuffer.mark(); if (outputBuffer.position()< outputBuffer.limit()){ //25fps IOException mix += outputBuffer.get(); } outputBuffer.reset(); if (mix > MAX_VALUE) { mix = MAX_VALUE; } else if (mix < MIN_VALUE) { mix = MIN_VALUE; } if (outputBuffer.position()< outputBuffer.limit()){ //25fps IOException outputBuffer.put((short) mix); } } f.setAudio(null); } } } @SuppressWarnings("unchecked") @Override public void run() throws NullPointerException{ stopMe = false; ArrayList<Frame> frames = new ArrayList<>(); ArrayList<Future<Frame>> resultsT = new ArrayList<>(); mark = System.currentTimeMillis(); int r = PreviewMixer.getInstance().getRate(); long frameDelay = 1000 / r; long timeCode = currentTimeMillis(); ExecutorService pool = newCachedThreadPool(); while (!stopMe) { timeCode += frameDelay; Frame targetFrame = frameBuffer.getFrameToUpdate(); frames.clear(); try { resultsT = ((ArrayList) pool.invokeAll(preStreams, 5, SECONDS)); //modify to 10 give more time to pause ArrayList<Future<Frame>> results = resultsT; int i=0; Frame f; for (Future stream : results) { if ((Frame)stream.get() != null) { if (!preStreams.isEmpty()) { f = (Frame)stream.get(); frames.add(f); } } i++; } mixAudio(frames, targetFrame); mixImages(frames, targetFrame); targetFrame = null; frameBuffer.doneUpdate(); PreviewMixer.getInstance().setCurrentFrame(frameBuffer.pop()); fps++; float delta = currentTimeMillis() - mark; if (delta >= 1000) { mark = System.currentTimeMillis(); PreviewMixer.getInstance().setFPS(fps / (delta / 1000F)); fps = 0; } long sleepTime = timeCode - currentTimeMillis(); if (sleepTime > 0) { sleep(sleepTime + 10); } } catch (InterruptedException | ExecutionException ex) { Logger.getLogger(MasterFrameBuilder.class.getName()).log(Level.SEVERE, null, ex); } } } }