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

Documents pareils