Introduzione
Solo alcuni mesi fa parlare della possibilità
di vedere, sul proprio cellulare un video, era considerata
una cosa fantascientifica, farlo poi utilizzando java
come linguaggio di programmazione sembrava davvero "irrealizzabile".
Ma, la ram aumenta, i processori corrono sempre più
richiedendo sempre meno energia, dunque eccoci qua a
parlare di MultiMedia Api (MMA) un insieme di librerie
java per la gestione di contenuti multimediali che si
basa su CLDC/MIDP.
Tutto ciò che verrà trattato in questo
articolo riguarda la Reference Implementation di Sun
(disponibile per piattaforma Window 2000). Le cose non
cambiano, come java ci ha abituato, se cambia la piattaforma.
Attualmente però, mentre sono molti i dispositivi
che supportano CLDC/MIDP, le MMA sono supportate solo
da un dispositivo reale (già disponibile sul
mercato): si tratta del Nokia 3650, un tri-band GSM
(900, 1800, 1900) che supporta inoltre le WMA (Wireless
Messaging Api) di cui abbiamo discusso nel numero di
ottobre di MokaByte.
Al di là delle varie implementazioni, il risultato
importante è quello di poter disporre di un unico
"strumento" per la gestione del "multimediale",
garantendo così la portabilità del codice.
E' vero, infatti, che molti dispositivi possiedono nel
proprio "pacchetto software" players e applicazioni
per questo scopo ma, oltre risultare inaccessibili da
java, potrebbero essere
una parte integrata (ad esempio un visualizzatore di
video mpeg) di una applicazione più complessa.
In quest'ultimo caso, infatti, ci si troverebbe a doversi
interfacciare con software proprietari molto differenti
tra loro. Si pensi ad esempio alla possibilità
di visualizzare sullo schermo del dispositivo le immagini
riprese da un sistema di telecamere di sicurezza. In
questo caso, si potrebbe desiderare che l'applicazione
non si limiti al solo rendering del video ma, che dia
anche la possibilità di commutare tra le varie
telecamere del sistema e di gestirne funzionalità
quali lo zoom e il brandeggio. Lasciando da parte i
contesti applicati pratici ed entriamo nel dettaglio
delle MMA.
Controllable
e Control
Tutta
la "filosofia" MMA ruota intorno al concetto
di "controllato e controllo" espressi in java
dalle interfacce Controllable e Control. Control rappresenta
il controllo che "può essere esercitato"
su di un oggetto controllabile (che estende Controllable).
Ogni oggetto controllabile deve poter fornire informazioni
sul controllo cui è soggetto, per questo l'interfaccia
Controllable possiede due metodi:
public
Control getControl( String controlType )
public Control[] getControls( )
il
primo permette di ottenere un controllo sull'oggetto
controllabile specificandone il nome, il metodo tornerà
null se il controllo non esiste. Il secondo metodo ritorna
l'elenco dei possibili controlli dell'oggetto Controllable.
Le MMA prevedono vari tipi di controlli, nella figura
1 ne riportiamo l'elenco:
Figura 1 - I controlli previsti dalle MMA
Prima
di entrare nei dettagli di ogni singolo controllo, vediamo
il più importante degli oggetti controllabili
il player.
Il
Player
Il
player (javax.microedition.media) si occupa del rendering
di contenuti multimediali, ed estendendo l'interfaccia
Controllable può essere controllato "sotto
molti punti di vista" che variano in base al tipo
di player che si intende realizzare. Esso rappresenta
il cuore di tutta l'architettura MMA, fornisce i metodi
per il rendering delle informazioni, per la gestione
del proprio ciclo di vita, per la sincronizzazione con
eventuali altri players e per la gestione dei dispositivi
necessari (es. display, altoparlante, ecc).
Come visibile in figura 2 un player può trovarsi
in diversi stati:
Figura 2 - Ciclo di vita di un Player
Lo
stato di partenza è UNREALIZED: il player non
esiste ancora occorre realizzarlo con una chiamata a:
public
void realize() throws MediaException
questo porta il player allo stato REALIZED.
In questo stato il player riceve le informazioni necessarie
per il rendering dei contenuti multimediali, può
leggere file o stabilire una connessione con il server
che contiene i contenuti.
Un player non può tornare nello stato UNREALIZED
a meno che non venga invocato il metodo:
public
void deallocate() throws MediaException
prima
di una chiamata a realize() (percorso tratteggiato in
figura 2).
Una
volta realizzato, il player non è ancora pronto
all'utilizzo, occorre ancora un "avanzamento di
stato" che si ottiene con la chiamata a:
public
void prefetch() throws MediaException
questa
operazione permette al player di prendere possesso delle
risorse di cui necessita e di eseguire tutte le operazioni
di inizializzazione.
Nello stato PREFETECH il player è finalmente
pronto all'utilizzo:
lo si può far partire con il metodo:
public
void start() throws MediaException
che
lo porta nell'omonimo stato (START), quello di esecuzione,
oppure si può tornare allo stato precedente (REALIZED)
con una chiamata a deallocate().
Durante la fase di START possono verificarsi due diverse
situazioni:
una chiamata a:
public
void stop() throws MediaException
lo
riporta nello stato PREFETECH, mentre invocando:
public
void close() throws MediaException
il
player rilascia tutte le risorse a sua disposizione
si pone nello stato CLOSED.
E'
possibile monitorare lo stato di un player ricorrendo
al metodo:
public
int getState() throws MediaException
che
ritorna appunto lo stato in cui si trova il player al
momento della chiamata e può assumere uno dei
valori costanti appena visti ( UNREALIZED, REALIZED,
PREFETCHED, STARTED, CLOSED ).
Altri
importanti parametri riguardanti il player possono essere
ottenuti e/o settati con i metodi:
public
TimeBase getTimeBase()
public void setTimeBase(TimeBase timeBase)
public void setLoopCount(int count)
I
primi due permettono rispettivamente di ottenere e settare
il TimeBase, cioè la base dei tempi (che deve
essere assolutamente costante nel tempo) utilizzata
dal player per la sincronizzazione durante la riproduzione
dei contenuti. TimeBase dispone di un unico metodo:
public
long getTime()
il
valore di tipo long ritornato rappresenta il tempo di
sincronizzazione espresso in microsecondi.
Per tornare al valore di default, che dipende dalle
varie implementazioni, si può richiamare setTimeBase
passando null come parametro.
Il
metodo setLoopCount, invece, permette di settare il
numero di volte che un determinato contenuto verrà
riprodotto dal player. Il valore di default è
1, ma può assumere un qualunque intero maggiore
di 0, mentre il valore -1 indica una riproduzione a
ciclo infinito.
Vediamo
ora alcuni metodi del player maggiormente legati al
contenuto da renderizzare, si tratta dei metodi:
public
long getMediaTime()
ritorna
il "puntatore" al tempo corrente relativo
al contenuto multimediale durante la riproduzione. Se
tale valore (espresso in microsecondi) non può
essere determinato verrà ritornata la costante
TIME_UNKNOWN.
public
long setMediaTime(long now)
permette
di spostare "il puntatore" nel tempo corrente
relativo al contenuto in riproduzione. Eventuali valori
negativi o superiori all'effettiva durata del contenuto
comporteranno, rispettivamente, un posizionamento all'inizio
e alla fine del contenuto stesso.
public
long getDuration()
questo
metodo infine è complementare ai due appena visti
e permette di ottenere la durata del contenuto che si
deve riprodurre. Se la durata non è ottenibile
il metodo ritorna la costante TIME_UNKNOWN.
Per
concludere la trattazione sui player vediamo come si
possono gestire gli eventi che esso è in grado
di generare.
La classe player contiene un metodo per agganciare un
"ascoltatore" al player stesso:
public
void addPlayerListener(PlayerListener playerListener)
ed
uno per una eventuale rimozione:
public
void removePlayerListener(PlayerListener playerListener)
La classe PlayerListener possiede un unico metodo:
public
void playerUpdate(Player player, String event, Object
eventData)
che
viene richiamato ogniqualvolta il player genera un evento.
I parametri del metodo rappresentano rispettivamente:
il player che ha generato l'evento, il tipo di evento
(in formato stringa) e un oggetto associato all'evento.
Riportiamo
qui di seguito un elenco dei possibili eventi con il
relativo significato:
DEVICE_AVAILABLE
DEVICE_UNAVAILABLE
questi due eventi indicano la disponibilità o
meno di una risorsa (esclusiva) di cui il player ha
bisogno. La ricezione di tali eventi può avvenire
se il player si trova nello stato REALIZED. La risorsa
potrà essere acquisita dal player con una chiamata
a prefetch().
CLOSED
STOPPED
STARTED
Questi eventi sono la conseguenza alla chiamata di close(),
stop() e start() rispettivamente.
BUFFERING_STARTED
BUFFERING_STOPPED
segnalano l'entrata e l'uscita del player dalla modalità
buffering, cioè dalla fase di caricamento dei
contenuti da renderizzare.
DURATION_UPDATED
viene lanciata a seguito dell'aggiornamento della durata
del contenuto in esecuzione se questa non è nota
a priori. Per alcuni tipi di contenuti multimediali
può non essere possibile stabilirne la durata
se non dopo averne riprodotto una porzione.
END_OF_MEDIA
viene lanciato quando la riproduzione del contenuto
è terminata.
ERROR
lanciato in caso si siano verificati errori.
STOPPED_AT_TIME
viene generato a seguito di una chiamata a setStopTime,
un metodo del controllo StopTimeControl di cui parleremo
più avanti, ed indica che il player si è
fermato come richiesto.
SIZE_CHANGED
indica un cambiamento delle dimensioni del contenuto.
La sorgente video e/o le dimensioni del display sono
cambiate.
RECORD_STARTED
RECORD_STOPPED
indicano rispettivamente l'inizio e la fine di una fase
di registrazione. Se durante tale operazione si verifica
un errore verà generato un evento di tipo:
RECORD_ERROR
VOLUME_CHANGED
Generato a seguito di una variazione del livello di
volume.
Abbiamo
visto che cos'è un player qual è il suo
ciclo di vita, come si utilizza e che cosa può
fare, ma non abbiamo ancora visto come si crea, cioè
come se ne possa ottenere un'istanza.
Per questo motivo ci occupiamo ora della classe Manager
presente in javax.microedition.media.
La
classe Manager: il gestore delle risorse
Manager
è il gestore delle risorse del sistema, si occupa
della gestione a "basso livello" dei player,
nel senso che gestisce l'allocazione e la deallocazione
di tutti i dispositivi (display, altoparlante, microfono,
fotocamera, ecc.) di cui necessitano i player per il
loro funzionamento fornendo inoltre informazioni sull'hardware
sottostante.
Attraverso la classe Manager si può ottenere
un'istanza di Player con il metodo createPlayer disponibile
con tre diverse firme:
public
static Player createPlayer(InputStream is, String type)
throws IOException, MediaException
public
static Player createPlayer(String locator) throws IOException,
MediaException
public
static Player createPlayer(DataSource source) throws
IOException, MediaException
La
prima firma del metodo permette la creazione di un player
a partire da uno stream di input associato alla risorsa
da riprodurre. Il secondo parametro passato (type) rappresenta
il "content-type" della risorsa.
Esempi di type possono essere:
audio/x-wav
: audio in formato Wave;
audio/basic : audio in formato AU;
audio/mpeg : audio in formato MP3 ;
audio/midi : audio in formato MIDI;
audio/x-tone-seq : sequenza di toni;
video/mpeg : video in formato mpeg;
Con
la seconda firma, il player creato è associato
al media-locator passato come parametro. La stringa
passata, locator appunto, non è una stringa arbitraria
ma una URI e come tale ha un ben preciso formato e deve
sottostare alle regole previste per le uri (schema,
caratteri riservati, caratteri di escape, ecc):
<scheme>:<scheme-specific-part>
dove
scheme indica il protocollo utilizzato per la trasmissione
dei dati (multimediali in questo caso).
Ad esempio locators validi (Nokia 3650) sono:
device://midi
capture://video
device://tone
http://server/music.mp3
Esistono
locator specifici per contenuti multimediali:
·
"capture://" device [ "?" params]
permette la "cattura" (registrazione) da sorgenti
multimediali (sorgenti audio/video).
Device rappresenta il tipo di dispositivo o il nome
del dispositivo stesso (es. "audio", "video",
"audio_video", nome device in formato alfanumerico)
mentre i parametri possono essere tipici del contenuto
oppure possono specifici di un dato apparato (es. rate=16000,
encoding=pcm, framepersecond=5, channels=2, bits=8 ecc.).
Una uri completa relativa alla registrazione da una
sorgente video potrebbe essere:
"capture://cam0?encoding=rgb&fps=25".
·
"rtp://" address [ ":" port ] [
"/" type ]
rpt
sta per Real-time Transport Protocol, si tratta di un
protocollo di di livello "trasporto", utilizzato
per lo streaming di contenuti multimediali. Address
e port servono per individuare la sorgente dalla quale
effettuare lo streaming, mentre type rappresenta il
tipo multimediale (video, audio, ecc.).
Tornando al metodo createPlayer l'ultima firma prevista,
prevede il passaggio di un DataSource.
La classe astratta DataSource rappresenta l'astrazione
dei protocolli di basso livello che gestiscono il trasferimento
dei dati multimediali da renderizzare. DataSource e
SourceStream permettono di usufruire di funzionalità
particolari non presenti nei normali stream ( InputStream
), ma particolarmente utili nella trasmissione e nella
gestione di file multimediali come l'accesso random
ai dati (e non solo in modalità "sequenziale")
e la possibilità di suddividere un flusso in
"porzioni minori". DataSource, implementando
l'interfaccia Controllable, può essere controllata
attraverso opportuni controlli ottenibili con i metodi
getControl e getControls.
Attraverso
i metodi:
public
abstract void connect()throws java.io.IOException
public abstract void start() throws java.io.IOException
public abstract void stop()
public abstract void disconnect()throws java.io.IOException
è
possibile gestire il trasferimento dei contenuti multimediali:
il
primo apre una connessione con una sorgente individuata
da locator (parametro passato nel costruttore della
classe DataSource( String locator) ), per mezzo di start
si può avviare il trasferimento dei dati che
può essere interrotto con il metodo stop. Infine,
con una chiamata a disconnect viene rilasciata la connessione
aperta.
Sono inoltre previsti i metodi:
public
abstract String getContentType()
public String getLocator()
che
ritornano, rispettivamente, il tipo del contenuto multimediale
restituito dal DataSource e il locator associato al
DataSource stesso.
Il metodo:
public
abstract SourceStream[] getStreams()
ritorna un array di SourceStream. Ogni SourceStream
nell'array rappresenta una porzione del contenuto totale
(un singolo stream). La classe (astratta) SourceStream
fornisce i metodi necessari alla manipolazione di questi
stream.
public
ContentDescriptor getContentDescriptor()
fornisce
un descrittore dello stream ( ContentDescriptor ),
public
long getContentLength()
ritorna
la lunghezza (numero di byte) dello stream,
public
int getTransferSize()
ritorna
la dimensione dello stream nella sorgente, cioè
la dimensione della singola "porzione" rappresentata
dallo stream stesso o -1 se questa non è disponibile.
public
int getSeekType()
verifica
se, e in che modo, lo stream supporta il seek, cioè
la possibilità di "spostarsi" all'interno
dello stream. I possibili valori di ritorno sono (le
costanti intere):
NOT_SEEKABLE:
non applicabile,
SEEKABLE_TO_START: ogni operazione di seek è
relativa al punto di inizio dello stream,
RANDOM_ACCESSIBLE: lo stream è accessibile in
modo random, è quindi possibile posizionarsi
in ogni suo punto.
public
long seek( int position )
permette
il posizionamento nel punto indicato da position, mentre
con
public
long tell()
è
possibile conoscere la posizione corrente nello stream.
Naturalmente
non poteva mancare:
public
int read(byte[] buffer, int offset, int length) throws
IOException
per
procedere alla effettiva lettura delle stream. La possibilità
di implementare propri DataSource è una caratteristica
molto potente delle MMA, in quanto permette il supporto
di qualunque protocollo.
La creazione di un player con il metodo Manager.createPlayer(
DataSource source), permette infatti di "mascherare"
dentro la particolare implementazione di DataSource
i dettagli relativi al protocollo. Tale implementazione
dovrà fornire al player i metodi appena visti,
e ottenere i dati da una implementazione di SourceStream.
Quest'ultima sarà il wrapping di una "sorgente"
di basso livello (stream, socket o altro) e dovrà
fornire al DataSource gli strumenti per il posizionamento
e la lettura dei dati ( read(..), seek(..), tell(),
etc).
Concludiamo la trattazione della classe Manager, analizzando
gli ultimi tre metodi previsti.
Abbiamo detto che Manager costituisce il "gestore"
del sistema sottostante ed in quanto tale è l'unico
punto da cui è possibile ottenere informazioni
riguardanti l'ambiente nel quale le MMA operano.
Il metodo:
public
static TimeBase getSystemTimeBase()
ritorna
il TimeBase (la base dei tempi), di cui abbiamo già
parlato, relativa al sistema.
public
String[] getSupportedContentTypes(String protocol)
ritorna
una lista di tutti i content-type supportati dal protocollo
passato come parametro (protocol).
Se viene passato null la lista conterrà tutti
i content-type supportati dall'implementazione.
Ad esempio passando null al metodo getSupportedContentTypes(
null ) si ottiene (reference implementation):
audio/x-tone-seq
audio/x-wav
audio/midi
video/mpeg
public
String[] getSupportedProtocols(String contentType)
ritorna,
invece, una lista dei protocolli previsti per un dato
content-type passato come parametro. Se viene passato
null, la lista contiene l'elenco di tutti i protocolli
supportati dall'implementazione.
Ad esempio passando null nella reference implementation
si ottiene:
http
device
capture
I controlli
Come
visibile in figura 1 le MMA prevedono numerosi controlli
che possono essere "applicati" ad un player.
FramePositioningControl
Permette
il controllo dei frame di video. Ogni frame è
numerato, la numerazione parte da 0 che corrisponde
all'inizio del video ( cioè quando una eventuale
chiamata del metodo getMediaTime() di Player ritorna
il valore 0 ), e dipende, ovviamente, dal numero di
frame presenti nel contenuto video. Il mapping tra la
sequenza di frame e la durata corrispondente è
ottenibile con i metodi:
public
long mapFrameToTime(int frameNumber)
public int mapTimeToFrame(long mediaTime)
Nel primo caso, dato il numero di un frame viene riportato
lo "spostamento temporale", espresso in microsecondi,
del frame stesso dall'inizio del video, il secondo metodo
fornisce il risultato contrario. Passando, infatti,
un valore temporale (sempre relativo all'inizio del
video) viene ritornato il frame corrispondente. Entrambi
i metodi effettuano esclusivamente le dette conversioni
senza operare in alcun modo sul contenuto video. Al
contrario i metodi:
public
int seek(int frameNumber)
public int skip(int framesToSkip)
permettono
di ricercare un determinato frame del video (il primo)
e di saltare un certo numero di frame rispetto al frame
corrente (il secondo).
GUIControl
Permette
di interagire con i player utilizzando i dispositivi
di i/o della piattaforma sottostante.
Prevede un unico metodo:
public
Object initDisplayMode(int mode, Object arg)
che
consente di inizializzare il GUIControl. Il parametro
mode passato al metodo specifica come il controllo deve
essere visualizzato (l'unico valore previsto è
USE_GUI_PRIMITIVE).
Il valore ritornato in questo caso è un oggetto
GUI di "tipo primitivo", ciò vuol dire
che se la piattaforma sottostante supporta AWT l'oggetto
sarà di tipo Component, mentre per il MIDP (LCDUI)
sarà di tipo Item. Una volta ottenuto un oggetto
di questo tipo, lo si può inserire in un container
come qualsiasi altro componente (button, textfield,
ecc) e visualizzarlo.
Il parametro arg dipende da mode. Nel caso USE_GUI_PRIMITIVE,
arg è il nome della classe del componente che
si vuole ottenere cioè, "java.awt.Component"
nel caso di AWT, "javax.microedition.lcdui.Item"
nel caso del midp. Il codice riportato sotto mostra
come creare e aggiungere un componente di tipo GUIControl
(sia nel MIDP sia in piattaforme che supportano awt).
MIDP
........................
Form form = new Form(title);
.......................
Player player = Manager.createPlayer(...player type...);
player.realize();
GUIControl guiControl = player.getControl("GUIControl");
if(guiControl != null)
{
Item gui = (Item) gc.initDisplayMode(USE_GUI_PRIMITIVE,
"javax.microedition.lcdui.Item");
form.append(gui);
}
Display.getDisplay(midlet).setCurrent(form);
player.start();
AWT
(es. configurazione CDC)
Player
player = Manager.createPlayer(...player type...);
player.realize();
GUIControl guiControl = player.getControl("GUIControl");
if(guiControl != null)
{
Component gui = (Component) gc.initDisplayMode(USE_GUI_PRIMITIVE,
"java.awt.Component");
add(gui);
}
Display.getDisplay(midlet).setCurrent(form);
player.start();
MetaDataControl
Questo
tipo di controllo può essere utilizzato per ottenere
metadata riguardanti il contenuto multimediale da riprodurre
con il player.
I metodi:
public
String[] getKeys()
public String getKeyValue(String key)
permettono
di ottenere la lista completa dei campi dei metadata
(il primo) e il valore di un determinato campo specificato
da key (il secondo).
Le quattro costanti:
AUTHOR_KEY
COPYRIGHT_KEY
DATE_KEY
TITLE_KEY
rappresentano
metadata molto comuni che forniscono informazioni circa
l'autore, il copyright, la data e il titolo di un dato
contenuto.
MIDIControl
E'
il controllo associato ai dispositivi (sintetizzatori
o porte esterne) MIDI.
Il metodi:
public
int[] getProgram(int channel)
permette
di ottenere il programma (riferito ad un singolo strumento)
associato ad uno dei 16 canali disponibili, mentre si
può accedere ai programmi relativi ad un banco
con il metodo:
public
int[] getProgramList(int bank)
il
numero massimo di programmi per ogni singolo banco è
128 (numerati da 0 a 127), mentre la variabile bank
può assumere un valore compreso tra 0 e 16383.
public
String getProgramName(int bank, int prog)
permette di ottenere il nome di un programma dato il
banco e la posizione (indice) al suo interno. Con il
metodo:
public
int[] getBankList(boolean custom)
si
può ottenere la lista dei banchi disponibili
sia quelli interni che quelli custom, se il parametro
è false, mentre solo questi ultimi nel caso sia
true.
public
String getKeyName(int bank, int prog, int key)
ritorna
il nome associato ad una particolare chiave (suono),
all'interno di un programma (individuato da prog) in
un determinato banco (bank) o null se tale associazione
non esiste.
Poiché questi metodi possono non essere supportati
da alcune implementazioni di MMA il metodo:
public
boolean isBankQuerySupported()
permette
di verificare la possibilità di utilizzarli o
meno per evitare eccezioni del tipo MediaException.
A esempio il seguente codice "esplora" i banchi
presenti in un dispositivo:
MIDIControl
synthetizer = (MIDIControl)p.getControl("MIDIControl");
if( synthetizer.isBankQuerySupported() )
{
int banks[] = synthetizer.getBankList(false);
for( int i=0; i<banks.length; i++)
{
int programs[] = synthetizer.getProgramList(banks[i]);
for( int x=0; x<programs.length; x++)
{
System.out.println( "BANK " + banks[i] + "
- PROGRAM " + programs[x] + " : "+
synthetizer.getProgramName(banks[i],programs[x]));
}
}
}
else
System.out.println(" Bank query not supported ");
Sul
Nokia 3650, questo codice produce un output del tipo:
BANK
0 - PROGRAM 0 : Piano
BANK 0 - PROGRAM 1 : Bright Piano
BANK 0 - PROGRAM 2 : Electric Grand
.............................................................
BANK 0 - PROGRAM 125 : Helicopter
BANK 0 - PROGRAM 126 : Applause
BANK 0 - PROGRAM 127 : Gunshot
BANK 1 - PROGRAM 27 : Hi-Q
BANK 1 - PROGRAM 28 : Slap
BANK 1 - PROGRAM 29 : Scratch Push
..............................................................
BANK 1 - PROGRAM 87 : Open Surdo
BANK 1 - PROGRAM 94 : Brush Snare High
BANK 1 - PROGRAM 95 : Brush Snare Low
BANK 2 - PROGRAM 0 : Soft Piano
BANK 2 - PROGRAM 1 : Reflection Piano
BANK 2 - PROGRAM 2 : Flange Piano
...............................................................
BANK 2 - PROGRAM 125 : Cosmic Ray
BANK 2 - PROGRAM 126 : SampHold
BANK 2 - PROGRAM 127 : SampHold 2
BANK 3 - PROGRAM 0 : tablaesque_lo
BANK 3 - PROGRAM 1 : tablaesque_hi
BANK 3 - PROGRAM 2 : nine_inch_kick
..................................................................
BANK 3 - PROGRAM 89 : Gate-tone
BANK 3 - PROGRAM 90 : chem-tone
BANK 3 - PROGRAM 91 : dub_kick
Per
potere agire separatamente sul livello del volume di
ogni singolo canale, si utilizzano i metodi:
public
int getChannelVolume(int channel)
public void setChannelVolume(int channel, int volume)
che
permettono, appunto di ottenere il volume di un determinato
canale e di settarlo.
public
void setProgram(int channel, int bank, int program)
permette
di settare un programma in un determinato banco, relativo
al canale indicato da channel.
I
due metodi
public
void shortMidiEvent(int type, int data1, int data2)
public int longMidiEvent(byte[] data, int offset, int
length)
infine,
servono per inviare eventi ai dispositivi midi.
Nel primo caso i dati passati possono essere 1,2 o 3
byte, il primo parametro (type) è suddiviso in
due parti (parte alta e bassa) permettendo quindi di
settare il tipo di evento e il canale sul quale lo si
vuole inviare. Ad esempio:
shortMidiEvent(
NOTE_ON | chNumber, note, velocity );
permette
di lanciare l'evento per richiedere l'esecuzione di
una data nota, specificando la velocità e il
canale di destinazione. Può capitare che alcune
implementazioni midi non supportino alcuni tipi di eventi
e, non è neppure garantito che un evento sia
effettivamente "ascoltato" in questi casi
il fallimento del metodo viene superato "in silenzio"
senza che venga rilanciata alcuna eccezione.
Il secondo metodo permette l'invio di eventi lunghi
ai dispositivi midi (è possibile inviare con
questo metodo un insieme di short event). Al metodo
devono essere passati l'array contenete i dati relativi
agli eventi, il punto di partenza nell'array (offset)
e la lunghezza dei dati.
PitchControl
E'
il controllo relativo al grado di tonalità dell'audio,
permette di aumentarne o attenuarne il valore durante
la riproduzione senza alterare altri parametri. I metodi:
public
int getMaxPitch()
public int getMinPitch()
ritornano, rispettivamente, la massima e la minima tonalità
consentita, mentre:
public
int setPitch(int millisemitones)
public int getPitch()
permettono
di settare ed ottenere la tonalità corrente.
I valori passati e ritornati da questi metodi sono espressi
su base mille e rappresentano un aumento, se positivi,
o una diminuzione, se negativi della tonalità
corrente rispetto a quella originale.
RateControl
Permette
il controllo della velocità di riproduzione di
un contenuto e rappresenta il rapporto tra il media
time e il Timebase di un player. Tale rapporto è
espresso su base mille.
Utilizzando i metodi:
public
int getMaxRate()
public int getMinRate()
public int getRate()
ritornano,
rispettivamente, il rate massimo, minimo e attuale di
un player, mentre
public
int setRate(int millirate)
permette
di settarne il valore.
RecordControl
Permette
di controllare le operazioni di registrazione di un
contenuto in riproduzione nel player.
La registrazione inizia con la chiamata al metodo:
public
void startRecord()
e
termina nel momento in cui viene invocato:
public
void stopRecord()
Il
metodo:
public
void reset()
permette
di "distruggere" il risultato della registrazione,
mentre
public
void commit()
completa
la registrazione e procede al salvataggio effettivo
dello stream registrato.
I
dati registrati (il flusso proveniente dal player) sono
inviati ad uno stream di output. Per far confluire i
dati provenienti della registrazione in detto stream
si utilizza il metodo:
public
void setRecordStream(OutputStream stream)
Per
limitare la quantità di dati, intesa come numero
di byte, registrati si può utilizzare il metodo:
public
int setRecordSizeLimit(int size)
Il
metodo:
public
void setRecordLocation(String locator)
infine, permette di stabilire dove i dati saranno salvati
(come abbiamo visto, locator ha la forma di una URL).
StopTimeControl
Permette
di controllare "il punto di arresto" di un
player.
public
long getStopTime()
ritorna
il valore (in microsecondi) dell'istante in cui il player
verrà bloccato. Tale valore è relativo
al media time del contenuto in esecuzione sul player
ed è quello precedentemente impostato con:
public
void setStopTime(long stopTime)
TempoControl
E'
il controllore del tempo (battute al minuto) di un player
per la riproduzione di contenuti musicali (es. MIDI).
I metodi:
public
int getTempo()
public int setTempo(int millitempo)
consentono,
rispettivamente, di ottenere e settare il tempo (espresso
su base mille).
ToneControl
Permette
il controllo della riproduzione di sequenze di toni
definiti dall'utente, per mezzo del metodo:
public
void setSequence(byte[] sequence)
sequence
rappresenta la sequenza da riprodurre, espressa come
un insieme di blocchi di coppie tono/durata. La forma
generale è la seguente:
sequence
= version tempo_definition resolution_definition block_definition
sequence_event;
dove:
version:
è il numero della versione;
tempo_definition: è la definizione degli attributi
riguardanti il tempo;
resolution_definition: rappresenta la risoluzione desiderata;
block_definition: è la definizione dei blocchi
(blocco di partenza e blocco di fine sequenza)
sequence_event: contiene le informazioni riguardanti
i toni da riprodurre, contiene i seguenti sottocampi:
tone_event
block_event
volume_event
repeat_event
che
servono a stabilire, rispettivamente il tono (nota e
durata), il numero del blocco, il valore del volume
e il numero di volte che il tono stesso deve essere
ripetuto.
VideoControl
E'
il controllo necessario alla realizzazione di player
video. Il suo nome non deve trarre in inganno: come
vedremo esso non si occupa della gestione del video
inteso come contenuto multimediale (gestione del flusso
video/audio in ingresso/uscita), ma solo del suo rendering
sui dispositivi di output ( per agire sul contenuto,
come abbiamo visto, esistono altri tipo di controlli
come FramePositioningControl, MetadataControl, RateControl,
RecordControl e VolumeControl che tratteremo tra breve).
VideoControl estende GUIControl dalla quale eredita
il metodo:
public
Object initDisplayMode(int mode, Object arg)
che
permette l'inizizializzazione del controllo video, stabilendo
le modalità con cui il video deve riprodotto.
Tali modalità possono essere:
USE_GUI_PRIMITIVE:
di cui abbiamo detto a proposito del GUIControl;
USE_DIRECT_VIDEO: che a differenza del primo può
essere utilizzato solo in ambiente MIDP (richiede il
supporto lcdui). In questo caso il parametro arg da
passare al metodo deve essere un oggetto Canvas o una
sua estensione.
I metodi:
public
void setDisplaySize(int width, int height)
public void setDisplayLocation(int x, int y)
public
int getDisplayHeight()
public int getDisplayWidth()
public
int getDisplayY()
public int getDisplayX()
permettono,
rispettivamente, di stabilire ed ottenere le dimensioni
e il posizionamento del video sullo schermo del dispositivo
( le coordinate di default sono 0,0 ).
public
void setVisible(boolean visible)
permette
di rendere o meno (passando true o false come parametro)
visibile il video sul display, mentre con:
public
void setDisplayFullScreen(boolean fullScreenMode)
permette
di ottenere una visione a pieno schermo.
public
int getSourceHeight()
public int getSourceWidth()
ritornano
altezza e larghezza della sorgente video (solitamente
corrispondono a quelle dello schermo).
Infine,
il metodo:
public
byte[] getSnapshot(String imageType)
permette
di "fotografare il video" ottenendo un'istantanea
del frame corrente.
Il parametro passato permette di stabilire il formato
dell'immagine (l'unico attualmente supportato è
quello PNG). I byte ritornati rappresentano il contenuto
dell'immagine.
VolumeControl
Permette
il controllo del volume delle sorgenti audio. Il livello
del volume è controllabile per mezzo dei metodi:
public
int getLevel()
public int setLevel(int level)
che
ritornano e settano il valore corrente del volume, e
public
void setMute(boolean mute)
public boolean isMuted()
che
permettono di disabilitare la riproduzione audio e verificare
se ci si trova in tale situazione.
Abbiamo
visto come le MMA siano ricche di controlli tra loro
distinti e indipendenti, questo permette la realizzazione
di player dei relativi controlli molto "leggeri"
e adattabili alle più diverse esigenze. Concludiamo
riportando quì di seguito un esempio relativo
ad un player video.
import
java.io.*;
import javax.microedition.midlet.*;
import javax.microedition.io.*;
import javax.microedition.lcdui.*;
public class VideoPlayer extends MIDlet {
private static VideoPlayer instance;
private JavaVideoPlayer jvPlayer;
private int screenWidth = 100;
private int screenHeight = 100;
private String videoUrl;
/** Il costruttore */
public VideoPlayer() {
instance = this;
/** Recupera le informazioni relative alle dimensioni
del display dal file JAD*/
String s = getAppProperty("screen-size");
videoUrl = getAppProperty("url");
if( s != null )
{
s = s.trim();
screenWidth = Integer.valueOf( s.substring(0, s.indexOf("x"))
).intValue();
screenHeight = Integer.valueOf( s.substring(s.indexOf("x")+1)
).intValue();
}
}
public void startApp() {
try
{
/** Crea un oggetto JavaVideoPlayer passando la url
del video da riprodurre e le dimensioni*/
/** del display*/
jvPlayer = new JavaVideoPlayer(videoUrl, screenWidth,
screenHeight);
} catch( Exception e)
{
e.printStackTrace();
}
/** Se la creazione del JavaVideoPlayer è riuscita
viene visualizzato il player sul display*/
/** altrimenti viene lanciato un messaggio di errore
*/
if( jvPlayer != null)
Display.getDisplay(this).setCurrent(jvPlayer);
else
Display.getDisplay(this).setCurrent(new Alert("Error",
"Unable to create Java Video Player", null,
AlertType.ERROR));
}
public void pauseApp() {
}
public void destroyApp(boolean unconditional) {
instance.notifyDestroyed();
instance = null;
}
}
import
java.io.*;
import javax.microedition.io.*;
import javax.microedition.lcdui.*;
import javax.microedition.media.*;
import javax.microedition.media.control.*;
public
class JavaVideoPlayer extends Canvas {
/** Il player */
private Player player = null;
/** Il controllo video */
private VideoControl videoCtrl = null;
/** Il controllo audio */
private VolumeControl volumeCtrl = null;
/** Il controllo sul posizionamento dei frame */
private FramePositioningControl frameCtrl = null;
/** Il percorso per individuare il video da riprodurre
*/
private String resUrl = null;
/** Le dimensioni del display */
private int screenHeight;
private int screenWidth;
/** Modalità "pieno schermo" */
private boolean fullScreen = false;
/** Costruttore*/
public JavaVideoPlayer(String url, int w, int h) throws
Exception
{
this.resUrl = url;
this.screenWidth = w;
this.screenHeight = h;
init();
}
/**
Inizializza il player e crea i controlli associati */
public void init()
{
try{
/** "Legge" il vedio da uno stream di input.
La risorsa in questo caso è sul dispositivo */
/** ma potrebbe essere su un server remoto ed essere
letta con un socket o una HttpConnection */
InputStream is = getClass().getResourceAsStream(resUrl);
/** Ottiene un il player video dal manager */
player = Manager.createPlayer(is, "video/mpeg");
/** Crea il player */
player.realize();
/** Crea i controlli */
videoCtrl = (VideoControl) player.getControl("VideoControl");
volumeCtrl = (VolumeControl) player.getControl("VolumeControl");
frameCtrl = (FramePositioningControl) player.getControl("FramePositioningControl");
/* Inizializza il controllo video */
videoCtrl.initDisplayMode(VideoControl.USE_DIRECT_VIDEO,
this);
int displayWidth = screenWidth*80/100;
int displayHeight = screenHeight*80/100;
/* Inposta le dimensioni e il posizionamento del video
sul display */
videoCtrl.setDisplaySize(displayWidth, displayHeight);
videoCtrl.setDisplayLocation( (screenWidth-displayWidth)/2,
(screenHeight - displayHeight)/2 );
videoCtrl.setVisible(true);
/** Porta il player nello stato PREFETCH */
player.prefetch();
} catch(Exception e){}
}
public void paint( Graphics g )
{
/** Si limita solo a scrivere la label Java Video Player
i alto sul display */
g.fillRect(0,15,screenWidth, screenHeight-15);
int oldColor = g.getColor();
g.setColor(255, 255, 255);
g.fillRect(0,0,screenWidth,14);
g.setColor(255, 0, 0);
g.drawString("Java Video Player", screenWidth/2,
0, g.TOP | g.HCENTER);
g.setColor(oldColor);
}
/** Gestisce gli eventi derivanti dalla pressione dei
tasti sul tastierino del dispositivo */
public void keyPressed(int keyCode)
{
try {
switch( keyCode )
{
case KEY_NUM0:
volumeCtrl.setMute( !volumeCtrl.isMuted() );
break;
case KEY_NUM1:
break;
case KEY_NUM2:
start_pause();
break;
case KEY_NUM3:
case KEY_NUM4:
break;
case KEY_NUM5:
stop();
break;
case KEY_NUM6:
break;
case KEY_NUM7:
rewind();
break;
case KEY_NUM8:
videoCtrl.setDisplayFullScreen(fullScreen = ! fullScreen);
break;
case KEY_NUM9:
forward();
break;
case KEY_POUND:
volumeCtrl.setLevel(volumeCtrl.getLevel()+10);
break;
case KEY_STAR:
volumeCtrl.setLevel(volumeCtrl.getLevel()-10);
break;
case UP:
case DOWN:
case LEFT:
case RIGHT:
case FIRE:
break;
}
}catch (Exception e){}
}
/** Fa partire il player o, se questo è già
in esecuzione lo porta nello stato di pausa */
public void start_pause() throws Exception
{
if(player.getState() == Player.PREFETCHED)
{
videoCtrl.setVisible(true);
player.start();
}
else if (player.getState() == Player.STARTED)
player.stop();
}
/** Ferma il player e riposiziona riporta il "puntatore"
all'inizio del video */
public void stop() throws Exception
{
player.stop();
player.setMediaTime(0);
}
/** Permette di spostare il "puntatore" nel
frame successivo */
public void forward() throws Exception
{
frameCtrl.skip(1);
}
/** Permette di spostare il "puntatore" nel
frame precedente */
public void rewind() throws Exception
{
frameCtrl.skip(-1);
}
}
Ecco come si presenta il player sull'emulatore MMA:
Figura 3 - Riproduzione di un video
Figura 4 - Modalità 'Full-Screen'
Riferimenti
http://www.iana.org/assignments/media-types/
http://www.nokia.com
http://www.ietf.org/rfc/rfc1889.txt
http://java.sun.com/j2me/
|