Traitement vidéo en Java

Mohamed Slim

Cet article est destiné aux développeurs Java qui souhaitent écrire des applications ou des applets permettant de traiter du son ou de la vidéo en Java en utilisant l'API Java Media Framework(JMF) sous un système Win32.
Commentez

- Titre : Traitement vidéo en Java

- Auteur : Mohamed Slim

- Parution : 25 juin 2010

- Mise à jour : 6 janvier 2020

- Licence :

Sommaire

- I. Introduction

- II. Java Media Framework

- III. Les formats supportés par l'API

- IV. Fonctionnalité de l'API

- V. Architecture de l'API

- VI. Étude de cas : affichage de la vidéo

- VI-A. Installation

- VI-B. Implémentation

- VII. Liens utiles

- VIII. Conclusion

- IX. Remerciements

I - Introduction

Certainement, l'utilisation de Java est étendue que ce soit dans des applications destinées pour le Web ou pour des applications bureautiques.
Grâce à la richesse de ses API standards et tierces, tout type d'application ou presque, peut être implémenté en Java. Prenons par exemple le domaine du multimédia où l'information est présente sous différents formats (vidéo, audio, audiovisuelle). Comme exemple d'application, on peut penser à écrire un lecteur vidéo personnel permettant de lire des données enregistrées dans des fichiers MP3, AVI, etc.
Ce type d'application peut paraitre un peu délicat pour un débutant. Cependant, grâce au niveau d'abstraction qu'offrent les API Java, cela deviendra certes beaucoup plus aisé.

II - Java Media Framework

Java Media Framework(JMF) est une API extension à la JDK, fournie par Sun Microsystems et IBM. Elle permet d'incorporer des données de type audio ou vidéo dans des applications Java et des applets.
On peut citer d'autres binding comme FMJ Project (Freedom for Media in Java) qui présente une alternative à JMF. Comme FMJ est compatible avec JMF, on peut l'utiliser avec du code JMF existant.
Depuis la version 1.0 de JMF, on peut lire divers formats vidéo dans des applications Java. D'autres fonctionnalités sont ajoutées depuis la version 2.0 comme la capture caméra, la sauvegarde, la transmission, le transcodage, etc.
Quand cet article a été rédigé, JMF était dans sa version 2.1.1, avec une mise à jour qui date de mars 2001. Elle a intégré plusieurs améliorations au niveau RTP / RTSP et de nombreux composants ont été ajoutés et plusieurs bogues ont été corrigés. JMF est considérée comme l'un des composants les plus importants de la famille Java Media, qui comprend également Java3D, Java2D, Java Sound et Java Advanced Image(JAI) qui permettent aux développeurs d'ajouter facilement et rapidement des fonctionnalités multimédias à leurs applications, telles que le streaming vidéo, le graphisme 3D et le traitement des images.
En fait, JMF utilise Java Sound pour le rendu sonore.

III - Les formats supportés par l'API

JMF prend en charge différents types de médias, y compris :

- les protocoles : FILE, HTTP, FTP, RTP ;

- les formats audio : AIFF, AU, AVI, GSM, MIDI, MP2, MP3, QT, RMF, WAV ;

- les formats vidéo : AVI, MPEG-1, QT, H.261, H.263 ;

Le format MP3 est pris en charge uniquement sur la plate-forme Windows, pour cela il faut installer le Plugin MP3.
Bien que JMF pourrait soutenir un certain type de médias, elle peut ne pas supporter la compression spécifique (CODEC) utilisée pour les données à l'intérieur du fichier ressource. Par exemple, un fichier QuickTime avec vidéo Cinepak peut être lu, mais un autre QuickTime avec une vidéo Sorensen ne peut pas être lu. Cela est dû au fait que JMF 2.1.1 ne prend pas en charge le décodage des vidéos à base de Sorensen.
Dans d'autres cas, on voit la vidéo s'afficher sans entendre le son. Cela est dû au fait que JMF ne prend pas en charge le format de compression de la piste audio. D'ailleurs, le format .AVI n'est pas totalement supporté par JMF et on risque de rencontrer des soucis avec des .AVI récents.
Pour plus de détails sur les différents formats traités par l'API, vous pouvez consulter cette page. Ne vous inquiétez pas, car l'un des avantages importants de JMF est qu'elle admet une architecture à base de plug-ins qui permet d'ajouter de nouveaux codecs que vous pouvez créer vous-même.

