Archives pour août 2008

h1

La vidéo par libplayer avec Enna

2008.08.24

Hello,

cet article tente d’expliquer techniquement et sans partir dans trop de détails, comment il est possible d’afficher la vidéo de MPlayer (ou même d’un autre wrapper comme xine) directement dans la fenêtre de Enna.

Avant la révision 801 de libplayer

Un problème majeur persistait avec libplayer depuis toujours. La fenêtre X11 créée pour la sortie vidéo avec Enna n’était pas bien adaptée à un environnement géré par un Window Manager. Cela veut dire que cette fenêtre ne peut pas être manipulée par l’utilisateur (invisible dans la barre des applications, elle ne peut pas être attrapée avec la souris, …), comme si elle n’existait pas.

Techniquement, la raison vient du flag override_redirect de la fenêtre principale (il faut comprendre par “principale” que la fenêtre a pour parent le root), mit à 1, qui veut dire qu’elle doit être ignorée du Window Manager. On peut alors légitimement se poser la question:

Pourquoi donc ne pas créer une fenêtre sans l’override_redirect et ainsi pouvoir la manipuler commes les autres?

Et bien la réalité n’est pas aussi simple. Une fenêtre atteignable par l’utilisateur est une fenêtre qui peut attraper des évènements (clavier et souris par exemple). Et dans le cas contraire, les évènements ignorés par la fenêtre sont soient propagés à la fenêtre parente (propagation ascendante), soient détruits (pour les lecteurs qui froncent les sourcils, comprenez que je ne parle pas des cas particuliers).
L’illustration ci-contre montre une fenêtre principale A qui a un enfant B qui lui à un enfant C. Les évènements étant alors propagés de C à A. On pourrait bien sûr imaginer de détruire les évènements dans B pour ne pas les propager sur A. Tout est possible (même rediriger les évènements ailleurs), mais l’idée présentée ici fait office de comportement par défaut.

C’est donc là que l’avantage principal de la fenêtre en override_redirect intervient. Celle-ci étant ignorée du Window Manager, aucun évènement ne lui parvient car ils sont naturellement envoyés à la fenêtre qui à le focus (logique non?). Prenons un exemple.. Vous avez une petite application basée sur libplayer qui fonctionne en mode console. Vous ouvrez votre terminal GNOME et lancez un film via cette application. libplayer va créer une fenêtre en override_redirect et en plein écran, les évènements seront donc envoyés au terminal (la fenêtre vidéo ne pouvant pas avoir le focus). Ainsi l’exposition de la fenêtre devant celle du terminal n’a aucun impacte.
Mais il y a un piège! Si vous changez de fenêtre à l’aide de ALT-TAB (par exemple), le focus sera dirigé ailleurs mais la vidéo restera toujours en avant plan. Il ne vous est plus possible de piloter votre programme vidéo car les évènements seront renvoyés à la fenêtre attrapée avec ALT-TAB jusqu’à ce que vous rendiez le focus au terminal.

Cette exemple peut être testé à l’aide de l’application test-player compilée avec libplayer.

$test-player -p mplayer video.mpg
action> p

Mouais, alors que faire?

Le fait de travailler sur Enna m’a forcé à trouver une solution propre. Le problème cité précédemment n’aurait pas d’impact direct sur GeeXboX car la seule application active dans X11 serait Enna. Mais libplayer doit être universel, alors un changement s’imposait de toute façon.

Les fenêtres qui se listent dans votre barre d’applications sont des fenêtres dont le parent est le root du display X11 en cours (grosso-modo). Mais une application peut elle-même être composée de sous-fenêtres, et elles ne sont pas toujours visibles directement par l’utilisateur (imaginez des calques qui se superposent comme dans un logiciel de dessin).
Il faut bien comprendre que le langage du serveur X est relativement bas niveau. Si vous utilisez GNOME, les fenêtres sont gérées par GTK. Ce que vous voyez, c’est uniquement ce que GTK veut bien vous montrer. Et c’est exactement la même chose avec Qt. Mais que se sois GTK ou Qt, derrière il y a toujours le serveur X avec son langage, exploitable via les librairies Xlib et XCB. libplayer utilisant Xlib (implicitement XCB par wrapping), il est donc facile de dialoguer avec le serveur X sans se soucier du Window Manager. Et pour ce faire, il faut connaître l’identifiant de la fenêtre avec laquelle on désire y faire des manipulations. Il est très facile de retrouver cet identifiant avec les EFL (cf. r275 de Enna).

