bytedeco/javacv

Player is not smooth, Frame are skipping

Open

#1,427 opened on May 11, 2020

View on GitHub
 (1 comment) (0 reactions) (0 assignees)Java (1,583 forks)batch import
help wantedquestion

Repository metrics

Stars
 (6,985 stars)
PR merge metrics
 (No merged PRs in 30d)

Description

I have created a simple player with FFMPEGFrameGrabber on normal videos its working fine, but on few high quality videos its stucking.

I have attached the src and the videos to replicate the issue.

`package com.mi6.videoviewer;

import java.awt.image.BufferedImage; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.nio.ByteBuffer; import java.nio.ShortBuffer; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit;

import javax.sound.sampled.AudioFormat; import javax.sound.sampled.AudioSystem; import javax.sound.sampled.DataLine; import javax.sound.sampled.LineUnavailableException; import javax.sound.sampled.SourceDataLine;

import org.bytedeco.ffmpeg.global.avutil; import org.bytedeco.javacv.FFmpegFrameGrabber; import org.bytedeco.javacv.Frame; import org.bytedeco.javacv.FrameGrabber.Exception; import org.bytedeco.javacv.Java2DFrameConverter; import org.slf4j.Logger; import org.slf4j.LoggerFactory;

public class FFmpegPlayer extends Thread implements PlayerControls{

final Logger log = LoggerFactory.getLogger(FFmpegPlayer.class);

private FFmpegFrameGrabber grabber;

private final Java2DFrameConverter converter;

private AudioFormat audioFormat;

private PropertyChangeSupport listener;

private ExecutorService executor;

private SourceDataLine soundLine = null;

private boolean audio = true;

public FFmpegPlayer() {
	
	avutil.av_log_set_level(avutil.AV_LOG_TRACE);

	converter = new Java2DFrameConverter();
	
	listener = new PropertyChangeSupport(this);
	
	executor = Executors.newSingleThreadExecutor();
}

public void setVideo(String videoFilename) {
	grabber = new FFmpegFrameGrabber(videoFilename);
}

public void addPropertyChangeListener(PropertyChangeListener l) {
	listener.addPropertyChangeListener(l);
}

public void removePropertyChangeListener(PropertyChangeListener l) {
	listener.removePropertyChangeListener(l);
}

@Override
public void run() {
	
	try {
		
		grabber.start();
		
	} catch (Exception e) {
		
		log.error("Cannot Start Grabber", e);
		
		endVideo();
		
		return;
	}

	audioFormat = new AudioFormat(grabber.getSampleRate(), 16, grabber.getAudioChannels(), true, true);

	final DataLine.Info info = new DataLine.Info(SourceDataLine.class, audioFormat);
	
	try {
		
		soundLine = (SourceDataLine) AudioSystem.getLine(info);
		
		soundLine.open(audioFormat);
		
		soundLine.start();
		
	} catch (LineUnavailableException e) {
		
		log.error("No Audio Found", e);
		
	} catch(IllegalArgumentException e) {
		
		log.error("No Audio Found", e);
		
	}

	
	while (!Thread.interrupted()) {

		Frame frame = null;
		
		try {
			frame = grabber.grabFrame(true, true, true, false, true);
		} catch (Exception e) {
			log.error("No Frame Found", e);
		}

		if (frame == null) {
			break;
		}

		if (frame.image != null) {

			BufferedImage image = converter.convert(frame);
			
			listener.firePropertyChange("Image", null, image);

		} else if (frame.samples != null && audio) {

			final ShortBuffer channelSamplesShortBuffer = (ShortBuffer) frame.samples[0];

			channelSamplesShortBuffer.rewind();

			final ByteBuffer outBuffer = ByteBuffer.allocate(channelSamplesShortBuffer.capacity() * 2);

			for (int i = 0; i < channelSamplesShortBuffer.capacity(); i++) {

				short val = channelSamplesShortBuffer.get(i);

				outBuffer.putShort(val);

			}

			try {

				if(soundLine == null)
					break;
				
				executor.submit(new Runnable() {
					public void run() {

						soundLine.write(outBuffer.array(), 0, outBuffer.capacity());

						outBuffer.clear();

					}
				}).get();

			} catch (InterruptedException interruptedException) {

				Thread.currentThread().interrupt();

			} catch (ExecutionException e) {
				
				e.printStackTrace();
				
			} catch (NullPointerException e) {
				
				break;
				
			}
			
		}
		
	}

	executor.shutdownNow();

	try {
		executor.awaitTermination(10, TimeUnit.SECONDS);
	} catch (InterruptedException e) {
		log.error("Cannot stop the sound thread", e);
	}

	if(soundLine!=null)
		soundLine.stop();

	try {
		grabber.stop();
	} catch (Exception e) {
		log.error("Cannot stop grabber", e);
	}

	try {
		grabber.release();
	} catch (Exception e) {
		log.error("Cannot release grabber", e);
	}
	
	endVideo();
	
}

public void endVideo() {
	listener.firePropertyChange("Stop", null, null);
}

public void playVideo() {
	start();
	
}

public void muteVideo() {
	audio = !audio;

}

public void stopVideo() {
	interrupt();
}

} `

