Bug fixes with vlc. Tested component with external modules.

This commit is contained in:
Jeff Wallace 2013-08-01 16:14:58 -04:00
parent 5f823cee30
commit ed75e9258b
3 changed files with 366 additions and 345 deletions

View File

@ -213,6 +213,14 @@
<runtime-relative-path>ext/Tsk_DataModel.jar</runtime-relative-path> <runtime-relative-path>ext/Tsk_DataModel.jar</runtime-relative-path>
<binary-origin>release/modules/ext/Tsk_DataModel.jar</binary-origin> <binary-origin>release/modules/ext/Tsk_DataModel.jar</binary-origin>
</class-path-extension> </class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/vlcj-2.4.1.jar</runtime-relative-path>
<binary-origin>release/modules/ext/vlcj-2.4.1.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/jna-3.5.2.jar</runtime-relative-path>
<binary-origin>release/modules/ext/jna-3.5.2.jar</binary-origin>
</class-path-extension>
</data> </data>
</configuration> </configuration>
</project> </project>

View File

@ -43,7 +43,7 @@ import org.sleuthkit.datamodel.TskData.TSK_FS_NAME_FLAG_ENUM;
public class DataContentViewerMedia extends javax.swing.JPanel implements DataContentViewer { public class DataContentViewerMedia extends javax.swing.JPanel implements DataContentViewer {
private String[] IMAGES; // use javafx supported private String[] IMAGES; // use javafx supported
private static final String[] VIDEOS = new String[]{".mov", ".m4v", ".flv", ".mp4", ".3gp", ".avi", ".mpg", ".mpeg", ".wmv"}; private static final String[] VIDEOS = new String[]{".swf", ".mov", ".m4v", ".flv", ".mp4", ".3gp", ".avi", ".mpg", ".mpeg", ".wmv"};
private static final String[] AUDIOS = new String[]{".mp3", ".wav", ".wma"}; private static final String[] AUDIOS = new String[]{".mp3", ".wav", ".wma"};
private static final Logger logger = Logger.getLogger(DataContentViewerMedia.class.getName()); private static final Logger logger = Logger.getLogger(DataContentViewerMedia.class.getName());

View File

@ -20,13 +20,10 @@ package org.sleuthkit.autopsy.corecomponents;
import com.sun.jna.Native; import com.sun.jna.Native;
import java.awt.Dimension; import java.awt.Dimension;
import java.awt.Image; import java.awt.image.BufferedImage;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set;
import java.util.concurrent.CancellationException; import java.util.concurrent.CancellationException;
import java.util.logging.Level; import java.util.logging.Level;
import javax.swing.BoxLayout; import javax.swing.BoxLayout;
@ -58,7 +55,7 @@ import uk.co.caprica.vlcj.player.MediaPlayerFactory;
import uk.co.caprica.vlcj.runtime.RuntimeUtil; import uk.co.caprica.vlcj.runtime.RuntimeUtil;
/** /**
* Video viewer part of the Media View layered pane * Video viewer part of the Media View layered pane.
*/ */
@ServiceProviders(value = { @ServiceProviders(value = {
@ServiceProvider(service = FrameCapture.class) @ServiceProvider(service = FrameCapture.class)
@ -66,22 +63,36 @@ import uk.co.caprica.vlcj.runtime.RuntimeUtil;
public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapture { public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapture {
private static final Logger logger = Logger.getLogger(MediaViewVideoPanel.class.getName()); private static final Logger logger = Logger.getLogger(MediaViewVideoPanel.class.getName());
private boolean vlcInited; private boolean vlcInited; // Has the vlc library been initalized properly?
private static final long MIN_FRAME_INTERVAL_MILLIS = 500; private static final long MIN_FRAME_INTERVAL_MILLIS = 500;
private static final long FRAME_CAPTURE_TIMEOUT_MILLIS = 1000; private static final long INTER_FRAME_PERIOD_MS = 20; // Time between frames.
private static final String MEDIA_PLAYER_ERROR_STRING = "The media player cannot process this file."; private static final String MEDIA_PLAYER_ERROR_STRING = "The media player cannot process this file.";
private static final int POS_FACTOR = 10000; private static final int POS_FACTOR = 10000; // The number of ticks on the video progress bar
//playback // Frame Capture Media Player Args
private static final String[] VLC_FRAME_CAPTURE_ARGS = {
"--intf", "dummy", // no interface
"--vout", "dummy", // we don't want video (output)
"--no-audio", // we don't want audio (decoding)
"--no-video-title-show", // nor the filename displayed
"--no-stats", // no stats
"--no-sub-autodetect-file", // we don't want subtitles
"--no-disable-screensaver", // we don't want interfaces
"--no-snapshot-preview" // no blending in dummy vout
};
// Playback state information
private long durationMillis = 0; private long durationMillis = 0;
private VideoProgressWorker videoProgressWorker;
private int totalHours, totalMinutes, totalSeconds; private int totalHours, totalMinutes, totalSeconds;
private boolean autoTracking = false; // true if the slider is moving automatically
private boolean replay;
// VLC Components
private MediaPlayer vlcMediaPlayer; private MediaPlayer vlcMediaPlayer;
private EmbeddedMediaPlayerComponent vlcVideoComponent; private EmbeddedMediaPlayerComponent vlcVideoComponent;
private boolean autoTracking = false; // true if the slider is moving automatically // Worker threads
private VideoProgressWorker videoProgressWorker;
private MediaPlayerThread mediaPlayerThread;
// Current media content representations
private AbstractFile currentFile; private AbstractFile currentFile;
private java.io.File currentVideoFile; private java.io.File currentVideoFile;
private boolean replay;
private Set<String> badVideoFiles = Collections.synchronizedSet(new HashSet<String>());
/** /**
* Creates new form MediaViewVideoPanel * Creates new form MediaViewVideoPanel
@ -124,11 +135,6 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt
progressSlider.setValue(0); progressSlider.setValue(0);
progressSlider.addChangeListener(new ChangeListener() { progressSlider.addChangeListener(new ChangeListener() {
/**
* Should always try to synchronize any call to
* progressSlider.setValue() to avoid a different thread changing
* playbin while stateChanged() is processing
*/
@Override @Override
public void stateChanged(ChangeEvent e) { public void stateChanged(ChangeEvent e) {
if (vlcMediaPlayer != null && !autoTracking) { if (vlcMediaPlayer != null && !autoTracking) {
@ -167,8 +173,7 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt
videoProgressWorker.cancel(true); videoProgressWorker.cancel(true);
videoProgressWorker = null; videoProgressWorker = null;
} }
mediaPlayerThread = new MediaPlayerThread();
logger.log(Level.INFO, "Resetting media");
replay = true; replay = true;
} }
@ -221,20 +226,27 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt
pauseButton.setEnabled(true); pauseButton.setEnabled(true);
progressSlider.setEnabled(true); progressSlider.setEnabled(true);
// Create and Configure MediaPlayer
vlcVideoComponent = new EmbeddedMediaPlayerComponent(); vlcVideoComponent = new EmbeddedMediaPlayerComponent();
vlcMediaPlayer = vlcVideoComponent.getMediaPlayer(); vlcMediaPlayer = vlcVideoComponent.getMediaPlayer();
vlcMediaPlayer.setPlaySubItems(true); vlcMediaPlayer.setPlaySubItems(true);
vlcMediaPlayer.addMediaPlayerEventListener(new VlcMediaPlayerEventListener()); vlcMediaPlayer.addMediaPlayerEventListener(new VlcMediaPlayerEventListener());
// Configure VideoPanel
videoPanel.removeAll(); videoPanel.removeAll();
videoPanel.setLayout(new BoxLayout(videoPanel, BoxLayout.Y_AXIS)); videoPanel.setLayout(new BoxLayout(videoPanel, BoxLayout.Y_AXIS));
videoPanel.add(vlcVideoComponent); videoPanel.add(vlcVideoComponent);
videoPanel.setVisible(true); videoPanel.setVisible(true);
logger.log(Level.INFO, "Created media player.");
// Create Extraction and Playback Threads
mediaPlayerThread = new MediaPlayerThread();
ExtractMedia em = new ExtractMedia(currentFile, getJFile(currentFile)); ExtractMedia em = new ExtractMedia(currentFile, getJFile(currentFile));
em.execute(); em.execute();
} }
/**
* Cleanup all threads and VLC components and reset UI.
*/
void reset() { void reset() {
// reset the progress label text on the event dispatch thread // reset the progress label text on the event dispatch thread
SwingUtilities.invokeLater(new Runnable() { SwingUtilities.invokeLater(new Runnable() {
@ -248,6 +260,11 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt
return; return;
} }
if (mediaPlayerThread != null) {
mediaPlayerThread.cancel();
mediaPlayerThread = null;
}
// get rid of any existing videoProgressWorker thread // get rid of any existing videoProgressWorker thread
if (videoProgressWorker != null) { if (videoProgressWorker != null) {
videoProgressWorker.cancel(true); videoProgressWorker.cancel(true);
@ -260,18 +277,23 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt
} }
vlcMediaPlayer.release(); vlcMediaPlayer.release();
vlcMediaPlayer = null; vlcMediaPlayer = null;
logger.log(Level.INFO, "Released media player");
} }
if (vlcVideoComponent != null) { if (vlcVideoComponent != null) {
vlcVideoComponent.release(true); vlcVideoComponent.release(true);
vlcVideoComponent = null; vlcVideoComponent = null;
logger.log(Level.INFO, "Released video component");
} }
currentFile = null; currentFile = null;
currentVideoFile = null; currentVideoFile = null;
} }
/**
* Start the media player.
*/
void playMedia() {
SwingUtilities.invokeLater(mediaPlayerThread);
}
private java.io.File getJFile(AbstractFile file) { private java.io.File getJFile(AbstractFile file) {
// Get the temp folder path of the case // Get the temp folder path of the case
String tempPath = Case.getCurrentCase().getTempDirectory(); String tempPath = Case.getCurrentCase().getTempDirectory();
@ -287,45 +309,6 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt
return tempFile; return tempFile;
} }
void playMedia() {
logger.log(Level.INFO, "In play media");
if (currentVideoFile == null || !currentVideoFile.exists()) {
progressLabel.setText("Error buffering file");
return;
}
boolean mediaPrepared = vlcMediaPlayer.prepareMedia(currentVideoFile.getAbsolutePath());
if (mediaPrepared) {
vlcMediaPlayer.parseMedia();
durationMillis = vlcMediaPlayer.getMediaMeta().getLength();
logger.log(Level.INFO, "Media loaded correctly");
} else {
progressLabel.setText(MEDIA_PLAYER_ERROR_STRING);
}
// pick out the total hours, minutes, seconds
long durationSeconds = (int) durationMillis / 1000;
totalHours = (int) durationSeconds / 3600;
durationSeconds -= totalHours * 3600;
totalMinutes = (int) durationSeconds / 60;
durationSeconds -= totalMinutes * 60;
totalSeconds = (int) durationSeconds;
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
progressSlider.setMaximum(POS_FACTOR);
progressSlider.setMinimum(0);
logger.log(Level.INFO, "Starting the media...");
vlcMediaPlayer.start();
pauseButton.setText("||");
videoProgressWorker = new VideoProgressWorker();
videoProgressWorker.execute();
}
});
}
/** /**
* @param file a video file from which to capture frames * @param file a video file from which to capture frames
* @param numFrames the number of frames to capture. These frames will be * @param numFrames the number of frames to capture. These frames will be
@ -343,42 +326,52 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt
return frames; return frames;
} }
// throw exception if this file is known to be problematic // Create Media Player with no video output
if (badVideoFiles.contains(file.getName())) { MediaPlayerFactory mediaPlayerFactory = new MediaPlayerFactory(VLC_FRAME_CAPTURE_ARGS);
throw new Exception("Cannot capture frames from this file (" + file.getName() + ").");
}
MediaPlayerFactory mediaPlayerFactory = new MediaPlayerFactory();
MediaPlayer mediaPlayer = mediaPlayerFactory.newHeadlessMediaPlayer(); MediaPlayer mediaPlayer = mediaPlayerFactory.newHeadlessMediaPlayer();
boolean mediaPrepared = mediaPlayer.prepareMedia(file.getAbsolutePath()); boolean mediaPrepared = mediaPlayer.prepareMedia(file.getAbsolutePath());
if (!mediaPrepared) { if (!mediaPrepared) {
logger.log(Level.WARNING, "Video file was not loaded properly.");
return frames; return frames;
} }
// get the duration of the video // get the duration of the video
long myDurationMillis = mediaPlayer.getLength(); mediaPlayer.parseMedia();
long myDurationMillis = mediaPlayer.getMediaMeta().getLength();
if (myDurationMillis <= 0) { if (myDurationMillis <= 0) {
return frames; return frames;
} }
// calculate the number of frames to capture // calculate the number of frames to capture
int numFramesToGet = numFrames; int numFramesToGet = numFrames;
long frameInterval = myDurationMillis / numFrames; long frameInterval = (myDurationMillis - INTER_FRAME_PERIOD_MS) / numFrames;
if (frameInterval < MIN_FRAME_INTERVAL_MILLIS) { if (frameInterval < MIN_FRAME_INTERVAL_MILLIS) {
numFramesToGet = 1; numFramesToGet = 1;
} }
mediaPlayer.start();
mediaPlayer.setPause(true);
BufferedImage snapShot;
// for each timeStamp, grap a frame // for each timeStamp, grap a frame
for (int i = 0; i < numFramesToGet; ++i) { for (int i = 0; i < numFramesToGet; ++i) {
long timeStamp = i * frameInterval; logger.log(Level.INFO, "Grabbing a frame...");
long timeStamp = i * frameInterval + INTER_FRAME_PERIOD_MS;
mediaPlayer.setTime(timeStamp); mediaPlayer.setTime(timeStamp);
mediaPlayer.pause(); mediaPlayer.setPause(true);
Image snapShot = mediaPlayer.getSnapshot(); snapShot = mediaPlayer.getSnapshot();
if (snapShot == null) {
continue;
}
frames.add(new VideoFrame(snapShot, timeStamp)); frames.add(new VideoFrame(snapShot, timeStamp));
} }
// cleanup media player
mediaPlayer.release();
mediaPlayerFactory.release();
return frames; return frames;
} }
@ -467,15 +460,12 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt
private void pauseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_pauseButtonActionPerformed private void pauseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_pauseButtonActionPerformed
if (replay) { if (replay) {
// File has completed playing. Play button now replays // File has completed playing. Play button now replays media.
logger.log(Level.INFO, "Replaying video.");
replay = false; replay = false;
playMedia(); playMedia();
} else if (vlcMediaPlayer.isPlaying()) { } else if (vlcMediaPlayer.isPlaying()) {
logger.log(Level.INFO, "Pausing.");
this.pause(); this.pause();
} else { } else {
logger.log(Level.INFO, "Playing");
this.unPause(); this.unPause();
} }
}//GEN-LAST:event_pauseButtonActionPerformed }//GEN-LAST:event_pauseButtonActionPerformed
@ -488,31 +478,21 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt
private javax.swing.JPanel videoPanel; private javax.swing.JPanel videoPanel;
// End of variables declaration//GEN-END:variables // End of variables declaration//GEN-END:variables
void releaseVlcComponents() { /**
if (vlcMediaPlayer != null) { * Thread that updates the video progress bar and current time with information
vlcMediaPlayer.release(); * queried from the MediaPlayer.
vlcMediaPlayer = null; */
}
if (vlcVideoComponent != null) {
vlcVideoComponent.release();
vlcVideoComponent = null;
}
}
private class VideoProgressWorker extends SwingWorker<Object, Object> { private class VideoProgressWorker extends SwingWorker<Object, Object> {
private String durationFormat = "%02d:%02d:%02d/%02d:%02d:%02d "; private String durationFormat = "%02d:%02d:%02d/%02d:%02d:%02d ";
private long millisElapsed = 0; private long millisElapsed = 0;
private float currentPosition = 0.0f; private float currentPosition = 0.0f;
private final long INTER_FRAME_PERIOD_MS = 20;
private final long END_TIME_MARGIN_MS = 50;
private boolean hadError = false;
private boolean isMediaPlayerReady() { private boolean isMediaPlayerReady() {
return vlcMediaPlayer != null; return vlcMediaPlayer != null;
} }
// TODO: Could be moved to finished()
private void resetVideo() throws Exception { private void resetVideo() throws Exception {
pauseButton.setText(""); pauseButton.setText("");
@ -521,17 +501,6 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt
progressLabel.setText(durationStr); progressLabel.setText(durationStr);
} }
// /**
// * @return true while millisElapsed is greater than END_TIME_MARGIN_MS
// * from durationMillis. This is used to indicate when the video has
// * ended because for some videos the time elapsed never becomes equal to
// * the reported duration of the video.
// */
// private boolean hasNotEnded() {
// boolean ended = (durationMillis - millisElapsed) > END_TIME_MARGIN_MS;
// return ended;
// }
@Override @Override
protected Object doInBackground() throws Exception { protected Object doInBackground() throws Exception {
@ -540,7 +509,6 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt
int elapsedHours = -1, elapsedMinutes = -1, elapsedSeconds = -1; int elapsedHours = -1, elapsedMinutes = -1, elapsedSeconds = -1;
while (isMediaPlayerReady() && !isCancelled()) { while (isMediaPlayerReady() && !isCancelled()) {
millisElapsed = vlcMediaPlayer.getTime(); millisElapsed = vlcMediaPlayer.getTime();
currentPosition = vlcMediaPlayer.getPosition(); currentPosition = vlcMediaPlayer.getPosition();
@ -577,15 +545,16 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt
} }
} //end class progress worker } //end class progress worker
/* Thread that extracts and plays a file */ /**
* Thread that extracts Media from a Sleuthkit file representation to a
* Java file representation that the Media Player can take as input.
*/
private class ExtractMedia extends SwingWorker<Object, Void> { private class ExtractMedia extends SwingWorker<Object, Void> {
private ProgressHandle progress; private ProgressHandle progress;
boolean success = false; boolean success = false;
private AbstractFile sFile; private AbstractFile sFile;
private java.io.File jFile; private java.io.File jFile;
private String duration;
private String position;
private long extractedBytes; private long extractedBytes;
ExtractMedia(org.sleuthkit.datamodel.AbstractFile sFile, java.io.File jFile) { ExtractMedia(org.sleuthkit.datamodel.AbstractFile sFile, java.io.File jFile) {
@ -614,6 +583,7 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt
} catch (IOException ex) { } catch (IOException ex) {
logger.log(Level.WARNING, "Error buffering file", ex); logger.log(Level.WARNING, "Error buffering file", ex);
} }
logger.log(Level.INFO, "Done buffering: " + jFile.getName());
success = true; success = true;
return null; return null;
} }
@ -632,7 +602,7 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt
} finally { } finally {
progress.finish(); progress.finish();
if (!this.isCancelled()) { if (!this.isCancelled()) {
logger.log(Level.INFO, "Loaded media file"); logger.log(Level.INFO, "ExtractMedia in done: " + jFile.getName());
currentVideoFile = jFile; currentVideoFile = jFile;
playMedia(); playMedia();
} }
@ -640,6 +610,63 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt
} }
} }
/**
* Thread that is responsible for running the Media Player.
*/
private class MediaPlayerThread implements Runnable, Cancellable {
private volatile boolean cancelled = false;
/* Prepare and run the current media. */
@Override
public void run() {
if (currentVideoFile == null || !currentVideoFile.exists()) {
progressLabel.setText("Error buffering file");
return;
}
boolean mediaPrepared = vlcMediaPlayer.prepareMedia(currentVideoFile.getAbsolutePath());
if (mediaPrepared) {
vlcMediaPlayer.parseMedia();
durationMillis = vlcMediaPlayer.getMediaMeta().getLength();
} else {
progressLabel.setText(MEDIA_PLAYER_ERROR_STRING);
}
// pick out the total hours, minutes, seconds
long durationSeconds = (int) durationMillis / 1000;
totalHours = (int) durationSeconds / 3600;
durationSeconds -= totalHours * 3600;
totalMinutes = (int) durationSeconds / 60;
durationSeconds -= totalMinutes * 60;
totalSeconds = (int) durationSeconds;
progressSlider.setMaximum(POS_FACTOR);
progressSlider.setMinimum(0);
if (!isCancelled()) {
vlcMediaPlayer.start();
pauseButton.setText("||");
videoProgressWorker = new VideoProgressWorker();
videoProgressWorker.execute();
} else {
logger.log(Level.INFO, "Media Playing cancelled.");
}
}
@Override
public boolean cancel() {
cancelled = true;
return cancelled;
}
private boolean isCancelled() {
return cancelled;
}
}
/**
* An Object that listens and handles events thrown by the VLC Media Player.
*/
private class VlcMediaPlayerEventListener extends MediaPlayerEventAdapter { private class VlcMediaPlayerEventListener extends MediaPlayerEventAdapter {
@Override @Override
@ -650,22 +677,8 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt
@Override @Override
public void error(MediaPlayer mediaPlayer) { public void error(MediaPlayer mediaPlayer) {
logger.log(Level.INFO, "an Error occured."); logger.log(Level.WARNING, "A VLC error occured. Resetting the video panel.");
} reset();
@Override
public void mediaDurationChanged(MediaPlayer mediaPlayer, long newDuration) {
logger.log(Level.INFO, "DURATION CHANGED: " + newDuration);
}
@Override
public void mediaFreed(MediaPlayer mediaPlayer) {
logger.log(Level.INFO, "Media was freed");
}
@Override
public void lengthChanged(MediaPlayer mediaPlayer, long newLength) {
logger.log(Level.INFO, "LENGTH CHANGED: " + newLength);
} }
} }
} }