Une solution se profile

Mon but était de créer la fenêtre non plus par rapport au root de X11, mais par rapport à la fenêtre de Enna (je connais son ID). La fenêtre de libplayer n’est donc plus sœur avec la fenêtre de Enna, mais enfant. Ainsi, les évènements envoyés sur la fenêtre de libplayer sont renvoyés sur Enna sans la moindre écriture de code. La propagation ascendante de X11 fait simplement son job. Il est donc désormais possible de changer de fenêtre avec ALT-TAB et de revenir sur Enna comme avec n’importe quel application. Car la vidéo n’étant plus rattachée au root, elle suivra implicitement sa fenêtre parente “Enna” comme si elle faisait partie intégrante du programme.

On peut alors se demander qu’est-ce qui se passe au niveau de Enna. Et bien Ecore_Evas ne rentre pas en compte car il ignore l’existence de cette fenêtre enfant.

A bientôt,

Mathieu SCHROETER

h1

Enna Media Center

2008.08.21

Hello,

actuellement il n’y a aucune version de GeeXboX 2.0, simplement parce que la prochaine interface graphique (Enna) n’est pas encore assez mature pour être intégrée dans la distribution. Mais ceci dit, rien ne vous empêche de la tester. Cela concerne uniquement les utilisateurs de Linux, et plus particulièrement ceux d’Ubuntu si vous désirez suivre le HOWTO à cette adresse. Je vous conseil d’utiliser le nouveau dépôt Mercurial de Enna à la place du Subversion (obsolète) mentionné dans l’article (hg clone http://hg.geexbox.org/enna).

Enna étant avant tout une interface pour l’utilisateur, le décodage vidéo et audio est transmis à un backend, qui, dans le cas de GeeXboX sera uniquement libplayer. Il est également possible d’utiliser un backend basé sur Emotion mais dont les performances sont bien en-dessous de libplayer/MPlayer. Le fait d’utiliser libplayer permet également d’accéder à xine, GStreamer et VLC pour autant qu’ils soient suffisamment implémentés (actuellement seul MPlayer et xine sont vraiment exploitables).

Le diagramme suivant résume trivialement le fonctionnement, depuis les actions de l’utilisateur jusqu’aux sorties audio/vidéo.

Et Freevo dans tout ça?

En référence à cette news en fin 2006 avec son ISO de démonstration

Aucun développeur de GeeXboX n’étions vraiment intéressé par l’écriture d’un GUI, à part Benjamin Zores qui avait lancé un projet nommé OMC basé sur les EFL pour répondre à ce manque (bien qu’il y ait eu le projet MPUI bien avant). Régulièrement des utilisateurs ouvrent différents postes dans les forums afin de demander un nouveau GUI qui permettrait d’afficher les pochettes par exemple. Ou alors en mettant des copies d’écran de Freevo, MythTV, XBMC ou que sais-je. Tout ceci est bien joli et nous avait finalement conduit sur Freevo.

Un enfer même pour un Paladin bien entrainé... (Diablo II)

Pourquoi Freevo? Et bien parce qu’il est très complet, il peut exploiter MPlayer, il est skinnable, plugable et à pleins d’avantages. La raison était avant tout de nous décharger du travail sur l’interface utilisateur. Ainsi on ne ferait “que” (et j’insiste sur les guillemets) adapter Freevo pour la GeeXboX.
Mais Freevo a un gros inconvénient, et je pèse mes mots.. Etant programmé en Python, la cross-compilation devient un enfer au fur et à mesure qu’on progresse. Il est nécessaire de créer deux fois Python, une fois pour compiler les éléments de Freevo qui sont en Python, et un second pour être intégré dans la distribution afin d’exécuter le code Python. Mais Freevo c’est aussi du code C, et là ou le bas blaisse se sont les dépendances. Il y a par exemple l’utilisation de la GLib (de GTK+) qui était utilisée uniquement pour exploiter les tables de hachage. Il y a d’autres exemples mais ce que je tenais à vous dire se sont quelques raisons qui nous ont forcés à abandonner l’utilisation de Freevo. Leur projet est très actif et en une semaine tout pouvait basculer. Dans le sens où ce n’était plus du tout compilable de notre côté (nouvelles dépendances?) ou alors plus du tout exécutable. Cela prenait tellement de temps à réparer qu’il valait mieux écrire soit même le GUI en C. En toute franchise, j’en avais marre de me battre à le faire fonctionner, quitte à installer une Debian avec Freevo, c’est plus simple et ça marche beaucoup mieux.

la démo de la news (citée plus haut) est en réalité une des rare fois ou Freevo a bien voulu se cross-compiler et s’exécuter à la fois ;-)

