MokaByte 96 - Maggio 2005
  MokaByte 96 - Maggio 2005  

 

 

 

MIDP2.0 Giochi e Suono
Una rapida occhiata alla riproduzione di suoni

Il profilo MIDlet 2.0 include le API Media, interfacce conformi alle specifiche del JSR-135 (Mobile Media API). Per farla breve, audio e video. Occupiamoci del suono. Formato e fonte dipendonon dal dispositivo. Consideriamo generalmente supportata la riproduzione del formato WAV da un file sorgente incluso nel pacchetto di distribuzione del nostro programma. Non è raro che il supporto al profilo MIDP2.0 sia incluso in dispositivi in grado di riprodurre file MP3. Si tratta, in ogni caso, di informazioni da prelevare sul sito del produttore.

Manager Player e latenza del suono
La riproduzione di file audio richiede l'uso delle classi javax.micredition.media.Manager e javax.microedition.media.Player. Manager è un produttore di oggetti Player. Player è l'oggetto che concretamente si occuperà di generare, attraverso la piattaforma hardware, il suono. Data la cronica scarsità di risorse dei dispositivi CLDC, l'acquisizione delle risorse hardware da parte di un player è rinviata fintantochè il programma non invochi il metodo prefetch. In altri termini, la costruzione di un oggetto Player non comporta l'acquisizione di risorse e non è sufficiente a preparare il suono alla riproduzione. Non è, per un videogioco, una questione marginale. Il suono segue l'azione, non può arrivare in ritardo. Allo scopo di ridurre il periodo di latenza iniziale, le MMAPI forniscono un Player del metodo prefetch. La preparazione di un player segue dunque questi passi:

InputStream source = getClass().getResourceAsStream("/suono.wav");
Player player = Manager.createPlayer(source, "audio/x-wav");
player.prefetch(); //preparazione

Una volta che abbia avuto accesso alle risorse hardware, il Player passerà allo stato PREFETCHED. In questa condizione, il suono è pronto per essere riprodotto in tempo reale. Quando il suono immagazzinato nel player risulti ancora necessario, ma non nell'immediato, le risorse hardware da questo occupate potranno essere liberate, attraverso l'invocazione del metodo deallocate. Un player per cui sia stato invocato il metodo deallocate è ancora valido, e si trova nello stesso stato precedente all'invocazione del metodo prefetch. Quando il suono non sia più necessario per il prosieguo del programma, tutte le risorse occupate dal Player potranno essere liberate attraverso il metodo close. Una volta chiuso, l'oggetto Player non è più usabile.

 

Riproduzione
Un Player riproduce il suono quando sia invocato il metodo start. Documentazione alla mano, start restituisce il controllo dopo la produzione di un evento STARTED. L'evento in questione è intercettabile da eventuali oggetti PlayerListener connessi al Player. La restituzione del controllo non implica l'istantanea riproduzione del suono. In altri termini, l'invocazione del metodo start non garantisce che il controllo sia restituito quando il player sia passato allo stato STARTED. Tuttavia, la previa invocazione del metodo prefetch è tutto quanto sia possibile fare per garantire una corretta riproduzione. Cerchiamo di esemplificare la riproduzione condizionata ad un evento. Definiamo come evento la pressione di un tasto. Nulla vieta che l'evento sia altro (ad esempio una collisione).

import javax.microedition.lcdui.*;
import javax.microedition.midlet.*;
import javax.microedition.media.*;
import java.io.*;

public class Sample extends MIDlet implements PlayerListener {
  Canvas canvas = new MyCanvas();
  Player player;

  public Sample() {
    try {
      InputStream source = getClass().getResourceAsStream("/spacemusic.wav");
      player = Manager.createPlayer(source, "audio/x-wav");
      player.addPlayerListener(this);
    } catch(Exception e) {
      e.printStackTrace();
    }
  }

  public void startApp() {
    try {
      player.prefetch();
    } catch(MediaException e) {
      e.printStackTrace();
    }
    Display.getDisplay(this).setCurrent(canvas);
  }

  public void pauseApp() {
    player.deallocate();
  }

  public void destroyApp(boolean u) {
    player.close();
    notifyDestroyed();
  }

  public void startSound() {
    try {
      player.start();
    } catch(MediaException e) {
      e.printStackTrace();
    }
  }

  public void stopSound() {
    try {
      player.stop();
      player.setMediaTime(0);
    } catch(MediaException e) {
      e.printStackTrace();
    }
  }

  public void playerUpdate(Player p, String event, Object eventData) {
    if(event == PlayerListener.STOPPED || event == PlayerListener.STARTED) {
      canvas.repaint();
    }    
  }
}

private class MyCanvas extends Canvas {

   protected void paint(Graphics g) {
      g.setColor(player.getState() == Player.STARTED ? 0xFFFF00 : 0);
      g.fillRect(0, 0, getWidth(), getHeight());
   }

  protected void keyPressed(int code) {
    if(code == KEY_NUM0) {
      startSound();
    }
    
else if(code == KEY_NUM1) {
      stopSound();
    }
  }

}

Il costruttore di Sample si occupa di creare un oggetto Player. Per rappresentare a video lo stato del Player, è aggiunto un PlayerListener. Si tratta di un'interfaccia essenziale, a cui Player notifica alcuni mutamenti di stato. Tra queste notifiche, catturiamo il momento in cui player entra in stato STARTED e STOPPED. Nel primo caso, coloriamo lo schermo di giallo, nel secondo useremo il nero (metodo paint della classe interna MyCanvas). Invochiamo prefetch nel momento in cui l'oggetto MIDlet è attivato. Liberiamo le risorse allocate con prefetch nel momento in cui l'oggetti MIDlet sia interotto (metodo pause). La pressione del tasto 0 (zero) invoca il metodo startSound che, a sua volta, gira la richiesta al player. La richiesta ha effetto solo se il suono non sia già in riproduzione. La pressione del tasto 1 invoca stop. Il metodo stopSound ferma il souno e azzera il tempo di esecuzione. Senza azzeramento, il Player conserverebbe la posizione del cursore di riproduzione. La successiva invocazione di start riprenderebbe il suono dal punto in cui si era fermato.

 

Conclusioni
Quanti suoni è possibile eseguire, contemporaneamente, su un dispositivo CLDC? Dipende dal dispositivo. La piattaforma software è in grado di caricare un numero non definito di suoni, ognuno incapsulato in un oggetto Player. Quanti, dipende dalle risorse di sistema. Di questi suoni precaricati, solo alcuni possono passare allo stato PREFETCHED. L'eccezione MediaException, eventualmente rilasciata durante l'esecuzione del metodo prefetch, indica esattamente questa momentanea indisponibilità di risorse hardware. Esiste poi la questione dei Thread dedicati alla riproduzione audio. Nelle piattaforme J2SE è la norma. I suoni sono eseguiti nutrendo un pool di N processi con uno o più buffer di dati audio. Per i PC, è una questione di semplicità d'uso, e non solo di prestanzioni. Per i processori CLDC, quantomeno gli attuali, è preferibile sfruttare l'asicronia propria del metodo start, senza ulteriori processi.

 

Bibliografia
[1] Sun Microsystem - "JSR-135 JCP Specifications", http://www.jcp.org/aboutJava/communityprocess/final/jsr135/
[2] Sun Microsystem - "JSR-118 JCP Specifications", http://jcp.org/aboutJava/communityprocess/final/jsr118/index.html
[3] Brackeen et al. - "Developing Games in Java", New Riders, 2002