IV - Fonctionnalité de l'API

Outre la possibilité d'incorporer de l'audio et de la vidéo dans vos applications, JMF permet l'envoi et la réception de médias à partir du réseau grâce à JMF RTP.
L'API permet en outre :

- la capture audio et vidéo avec un microphone et une caméra, puis l'enregistrement de ces données dans un format donné ;

- le traitement des fichiers vidéo (multiplexage, segmentation, compression) ;

- la transmission et la réception de l'audio et de la vidéo en temps réel sur Internet (streaming vidéo) ou sur le réseau comme déjà mentionné ;

- le transcodage des données, c'est-à-dire le passage d'un format à un autre avec de nouveaux codecs tout en admettant une architecture de plug-in qui permet d'intégrer facilement de nouveaux codecs.

V - Architecture de l'API

Comme le montre la figure ci-dessous, des appareils tels que le lecteur de cassettes et le magnétoscope fournissent un modèle familier pour l'enregistrement, le traitement et la présentation des médias à base de temps.
En effet, lorsque vous regardez un film à l'aide d'un magnétoscope, vous devez fournir le flux média au magnétoscope en insérant une cassette vidéo. Le magnétoscope lit et interprète les données sur la bande et envoie les signaux appropriés à votre téléviseur et au haut-parleur pour la diffusion vidéo.

La chaine de traitement en JMF peut être assimilée à ce processus de diffusion en admettant quatre nœuds principaux pour le traitement :

- Capture Device : est le périphérique de capture ;

- Data Source : c'est la source de données qui modélise les sources de données audio et vidéo ;

- Processor (player) : qui assemble les sources de données pour construire un format vidéo pouvant être affiché à l'utilisateur ;

- OutPutDevice : est la sortie du player récupérée par le DataSink qui peut être un fichier, un écran, etc. ;

- Datasink : une référence de l'interface DataSink récupère le média du player et le redirige vers une destination. Un DataSink permet par exemple d'enregistrer un média dans un fichier ou de l'afficher directement sur l'écran.