Retour au point de départ

L’idée était de réécrire un GUI mais cette fois en SDL (Benjamin Zores et Guillaume Lecerf s’en occupaient principalement). J’ai pris l’initiative d’écrire un wrapper MPlayer pour libplayer pendant cette période. libplayer est resté finalement un des seul projet a avoir survécu depuis le projet OMC et c’est tant mieux. Puis on a abandonné SDL pour revenir sur les EFL et cela s’est terminé par un nouvel échec (sauf pour libplayer) jusqu’au jour où on a découvert le projet Enna.
Ecrit en C il était (est) devenu très intéressant pour GeeXboX. En plus de cela, il est basé sur les EFL. Et il faisait (fait) un candidat parfait pour libplayer sans oublier le point le plus important, son développeur Nicolas Aguirre, qui fait un énorme travail.

A bientôt,

Mathieu SCHROETER

h1

Une petite rétrospective de libplayer

2008.08.13

Le plus grand challenge que j’ai eu sur l’écriture de libplayer fut le wrapper MPlayer. La première version date d’à peine plus d’une année, c’est à dire la rév.44, le 27 juillet 2007. Aujourd’hui on est à la rév.786 et de très nombreuses améliorations ont été apportées. MPlayer n’étant pas utilisable en tant que librairie il a fallu passer par le mode esclave. Le principe est relativement simple, des commandes sont envoyées sur le stdin de MPlayer, tandis que celui-ci répond sur les stdout et stderr.


Avant d’écrire ce wrapper j’ai passé passablement de temps à regarder ce qui se faisait déjà, et j’ai été surpris de voir que la majorité des front-ends exécutent MPlayer le temps de jouer le média et attendent que celui-ci se ferme. Ainsi MPlayer est chargé et déchargé continuellement. Trouvant cela absurde, j’ai donc choisi de travailler avec les arguments ‘-idle -slave’ d’MPlayer. Ainsi, le player s’exécute une seule fois et attend de recevoir des ordres sur son mode esclave. Je dois reconnaître que cela a énormément compliqué l’implémentation dans libplayer. J’ai été rapidement confronté à plusieurs problèmes majeurs. Tout d’abord il n’existait aucune commande esclave pour stopper la lecture. Cela veut dire que le seul moyen de stopper proprement est de quitter MPlayer. Et je pense que c’est une des raison principale qui font que les front-ends n’utilisent pas le mode -idle.
Il me fallait donc résoudre deux cas, le premier était de savoir comment stopper sans quitter MPlayer pour être rétro-compatible, et le second cas, comment implémenter le ’stop’ dans MPlayer pour leur envoyer un patch.

Stopper la lecture

