Package memory.lecteur

Source Code of memory.lecteur.LecteurMP3

package memory.lecteur;


import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;

import javax.sound.sampled.AudioFileFormat;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.FloatControl;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
import javax.sound.sampled.UnsupportedAudioFileException;

import javazoom.spi.PropertiesContainer;
import javazoom.spi.mpeg.sampled.file.MpegAudioFileReader;

import org.tritonus.share.sampled.file.TAudioFileFormat;




/**
* LecteurMP3 est un lecteur de fichier MP3 simplissime basé sur l'API JavaSound
* et sur le BasicPlayer de JavaZoom
*/
public class LecteurMP3 implements Runnable
{
    /************** LES CONSTANTES **************************************************************************/
    //elles indiquent dans quel état est le lecteur et permettent de gérer le thread
    private static final int UNKNOWN = -1,
                             PLAYING = 0,
                             PAUSED = 1,
                             STOPPED = 2,
                             SEEKING = 4,
                             OPENED = 3;
    /************** LES ATTRIBUTS ***************************************************************************/
    //le tampon de lecture
    private static int EXTERNAL_BUFFER_SIZE = 4000 * 4;
   
    //un lecteur de fichier MP3
    private static MpegAudioFileReader mafr = new MpegAudioFileReader();

   
//    // ? peut-être les octets d'entête des fichiers MP3 ?
//    private static int SKIP_INACCURACY_SIZE = 1200;
    //la source audio sous forme d'un fichier mp3
    private File source;
    //le thread de lecture
    private Thread thread = null;
    //gestion du flux de lecture (le flux encodé en MP3, IO)
    private AudioInputStream encodedAudioInputStream;
    //la longueur du fichier en octet
    private int nbBytes = -1;
    //gestion du flux audio (le flux décodé, AudioSystem)
    private AudioInputStream audioInputStream;
    //les infos sur le format du fichier
    private AudioFileFormat audioFileFormat;
    //la sortie son (AudioSystem)
    private SourceDataLine sortieSon;
    //le controle de volume
    protected FloatControl controleVolume;
    //l'état du lecteur (au départ inconnu)
    private int etat = UNKNOWN;
    // les écouteurs qui seront informés des changements d'état
    private Collection listeners = null;
    //le nombre d'octets à passer avant de commencer à jouer
    private int nbByteSkip = -1;
   
    /************** CONSTRUCTEUR ******************************************************************/
   
    public LecteurMP3()
    {
        //lorsque l'on crée le lecteur, la source n'existe pas
        this.source = null;
        //il n'y a pas non plus d'écouteurs d'évènements (liste vide)
        this.listeners = new ArrayList();
        //initialiser le lecteur
        reset();
    }

    /************** INITIALISATIONS ***************************************************************/
   
    /**
     * initialiser le lecteur
     */
    protected void reset()
    {
        //l'état du lecteur est inconnu (en fait ni lecture en cours, pause, arrêté ...)
        this.etat = UNKNOWN;
        //si un fichier audio est en lecture au moment de l'initialisation, fermer le flux
        if (this.audioInputStream != null)
        {
            synchronized (this.audioInputStream)
            {
                closeStream();
            }
        }
        //le flux entrant est null
        this.audioInputStream = null;
        //le format du fichier est null
        this.audioFileFormat = null;
        //l'encodage du flux est null
        this.encodedAudioInputStream = null;
        //la longueur de l'encodage est -1 (sans objet)
        this.nbBytes = -1;       
        //Si la ligne de sortie son n'est pas nulle, on l'arrête, la ferme et la passe à null
        if (this.sortieSon != null)
        {
            this.sortieSon.stop();
            this.sortieSon.close();
            this.sortieSon = null;
        }
        //le volume est null
        this.controleVolume = null;
    }

