|
Introduzione
Consideriamo lo schema illustrato nella Figura 1: l'input
è il file video, l'output è costituito dai file
in cui saranno salvati i frames componenti il video, in formato
JPEG. L'elaborazione è realizzata utilizzando le JMF
API.

Figura 1 - Modello di elaborazione di dati multimediali
Le
uniche due classi di JMF che utilizzeremo e che non abbiamo
già incontrato nell'articolo dello scorso mese sono:
- javax.media.control.FramePositioningControl:
interfaccia per a controllare il posizionamento di un frame
all'interno di un video, per i Player e i Processor. C'è
una corrispondenza diretta tra il numero identificativo
di un frame e l'istante di tempo in cui esso viene visualizzato
(media time). Non tutti i Player sono in grado di realizzare
quest'associazione, ma per quelli per i quali ciò
è possibile, si possono usare i metodi mapFrameToTime
e mapTimeToFrame. In realtà, utilizzando formati
video standard e soprattutto file in "buone condizioni",
il Player sarà sempre in grado di fare quanto detto
e quando sarà "spostato" su un certo frame,
il media time del Player diventerà quello del frame.
- javax.media.control.FrameGrabbingControl:
interfaccia per estarre un frame di un video da un Player.
Preparazione delle risorse: utilizzo del
Player.
Per semplicità facciamo riferimento ad un video di
tipo QuickTime e salviamo i frames in formato JPEG, ma si
possono utilizzare tutti i formati video supportati da JMF.
Il primo passo consiste nel definire le risorse in ingresso
e in uscita. Costruiamo quindi un MediaLocator sulla URL del
video che vogliamo grabbare e attraverso il Manager creiamo
il DataSource:
MediaLocator
ml = new MediaLocator(urlVideo);
DataSource ds = Manager.createDataSource(ml);
A
questo punto non utilizzeremo, ai fini della nostra elaborazione,
un Processor, come fatto nell'articolo del mese scorso, bensì
un Player. Il Player processa i dati multimediali che riceve
in ingresso mediante il DataSource, ed è capace di
scorrere sul flusso di dati.

Figura 2 - Modello del Player in JMF
Un
Player può trovarsi in uno dei seguenti stati: i due
stati primari, Stopped and Started, definiti dall'interfaccia
Clock; per facilitare la gestione delle risorse, l'interfaccia
Controller suddivide lo stato Stopped in cinque stati di "standby":
Unrealized, Realizing, Realized, Prefetching e Prefetched.

Figura 3 - Stati del Player
L'interfaccia
ControllerListener fornisce i metodi per determinare in quale
stato si trova il Player.
Prima di poter effettuare le operazioni preliminari di video
grabbing abbiamo bisogno che il Player si trovi nello stato
Realized.
Player
p = Manager.createPlayer(ds);
p.addControllerListener(this);
p.realize();
Quando
il Player viene creato si trova nello stato Unrealized e non
conosce nulla riguardo il flusso multimediale. Quando viene
chiamato il metodo realize, il Player transita nello stato
Realizing. In questa fase il Player acquisisce le risorse
per poi passare allo stato Realized nel quale sa come renderizzare
i dati e può disporre di componenti visuali e controlli.
Sono proprio i controlli FramePositioningControl e FrameGrabbingControl
che ci serviranno. Li otteniamo dal Player tramite il metodo
getControl:
FramePositioningControl
fpc = (FramePositioningControl)p.getControl
("javax.media.control.FramePositioningControl");
FrameGrabbingControl fgc = (FrameGrabbingControl)p.getControl
("javax.media.control.FrameGrabbingControl");
La
prima cosa che facciamo ora è ottenere la durata e
il numero di frames del video:
Time
duration = p.getDuration();
int totalFrames = fpc.mapTimeToFrame(duration);
Portiamo
quindi il Player nello stato Prefetched:
p.prefetch();
Quando
viene chiamato il metodo prefetch il Player transita dallo
stato Realized allo stato Prefetching. Durante questa fase
il Player carica i dati multimediali e si prepara a visualizzarli.
Fatto ciò passa nello stato Prefetched, in cui è
pronto ad essere mandato in esecuzione.
Costruzione
di un'interfaccia grafica per il grabbing
Prima di entrare nel vivo del processo di grabbing, costruiamo
un'interfaccia grafica mediante la quale l'utente potrà
avviare il grabbing e seguirne l'evoluzione.
Utilizzeremo un oggetto Panel a cui aggiungeremo dei tasti
"Grab" e "Close" e che inseriremo nella
componente visuale del Player, di cui possiamo modificare
a nostro piacimento il layout, il titolo, la posizione sullo
schermo,ecc. Questo perché la nostra classe principale
VideoGrabbing è un'estensione della classe Frame:
setLayout(new
BorderLayout());
Panel
controlPanel = new Panel();
Button grabButton = new Button("Grab");
Button closeButton = new Button("Close");
grabButton.addActionListener(this);
closeButton.addActionListener(this);
controlPanel.add(new Label(urlVideo.substring(5,urlVideo.length())));
controlPanel.add(grabButton);
controlPanel.add(closeButton);
Component vc;
if ((vc = p.getVisualComponent()) != null) {
add("Center", vc);
}
add("South", controlPanel);
setTitle("VideoGrabber");
setLocation(500,100);
setVisible(true);
L'interfaccia
grafica avrà quindi il seguente aspetto:

Figura 4 - Interfaccia grafica del VideoGrabber
Grabbing del video.
Ovviamente il grabbing del video avrà inizio alla pressione
del tasto Grab. Per rendere sensibile l'applicazione a questa
azione, la classe VideoGrabbing implementa l'ActionListener.
Avremo quindi un metodo actionPerformed(ActionEvent ae) che
a seguito della pressione del tasto Grab o del tasto Close
provvederà a realizzare il grabbing oppure a chiudere
l'interfaccia e uscire dall'applicazione.
Supponiamo di aver avviato il grabbing. L'utente quando avvia
l'applicazione ha la possibilità di scegliere il campionamento,
ovvero il numero di frame che intercorre tra quelli che vorrà
che siano estratti: con un campionamento pari a 15 saranno
estratti i frames numero 0, 15, 30, 45, ecc., con un campionamento
pari a 1 saranno estratti tutti i frames.
Prima di tutto spostiamo il Player di un numero di frame che
sarà 0 all'inizio (prendiamo il frame corrispondente
all'istante 0) e poi sarà pari al campionamento:
fpc.skip(camp);
Quindi
otteniamo il numero identificativo del frame e il corrispondente
media time:
int
currentFrame = fpc.mapTimeToFrame(p.getMediaTime());
Time currentTime = fpc.mapFrameToTime(i);
dove
i = 0 all'inizio e poi i = i + camp.
Possiamo
ora grabbare il frame corrente, utilizzando il metodo grabFrame()
di FrameGrabbingControl, che ci restituisce il nostro frame
sotto forma di Buffer:
Buffer
buff = new Buffer();
buff = fgc.grabFrame();
Salvataggio
dei frames in formato JPEG.
Poiché noi vogliamo delle immagini, la prima cosa che
facciamo è una conversione Buffer - Image:
BufferToImage
bti = new BufferToImage((VideoFormat)buff.getFormat());
Image img = bti.createImage(buff);
Costruiamo
i file su cui salvare le immagini:
String
pathFrame = (urlFrames+"/"+currentFrame+".jpg");
File fileFrame = new File(pathFrame);
La
conversione nel formato JPEG e il salvataggio su file vengono
effettuati dal metodo ImageSaver, a cui passiamo come argomenti
l'immagine e il file su cui salvarla:
ImageSaver(img
, fileFrame);
Questo
metodo converte Image in BufferedImage e, utilizzando la classe
ImageIO, contemporaneamente la converte in JPEG e la scrive
su File:
BufferedImage
bi=(BufferedImage)img;
ImageIO.write(bi, "JPG", fileFrame);
Implementazione.
Riportiamo di seguito, il codice che esegue il video grabbing.
Per l'esecuzione corretta bisogna passare come argomenti la
url del video, il path dove salvare i frames e il passo di
campionamento:
java
VideoGrabbing <url video> <path frames> <passo
di campionamento>
java
VideoGrabbing file:c:\video\\prova.MOVi c:\immagini\prova
5
import
java.awt.*;
import java.awt.event.*;
import javax.media.*;
import javax.media.format.VideoFormat;
import javax.media.control.FramePositioningControl;
import javax.media.protocol.DataSource;
import javax.media.control.FrameGrabbingControl;
import javax.media.util.*;
import java.io.*;
import java.lang.Object;
import java.awt.image.ImageObserver;
import java.awt.image.BufferedImage;
import java.io.File;
import javax.imageio.ImageIO;
import java.awt.Image;
public
class VideoGrabbing extends Frame implements ControllerListener,
ActionListener {
Player p;
FramePositioningControl fpc;
FrameGrabbingControl fgc;
Object waitSync = new Object();
boolean stateTransitionOK = true;
int totalFrames = FramePositioningControl.FRAME_UNKNOWN;
Image img;
int currentFrame;
Time currentTime;
static String urlVideo,urlFrames;
static int camp;
Panel controlPanel;
Button grabButton;
Button closeButton;
/**
* Creazione del Player che verrà utilizzato per il
grabbing.
*/
public boolean open(DataSource ds) {
System.err.println("Creato player per video in formato
" +
ds.getContentType());
try {
p = Manager.createPlayer(ds);
} catch (Exception e) {
System.err.println("Creazione del player per il DataSource:
"
+ e + " fallita!");
return false;
}
p.addControllerListener(this);
p.realize();
if (!waitForState(p.Realized)) {
System.err.println("Realizzazione del player fallita!");
return false;
}
// Provo a ottenere FramePositioningControl dal player.
fpc = (FramePositioningControl)p.getControl
("javax.media.control.FramePositioningControl");
// Provo a ottenere FrameGrabbingControl dal player.
fgc = (FrameGrabbingControl)p.getControl
("javax.media.control.FrameGrabbingControl");
if (fpc == null) {
System.err.println("Il player non supporta il "
+
"FramePositioningControl.");
return false;
}
Time duration = p.getDuration();
if (duration != Duration.DURATION_UNKNOWN) {
System.err.println("Durata del video: " +
duration.getSeconds() + " secondi.");
totalFrames = fpc.mapTimeToFrame(duration);
if (totalFrames != FramePositioningControl.FRAME_UNKNOWN){
System.err.println("Numero totale di frames del video:
"
+ totalFrames);
} else
System.err.println("Il FramePositiongControl non "
+
" supporta mapTimeToFrame.");
} else {
System.err.println("Durata del video sconosciuta!");
}
// Prefetch del player.
p.prefetch();
if (!waitForState(p.Prefetched)) {
System.err.println("Prefetch del player fallito.");
return false;
}
// Costruzione dell'interfaccia grafica.
setLayout(new BorderLayout());
controlPanel = new Panel();
grabButton = new Button("Grab");
closeButton = new Button("Close");
grabButton.addActionListener(this);
closeButton.addActionListener(this);
controlPanel.add(new Label
(urlVideo.substring(5,urlVideo.length())));
controlPanel.add(grabButton);
controlPanel.add(closeButton);
Component vc;
if ((vc = p.getVisualComponent()) != null) {
add("Center", vc);
}
add("South", controlPanel);
setTitle("VideoGrabber");
setLocation(500,100);
setVisible(true);
return true;
}
public void addNotify() {
super.addNotify();
pack();
}
/**
* Aspetta finché il player non è transitato
in un dato stato.
* Return false se la transizione è fallita.
*/
boolean waitForState(int state) {
synchronized (waitSync) {
try {
while (p.getState() < state && stateTransitionOK)
waitSync.wait();
} catch (Exception e) {}
}
return stateTransitionOK;
}
public void actionPerformed(ActionEvent ae) {
String command = ae.getActionCommand();
if (command.equals("Grab")){
int frameId=0;
for ( int i=0; i<=totalFrames; i=i+camp) {
frameId++;
// inizialmente mi posiziono sul frame 0 (istante 0)
if (i==0)
fpc.skip(0);
// poi mi posiziono sui frame successivi, secondo il
// campionamento
else
fpc.skip(camp);
// grabba il frame relativo
if (currentFrame !=
FramePositioningControl.FRAME_UNKNOWN) {
currentFrame =
fpc.mapTimeToFrame(p.getMediaTime());
currentTime = fpc.mapFrameToTime(i);
}
Buffer buff = new Buffer();
buff = fgc.grabFrame();
// converte buffer - image
BufferToImage bti = new BufferToImage((VideoFormat)
buff.getFormat());
img = bti.createImage(buff);
String pathFrame = (urlFrames+"/"+currentFrame+".jpg");
File fileFrame = new File(pathFrame);
try {
System.err.println("Sto salvando il frame n." +
currentFrame + " del video " +
" Tempo: " +
currentTime.getSeconds());
ImageSaver(img , fileFrame);
} catch (Exception e) {
System.err.println("Non riesco a salvare il " +
"frame, forse non esiste " +
"la cartella " + e);
}
}
System.err.println("Numero di frames salvati:" +
frameId);
} else if (command.equals("Close")) {
this.dispose();
System.exit(0);
}
}
/**
* Controller Listener.
*/
public void controllerUpdate(ControllerEvent evt) {
if (evt instanceof ConfigureCompleteEvent ||
evt instanceof RealizeCompleteEvent ||
evt instanceof PrefetchCompleteEvent) {
synchronized (waitSync) {
stateTransitionOK = true;
waitSync.notifyAll();
}
} else if (evt instanceof ResourceUnavailableEvent) {
synchronized (waitSync) {
stateTransitionOK = false;
waitSync.notifyAll();
}
} else if (evt instanceof EndOfMediaEvent) {
p.setMediaTime(new Time(0));
} else if (evt instanceof SizeChangeEvent) {
}
}
void this_windowClosing(WindowEvent e) {
this.dispose();
}
public void ImageSaver (Image img,File fileFrame) throws IOException
{
/**
* Codifica un BufferedImage in formato Jpeg format e lo salva
su
* file
* @param img è l'immagine da salvare
* @param fileFrame è un File che decrive dove si vuole
salvare
* l'immagine
* @throws java.io.IOException se l'immagine non può
essere
* codificata
* e/o salvata
*/
BufferedImage bi=null;
try {
if (!(img instanceof BufferedImage)) {
ImageObserver io=new ImageObserver() {
public boolean imageUpdate(Image im, int info,int
x, int y, int w, int h) {
return false;
}
};
bi = new
BufferedImage(img.getWidth(io),img.getHeight(io),
BufferedImage.TYPE_INT_RGB);
bi.getGraphics().drawImage(img,0,0,io);
img=bi;
} else
bi=(BufferedImage)img;
ImageIO.write(bi, "JPG", fileFrame);
} catch (Exception e) {
throw new IOException("Salvataggio dell'immagine fallito:
" +
e);
}
}
/**
* Main program
*/
public static void main(String[] args) {
if (args.length <= 2) {
prUsage();
System.exit(0);
} else {
urlVideo = args[0];
urlFrames = args[1];
camp = new Integer(args[2]).intValue();
MediaLocator ml;
if ((ml = new MediaLocator(urlVideo)) == null)
System.err.println("Impossibile costruire MediaLocator
"
+ "dalla url: " + urlVideo);
DataSource ds = null;
// Crea un DataSource da un dato MediaLocator
try {
ds = Manager.createDataSource(ml);
} catch (Exception e) {
System.err.println("Impossibile creare Datasource da:
"
+ ml);
System.err.println("Eccezione: "+e);
System.exit(0);
}
VideoGrabbing VideoGrabbing = new VideoGrabbing();
if (!VideoGrabbing.open(ds))
System.exit(0);
}
}
static void prUsage() {
System.err.println("Usage: java VideoGrabbing <url
video> " +
"<url frames> <passo di campionamento> ");
System.err.println("Example: java VideoGrabbing "
+
"file:c:\\video\\prova.avi c:\\immagini\\prova 5");
}
}
Conclusioni
Con questo articolo abbiamo dato un'altra dimostrazione delle
enormi potenzialità di JMF. In particolare abbiamo
visto come grabbare un video, cioè poter salvare su
file tutte le immagini di cui è composto.
Un'applicazione di questo tipo può essere utile in
sistemi di video sorveglianza, in cui si vuole isolare un
evento all'interno di un filmato, oppure per tutti quelli
che vogliono realizzare un bell'album fotografico dopo aver
girato un filmino con la propria videocamera digitale.
Bibliografia
[1] "JMF API Specification", java.sun.com/products/java-media/jmf/index.jsp
Vincenzo Viola, nato
a Formia (LT) il 03/11/1977, è laureato in Ingegneria
delle Telecomunicazioni presso l'Università Federico
II di Napoli con una tesi sulla segmentazione automatica di
video digitali in scene, con sviluppo software implementato
su piattaforma Java - Oracle.
Lavora come progettista software per una Mobile Company che
offre la sua consulenza ai gestori di telefonia e alle major
del settore ICT.
|