Pour le premier cas j’ai d’abord eu une idée stupide mais qui a dépanné pendant quelque temps. Il faut savoir qu’en mode -idle, quand MPlayer a terminé de lire un stream il stoppe et ne fait plus rien. Et surtout, il ne se termine pas. La solution a donc été de forcer un “seek” à 100% pour simuler le stop. Avec la plupart des fichiers audio et vidéo cela marche plutôt bien, néanmoins tous les types de ressources ne sont pas “seekable” et donc ne se stoppe pas (le streaming réseau par exemple).
J’ai donc corrigé ce comportement en rév.237 pour un nouveau hack bien plus malin. Le meilleur moyen de stopper la lecture était de faire croire à MPlayer qu’il devait charger un nouveau stream. La solution est donc de lui dire de charger quelque chose qui n’existe pas avec la commande loadfile ”. Aucun fichier n’est donné avec le loadfile mais néanmoins il y a les apostrophes. La commande ne retourne donc pas d’erreur sur le nombre d’arguments utilisés mais retourne une erreur car MPlayer ne trouve pas le fichier ” (et du même coup, stoppe la lecture en cours) et dit simplement “File not found: ””. Il est donc facile de détecter également que MPlayer a bien stoppé son activité.

Ce hack est toujours implémenté dans libplayer pour une question de rétro-compatibilité. Néanmoins la commande ’stop’ existe désormais dans le mode esclave (patch MPlayer). Celle-ci a été ajouté dans libplayer avec la rév.458, c’est à dire il y a à peine 5 semaines. A l’initialisation de libplayer, des tests sont effectués sur le MPlayer hôte, et si la commande ’stop’ n’existe pas alors le hack est utilisé à la place.

Détecter la fin de la lecture

Le second problème majeur dont je voulais parler concerne les évènements envoyés à l’utilisateur de la librairie. Et un de ces évènement n’était pas trivial à gérer. Lorsqu’un stream est lu en entier, MPlayer ne fait plus rien mais ne dit pas grand chose. En réalité MPlayer est tellement peu bavard par défaut qu’il est impossible de savoir quoi que se sois sans lui forcer la main. J’ai donc été obligé d’augmenter significativement sa verbosité (voir les rev.78 et rev.552) pour détecter les codes EOF (et bien d’autres choses encore). Ainsi il aurait pu être facile de savoir quand envoyer un évènement de type “PLAYBACK_FINISHED”. Mais la encore, un problème n’arrive jamais seul et j’étais confronté à un autre  qui dépend directement de l’implémentation que j’avais fais du wrapper. Le principe était assez simple, après avoir envoyé une commande, je lisais la sortie de MPlayer pour voir ce qu’il faisait et ensuite je pouvais retourner les résultats adéquats à l’utilisateur de la librairie. Mais le EOF étant complètement asynchrone, je ne pouvais pas savoir quand est-ce qu’il fallait faire quelque chose. Pour régler ce cas, une seule solution propre. Utiliser un thread pour s’occuper de lire les messages de MPlayer (rév.77). Et qui dit thread, dit “race“, “deadlock” et bien d’autres problèmes..

Mais encore…

Les deux cas au-dessus ne sont qu’une partie des problèmes rencontrés, il y en a eu bien d’autres et les 700 révisions ne sont pas tombées du ciel. Mais quoi qu’il en soit, aujourd’hui le wrapper MPlayer se porte bien, tous les patchs que j’ai soumis à MPlayer ont trouvés leur place et la rétro-compatibilité est assurée.
Pour résumer quelque peu l’état actuel de la librairie (de manière non exhaustive), on peut dire que le wrapper est implémenté à plus de 70% et permet de faire largement le job demandé par un lecteur audio/vidéo.

Ci-dessous, vous pouvez voir un extrait de la sortie donnée par le script ./stats.sh disponible dans le dépôt de libplayer:

Global statistics (sum):
~~~~~~~~~~~~~~~~~~~~~~~~

gstreamer  : (11)  13%  [#####.......................................]
mplayer    : (58)  72%  [###############################.............]
vlc        : (22)  27%  [###########.................................]
xine       : (30)  37%  [################............................]

Maximum: 80

libplayer  :       37%  [################............................]

A bientôt,

Mathieu SCHROETER