    /**
     * Initialiser le flux et le format audio à partir du fichier source
     * @throws LecteurException les exceptions du lecteur
     */
    protected void initAudioInputStream() throws LecteurException
    {
        try
        {
            //on réinitialise le lecteur
            reset();
            //informer les listeners de l'état du lecteur
            //cette information sera lancée dans un thread à part pour ne pas ralentir le lecteur
            //On s'intéresse essentiellemnt à la position dans le fichier de lecture
            notifyEvent(LecteurEvent.OPENING);
            //initialiser le flux audio (c'est un fichier local)
            initAudioInputStream(this.source);
            //création de la ligne de sortie du son
            createLine();
            //Pour les mp3 il faut passer par l'API Tritonus SPI. Il faut cependant prévoir le cas
            //où le fichier lu ne serait pas compatible avec l'interface Tritonus
            Map proprietesMP3 = null;
            if(this.audioFileFormat instanceof TAudioFileFormat)
            {
                //on passe le format compatible avec l'API Tritonus
                proprietesMP3 = ((TAudioFileFormat) this.audioFileFormat).properties();
            }
            else throw new LecteurException(this.source + " n'est pas un fichier mp3");

            //le nombre d'octets du fichier : utile pour déterminer approximativement le nombre d'octets
            //à sauter avant de commencer à jouer un morceau. Ce nombre d'octet ne comporte pas que les
            //octets audio mais aussi les tags. Ces derniers représentent une infime partie du fichier
            //audio lui-même et donc l'erreur engendrée est minime.
            this.nbBytes = this.audioFileFormat.getByteLength ();
            //la durée du morceau en microsecondes
            Long duree = (Long) proprietesMP3.get ("duration");
            //prévenir chaque écouteur de la durée du chant
            Iterator it = this.listeners.iterator();
            while (it.hasNext())
            {
                LecteurListener listener = (LecteurListener) it.next();
                listener.opened (duree);
            }
            //le flux est "ouvert"
            this.etat = OPENED;
            //il faut en informer les listeners
            notifyEvent(LecteurEvent.OPENED);
        }
        //les erreurs susceptibles de survenir
        catch (LineUnavailableException e)
        {
            throw new LecteurException(e);
        }
        catch (UnsupportedAudioFileException e)
        {
            throw new LecteurException(e);
        }
        catch (IOException e)
        {
            throw new LecteurException(e);
        }
    }

//    /**
//     * Initialiser la ressource audio à partir du fichier MP3
//     * @param file                              le fichier à lire
//     * @throws UnsupportedAudioFileException    erreur audio
//     * @throws IOException                      erreur fichier
//     */
//    protected void initAudioInputStream(File file) throws UnsupportedAudioFileException, IOException
//    {
//        this.audioInputStream = AudioSystem.getAudioInputStream(file);
//        this.audioFileFormat = AudioSystem.getAudioFileFormat(file);
//    }

    /**
     * Initialiser la ressource audio à partir d'une URL représentant un fichier MP3
     * @param file                              le fichier à lire
     * @throws UnsupportedAudioFileException    erreur audio
     * @throws IOException                      erreur fichier
     */
    protected void initAudioInputStream(URL url) throws UnsupportedAudioFileException, IOException
    {
//      System.out.println("initAudioInputStream " + url);
        this.audioInputStream = AudioSystem.getAudioInputStream(url);
        this.audioFileFormat = AudioSystem.getAudioFileFormat(url);
    }
   
    /************* ACTIONS SUR LE FLUX ***********************************************************/

    /**
     * fermer le flux
     */
    private void closeStream()
    {
        try
        {
            if (this.audioInputStream != null) this.audioInputStream.close();
        }
        catch (IOException e)
        {
            System.out.println("erreur sur la fermeture du flux");
            e.printStackTrace ();
        }
    }
   
    /************* ACTIONS SUR LE LECTEUR ***********************************************************/
   