Regardons ces notions avec plus de détails.
Périphérique de capture (Capture Device)
Comme son nom l'indique, ce périphérique représente le matériel utilisé pour saisir les données multimédias, comme un microphone ou une caméra vidéo.
La source de données ( DataSource)
Peut être assimilée à un support de stockage comme un CD contenant de la musique ou un magnétoscope comme schématisé dans la figure. Côté JMF, on doit créer un objet de la classe DataSource, pour représenter les différents médias. Cet objet peut être un fichier ou un flux entrant du réseau et peut donner des informations sur l'emplacement des médias, les protocoles et les techniques utilisées pour les livrer.
Par la suite, cet objet sera traité par le player et de cette façon, ne peut pas être réutilisé pour fournir d'autres médias .
Plusieurs sources de données peuvent être combinées en une seule, par exemple lors de la capture d'une scène, on peut avoir une source audio et une autre pour l'image. Dans ce cas, on peut penser à la combinaison de ces deux sources pour un meilleur contrôle.
Le player
C'est un objet dont le rôle est la lecture des données audio ou vidéo à partir de la source de données et de leur renvoi, après traitement et à un moment précis à la carte son ou à la carte graphique pour affichage. C'est similaire à un lecteur CD qui lit un CD audio et renvoie le son au haut-parleur.
Le player est l'acteur capital de l'API JMF. Il contrôle le chargement, l'acquisition des ressources et l'exécution (démarrage, arrêt, vitesse d'exécution…) des données multimédia. Avant d'émettre du son à la carte son ou des informations vidéo à la carte graphique, le player doit passer par six états, quatre d'entre eux sont fondamentaux et les autres sont considérés comme intermédiaires. Bref, un état intermédiaire se situe entre deux états fondamentaux.
Vu que le player effectue un traitement ou attend la disponibilité d'une ressource, on peut en fait considérer les états intermédiaires comme des états d'attente pour passer à un état fondamental.
On peut schématiser le passage du player par ces états selon le schéma suivant :

Comme montre le schéma, certaines transitions peuvent être invoquées par des appels de méthodes.
Voyons donc de plus près ces différents états.
Unrealized
Le player est instancié comme un nouveau-né qui ne connait rien de son environnement et des ressources qu'il doit réaliser. On peut obtenir cet objet à partir du gestionnaire de document multimédia (la classe Manager).

MonPlayer player = Manager.createPlayer(URL du document);

Realizing
C'est un état intermédiaire dans lequel le player détermine ses ressources qu'il pourrait partager avec d'autres players. Ce sont la plupart du temps des ressources réseau. Du fait qu'elles peuvent être partagées entre plusieurs players, on peut les appeler des ressources non exclusives.
Le passage à cet état est fait suite à un appel à la méthode realize() :

monPlayer .realize() ;

Realized
Le player connait à cet état le type de document multimédia qu'il doit traiter et sait quelles ressources il doit acquérir. De ce fait, il peut préparer le composant visuel pour l'affichage de la vidéo ainsi que le panneau des boutons de commandes et de lecture.
Prefetching
État intermédiaire où le player se prépare à présenter cette fois-ci ses ressources exclusives, c'est-à-dire celles qu'il doit être le seul à utiliser.
Le passage à cet état se fait suite à l'appel de la méthode prefetch() :

monPlayer.prefetch();

Prefetched
À cette étape, le palyer aura reçu toutes ses ressources exclusives pour traiter convenablement le document multimédia. Il est prêt ainsi à le présenter correctement.
Started
L'appel à la méthode start() mène le player à l'état started entrainant ainsi l'affichage de la vidéo à l'écran.

monPlayer.start();

Un appel à la méthode stop() permet de retourner à l'état Prefetched.

monPlayer.stop();

Quand les traitements prévus dans l'état intermédiaire seront achevés, le passage à l'état fondamental sera automatique. Ce passage est accompagné par la création d'un objet d'une classe héritant de TransitionEvent par le player. La figure ci-dessous présente les différentes sous-classes de TransitionEvent que le player peut instancier lors de son passage de l'état Unrealized à l'état Started.

Ce mécanisme est semblable à la gestion d'événements en SWING. Chaque objet créé est passé comme argument de la méthode controllerUpdate() :

public void controllerUpdate(ControllerEvent event)
{
if (event instanceof PrefetchCompleteEvent)
{
}
else if (event instanceof RealizeCompleteEvent)
{
}
else if (event instanceof StartEvent)
{
}
else if (event instanceof stopEvent)
{
}
}

Cependant, on peut faire des appels de méthodes pour faire le passage à un état donné d'une façon asynchrone. Par exemple, dans le cas où on fait une invocation de la méthode prefetch() quand le player est Unrealized. Suite à cet appel, la méthode prefetch() essayera d'emmener le player à l'état Realizing puis à l'état Realized pour atteindre à la fin l'état Prefetching.
De même, un appel à la méthode start() peut se faire à un état différent de Prefetched, ce qui entraine un parcours de l'ensemble des états intermédiaires dans l'ordre.
Après avoir eu une idée sur l'architecture de JMF et de ces différents composants, essayons maintenant d'exploiter ces connaissances dans un exemple qui permet d'afficher une vidéo dans une fenêtre.

VI - Étude de cas : affichage de la vidéo

VI-A - Installation

Pour développer en Java, il faut avoir installé la JDK. Mieux vaut travailler avec une version récente. Vous pouvez consulter la dernière mise à jour sur la page de téléchargement du site de SUN.
Il faut aussi utiliser un EDI puissant comme Eclipse ou netBeans. Une liste non exhaustive des différents EDI de développement en Java est donnée sur la page outils avec les liens de téléchargement.
Comme indiqué au début de l'article, l'API JMF est une extension à la JDK, donc une installation préalable de cette API est nécessaire afin de pouvoir exécuter votre code.
Si vous souhaitez déployer vos applications, il faut aussi avoir recours à la JMF et l'installer sur vos postes clients.
Sur le site de SUN, on trouve différentes versions de JMF 2.1.1 pour différents systèmes d'exploitation. Dans le cadre de cet article, on vise les systèmes Win32.
Dans la page d'installation, vous devrez sélectionner votre système d'exploitation (on devra choisir Windows), par la suite, il faudra installer le fichier exécutable qui portera le numéro de la version de JMF :

L'installation de l'API se lance après un double-clic sur l'exécutable.
Si vous espérez écrire des programmes permettant de capturer des flux de données reçus à partir de votre caméra, l'installation devrait alors suivre cet ordre : le JDK au début, le pilote DE LA caméra par la suite et JMF à la fin. Cet ordre n'est pas décisif, mais il est conseillé pour avoir un bon fonctionnement.

Lors de l'installation, vous remarquez trois raccourcis sur votre bureau :

Pour terminer l'installation, il faut redémarrer votre système.
Un dossier portant le nom de JMF 2.1.1e sera créé dans le répertoire C:\Program Files. Ce répertoire sera composé de trois sous dossiers :
Bin
Contient les applications suivantes :
JMFRegistry
Une application, qui peut être utilisée pour la configuration de JMF. Elle permet de lister les différents plug-ins et la liste des périphériques de capture installés sur la machine. On peut avec cette application changer certains paramètres dans le fichier binaire jmf.properties.
JMStudio
Une application qui peut être utilisée pour exploiter les fonctionnalités de l'API telles que la lecture et l'enregistrement des médias, la capture des flux caméra, le transcodage, etc. On peut aussi utiliser cette application pour essayer l'envoi et la réception des données multimédia sur le réseau via le RTP.
Doc
Contient le fichier de documentation.
Lib :
Contient les classes de l'API JMF compilée dans des archives .jar.
Lors de l'installation de JMF, un plug-in sera installé pour permettre l'affichage des applets utilisant JMF dans les navigateurs.
L'installation de JMF est accompagnée par l'ajout de bibliothèques natives. Ces bibliothèques seront ajoutées à la variable d'environnement PATH.
Sous Windows, la configuration des variables d'environnement CLASSPATH et PATH sera automatique :

set CLASSPATH =%% JMFHOME \ lib jmf.jar \;%% JMFHOME \ lib sound.jar \;.;% CLASSPATH%
SET PATH =% WINDIR% \ System32;% PATH% (sur Windows NT)

Si vous utilisez un IDE pour vos développements, il faut donc spécifier les archives jmf.jar, sound.jar dans la CLASSPATH de votre projet.
Enfin, pour tester si tout s'est bien passé, vous pouvez exécuter l'applet de diagnostic fourni par SUN.
Le résultat que vous obtiendrez devra être semblable à ce qui est donné par cette figure :

Maintenant que vos tests sont positifs, commençons alors notre application.

VI-B - Implémentation

L'application est composée par une seule classe de nom MonLecteur.
Cette classe va implémenter l'interface ControllerListener pour gérer le changement d'état du Player. Cependant, comme pour la gestion des événements en SWING, on peut créer une classe à part qui jouera le rôle de l'auditeur du player.
Cette classe comporte un menu de nom « fichier » sur lequel on doit appuyer pour lancer la lecture du fichier vidéo.

setJMenuBar(menuBar);

menuBar.add(ouvrirMenu);

ouvrirMenu.setText("Ouvrir");

ouvrirMenu.add(newItemFichier);

newItemFichier.addActionListener((ActionListener) this);

newItemFichier.setText("fichier");

Cette classe doit donc implémenter l'interface ActionListener et redéfinir la méthode actionPerformed(ActionEvent e) qui va être invoquée suite à l'appui sur le menu « fichier ».
De ce fait, l'instruction de création du player doit se faire dans cette méthode.
La classe java.media.Manager utilise l'URL de notre fichier vidéo pour construire le player.
Ce modèle de création est très similaire à l'établissement des connexions aux bases de données avec JDBC.

URL url = fileChooserSaveImage.getSelectedFile().toURL();

L'objet player admet des méthodes pour connaitre l'état actuel du player et acquérir les ressources nécessaires.
D'autres méthodes permettent de démarrer, d'arrêter ou de contrôler la lecture effective d'un fichier média en local ou sur le réseau.

monPlayer = Manager.createPlayer(url);

À chaque changement d'état du player, la méthode controllerUpdate() est appelée automatiquement.

monPlayer.addControllerListener((ControllerListener) this);

Avant d'être démarré, le player doit avoir l'état Realized et doit passer ensuite à l'état Prefetched.

monPlayer.realize();

Suite à cet appel, il y aura création d'un objet de type RealizeCompleteEvent qui sera le paramètre de la méthode controllerUpdate().
La méthode controllerUpdate(ControllerEvent arg0) appartient à l'interface ControllerListener. Elle doit être redéfinie par la classe Monlecteur. Cette méthode doit contenir les instructions pour la gestion de changement d'état du Player.

public void controllerUpdate(ControllerEvent arg0)
{
if (arg0 instanceof RealizeCompleteEvent)
// état realizing
{
monPlayer.prefetch();
}
else if (arg0 instanceof PrefetchCompleteEvent)
// état perfetching
{

L'objet monPlayer prend en entrée un flux de données (audio et/ou vidéo) et effectue un traitement afin de présenter ces données à l'écran.
Quand ce dernier atteint l'état Prefetched, on peut récupérer deux composants graphiques permettant l'affichage et le contrôle de la vidéo.
La méthode getVisualComponent() renvoie un composant AWT destiné à la visualisation de la vidéo.

if ((visual = (Component) monPlayer.getVisualComponent()) != null)

getContentPane().add("Center", visual);

La méthode getControlPanelComponent() renvoie à son tour un panneau de commande, qui permet d'arrêter ou de reprendre la lecture de la vidéo.
On peut aussi couper la sortie sonore et avoir des informations sur la vidéo comme son emplacement, sa durée, etc.

Si vous préférez définir des composants GUI personnels, vous pouvez implémenter l'interface Composants et appeler les méthodes de la classe Player pour gérer les actions utilisateur comme la lecture, l'arrêt, etc.
Si notre ressource multimédia est un fichier audio par exemple, seul le panneau de contrôle est affiché c'est pourquoi on a fait un test avant d'ajouter le composant visuel.

if ((visual = (Component) monPlayer.getVisualComponent()) != null)
getContentPane().add("Center", visual);

Si les composants GUI sont bien disponibles, il faut valider leur ajout avec validate().

validate();

Enfin, on lancera l'exécution du player avec start(). L'affichage de la vidéo étant en cours.

monPlayer.start();

Lorsque la vidéo est terminée et que vous voulez reprendre la lecture, vous devez utiliser la méthode setMediaTime() pour rendre la tête de lecture à l'instant 0.
La fin de la lecture est repérée par un objet de type EndOfMediaEvent.

if (arg0 instanceof EndOfMediaEvent)
{
monPlayer.setMediaTime(new Time(0));
monPlayer.start();
}

À la fin, il ne faut pas oublier de libérer les ressources détenues par le player lors de la fermeture de la fenêtre. Le player ne sera plus utilisé et peut être donc fermé.

public void windowClosing(WindowEvent arg0)
{
if(monPlayer !=null)
monPlayer.close();
}

Voila enfin ce que vous devez faire pour avoir une vidéo qui s'affiche dans votre fenêtre.
Vous pouvez télécharger le code de l'application ici.

VII - Liens utiles

- Ensemble de vidéos à tester

- Documentations de l'API

- Formats supportés

- Documentations

- Guide officiel

- Sources utiles

VIII - Conclusion

Dans cet article, on a donné une vue générale de L'API JMF utilisé pour permettre le traitement des médias basés sur le temps en Java comme le son et la vidéo. On a donc présenté les différentes fonctionnalités de cette API et son architecture. On a entamé notre présentation par une étude de cas dans laquelle on a fourni le code d'une application qui permet d'afficher la vidéo dans une fenêtre.
Certainement, l'atout de cet API dépasse la simple lecture d'un média en local, vers la capture et l'enregistrement des flux multimédias et de la vidéo streaming.
Dans des mises à jour futures, nous verrons d'autres exemples où nous essayerons d'exposer d'autres fonctions de l'API.

IX - Remerciements

Je tiens à remercier la rubrique Java de Developpez.com, et son responsable Ricky81.
Je remercie aussi Monsieur Jacques pour sa relecture orthographique et ses corrections.