`package com.mi6.videoviewer;

import java.awt.Dimension; import java.awt.EventQueue; import java.awt.Graphics; import java.awt.GridLayout; import java.awt.image.BufferedImage; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List;

import javax.swing.JComponent; import javax.swing.JFrame;

import org.slf4j.Logger; import org.slf4j.LoggerFactory;

public class VideoPanel extends JComponent implements PlayerControls, PropertyChangeListener {

final Logger log = LoggerFactory.getLogger(VideoPanel.class);

private static final long serialVersionUID = 1L;

private volatile FFmpegPlayer processor;

private BufferedImage image;

private List<String> videos;

private int index = 0;

public VideoPanel(List<String> videos) {
	this.videos = videos;
}

public void setVideos() {
	
	if(videos == null || videos.isEmpty()) {
		log.error("Playlist is Empty");
		return;
	}
	
	String videoFilename = videos.get(index++);
	
	if(index > videos.size() -1) {
		log.info("Resetting Playlist Order. ");
		index = 0;
	}
	
	log.info("Setting Video to Play, Video Path is: "+ videoFilename);
	
	if(processor!=null) {
		processor.stopVideo();
		processor.removePropertyChangeListener(this);
	}
		
	processor = new FFmpegPlayer();
	processor.addPropertyChangeListener(this);
	processor.setVideo(videoFilename);
	
	playVideo();
}

public void playVideo() {
	log.info("Play Video Event arise");
	processor.playVideo();

}

public void stopVideo() {
	log.info("Stop Video Event arise. ");
	processor.stopVideo();

}

public void muteVideo() {
	log.info("Mute Video Event arise");
	processor.muteVideo();

}

public void cleanMemory() {
	log.info("Memory Cleaning Event arise. ");
	Object obj = new Object();
	WeakReference<Object> ref = new WeakReference<>(obj);
	obj = null;
	while (ref.get() != null) {
		System.gc();
	}

}

@Override
protected void paintComponent(Graphics g) {
	if (image == null)
		return;
	g.drawImage(image, 0, 0, getWidth(), getHeight(), null);
	g.dispose();
}

public void propertyChange(PropertyChangeEvent evt) {
	if (evt.getPropertyName().equals("Image")) {

		image = (BufferedImage) evt.getNewValue();
		repaint();

	}
	
	if (evt.getPropertyName().equals("Stop")) {
		log.info("Stop Playing Video. ");
		setVideos();
	}

}

public static void main(String[] args) {

	EventQueue.invokeLater(() -> {

		int rows = 1;
		int cols = 1;
		int size = rows * cols;
		
		List<String> videos = new ArrayList<>();
		//videos.add("C:\\Users\\Mi6\\Desktop\\test\\1.mp4");
		videos.add("C:\\Users\\Mi6\\Desktop\\test\\Video257.mp4");
		videos.add("C:\\Users\\Mi6\\Desktop\\test\\Video264.mp4");
		videos.add("C:\\Users\\Mi6\\Desktop\\test\\Video258.mp4");
		
		VideoPanel[] video = new VideoPanel[size];

		for (int i = 0; i < size; i++)
			video[i] = new VideoPanel(videos);

		JFrame frame = new JFrame();
		frame.setPreferredSize(new Dimension(1920, 1024));
		frame.setLayout(new GridLayout(rows, cols, 1, 1));

		for (int i = 0; i < size; i++)
			frame.add(video[i]);

		frame.pack();
		frame.setVisible(true);

		for (int i = 0; i < size; i++)
			video[i].setVideos();
			
	});

}

} `

[https://1drv.ms/u/s!As2o7s_R_SwDgg31nsERCTgd414A?e=dMPe5d](Test Videos)

Contributor guide