    /**
     * Arrêter la lecture
     * Etat du lecteur = STOPPED.
     * le thread doit libérer les ressources audio
     */
    private void stopPlayback()
    {
        if ((this.etat == PLAYING) || (this.etat == PAUSED))
        {
            if (this.sortieSon != null)
            {
                this.sortieSon.flush();
                this.sortieSon.stop();
            }
            this.etat = STOPPED;
            notifyEvent(LecteurEvent.STOPPED);
            synchronized (this.audioInputStream)
            {
                closeStream();
            }
        }
    }

    /**
     * Faire une pause dans la lecture
     *
     * Etat du lecteur = PAUSED.
     */
    private void pausePlayback()
    {
        if (this.sortieSon != null)
        {
            if (this.etat == PLAYING)
            {
                this.sortieSon.flush();
                this.sortieSon.stop();
                this.etat = PAUSED;
                notifyEvent(LecteurEvent.PAUSED);
            }
        }
    }

    /**
     * Relancer la lecture après une pause
     * Etat du lecteur = PLAYING.
     */
    private void resumePlayback()
    {
        if (this.sortieSon != null)
        {
            if (this.etat == PAUSED)
            {
                this.sortieSon.start();
                this.etat = PLAYING;
                notifyEvent(LecteurEvent.RESUMED);
            }
        }
    }

    /**
     * Démarrer la lecture
     */
    private void startPlayback() throws LecteurException
    {
        if (this.etat == STOPPED) initAudioInputStream();
        if (this.etat == OPENED)
        {
            //ATTENTION : le thread précédent est encore en lecture
            if ((this.thread != null) && (this.thread.isAlive()))
            {
                int cnt = 0;
                while (this.etat != OPENED)
                {
                    try
                    {
                        if (this.thread != null)
                        {
                            cnt++;
                            Thread.sleep(1000);
                            if (cnt > 2)
                            {
                                //s'il ne veut pas s'arrêter de lui même on l'interrompt
                                this.thread.interrupt();
                            }
                        }
                    }
                    catch (InterruptedException e)
                    {
                        throw new LecteurException(LecteurException.WAITERROR, e);
                    }
                }
            }
            // Ouvrir la sortie son
            try
            {
                initLine();
            }
            catch (LineUnavailableException e)
            {
                throw new LecteurException(LecteurException.CANNOTINITLINE, e);
            }
            // créer un nouveau thread
            this.thread = new Thread(this, "LecteurMP3");
            this.thread.start();
            if (this.sortieSon != null)
            {
                this.sortieSon.start();
                this.etat = PLAYING;
                notifyEvent(LecteurEvent.PLAYING);
            }
        }
    }
   
    /************* ACTIONS SUR LA SORTIE SON *****************************************************/
   
    /**
     * Initialiser la sortie son
     * C'est là que les choses sérieuses commencent
     * @throws LineUnavailableException     les erreurs sur la sortie son
     */
    private void initLine() throws LineUnavailableException
    {
        //créer la sortie si elle n'existe pas
        if (this.sortieSon == null) createLine();
        //l'ouvrir si elle n'est pas ouverte
        if (!this.sortieSon.isOpen())
        {
            openLine();
        }
        else
        {
            //comparer le format audio qu'attend la sortie son et celui du flux
            //s'ils sont différents, fermer puis rouvrir la ligne
            AudioFormat lineAudioFormat = this.sortieSon.getFormat();
            AudioFormat audioInputStreamFormat = (this.audioInputStream == null ? null : this.audioInputStream.getFormat());
            if (!lineAudioFormat.equals(audioInputStreamFormat))
            {
                this.sortieSon.close();
                openLine();
            }
        }
    }

