MokaByte
Numero 11 - Settembre 1997
|
|||
|
|
||
Massimo Carli |
|
||
Che cosa sono le Java Media Framework
Come accennato gli obiettivi delle JMF sono quelli di fornire gli strumenti per la realizzazione di oggetti multimediali in Java che vadano oltre la semplice animazione ottenuta come successione di immagini. Gli strumenti che si vogliono fornire devono possedere tre caratteristiche principali:
* Permettere
la semplice realizzazione di oggetti multimediali per la riproduzione audio
e video(aspetto di cui ci occuperemo in questo articolo).
* Permettere
l'estensione degli strumenti già presenti in modo da dotare loro
di maggiori funzionalità.
* Fornire le
basi per l'aggiunta di player per nuovi formati audio o video non già
interpretati dalle JMF.
La versione delle
API che ho utilizzato per la creazione del player multimediale, è
la beta e permette già di riprodurre suoni nei formati WAV, AU,e
MIDI e video nei formati MPEG-1,MPEG-2, Quicktime ed AVI. Saranno questi
i formati che il nostro lettore multimediale riuscirà infatti a
riprodurre. Vedremo, inoltre, che la cosa sarà semplicissima. Nella
gestione dei player, le JMF fanno una distinzione sulla sorgente dei dati
a seconda del modo con cui essi vendono forniti al riproduttore. Esistono
sorgenti:
* Reliable
: sono sorgenti che forniscono tutti i dati relativi allo stream
del media da riprodurre. In questo tipo di sorgente ogni dato che arriva
al player deve essere riprodotto esattamente. Non ci devono essere delle
perdite di dati. Il protocollo utilizzato in questo caso è l'HTTP
(HyperText Tranfert Protocol), e la risorsa è individuata da un
URL (Uniform Resurs Locator). Come avviene per le immagini (la risorsa
individuata da un URL viene prelevata come vettore di byte, i primi dei
quali ne determinano il formato da cui la interpretazione dei restanti
come appartenenti ad una JPF o GIF), anche per queste risorse, la JMF metterà
in azione l'handler appropriato. I dati ottenuti da questo tipo di sorgenti
hanno la caratteristica di essere in numero finito e quindi la loro riproduzione
può avvenire anche in modo ripetuto. Nel nostro esempio la sorgente
è reliable.
* Streaming:
questo tipo di sorgenti è quello che si utilizzerà nella
videoconferenza. Le sorgenti preleveranno dati da uno strumento di rilevazione
audio e/o video, e li riprodurranno attraverso il player adatto. Caratteristica
dei dati relativi a questo tipo di sorgente è il fatto che non tutti
saranno utilizzati. Molto dipenderà dalle prestazioni del player.
Inoltre i dati sono riprodotti mano a mano che giungono al player e non
potranno, se non registrati, essere riprodotti più volte come nel
caso precedente. Il protocollo di trasmissione varia a seconda del tipo
di sorgente. Per esempio, è in preparazione il protocollo VDO (Video
On Demand) per la trasmissione video a richiesta.
I due tipi di dati precedenti si caratterizzano anche in relazione al funzionamento del player nello stato di stop. Nel primo caso lo stop del player causerà l'interruzione del prelevamento dei dati dalla sorgente, mentre nel secondo caso i dati continueranno a giungere al player anche se non verranno riprodotti. Le JMF sono, quindi, un insieme di classi ed interfacce che , ad oggi, permettono la implementazione di player. Esse sono raggruppate in due package principali:
* java.media
: Contiene le basi per la realizzazione dei player e per la gestione
dei controller relativi. Le interfacce sono principalmente dedicate a fornire
strumenti di sincronizzazione (legati molto alla piattaforma), mentre le
classi sono prevalentemente caratterizzanti tipi di eventi che, come vedremo,
sono fondamentali nella programmazione con JMF.
* java.media.protocol:
contiene interfacce e classi che forniscono strumenti per la gestione delle
sorgenti dati ed i meccanismi di ottenimento di dati da esse.
Nel nostro caso
utilizzeremo solamente gli strumenti del package java.
Come funzionano le JMF
Le API fornite con la beta delle JMF permettono la creazione di player. Possiamo considerare un player come una macchina software che elabora uno stream di dati provenienti da una sorgente rendendoli in maniera appropriata nei giusti tempi [1]. La sincronizzazione assume un ruolo fondamentale. Infatti, come vedremo, i dati prima di essere resi possono subire delle elaborazioni che necessitano di un certo tempo, per cui è necessario disporre di strumenti che notifichino lo stato dei calcoli. Infatti, un Java Media Player implementerà le seguenti interfacce:
* Clock
: rappresenta lo strumento di sincronizzazione principale cui tutti i player
si riferiranno. Dispone dei metodi principali di start() e stop() per l'avvio
e lo stop della riproduzione del media.
* Controller:
questa interfaccia estende la precedente e fornisce gli strumenti per la
notifica da parte dei controller dell'avvenuta modifica di parametri di
riproduzione del media. Essa contiene i metodi principali per la variazione
dello stato del player.
* Duration:
fornisce gli strumenti per determinare la durata del media relativamente
al tempo scandito dal Clock.
* Player:
estende Controller e Clock e permette la riproduzione del media.
Definizione importante è quella di media-time che rappresenta la posizione temporale dello stream relativamente alla sua riproduzione. Affinché più player siano sincronizzati, devono avere lo stesso media-time. A parte questi aspetti teorici ad ogni tipo di media JMF associa due particolari componenti con cui è possibile interagire attraverso una opportuna gestione degli eventi del tipo Delegation Model [2]. Ad ogni media sono associati i seguenti tipi di componenti:
* Control-Panel:
rappresenta proprio il pannello su cui saranno messi i controlli con cui
interagire con il media. In un control-panel vi saranno, per esempio, i
pulsanti di start e stop di un filmato. Una volta creato un player sarà
possibile verificare il numero ed il tipo di controller attraverso il metodo
getControls() della interfaccia player. Oltre ad un controller di default,
ogni player associato ad un particolare tipo di media dispone di eventuali
altri dispositivi di controllo. Uno di questi è, per esempio, quello
che permette di conoscere lo stato di bufferizzazione dei dati (CachingControl
,utile se la risorsa è prelevata dalla rete).
* Visual-Component:
rappresenta il pannello su cui si ha la riproduzione del media. Nel caso
di un video, il Visual-Panel non è altro che lo "schermo". Nel caso
della riproduzione audio può non essere presente.
La realizzazione del nostro lettore multimediale non sarà altro che l'associazione di questi due componenti ad ogni tipo di media, e la gestione degli eventi che il Control-Panel emetterà. Gli eventi gestiti dal JMF sono tutti derivati dalla classe ControllerEvent e si possono classificare in tre categorie:
* Notifiche
di variazione: sono eventi che notificano delle variazioni relative
ai parametri del player e del media riprodotto. Per esempio abbiamo l'evento
RateChangeEvent che notifica la variazione nella velocità di riproduzione
con conseguente generazione di un evento di tipo DurationUpdateEvent che
aggiorna al nuovo stato i parametri del player quali la durata di riproduzione.
* Eventi
di transizione: questi, come vedremo nel prossimo paragrafo, sono
molto importanti perché notificano la variazione dello stato di
un player.
* Eventi
di notifica di un errore: ogni volta che si verifica un errore
nella gestione di un player o nella riproduzione di un media, si ha la
generazione di un evento esplicativo del problema.
Possiamo allora dire che per la realizzazione di un player dobbiamo tenere conto dei seguenti aspetti fondamentali:
* Creazione del
player in relazione al tipo di media determinato dall'URL
* Creazione
del Visual-Component da inserire nel layout del player
* Creazione
del Control-Panel da inserire anch'esso nel layout del player
* Gestione degli
stati del player attraverso una controllo efficace degli eventi generati
da esso o dai Controller associati
* Rilascio delle
risorse
Questo sarà
anche il procedimento che seguiremo nella realizzazione del nostro esempio.
L'ultimo punto è molto importante in quanto esistono delle risorse
(risorse esclusive) che non possono essere utilizzate da più processi
contemporaneamente. Per esempio la scheda audio non può essere condivisa
per cui se un determinato player ne fa uso e termina il suo compito, deve
rilasciare la risorsa a favore di un eventuale altro processo.
Stati di un player
Per la riproduzione audio e video sono necessarie delle risorse che impegnano il sistema in modo non indifferente. Le JMF hanno, allora, diviso lo stato di un player in 6 fasi, ciascuno caratterizzato dalle operazioni che è possibile effettuare. Questi stati percorrono la "vita" di un player durante la riproduzione di un media. Essi sono i seguenti (Figura 1):
* Unrealized
: Il player si trova in questo stato all'atto della sua creazione,
cioè quando è solamente a conoscenza dell'URL della risorsa
da riprodurre. Un opportuno Manager è responsabile della creazione
del player attraverso il metodo static createPlayer() il cui unico parametro
è proprio l'URL associato.
* Realizing:
Una volta che il player è stato creato, attraverso il metodo realize()
della interfaccia Controller, è possibile portarlo in questo stato.
Qui il player acquisisce le risorse di sistema che utilizzerà durante
la riproduzione ma che non sono utili ad altri eventuali riproduttori.
Si dice che il player si impossessa delle risorse non esclusive.
* Realized:
Una volta che il player ha acquisito le risorse relative alla fase precedente,
si porta automaticamente nello stato Realized. Qui il player è a
conoscenza del suo compito per cui è qui che si rendono disponibili
i Control-Panel ed il Visual-Panel. Un player nello stato realized non
blocca nessun altro player candidato alla riproduzione del media, per cui
possono esistere più player in questo stato.
* Prefetching:
quando il player è nello stato Realized, è possibile utilizzare
il metodo prefetch() per portarsi nello stato di Prefetching. Questo stato
è caratterizzato dal fatto che il player inizia ad acquisire le
risorse per la riproduzione del media, comprese le risorse esclusive se
accessibili. Esso si riserva anche un certo buffer in cui farà il
preloading dei primi dati da riprodurre. Il contenuto di questo buffer
potrà eventualmente essere visualizzato da un Control-Panel di tipo
CachingControl.
* Prefetched:
Terminata la precedente fase, si ha il passaggio automatico allo stato
di prefetched. Qui il player è pronto a partire. Tutte le risorse
necessarie sono state acquisite ed eventuali altri player che utilizzassero
le stesse risorse sono impossibilitati all'esecuzione fino a che le risorse
stesse non saranno rilasciate.
* Started:
Finalmente attraverso il metodo start() è possibile iniziare la
riproduzione della risorsa attraverso il player.
Per il passaggio da uno stato ad un altro esistono cinque metodi principali che possono essere applicati solamente quando il player è in un particolare stato pena la generazione di una eccezione anch'essa esplicativa del problema. Esistono i seguenti cinque metodi:
* start()
: appartiene all'interfaccia Clock e può essere eseguito solamente
quando il player è nello stato Prefetched. Lo stato seguente è
lo stato Started.
* stop():
appartiene all'interfaccia Clock e può essere eseguito solamente
quando il player è nello stato Started. Lo stato seguente è
lo stato Prefetched.
* realize():
Serve per portare il player dallo stato di Unrealized allo stato di Realizing.
* prefetch();
Serve per iniziare la fase di prefetching
* deallocate():
Serve per liberare le risorse esclusive acquisite dal player. In questo
modo un eventuale altro player nello stato di Realized, può eseguire
la prefetch().
Il passaggio
tra gli stati è notificato attraverso la emissione di un evento
la cui gestione è alla base della programmazione con le JMF.
Al Lavoro....
Facciamo un attimo il punto della situazione. Vogliamo fare in modo di leggere un determinato file audio o video e riprodurlo in maniera appropriata (Listato 1). Dal file letto, dovremmo ottenere l'URL che ci permetterà di creare il player. Per ottenere un player da un oggetto URL basta utilizzare la seguente istruzione:
Player player=Manager.createPlayer(url);
Esiste, infatti, la classe Manager che, attraverso i suoi metodi static, permette la creazione del player a partire dal suo URL (nel caso specifico si usa il metodo createPlayer()). A questo punto il manager esaminerà l'URL e creerà il player opportuno. Eseguiremo, quindi, il metodo realize() per procedere alla fase di Realizing. La generazione di un evento di tipo RealizeCompleteEvent, ci dirà che la fase di Realizing è terminata e che il player è nello stato di Realized. Esso si è "reso conto" di quello che deve fare e attende che gli venga dato il via per procedere. Ovviamente, per sentire l'evento, bisognerà registrarsi ad esso attraverso una istruzione del tipo
player.addControllerListener(this);
Attraverso l'implementazione della precedente interfaccia è possibile controllare lo stato del player in ogni istante attraverso l'ascolto di numerosi eventi che rappresentano la maggior parte delle classi del package java.media. Dovremo infatti definire il metodo
public synchronized void controllerUpdate(ControllerEvent ce)
esaminando l'oggetto ControllerEvent passato come parametro.
Un volta nello stato Realized, notificato dal fatto che è stato generato un evento del tipo realizeCompleteEvent, possiamo prelevare quelli che saranno i componenti caratteristici il media. Per fare questo utilizziamo due particolari metodi dell'interfaccia Player:
* getVisualComponent()
: permette di ottenere il visual-component
* getControlPanelComponent():
permette di ottenere il Panel con gli strumenti di controllo del flusso
del player.
Gli oggetti ritornati
dai metodi precedenti sono oggetti Component che inseriremo nel nostro
Frame principale utilizzando un layout di Java, nel nostro caso il BorderLayout.
Metteremo il Visual-Component a "Center" ed il Control-Panel a "South".
A questo punto il lettore multimediale si può considerare concluso.
Abbiamo dotato la piccola application anche delle normali funzioni di caricamento.
Abbiamo inserito anche l'opzione di chiusura in modo da permettere il rilascio
delle risorse. Notiamo come la realizzazione sia stata molto semplice in
quanto abbiamo voluto utilizzare gli strumenti standard offerti dalle JMF.
Conclusioni
In questo articolo
abbiamo visto come si possano creare dei lettori multimediali in Java attraverso
l'utilizzo delle API Java Media Framework. Abbiamo visto come alla base
della programmazione con le JMF vi sia la gestione degli eventi secondo
il modello Delegation Model. Si tratta, infatti, di gestire gli eventi
generati dal player nelle fasi di cambiamento di stato. Le JMF permettono
la riproduzione audio e video dei formati più comuni ma le aspirazioni
di tali API sono molto superiori. Si vogliono, infatti, creare degli strumenti
per la cattura video ed audio a livello di client che permettano di rendere
Java uno strumento utile anche alla creazione di applicazioni che permettano
la videoconferenza o la comunicazione audio a distanza. Staremo a vedere.
Bibliografia
[1] " Java Media Players" Version .95, JavaSoft 31 Dicembre 1996
[2] Massimo Carli, " La gestione degli eventi con il JDK1.1", Mokabyte Marzo 1997
/*******************************************************
* Application : MultiMediaPlayer *
********************************************************
* *
* Questa applicazione rappresenta la realizzazione in *
* Java di un lettore multimediale utilizzando le JMF *
* *
* Environment: JDK1.1.1 JMFBeta1.2 *
* Author: Massimo Carli Release: 1.0 Date:24/05/97 *
* *
********************************************************/
import java.awt.*; // Per GUI
import java.awt.event.*; // Per la gestione degli eventi
import java.media.*; // Per le JMF
import java.net.*; // Per l'URL
import java.io.*; // per l'I/O
public class MultiMediaPlayer extends Frame
implements ActionListener,ControllerListener {
protected URL url_media; // URL del media da riprodurre
protected MenuBar menubar; // Barra del menu
protected Menu file; // Menu relativo ai file
protected MenuItem open; // MenuItem open
protected MenuItem close; // MenuItem close
protected Player player; // E' il player da creare
protected Component visual; // Il visual component
protected Component control; // Il control panel
protected Canvas testa; // Intestazione
/*
*Costruttore vuoto
*/
public MultiMediaPlayer(){
super("MediaPlayer"); // Costruttore padre
crea_menu(); // Creiamo i menu
setSize(new Dimension(400,300)); // Settaggio delle dimensioni del frame
setLayout(new BorderLayout(5,5)); // Settiamo il layout
setBackground(new Color(200,200,200)); // Settiamo un colore diverso per gli insets
enableEvents(AWTEvent.WINDOW_EVENT_MASK); // Abilitiamo gli eventi della Window
}// fine costruttore vuoto
// Permette di creare la MenuBar
private final void crea_menu(){
// Creazione dei menu
menubar = new MenuBar();
file = new Menu("File");
open = new MenuItem("Open");
close = new MenuItem("Close");
// Registrazione degli ebenti ai menu
open.addActionListener(this);
close.addActionListener(this);
close.setEnabled(false);
// Composizione
file.add(open);
file.add(close);
menubar.add(file);
setMenuBar(menubar);
}// fine crea_menu
// Gestisce le scelte fatte con i menu
public void actionPerformed(ActionEvent ae){
MenuItem sorg= (MenuItem)(ae.getSource());
if (sorg.equals(open)){
manage_open();
} else if (sorg.equals(close)){
manage_close();
}
}// fine actionPerformed
// Rappresenta il metodo principale perchè permette di gestire
// i cambiamenti di stato del Player
public synchronized void controllerUpdate(ControllerEvent ce){
if (player!=null){ // Se esistem il player
if (ce instanceof RealizeCompleteEvent){ // Se il player è nello stato Realized
if ((control=player.getControlPanelComponent())!=null) // Se c'è prendiamo il
add("South",control); // ControlPanel
if ((visual=player.getVisualComponent())!=null) // Se esiste prendiamo il VisualPanel
add("Center",visual); // E lo mettiamo al centro
validate(); // Mettiamo a posto il layout
}// fine if
} // finr if esterno
}// fine controllerUpdate
// Permette di creare il player a partire dall'url
private final void crea_player(){
if (url_media!=null){ // Se e' stato creato un url
try{
player = Manager.createPlayer(url_media); // Creiamo il player
player.addControllerListener(this); // Sentiamo i ControllerListener
player.realize(); // Inizialo il Realizing
open.setEnabled(false); // Disabilitiamo l'open
close.setEnabled(true); // Abilitiamo la close
}catch(IOException ioe){
System.out.println("ERRORE "+ioe.toString());
}
}
}// fine creaplayer
// Permette di scegliere il file e di ottenere da esso
// l'url
private final void manage_open(){
url_media=null;
FileDialog fd= new FileDialog(this,"LOAD FILE",FileDialog.LOAD);
fd.setFile("*.au;*.wav;*.mid;*.avi;*.mpg");
fd.setVisible(true);
if ((fd.getFile()!=null)&&(fd.getDirectory()!=null)){
try{
String str=fd.getDirectory()+fd.getFile();
url_media=new URL("file:/"+str);
setTitle("MediaPlayer :"+fd.getFile());
} catch(MalformedURLException mue){
System.out.println("ERRORE "+mue.toString());
} catch(IOException ioe){
System.out.println("ERRORE "+ioe.toString());
}
crea_player();
}// fine if
fd.dispose();
}// fine manage_open
// Chiude il player
private final void manage_close(){
player.deallocate(); // Libera le risorse esclusive
url_media=null; // mette l'url a null
removeAll(); // Toglie i Panel
open.setEnabled(true); // Riabilita la open
close.setEnabled(false); // Ridisabilita la close
setTitle("MediaPlayer"); // Aggiorna il titolo del Frame
}// fine manage_close
/**
* Permette di mettere il Dialog in centro
*/
public void setVisible(boolean vis){
Dimension sc=Toolkit.getDefaultToolkit().getScreenSize(); // Dim. Schermo
int pos_x=((sc.width-getSize().width)/2); // Posizione x centrata
int pos_y=((sc.height-getSize().height)/2); // Posizione y centrata
setBounds(pos_x,pos_y,getSize().width,getSize().height);
super.setVisible(vis);
}// fine setVisible()
// Gestisce la chiusura
protected void processWindowEvent(WindowEvent e){
if ((e.paramString()).equals("WINDOW_CLOSING")){
System.exit(0);
if (player!=null)
player.deallocate();
} // fine if
if ((e.paramString()).equals("WINDOW_CLOSED"))
System.exit(0);
}// fine processWindowEvent
public static void main(String[] str){
MultiMediaPlayer mp= new MultiMediaPlayer();
mp.setVisible(true);
}
}// fine MultiMediaPlayer
|
||
|
||
MokaByte ricerca
nuovi collaboratori
|
||
|