Le streaming - Polytech2go
Transcription
Le streaming - Polytech2go
L'API JMF Présentation, exemples et exercices P. Bakowski (ETN5:SMTR-M1) JMF est donc une API permettant l'exploitation du streaming avec le langage java. Elle offre de nombreuses possibilités et est très simple d'utilisation. Elle a été conçu dans sa version 2 pour répondre aux attentes suivantes : • • • • Permettre la présentation et la capture de données multimédias Permettre le développement d'application java utilisant le streaming ou les conférences vidéos. Permettre l'accès à un large type de données Offrir un support pour le protocole RTP (Real-Time Transport Protocol) L'API dans sa deuxième version reste entièrement compatible avec la première version. Les applications développées avec JMF 1 seront toujours d'actualité. Décomposition JMF se décompose en deux modules distinct : • • L'API de base : Elle fournit toute une architecture permettant de gérer l'acquisition, le traitement et l'affichage de données multimédias. On peut alors facilement à, l'aide de JMF, créer une applet ou une application qui présente capture, manipule ou enregistre des flux multimédia. On trouve alors différents outils comme les Players qui vont permettre la visualisation et le traitement des données. On pourra alors grâce à eux traiter le flux vidéo et permettre les options que l'on souhaite sur le lecteur media. L'API RTP : Jusque là, JMF ne permettait que de lire, traiter et présenter un flux arrivant à un utilisateur. Grâce à l'API RTP on va maintenant pouvoir transmettre un flux et ainsi créer sn propre serveur de streaming. On peut maintenant capturer un flux à partir d'une caméra ou un micro et le transmettre à différents utilisateurs ou encore centraliser un ensemble vidéos et sons et les transmettre sur demande. Enfin JMF est prévue pour être étendue. En effet elle permet de développer ses propres plugins afin d'effectuer des traitements particuliers de fichiers audio ou vidéos ou encore de traiter certains fomats non supportés pour des besoins particuliers. L'API JMF : API de base L'API de base contient les outils nécessaires à la manipulation de données multimédia. Elle fournit les méthodes pour gérer des données média(DataSource), les lire (Player) ou encore effectuer des traitements (Processor). Gestion des données Les lecteurs JMF utilisent en général des DataSource pour gérer le transfert du contenu d'un media. Il encapsule un ensemble de données sur le transfert à effectuer : • • • L'emplacement du fichier que l'on veut transporter Le protocole que l'on va utiliser pour délivrer le flux Les outils utilisés pour le transfert Une DataSource est identifiée par un objet MediaLocator ou URL permettant de spécifier l'emplacement du media à gérer. Un MediaLocator est similaire à l'objet URL et peut être construit à partir de celui-ci. La seule différence est qu'il ne nécessite pas que le handler (gestionnaire) du protocole utilisé soit installé sur la machine pour être utilisé. ex : ftp://... On pourrait l'utiliser même si le handler ftp n'était pas installé Il existe alors deux types de DataSources en fonction de l'émetteur et du récepteur : • • Pull DataSource (en entrée) : Le client initialise le transfert de données et contrôle le flot de données avec le PullDataSource Push DataSource (en sortie): Le serveur initialise le transfert de données et contrôle le flow de données avec le Push DataSource Les formats de fichiers multimédias sont gérés par des classes de type Format contenant les attributs spécifiques du format utilisé. Les objets Manager Pour simplifier l'utilisation des différentes fonctions de l'API, JMF fournit des classes Managers permettant d'accéder à tous les services principaux. Cela permet de centraliser les différentes fonctions : • • • • Manager : Cette classe permet la construction des objets principaux, autrement dit les Players, Processors, DataSources et DataSinks (permettre l'enregistrement d'un flux ou la capture) PackageManager : Maintient un registre de packages contenant des classes JMF customisés(Players particuliers ...) CaptureDeviceManager : Maintient une liste des périphériques de capture valable dans JMF PlugInManager : maintient une liste des plugins JMF de traitement utilisables (Multiplexers, Demultiplexers, Codecs, Effects, Renderers) L’objet Player Un objet Player prend un flux en entrée sous la forme d'un DataSource et le présente(effectue un rendu) à un temps donné. On peut alors le configurer pour obtenir le rendu que l'on désire. Il est en fait le lecteur qui va nous permettre d'interpréter le flux et l'afficher. Il peut se trouver dans six états différents en respectant l'ordre suivant : • • • • • • Unrealized : Le Player vient d'être instancié mais ne connais rien sur le média qu'il doit traiter Realizing : A l'appel de la méthode "realize" il passe dans cet état. Cela consiste à acquérir les ressources qu'il n'a besoin d'acquérir qu'une fois. Cela comprend le rendu et les différentes informations dont il a besoin sur le media à présenter Realized : Le player passe dans cet état à la sortie de la méthode "realize". Il connaît toutes les ressources dont il a besoin, le type du media à présenter et la façon dont il doit le faire. Prefetching : Il passe dans cet état à l'appel de "prefetch". Le player pré-charge le média, prend un accès exclusif sur les ressources dont il a besoin et prépare tout ce dont il a besoin pour lancer la lecture. Prefetched : Le player est près a être lancé. Started : Il a commencé la lecture ou la commencera à un temps donné. Le Player envoie des "TransitionEvents" lorsqu'il passe d'un état à un autre. L'objet Processor Les données d'un flux média sont soumises à un traitement avant d'être présentées à un utilisateur. On pourra alors effectuer les opérations suivantes avant la présentation du média : • • • • Si le flux est multiplexé, on peut extraire les pistes individuelles Si les pistes sont compressées, elles sont décodées et décompressées On peut convertir le flux dans un autre format On peut appliquer des filtres d'effets sur le flux L'objet Processor est en fait un Player particulier pouvant contrôler les traitement effectués sur le flux. Il peut présenter un média ou proposer un nouveau DataSource en sortie. Exemple 1 : Un simple audio player import import import import javax.media.*; java.awt.*; java.io.*; java.net.*; public class SimpleAudioPlayer { private Player audioPlayer=null; public SimpleAudioPlayer(URL url) throws Exception { audioPlayer = Manager.createRealizedPlayer(url); } public SimpleAudioPlayer(File file) throws Exception { this(file.toURL()); } public void play() { audioPlayer.start(); } public void stop() { audioPlayer.stop(); audioPlayer.close(); } public static void main(String[] args) { SimpleAudioPlayer player = null; String nom= args[0]; } } try { File audioFile = new File(nom); player = new SimpleAudioPlayer(audioFile); } catch (Exception e) {} player.play(); //player.stop(); Exemple2 : Un player vidéo en applet. import import import import java.applet.*; java.awt.*; java.net.*; javax.media.*; public class PlayerApplet extends Applet { Player player = null; public void init() { setLayout(new BorderLayout()); String mediaFile = getParameter("FILE"); System.out.println(mediaFile); try { URL mediaURL = new URL(getDocumentBase(),mediaFile); player = Manager.createRealizedPlayer(mediaURL); if (player.getVisualComponent() != null) add("Center", player.getVisualComponent()); if (player.getControlPanelComponent() != null) add("South", player.getControlPanelComponent()); } catch (Exception e) { System.err.println("Got exception: " + e); } } public void start() { player.start(); } public void stop() { player.stop(); player.deallocate(); } public void destroy() { player.close(); } } Code HTML pour l’utilisation de l’applet ci-dessus <HTML> <HEAD> <TITLE> PlayerApplet </TITLE> </HEAD> <BODY> <APPLET CODE=PlayerApplet WIDTH=320 HEIGHT=240> <PARAM NAME=FILE VALUE="sujet.mpg"> </APPLET> </BODY> </HTML> Exemple 3 : Un player vidéo en application import import import import import import javax.media.*; java.awt.*; java.awt.event.*; java.io.*; java.net.*; javax.swing.*; public class MediaPlayerFrame extends JFrame{ Player player; { setSize(400,80); addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { player.stop();player.deallocate();player.close();System.exit(0);} }); JPanel panel = (JPanel) getContentPane(); panel.setLayout(new BorderLayout()); String mediaFile = "C:\\Kalimba.mp3"; try { //File audioFile = new File(mediaFile); MediaLocator mlr = new MediaLocator("rtp://127.0.0.1:49158/audio/1"); //player = Manager.createRealizedPlayer(audioFile.toURL()); player = Manager.createRealizedPlayer(mlr); if(player.getVisualComponent() != null) panel.add("Center",player.getVisualComponent()); if(player.getControlPanelComponent() != null) panel.add("South",player.getControlPanelComponent()); } catch(Exception e ) { System.err.println("Got exception " + e);} } public static void main(String[] args) { MediaPlayerFrame mpf = new MediaPlayerFrame(); mpf.show(); } } Exemple 4 : Capture de l'audio et un traitement type encodage import import import import import import javax.media.*; javax.media.format.AudioFormat; javax.media.protocol.DataSource; java.io.*; java.util.*; java.net.*; import java.util.*; public class CaptureAudio { public static void main(String[] args) { DataSource ds = null; MediaLocator dest=null; CaptureDeviceInfo di=null; try{ //obtenir une DataSource a partir d’un microphone AudioFormat df=new AudioFormat(AudioFormat.LINEAR,8000,8,1); Vector devices = CaptureDeviceManager.getDeviceList(df); if(devices.size() > 0) di=(CaptureDeviceInfo) devices.firstElement(); Processor p = Manager.createProcessor(di.getLocator()); p.configure(); while (p.getState()!=Processor.Configured) { Thread.sleep(10);} p.realize(); while (p.getState() != Processor.Realized) {} ds = p.getDataOutput(); } catch (Exception e) {System.out.println("Processor n’est pos configure\n");} } } DataSink sink; try { dest = new MediaLocator("file:C:\\output.wav"); } catch (Exception e) {System.out.println("Pas de fichier\n");} try { sink = Manager.createDataSink(ds, dest); sink.open(); sink.start(); } catch (Exception e) {} Exemple 5 : Utilisation d’un flot RTP source en entrée (dans un applet) public static void init { //adresse de la source correspondant à un flux RTP String SourceAddress = "rtp://192.168.0.1:33335/video/1"; //création de la source MediaLocator SourceLocator = new MediaLocator(SourceAddress); //Verification que la source existe if(SourceLocator == null) { System.out.println("pas de source"); System.exit(-1); } else { System.out.println("Connecté au flux"); } } //l'objet Player qui va présenter le flux Player player; try { //Creation du player dans l'etat realized avec la source player = Manager.createRealizedPlayer(SourceLocator); //Demarrage du Player player.start(); //Creation d'une fenetre de test JFrame fenetre = new JFrame("Player"); fenetre.setSize(180,160); //Ajout du Composant visuel du Player dans la fenetre fenetre.getContentPane().add(player.getVisualComponent()); fenetre.setVisible(true); } catch (Exception e) { e.printStackTrace(); } Cet exemple montre uniquement la création d'un Player basique. Il est possible ensuite de lui ajouter de nombreuses fonctionnalités afin d'améliorer les services offerts à l'utilisateur On peut également choisir d'enregistrer le flux arrivant plutôt que de le présenter en utilisant un objet DataSink : DataSink sink; MediaLocator dest = new MediaLocator("file://newfile.wav"); try { //p est le processor qui a reçu le flux sink = Manager.createDataSink(p.getDataOutput(), dest); sink.open(); sink.start(); } catch (Exception) {} Exercices (partie 1): 1. Développer une application qui permet la capture d’un flot audio et sa présentation dans un Player. 2. Développer une application qui permet de transcoder une source (fichier) donnée en format mp3 dans le format wav. 3. Développer une application qui permet la capture d’un flot audio et son encodage en format mp3 dans un fichier. RTP par l'exemple Dans le premier exemple nous captons un flux audio à partir d’un microphone et nous l’envoyons sur le réseau sur le protocole RTP directement par le mécanisme de DataSink. import import import import import import import import import import java.net.*; javax.media.*; javax.media.protocol.*; javax.media.format.*; javax.media.control.*; javax.media.rtp.*; javax.media.rtp.event.*; java.util.*; com.sun.media.rtp.*; java.io.*; public class RtpDataSink { public static void main(String[] args) { //cherche le dispositif de capture AudioFormat format= new AudioFormat(AudioFormat.LINEAR,8000,8,1); AudioFormat gsm = new AudioFormat(AudioFormat.GSM_RTP,8000,8,1); Vector devices = CaptureDeviceManager.getDeviceList(format); CaptureDeviceInfo di=null; Processor p = null; if(devices.size()>0) { di = (CaptureDeviceInfo) devices.elementAt(0); } else { // pas de dispositif disponible pour ce format System.out.println("No devices"); System.exit(-1); } // creer processor pour ce dispositif try { p = Manager.createProcessor(di.getLocator()); } catch (IOException e) { System.exit(-1); } catch (NoProcessorException e) { System.exit(-1); } // configure processor pour le flux d’entree p.configure(); p.setContentDescriptor(new ContentDescriptor(ContentDescriptor.RAW)); TrackControl track[] = p.getTrackControls(); boolean encodingOk = false; // traitement pour la sortie type gsm if(((FormatControl)track[0].setFormat(gsm))==null){ track[0].setEnabled(false); } else { encodingOk= true; } // realisation du traitement pour codec gsm if(encodingOk) p.realize(); } } DataSource ds= null; try { ds = p.getDataOutput(); } catch (NotRealizedError e){ System.exit(-1); } // creation de flux RTP try { String url="rtp://192.168.1.255:49150/audio/1"; MediaLocator m = new MediaLocator(url); DataSink d = Manager.createDataSink(ds, m); d.open(); d.start(); } catch (Exception e) { System.exit(-1); } Utilisation du RTPManager Dans l’exemple suivant nous envoyons les flux vidéo (à partir d’un fichier) par le protocole RTP /RTCP en exploitant le mécanisme du RTP Manager. Le fichier source est traité par un Processor qui génère une source utilisable par le protocole RTP. Le fichier multimédia en format mpg contient une piste audio et une piste vidéo. RTP manager va créer 2 flux RTP associés aux portes 22224 et 22226 (numéro paire) import import import import import javax.media.*; javax.media.rtp.*; javax.media.format.*; javax.media.control.*; javax.media.protocol.*; public class StreamingServer { // // // public static void main(String[] args) { Chemin du fichier String FichierAdresse = "file:////D://Java//Test.avi"; Création du MediaLocator à partir du fichier MediaLocator FichierLocator = new MediaLocator(FichierAdresse); Déclaration du processeur Processor FichierCessor = null; DataSource ds=null; try{ //Création du Processor à partir du MediaLocator try{ ds = Manager.createDataSource(FichierLocator); } catch(Exception e){} FichierCessor = Manager.createProcessor(ds); //Appel des fonctions qui vont permettre le lancement du flux RTP configure(FichierCessor); SetSupportedFormat(FichierCessor); //passer dans l'etat realized du processor realize(FichierCessor); //start Demarre(FichierCessor); launchRTPManager(FichierCessor); } catch(IOException e) { System.out.println("Erreur : "+e.getMessage()); } catch(NoProcessorException e) { System.out.println("Erreur : "+e.getMessage()); } } public static Processor configure(Processor p) { //Attendre tant que le Processor n'est pas configuré. while(p.getState() < Processor.Configured) { //Configuration du Processor p.configure(); } return p; } public static void SetSupportedFormat(Processor p) { //On met la description du contenu de sortie à RAW_RTP ContentDescriptor cd = new ContentDescriptor(ContentDescriptor.RAW_RTP); p.setContentDescriptor(cd); //On obtient les différentes pistes du processor TrackControl track[] = p.getTrackControls(); for(int i = 0 ; i < track.length ; i++) { //on obtient les formats supportés pour cette piste Format suppFormats[] = track[i].getSupportedFormats(); //Si il y a au moins un format supporté if(suppFormats.length > 0) { track[i].setFormat(suppFormats[0]); System.err.println("Track " + i +" est transmis as :"+suppFormats[0]); } else { track[i].setEnabled(false); } } } public static Processor realize(Processor p) { //Attendre tant que le Processor n'est pas réalisé. while(p.getState() < Processor.Realized) { //Réalisation du Processor p.realize(); } return p; } public static void Demarre(Processor p) { //Demarrage du Processor p.start(); System.err.println("started"); } public static void launchRTPManager(Processor p) { //Creation du DataSource correspondant au Processor DataSource OutputSource = p.getDataOutput(); PushBufferDataSource pbds = (PushBufferDataSource)OutputSource; PushBufferStream pbss[] = pbds.getStreams(); //Nouvelle Instance d'un RTPManager RTPManager rtpm[] = new RTPManager[pbss.length]; //System.out.println("taille:" +pbss.length); //RTPManager rtpm; for(int i=0; i < pbss.length;i++) { try{ rtpm[i] = RTPManager.newInstance(); int port = 22224 + 2*i; //Création d'une SessionAddress SessionAddress localaddr = new SessionAddress(InetAddress.getLocalHost(),port); //Initialisation du RTPManager rtpm[i].initialize(localaddr); //Création d'une SessionAddress SessionAddress destaddr = new SessionAddress (InetAddress.getByName("192.168.1.12"),port); //Ajout de cette SessionAddress dans le RTPManager rtpm[i].addTarget(destaddr); System.err.println("Creation RTP session 192.168.1.12 port : "+port); //Creation d'un SendStream à partir du DataSource SendStream ss2 = rtpm[i].createSendStream(OutputSource,i); //Demarrage du SendStream ss2.start(); System.out.println("Started "); } catch(Exception e) { e.printStackTrace(); } } } } Réception directe d’un flot RTP: package clientnetwork; import import import import import import import import import import import import javax.media.*; javax.media.rtp.*; javax.media.rtp.event.*; javax.media.format.*; javax.media.control.*; javax.media.protocol.*; javax.media.rtp.rtcp.*; java.io.*; java.util.*; java.awt.*; java.net.*; javax.swing.*; public class Main { public static void main(String[] args) { //Adresse de la source String SourceAddress = "rtp://127.0.0.1:1234"; // en local //Creation Medialocator avec l'adresse de la source MediaLocator SourceLocator = new MediaLocator (SourceAddress); //Verification de la source if (SourceLocator == null) { System.out.println("No sources"); System.exit(-1); } else { System.out.println("Connected"); } //Declaration du player Player player; System.out.println("Player created"); try { // Création du player réalisé à partir du Médialocator de la source player = Manager.createRealizedPlayer(SourceLocator); System.out.println("Player assigned"); // Demarrage du player player.start(); System.out.println("Player started"); // Création d'une JFrame JFrame fenetre = new JFrame("Player"); fenetre.setSize(160,140); // Règle la résolution de la fenêtre // Ajout du composant visuel du player dans la fenêtre fenetre.getContentPane().add(player.getVisualComponent()); fenetre.setVisible(true); } catch (NoPlayerException e){} catch(IOException e){} catch (CannotRealizeException e){} } } Réception via un RTPManager: public class Main { public class MainManagerReception implements ReceiveStreamListener { public void main(String[] args) { new MainManagerReception(); } public MainManagerReception() { JFrame mainfra = new JFrame(); mainfra.setVisible(true); //Instanciation du RTPManager RTPManager VideoManager = RTPManager.newInstance(); VideoManager.addFormat(new VideoFormat( VideoFormat.H263_RTP),18); try {//Creation d'une SessionAddress pour l'adresse locale SessionAddress add = new SessionAddress( InetAddress.getLocalHost(),22224); //Initialisation du RTPManager à partir de cette SessionAddress VideoManager.initialize(add); //Creation d'une SessionAddress pour l'adresse source SessionAddress add2 = new SessionAddress( InetAddress.getByName("192.168.1.210"),8000); //Ajout de cette SessionAddress dans le RTP Manager VideoManager.addTarget(add2); } catch(InvalidSessionAddressException e){} catch(IOException e){} //Ajout du Listener de Reception de Stream dans le RTPManager VideoManager.addReceiveStreamListener(this); System.out.println("Client Started");} public void update(ReceiveStreamEvent event) {//Verification que l'event est un nouvel event if(event instanceof NewReceiveStreamEvent) { System.out.println("New Reception"); //Nouveau Flux Recu obtenu ReceiveStream rs = event.getReceiveStream(); try {//Creation du Player sur ce flux Player p = Manager.createRealizedPlayer( rs.getDataSource()); //Si le player a un composant visuel, alors creation d'une fenetre if(p.getVisualComponent() != null) { JFrame fenetre = new JFrame(); fenetre.setSize(160,140); fenetre.getContentPane().add(p.getVisualComponent()); fenetre.setVisible(true); } //Demarrage du Player p.start(); } catch(NoPlayerException e){} catch(CannotRealizeException e){} catch(IOException e) {} }} } } Exercices (partie 2): 4. Développer une application qui permet la lecture d’un fichier vidéo et son envoi sur l’Internet par le protocole RTP avec DataSink 5. Développer une application qui permet la lecture d’un fichier vidéo et son envoi sur l’Internet par le protocole RTP avec RTPManager 6. Développer une application qui permet la capture d’un flot audio et son envoi sur l’Internet par le protocole RTP avec DataSink 7. Développer une application qui permet la capture d’un flot audio et son envoi sur l’Internet par le protocole RTP/RTCP avec RTPManager