    /**
     * Créer la sortie son
     * @throws LineUnavailableException     les erreurs sur la sortie son
     */
    private void createLine() throws LineUnavailableException
    {
        if (this.sortieSon == null)
        {
            //récupérer le format de la source
            AudioFormat sourceFormat = this.audioInputStream.getFormat();
            //pour les fichiers MP3 choisis nSampleSizeInBits = 16;
            AudioFormat targetFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED,
                                                       sourceFormat.getSampleRate(), 16,
                                                       sourceFormat.getChannels(),
                                                       2 * sourceFormat.getChannels(),
                                                       sourceFormat.getSampleRate(), false);
            // garder une référence sur le flux audio pour la progression de la lecture
            // encodedAudioInputStream est le format encodé de départ (IO)
            // audioInputStream sera le format décodé (système audio)
            this.encodedAudioInputStream = this.audioInputStream;
            //Créer le flux décodé
            this.audioInputStream = AudioSystem.getAudioInputStream(targetFormat, this.audioInputStream);
            //récupérer le format décodé
            AudioFormat audioFormat = this.audioInputStream.getFormat();
            //Informer la sortie son
            DataLine.Info info = new DataLine.Info(SourceDataLine.class, audioFormat, AudioSystem.NOT_SPECIFIED);
            this.sortieSon = (SourceDataLine) AudioSystem.getLine(info);
        }
    }

    /**
     * Ouvrir la sortie son
     * @throws LineUnavailableException les erreurs sur la sortie son
     */
    private void openLine() throws LineUnavailableException
    {
        if (this.sortieSon != null)
        {
//            if (this.sortieSon.isOpen ()) this.sortieSon.close ();
            //créer une sortie son
            AudioFormat audioFormat = this.audioInputStream.getFormat();
            int buffersize = this.sortieSon.getBufferSize();
           
            this.sortieSon.open(audioFormat, buffersize);
            //la sortie son supporte-t-elle les modifications de volume ?
            if (this.sortieSon.isControlSupported(FloatControl.Type.MASTER_GAIN))
            {
                this.controleVolume = (FloatControl) this.sortieSon.getControl(FloatControl.Type.MASTER_GAIN);
            }
        }
    }

    /************* LA BOUCLE PRINCIPALE **********************************************************/

    /**
     * la boucle principale
     *
     * Etat == STOPPED || SEEKING => Fin du thread et libérer les ressources audio
     * Etat == PLAYING => Le flux audio est envoyé vers la sortie
     * Etat == PAUSED => Attendre un nouvel état
     *
     */
    public void run()
    {
        int nBytesRead = 1;
        byte[] abData = new byte[EXTERNAL_BUFFER_SIZE];
        // Verrouiller le flux pendant la lecture
//        if (this.audioInputStream!=null)
//        {
        //bloquer le flux pendant la lecture
        synchronized (this.audioInputStream)
        {
            //la boucle play/pause
            while ((nBytesRead != -1) && (this.etat != STOPPED) && (this.etat != UNKNOWN) /*&& (this.etat != SEEKING)*/)
            {
                if (this.etat == PLAYING)
                {
                    // Play.
                    try
                    {
                        if (this.nbByteSkip > 0)
                        {
                            this.audioInputStream.skip (this.nbByteSkip);
                            this.nbByteSkip = 0;
                        }
                        nBytesRead = this.audioInputStream.read(abData, 0, abData.length);
                        if (nBytesRead >= 0)
                        {
                            byte[] pcm = new byte[nBytesRead];
                            System.arraycopy(abData, 0, pcm, 0, nBytesRead);
                               
                            this.sortieSon.write(abData, 0, nBytesRead);
//                            int nBytesWritten = this.sortieSon.write(abData, 0, nBytesRead);
//                            // Compute position in bytes in encoded stream.
//                            int nEncodedBytes = getEncodedStreamPosition();
                            // Notify listeners
                            Iterator it = this.listeners.iterator();
                            while (it.hasNext())
                            {
                                LecteurListener listener = (LecteurListener) it.next();
                                if (this.audioInputStream instanceof PropertiesContainer)
                                {
                                    Map proprietes = ((PropertiesContainer) this.audioInputStream).properties ();
                                    long y = (Long) proprietes.get("mp3.position.microseconds");

                                    listener.progress (y);
                                }
                                else throw new LecteurException("impossible d'obtenir la progression de la lecture");
//                                    bpl.progress (nEncodedBytes, this.sortieSon.getMicrosecondPosition(), pcm, empty_map);
                            }
                        }
                    }
                    catch (IOException e)
                    {
                        this.etat = STOPPED;
                        notifyEvent(LecteurEvent.STOPPED);
                        e.printStackTrace ();
                    }
                    catch (LecteurException e)
                    {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
                else
                {
                    // Pause
                    try
                    {
                        Thread.sleep(1000);
                    }
                    catch (InterruptedException e) {}
                }
            }
               
            // Libérer les ressources car la lecture est finie
            if (this.sortieSon != null)
            {
                this.sortieSon.drain();
                this.sortieSon.stop();
                this.sortieSon.close();
                this.sortieSon = null;
            }
            // Notification of "End Of Media"
            if (nBytesRead == -1)
            {
                notifyEvent(LecteurEvent.EOM);
            }
               
            closeStream();
        }
        this.etat = STOPPED;
        //informer de l'arrêt
        notifyEvent(LecteurEvent.STOPPED);
    }

    protected String etatEnChaine ()
    {
        switch (this.etat)
        {
            case UNKNOWN : return "inconnu";
            case PLAYING : return "en lecture";
            case PAUSED  : return "en pause";
            case STOPPED : return "à l'arrêt";
            case SEEKING : return "saut";
            case OPENED  : return "ouvert";
        }
        return "cas non prévu" ;
       
    }

    /**
     * Informer les écouteurs d'un évènement lié au lecteur. Cette information est lancée dans
     * un thread spécifique pour ne pas ralentir la lecture
     * @param code          le code de l'évènement
     */
    protected void notifyEvent(int code)
    {
        LecteurEventLauncher trigger = new LecteurEventLauncher(code,new ArrayList(this.listeners));
        trigger.start();
    }

    /**
     * @return la position dans le flux de lecture
     */
    protected int getEncodedStreamPosition()
    {
        int nEncodedBytes = -1;
        try
        {
            if (this.encodedAudioInputStream != null)
            {
                //encodedAudioInputStream.available() = ce qui reste à lire
//                nEncodedBytes = this.encodedLength - this.encodedAudioInputStream.available();
                nEncodedBytes = this.nbBytes - this.encodedAudioInputStream.available();
            }
        }
        catch (IOException e) {}
        return nEncodedBytes;
    }


    /**
     * @return vrai si le controle de volume est supporté
     */
    public boolean hasGainControl()
    {
        if (this.controleVolume == null)
        {
            // Try to get Gain control again (to support J2SE 1.5)
            if ( (this.sortieSon != null) && (this.sortieSon.isControlSupported(FloatControl.Type.MASTER_GAIN)))
                this.controleVolume = (FloatControl) this.sortieSon.getControl(FloatControl.Type.MASTER_GAIN);
        }
        return this.controleVolume != null;
    }

    /**
     * @return  la valeur du volume
     */
    public float getGainValue()
    {
        if (hasGainControl()) return this.controleVolume.getValue();
        else return 0.0F;
    }

    /**
     * @return la valeur maximale du volume
     */
    public float getMaximumGain()
    {
        if (hasGainControl())
        {
            return this.controleVolume.getMaximum();
        }
        else
        {
            return 0.0F;
        }
    }

    /**
     * @return la valeur minimale du volume
     */
    public float getMinimumGain()
    {
        if (hasGainControl())
        {
            return this.controleVolume.getMinimum();
        }
        else
        {
            return 0.0F;
        }
    }

//    public int getNbByteSkip ()
//    {
//        return this.nbByteSkip ;
//    }

//    public void setNbByteSkip (int nbByteSkip)
//    {
//        this.nbByteSkip = nbByteSkip ;
//    }

    /**
     * lancer la lecture
     * @throws LecteurException    erreur de lecture
     */
    public void play() throws LecteurException
    {
      startPlayback();
    }

    /**
     * Arrêter la lecture
     * @throws LecteurException
     */
    public void stop()
    {
        stopPlayback();
    }

    /**
     * Faire une pause
     * @throws LecteurException
     */
    public void pause()
    {
        pausePlayback();
    }

    /**
     * Reprendre la lecture après une pause
     * @throws LecteurException
     */
    public void resume()
    {
        resumePlayback();
    }

    /**
     * modifie le volume
     * La sortie son est nécessairement ouverte avant l'appel
     * Echelle linéaire 0.0  <-->  1.0
     * Threshold Coef. : 1/2 pour éviter la saturation.
     */
    public void setGain(double fGain) throws LecteurException
    {
        if (hasGainControl())
        {
            double minGainDB = getMinimumGain();
            double ampGainDB = ((10.0f / 20.0f) * getMaximumGain()) - minGainDB;
            double cste = Math.log(10.0) / 20;
            double valueDB = minGainDB + (1 / cste) * Math.log(1 + (Math.exp(cste * ampGainDB) - 1) * fGain);
            this.controleVolume.setValue((float) valueDB);
            notifyEvent(LecteurEvent.GAIN);
        }
        else throw new LecteurException(LecteurException.GAINCONTROLNOTSUPPORTED);
    }

    /**
   * Initialiser la ressource audio à partir du fichier MP3
   * @param file                              le fichier à lire
   * @throws UnsupportedAudioFileException    erreur audio
   * @throws IOException                      erreur fichier
   */
  protected void initAudioInputStream(File file) throws UnsupportedAudioFileException, IOException
  {
//      this.audioInputStream = AudioSystem.getAudioInputStream(file);
//      this.audioFileFormat = AudioSystem.getAudioFileFormat(file);
      this.audioInputStream = mafr.getAudioInputStream(file);
      this.audioFileFormat = mafr.getAudioFileFormat(file);
  }

  /**
     * On se positionne à n'importe quel endroit du morceau exprimé sous la forme
     * d'un pourcentage. Il est nécessaire ensuite d'utiliser play pour écouter
     * ce qui reste du morceau.
     * @param x un nombre entre 0 et 1 exprimant le pourcentage à sauter
     */
   
    public void skip (double x)
    {
        if ((x < 0) || (x>1)) throw new IllegalArgumentException("paramètre illégal : x (" + x + ") devrait être entre 0 et 1");
        //le nombre d'octets à sauter se calcule à partir du nombre d'octets du fichier. Ce nombre
        //d'octets ne comporte pas que les octets audio mais aussi les tags. Ces derniers représentent
        //une infime partie du fichier audio lui-même et donc l'erreur engendrée est minime.
         this.nbByteSkip = (int) (x * this.nbBytes);
    }
   
    /**************************************************************************************************/
   
    /**
     * Ajouter un écouteur à informer
     * @param bpl   le listener
     */
    public void addLecteurListener(LecteurListener bpl)
    {
        this.listeners.add(bpl);
    }

/************** TEST *****************************************************************************/

/**
   * Initialiser la source        
   * @param file                 Le fichier à lire (mp3)
   * @throws LecteurException    les exceptions du lecteur
   */
  public void open(File file) throws LecteurException
  {
      if (file != null)
      {
//        System.out.println(file.getAbsolutePath());
          //définir la source
          this.source = file;
          //une fois la source connue, initialiser le flux
          initAudioInputStream();
      }
  }

/**
* un test
* @param args
*/
    public static void main(String[] args)
    {
        final LecteurMP3 lecteur = new LecteurMP3();
        File file = new File("chants\\merle noir.mp3");
//        URL url = Fichier.chargerChant("merle noir.mp3");
//        System.out.println(file.getAbsolutePath());
        try
        {
            lecteur.open (file);
            lecteur.skip (0.90);
            lecteur.play ();
        }
        catch (LecteurException e)
        {
//            System.out.println("erreur en ouvrant : " + url);
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }   
}
TOP

Related Classes of memory.lecteur.LecteurMP3

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.