h1

Mercurial, depuis fin 2006

2010.01.10

Hello,

Cela fait plus de 3 ans qu’on utilise Mercurial dans les projets GeeXboX (avant on travaillait avec TLA Arch). Néanmoins on a de la peine à se conformer aux nouvelles fonctionnalités. Je souhaite en fait parler des branches. Avec Subversion (par exemple), les branches se présentent sous la forme de répertoires. Ça a le mérite d’être clair mais c’est un peu comme collectionner des copies du dépôt principal. Je n’ai jamais trop aimé cette approche, car personnellement je trouve ça un peu brouillon (ça n’engage que moi).

En parlant de brouillon, je vous invite à consulter cette page http://hg.geexbox.org qui liste tous les dépôts existants sous GeeXboX. Si vous regardez la colonne « Last change », vous verrez des « 3 years ago » par exemple. Autant dire que certains dépôts sont complètement obsolètes aujourd’hui. Et c’est même confus, prenez par exemple « geexbox-v2« . Son nom est devenu absurde avec le temps. Car la dedans vous trouverez nos tentatives foireuses de supporter Freevo ;-). Je vous épargne la description des autres.

Bref, là où je veux en venir c’est que pour faire des branches, on s’y prend à l’ancienne et on clone les dépôts. Je pense donc spécialement à « geexbox-1.2 » qui est de loin pas obsolète, car c’est lui qui contient tous les backports (spécialement les corrections de bugs) qui ont permis les versions 1.2.1 à 1.2.4 de GeeXboX. Personnellement il ne me plait pas beaucoup. Toutes les versions 1.2.x devraient être dans le dépôt principal « geexbox« .

Ainsi depuis quelques temps je me suis un peu intéressé aux branches Mercurial. Et c’est sur ce point que je vais m’attarder dans ce billet. Il faut aussi savoir qu’en 2006-2007, une fonctionnalité que je vais présenter ici n’existait pas encore (en tout cas pas sous la même forme). Il n’y a donc pas vraiment de mal d’avoir trainé les bonnes vieilles copies de dépôts pour faire des branches…

Cet intérêt soudain pour les branches m’est venu spécialement depuis les releases de libnfo, libplayer et libvalhalla. Je pense par exemple à avoir des branches pour des éventuels corrections sur les versions 1.0.0. Et surtout, sans faire des copies à l’ancienne.

Les branches nommées (NamedBranches)

Mon but n’est pas d’écrire un tutoriel sur Mercurial. Je vais ainsi essayer d’aller à l’essentiel.  Un dépôt peut contenir des « heads » et des branches. Plusieurs « heads » apparaissent quand on commit des changements par rapport à une révision antérieure au « tip« . Ce qui demande donc de faire un « merge » et de le « comitter« .

Par défaut il y a toujours une branche dans chaque dépôt et elle se nomme « default ». Passons directement à la pratique. J’aimerais gérer les corrections de bugs pour libplayer-1.0.0. Admettons que je sois à la racine du dépôt, je créer alors une branche « v1.0″ à partir du tag « v1.0.0″.

$ hg update v1.0.0
33 files updated, 0 files merged, 7 files removed, 0 files unresolved
$ hg branch v1.0
marked working directory as branch v1.0

A priori j’ai bien une nouvelle branche. Mais voyons ce que dit `hg branch` et `hg branches`.

$ hg branch
v1.0
$ hg branches
default                     1326:cac53e7d727f

Du fait que je n’ai pas « commité » la nouvelle branche, on ne la voit pas dans la liste des branches. Mais la première commande confirme que je suis bien dans la « v1.0″ fraichement créée. Je vais donc « commiter » ce changement localement.

$ hg commit -m "new branch v1.0 for bugfix"
created new head
$ hg branches
v1.0                        1327:1c0c025f8a73
default                     1326:cac53e7d727f (inactive)

Si on désire passer d’une branche à l’autre, c’est très simple.

$ hg update default
40 files updated, 0 files merged, 0 files removed, 0 files unresolved
$ hg branch
default
$ hg update v1.0
33 files updated, 0 files merged, 7 files removed, 0 files unresolved
$ hg branch
v1.0

A noter que s’il y avait des changements non « commités » dans le dépôt, il ne serait pas possible de passer d’une branche à l’autre sans qu’ils soient tous annulés ou « commités ». Mais regardons un peu ce que ça donne avec `hg view`.

On voit deux colonnes bien distinctes qui sont rattachées depuis le « tag » v1.0.0. Un petit tour dans `hg heads` nous montre bien qu’il y en a deux. Et chacun est sur une branche différente.

$ hg heads
changeset:   1327:1c0c025f8a73
branch:      v1.0
tag:         tip
parent:      1230:6b3e2fed5f7a
user:        Mathieu Schroeter <mathieu.schroeter@mycable.ch>
date:        Sun Jan 10 10:26:50 2010 +0100
summary:     new branch v1.0 for bugfix

changeset:   1326:cac53e7d727f
user:        Mathieu Schroeter <mathieu.schroeter@mycable.ch>
date:        Sat Jan 09 20:34:51 2010 +0100
summary:     set winid to 0 by default (fix warning if USE_X11 is not defined)

La branche « default » n’est pas montrée explicitement.

Ce qui m’intéresse maintenant c’est de gérer les corrections de bugs. Cette branche « v1.0″ est à considérer pour exister aussi longtemps que tout le dépôt.

Le « cherry picking »

C’est ce qu’on appel le « cherry picking » car seulement quelques « changesets » spécifiques m’intéressent. Les corrections de bugs sont toutes dans la branche « default ». Pour importer ces changements dans le dépôt « v1.0″ il y a plusieurs manières de faire. Mais personnellement la majorité des solutions que j’ai trouvé ne me plaisent pas. Et il y a peu de temps, je suis tombé sur une extension officielle (mais non activée par défaut) qui permet de faire exactement ce genre de traitements (et même plus). Elle s’appelle « transplant » et c’est elle que je vais utiliser pour mon exemple.

Il faut commencer par l’activer dans le fichier ~/.hgrc:

[extensions]
transplant=

Je vais prendre une partie des corrections intéressantes et les transplanter dans la nouvelle branche.

$ hg transplant 1242
application de 95461fb8613f
patching file Makefile
Hunk #1 succeeded at 18 with fuzz 1 (offset -3 lines).

Je répète pour chaque « changeset ». On peut spécifier plusieurs « changesets » en les séparant par ‘:’ pour une plage REV1:REV4 par exemple. Mais avec le Mercurial d’Ubuntu j’évite de le faire, simplement parce que l’extension me retourne une exception et un joli traceback Python. En prenant un après l’autre, aucun problème.

Le « screenshot » ci-dessus permet de voir les différents « changesets » appliqués sur la branche « v1.0″.

Pour continuer à travailler sur la devel (branche « default »), rien de plus simple.

$ hg update default
40 files updated, 0 files merged, 0 files removed, 0 files unresolved
$ vi Makefile
$ hg commit -m "dummy commit"
$ hg heads
changeset:   1335:55589e41d0eb
tag:         tip
parent:      1326:cac53e7d727f
user:        Mathieu Schroeter <mathieu.schroeter@mycable.ch>
date:        Sun Jan 10 13:07:34 2010 +0100
summary:     dummy commit

changeset:   1334:81c124600c6b
branch:      v1.0
user:        Mathieu Schroeter <mathieu.schroeter@mycable.ch>
date:        Sat Jan 09 20:29:01 2010 +0100
summary:     fix 'make dist' in src

On voit clairement évoluer le « tip » de la branche « default » en parallèle à la branche « v1.0″.

Un « push » avec plusieurs branches

Tout ceci n’est qu’un exemple, ainsi pour montrer le « push » je vais utiliser un répertoire local. Admettons que je clone depuis http://hg.geexbox.org/libplayer.

$ cd ..
$ hg clone http://hg.geexbox.org/libplayer libplayer-local
requesting all changes
adding changesets
adding manifests
adding file changes
added 1327 changesets with 2268 changes to 111 files
updating working directory
104 files updated, 0 files merged, 0 files removed, 0 files unresolved

C’est donc le dépôt original où j’ai qu’une seule branche. Je retourne dans le dépôt qui contient la branche « v1.0″ et je vais faire un « push » sur libplayer-local.

$ cd libplayer
$ hg push ../libplayer-local
pushing to ../libplayer-local
searching for changes
abort: push creates new remote branch 'v1.0'!
(did you forget to merge? use push -f to force)

Le « push » est automatiquement annulé. Ce qui est très bien, ainsi Mercurial nous préviens qu’on a créé une nouvelle branche. Il est alors nécessaire de le forcer (la première fois). Mercurial nous demande aussi si on a pas oublié de faire un « merge ». C’est légitime dans le cas où on ne veut pas avoir deux branches séparées sur le dépôt distant.

$ hg push -f ../libplayer-local
pushing to ../libplayer-local
searching for changes
adding changesets
adding manifests
adding file changes
added 9 changesets with 6 changes to 2 files (+1 heads)

Mercurial nous confirme qu’on a bien ajouté un « head ». A partir de ce point je me suis posé la question de ce qui se passe si quelqu’un clone ce dépôt avec ces deux « heads ». Est-ce qu’il va avoir un avertissement s’il tente de faire un « push », du genre qu’il devrait faire un « merge » ou forcer le « push »?! Ce serait ennuyeux si c’était le cas, car les branches ne devraient pas intervenir. Le « head » n’étant pas sur la même branche. J’ai donc simplement testé pour m’en assurer:

$ cd ..
$ hg clone libplayer-local libplayer-foobar
updating working directory
104 files updated, 0 files merged, 0 files removed, 0 files unresolved

Cette manipulation revient au même que de cloner le dépôt sur hg.geexbox.org si j’avais « pushé » les modifications. Sans me soucier des branches, je vais faire une modification dans libplayer-foobar, la « commiter », puis faire un « push » dans libplayer-local comme si c’était hg.geexbox.org/libplayer.

$ cd libplayer-foobar
$ vi Makefile
$ hg commit -m "still a dummy commit"
$ hg push ../libplayer-local
pushing to ../libplayer-local
searching for changes
adding changesets
adding manifests
adding file changes
added 1 changesets with 1 changes to 1 files

Il n’y a aucune remarque de Mercurial par rapport aux branches. Cela prouve que tout fonctionne correctement. Honnêtement j’avais toujours eu des doutes sur ces fonctionnalités. Au moins maintenant c’est clair. Je vais alors retourner dans le dépôt libplayer-local pour vérifier le résultat avec `hg view`.

Tout a fonctionné parfaitement..

Conclusion

Maintenant que j’en sais suffisamment sur les branches, je pense passer par ce moyen à l’avenir. Il pourrait être même judicieux (peut être) de repasser tous les backports de geexbox-1.2 dans le dépôt geexbox. L’idée serait de ne jamais écrire de nouveaux patchs dans les dépôts faisant références à des versions antérieures. Mais de toujours faire les modifications dans le « default » et uniquement des « transplant » dans les autres branches. Je pense par exemple au dossier debian/ dans les dépôts. Afin de garder l’historique, une nouvelle release tel que libplayer-1.0.1 par exemple, serait enregistrée dans les changelogs du « default », puis un « transplant » mettrait à jour la branche « v1.0″.

Il reste une utilisation des branches qui me pose encore un problème. Les cas où l’écriture d’une fonctionnalité ne devrait pas se faire directement dans le « default » car elle introduirait des régressions. Une branche supplémentaire tel que « experimental » pourrait faire l’affaire. On devrait ensuite faire un « merge » de cette branche dans le « default ». Ce qui me dérange c’est qu’on finit par collectionner les branches comme par exemple ici: http://hg.geexbox.org/enna/. Il y a « new_vfs » et « new_vfs_system ». Ces branches n’ont plus vraiment de raison d’être car elles ont été « mergées ». Dans le cas de l’exemple présenté au-dessus avec la branche « v1.0″, c’est parfaitement normal de la garder pour toujours. Je n’ai donc pas encore trouvé de solution propre (c’est une question de point de vue) pour travailler avec des branches de courtes durées de vie.

Si quelqu’un à une idée…

A bientôt,

Mathieu SCHROETER

h1

Quoi de neuf sur les libs

2010.01.03

Hello,

maintenant qu’Enna 0.4.0 est dehors et que les paquets sont disponibles, cet article me donne l’occasion de faire un peu la synthèse des modifications apportées à libvalhalla et à libplayer depuis les versions 1.0.0. A noter que la première version d’Enna concerne uniquement les versions 1.0.0, et en aucun cas ce que je liste ci-dessous.

libplayer

Concernant libplayer il n’y a pas grand chose de neuf. La plupart des patchs se rapportent à des fix mineurs dans les scripts et Makefiles. Ainsi que le support (en théorie) de Darwin. Je n’ai pas la moindre idée si la compilation pour MacOS X fonctionne car je n’ai jamais eu de Mac à ma disposition. A part une image émulée d’une Tiger qui n’est pas spécialement rapide à l’utilisation.

Mise à part ça, la modification la plus importante est le remplacement de Xlib par XCB. Xlib a toujours été un problème néanmoins une astuce permettait de le rendre utilisable dans le contexte de libplayer. X11 dans libplayer a deux raisons d’être. Tout d’abord c’est un bon moyen pour créer une fenêtre X pour MPlayer. Cette fenêtre étant entièrement contrôlée par libplayer, cela évite d’avoir des problèmes avec les événements X11 et il est facile de l’embarquer dans Enna. Ensuite cette fenêtre est indispensable pour xine-lib.

Le problème d’Xlib intervient au niveau des threads. Afin de garantir d’être thread-safe il est nécessaire d’appeler la fonction XInitThreads() avant n’importe quel autres fonctions d’Xlib. Ou alors, il faut gérer les locks sur les appels Xlib sois-même. J’ai donc opté pour la deuxième solution (dans le cas de libplayer-1.0.0). Des mécanismes dans xine-lib permettent d’appeler les locks créés par libplayer. Mais il y a un point à soulever. Selon les dires des développeurs de xine, ces mécanismes ne sont pas exempt de bugs.
Et l’utilisation d’XInitThreads() est impossible dans le cas de libplayer. La raison est très simple. Étant donné que ce doit être la première fonction à appeler, il faudrait être sûr que par exemple avec Evas, Qt ou GTK (si libplayer serait utilisé dans une application qui en dépend tel qu’Enna avec Evas), que libplayer soit initialisé avant ces libs. Ou alors qu’Evas fasse lui même un XInitThreads(). Ou encore que celui qui développe le GUI (tel qu’Enna) fasse un XInitThreads() dans son main() (avant toutes les autres initialisations). Et donc l’utilisation d’une telle bibliothèque deviendrait très contraignante.

La solution c’est XCB. Il n’y a aucun besoin d’initialiser quoi que se sois pour les threads contrairement à Xlib. XCB peut donc être utilisé de manière sûre dans libplayer et sans fournir de locks supplémentaires. C’est sur XCB que les développeurs de xine se tournent également. Car il y a au moins les sorties video Xshm et Xv qui sont portées pour XCB.

La prochaine release de libplayer utilisera donc XCB. Pour être plus précis, le fait d’utiliser XCB dans libplayer n’empêche pas d’utiliser Xlib pour un wrapper de libplayer, pour autant que les mécanismes pour garantir le thread-safe soient implémentés.

VDPAU

NVidia fait des efforts pour Linux depuis longtemps. VDPAU en est un exemple parmi d’autres. Mais tout ceci reste qu’en même du code fermé avec tous les désavantages qui en découlent. Dans le cas de libplayer, il est aujourd’hui impossible de supporter VDPAU avec xine. Non pas que xine ne peut pas l’utiliser, mais plutôt que xine est obligé de passer par Xlib pour pouvoir l’exploiter.

On pourrait croire alors que je n’aurais pas du changer Xlib pour XCB dans libplayer, mais en réalité ça n’a strictement rien à voir. Le problème est au niveau de VDPAU. Celui-ci étant basé sur Xlib, il est nécessaire d’avoir l’initialisation des locks. Le seul moyen actuel est de devoir faire appel à XInitThreads(). Ainsi xine refuse de charger VDPAU dans les seuls cas thread-safe tel que XCB ou la variante Xlib (soit disant « buggée » qui fonctionne qu’en même bien par rapport à ce que libplayet-1.0.0 en fait).

Pour être honnête, il y a un moyen mais c’est un hack. En ajoutant un XInitThreads() dans Enna avant l’initialisation d’Evas ainsi qu’une modification dans le wrapper xine de libplayer pour lui dire qu’il doit travailler avec Xlib.

Tout ceci ne concerne pas le wrapper MPlayer, qui peut parfaitement utiliser VDPAU pour la sortie vidéo comme pour les codecs, étant donné que c’est un processus « forké ».

libvalhalla

C’est sur cette bibliothèque où j’ai le plus travaillé depuis la 1.0.0. Mise à part des correctifs sur les scripts et Makefiles comme pour libplayer et Darwin, il y a aussi de nouvelles fonctionnalités.

API

Tout d’abord l’API publique est maintenant plus facile à étendre sans la casser à l’ajout de nouveaux paramètres à l’initialisation par exemple. J’ai également factorisé toutes les fonctions qui permettent de configurer libvalhalla en une seule fonction variadique.

Statistiques

Avec libvalhalla-1.0.0 il y a déjà quelques statistiques. Par exemple les résultats et les temps utilisés par les grabbers, ou encore un résumé des actions qui ont été faites sur la base de donnée. Néanmoins ces informations ne sont pas disponibles depuis l’API. Ce qui change dans le prochain libvalhalla c’est que toute les statistiques de ce type sont récupérables facilement. Il y en a également plus qu’avant. Je dois encore en ajouter sur certaines parties tel que le scanner. Mais les ajouts n’affecteront en rien l’API publique.

Dans le cas d’Enna ça pourra être utilisé pour montrer (pour le fun) l’état des différentes parties de libvalhalla dans une fenêtre d’information. Les statistiques peuvent être interrogées à n’importe quel instant, ce qui permet de suivre l’évolution.

Événements globaux

Il est possible d’avoir des événements globaux comme par exemple une information qui prévient que tous les fichiers  (pour une passe complète du scanner) ont été traités. Il n’y a pas beaucoup d’événements pour l’instant. Ils ne sont pas des plus utiles, mais dans le cas d’Enna il permettront d’avoir une notification. Il est également facile d’en ajouter des nouveaux.

Metadata callback

C’est un callback qui a été ajouté suite à une proposition d’un tiers pour une demande assez spécifique. Le but est de pouvoir récupérer depuis l’API publique toutes les metadata en même temps qu’elles sont récupérées par les parsers et grabbers. C’est donc un moyen d’avoir accès aux données sans passer par la base de donnée. Je ne recommande pas son utilisation pour plusieurs raisons. Tout d’abord si le callback est bloqué relativement longtemps pour chaque metadata, l’utilisation mémoire va augmenter en fonction (le blocage du callback ne bloque pas le reste de la bibliothèque). Il faut absolument veiller à traiter les données aussi vite que possible. La deuxième raison est que SQLite est beaucoup plus performant pour rendre toutes les metadata. Les fonctions de sélections sont relativement haut-niveaux et permettent de récupérer les informations de manière efficaces et ordonnée. Une de ces fonctions a également été un peu améliorée dans le cadre des modifications depuis la version 1.0.0.

J’ai qu’en même rajouté la fonctionnalité dans la bibliothèque car elle n’est pas intrusive et ne peut pas introduire des régressions ou des ralentissements.

Grabbers parallélisés

C’est la plus grosse nouveauté pour la prochaine release. Dans le cas de libvalhalla-1.0.0, beaucoup d’éléments travaillent en parallèle, à l’exception des grabbers (entre eux). Il est possible maintenant d’avoir « autant » de grabbers que l’on veut simultanément. Cette fonctionnalité permet d’économiser environ 30% du temps selon mes essais. L’intérêt est également que les fichiers vidéos ne sont plus bloqués sur les grabbers dédiés à l’audio et inversement. Par exemple le grabber LyricWiki qui est spécialement lent, était un vrai goulot d’étranglement pour les fichiers vidéos (aussi pour les autres fichier audio, ce qui est implicite). La parallélisation permet à ces fichiers (non-audio) de se terminer indépendamment de ce grabber (et des autres).

J’ai mis la limite maximum à 16 grabbers en parallèle (ce qui est plus que le nombre de grabbers différents qui existent). Un choix efficace et d’opter pour 3 ou 4. L’utilisation de la RAM n’est pas spécialement affectée car la plupart utilisent des services web qui sont plutôt gourmand en temps.

Il y a deux autres effets à noter:

  • Les priorités ne sont plus complètement respectées si on défini plus d’un grabber à la configuration. Ce qui veut dire que par exemple un fichier peut être envoyé dans un grabber qui devrait (selon les priorités) être interrogé dans les derniers. Il est donc possible de continuer à travailler comme libvalhalla-1.0.0 en forçant explicitement le nombre à 1.
  • Le second effet est lié au ondemand. Celui-ci est plus rapide car il y a plusieurs threads de grabbers et donc il peut rapidement avoir toutes ces demandes sur tous les threads (à cause des priorités du ondemand).

Le fonctionnement en quelques mots

La parallélisation des grabbers n’est pas comparable à celle des parsers. Lorsqu’un fichier doit être traité par un parser, il attend simplement dans la queue jusqu’à ce qu’un parser le POP et le traite vraiment. Il n’y a pas de raison de faire autrement car tous les parsers font le même travail. Les grabbers sont différents car chacun d’eux fait un travail spécifique. Ainsi lorsqu’un fichier se retrouve dans la queue et qu’un thread de grabber le POP, il va chercher un grabber de libre. Le cas échéant il renvoi le fichier dans la queue et réessaye avec le suivant. Il n’y a donc jamais de longue attente, ce qui permet de bien enchaîner tous les fichiers.

Il y a aussi quelques conditions. Un même fichier n’est jamais parallélisé entre les différents threads des grabbers.  Et un même grabber travail que dans un seul thread à la fois. Ce qui veut dire par exemple, que s’il y a 10 threads; pour que les 10 soient actifs il faut au moins 10 grabbers différents (FFmpeg, LyricWiki, Amazon, etc,…), et 10 fichiers différents.

Il est ainsi évident que d’avoir beaucoup de threads n’apportent absolument plus rien au delà d’une certaine limite que j’estime à environ 4. Mais ceci dépend fortement des types de fichiers différents, des types de grabbers compilés et leur nombre.

Bonne année,

Mathieu SCHROETER

h1

Autour de la compilation d’Enna

2009.12.17

Hello,

je tiens juste à donner quelques précisions pour les personnes qui désirent compiler Enna, et donc également les bibliothèques rattachées. Cet article n’est pas un tutoriel. Si vous ne savez pas comment vous y prendre, il vaut mieux rechercher l’information ailleurs.

Faites aussi un tour à ces adresses (en anglais / in english) :
http://enna.geexbox.org/developers.html
http://captainigloo.wordpress.com/2009/12/21/enna-compilation-on-ubuntu-3/

Evas

Comme vous le savez, Enna se base sur les EFL. Je vous conseil fortement d’utiliser les snapshots ne serait-ce au moins pour ne pas que vous reportiez des « bugs » juste parce que les APIs des EFL auraient trop évoluées. En partant du principe que vous savez compiler les EFL, je vous rappel de ne pas oublier le flag ––enable–gl–x11 avec Evas, si vous avez l’accélération 3D avec votre PC. Les performances graphiques d’Enna sont nettement meilleurs. Toujours concernant Evas, il y a une petite modification dans les sources qui vous permettra d’avoir des textures anti-aliasées. Dans le cas contraire, Enna est plus beau (mais plus lent) en X11 Software plutôt qu’en OpenGL. Il faut tout simplement modifier les définitions GL_NEAREST en GL_LINEAR dans toutes les sources.

cd ~/e17_src/evas
sed -i "s/GL_NEAREST/GL_LINEAR/g" \
    `grep -Rl GL_NEAREST . | grep "\.c$"`

Libgeexbox

Libgeexbox est une manière naïve de parler de toutes les bibliothèques que nous développons en parallèle à la distribution et à Enna. Depuis quelques temps, des releases sont apparues pour libnfo, libplayer et libvalhalla. Il faut savoir que se sont les toutes premières releases! Là où je veux en venir c’est qu’il était totalement justifié de toujours se baser sur les versions de développement pour compiler Enna. Par exemple le premier import de libplayer date de 2006 et c’était une habitude que de faire un hg pull -u régulièrement. Mais maintenant que les versions 1.0.0 sont disponibles, veuillez s.v.p. vous baser uniquement sur celles-ci (ou sur n’importe quelles nouvelles releases de ces libs dans le futur).

Il y a deux solutions, en passant sur les sites web respectifs des projets (les liens sont disponibles à droite des articles de ce blog) ou alors avec Mercurial. Mais dans ce cas veuillez préciser la version de la dernière release. Tel que par exemple:

hg clone -r v1.0.0 http://hg.geexbox.org/libvalhalla

Si vous chargez la devel (donc le « tip ») vous ne pourrez pas compiler Enna ou alors vous avez de la chance :-). Depuis qu’il y a les releases je me permets de casser les APIs des bibliothèques parce que je suis un éternel insatisfait (je rigole…) et ça faisait longtemps que je voulais faire un peu de ménage dans certaines en-têtes publiques.

Et après l’installation des libgeexbox, n’oubliez pas de faire un `sudo ldconfig`. Question que le chargeur de programme ait les nouvelles bibliothèques de référencées.

libvalhalla

Quand vous exécutez le configure de libvalhalla, jetez un œil aux informations retournées avant de faire bêtement un `make`. Le configure désactive les éléments en fonction des bibliothèques qu’il ne trouve pas. Par exemple, si vous n’avez pas la libcurl-dev, le support des grabbers est complètement désactivé mais cela n’empêche pas la compilation.

Admettons que vous avez toutes les libs nécessaires et que vous voyez certains grabbers de désactivés. Je pense par exemple à lyricsfly. Ce n’est pas un bug. Lyricsfly n’est pas utilisable car la clef de l’API pour le webservice était provisoire. Ce grabber est donc désactivé par défaut. Si vous utilisez ––enable–grabbers pour être sûr d’activer tous les grabbers vous n’aurez rien à gagner. Vous ferez perdre du temps à libvalhalla sur lyricsfly qui ne retournera jamais rien (je parle de lyricsfly, mais ça peut être d’autres à l’avenir).

A noter également que dès que vous forcez les grabbers ou seulement quelques grabbers, leurs dépendances deviennent obligatoires. Par exemple si vous faites ––enable–grabber–ffmpeg vous forcez la dépendance sur libavcodec. Si vous avez vraiment libavcodec et que ça échoue, c’est simplement que votre version est trop ancienne. Par exemple, le libavcodec que vous trouvez avec Ubuntu Karmic ne supporte pas la fonction av_lockmgr_register() qui garanti l’utilisation des codecs à être multi-thread safe.

libplayer

Concernant libplayer, c’est la dépendance indirecte avec MPlayer qui est la plus importante. Assurez-vous que votre MPlayer est en anglais uniquement. Libplayer peut détecter les MPlayer incompatibles jusqu’à un certain point. En fonction de la manière dont MPlayer a été compilé, libplayer ne peut pas savoir s’il est en anglais ou non et va donc l’utiliser (pour les curieux, je parle de la variable d’environnement LINGUAS à la compilation d’MPlayer; libplayer ne détecte la langue que si celle-ci est passée avec ––language–msg= ou ––language=).

Démarrer Enna

Contrairement à certains tutoriels sur Enna, il n’est pas nécessaire de copier le fichier d’exemple enna.cfg qui se trouve à la racine des sources, dans le dossier ~/.enna. Parce que ce fichier est automatiquement créé au premier démarrage de l’interface. Et allez jeter un œil au contenu. Si vous voulez que libvalhalla puisse faire son travail, vaut mieux lui dire où il doit scanner. Dans le cas contraire 100% des fichiers seront traités en ondemand (je vous laisser chercher dans ce blog si vous ne comprenez pas).

Et maintenant que vous avez l’OpenGL, vous remarquerez assez vite qu’en le spécifiant dans ~/.enna/enna.cfg ça ne change pas grand chose. Pire que cela, ça ne change absolument rien ;-).

Pour l’instant il faudra le faire à la main depuis un terminal, avec:

ELM_ENGINE=gl enna

A bientôt,

Mathieu SCHROETER

h1

Le « decrapifier » de libvalhalla

2009.12.02

Hello,

sous ce nom barbare se cache un modeste et petit mécanisme pour nettoyer les noms des fichiers. Il faut savoir qu’avec des conteneurs audio et vidéo de bonne qualité, le titre du média est généralement stocké dans une méta-donnée « title ». En général ce titre est propre et peut être utilisé tel quel pour une recherche d’information sur internet. Dans notre contexte, il est utilisé en général par les « grabbers » pour rechercher les données, paroles des chansons, descriptions, liste des acteurs, etc, … Malheureusement, on traine encore des boulets au niveau conteneur multimédia.

Un petit bond en arrière

Je me rappel encore très bien, à l’époque de Windows 3.1/3.11 d’avoir installé le composant optionnel « Video for Windows ». Celui-ci rendait possible la lecture de petites vidéos dans des conteneurs AVI (entre autres). J’avais les yeux qui brillaient quand je voyais ces mini-films défiler. Pourtant aujourd’hui on se plaint quand une vidéo n’est même pas en 720p. D’un autre côté quand on voit le lecteur Flash qui bouffe toute la puissance CPU avec des vidéos de mauvaise qualité, y a de quoi se poser des questions par rapport à ce qui se faisait déjà il y a 15 ans en arrière. Voyez par exemple la vidéo suivante:

Cliquez dessus pour la lire! (si vous le pouvez :-P)

C’est un petit film qui date des alentours de 1997 (peut être même 1996), et qui est enregistré au format FLI. C’est un format d’animation d’image dans la même idée que le GIF. Ce petit film est cadencé à 14 images/sec avec une définition d’image de 320×200 px. Il peut y avoir également de la compression avec ce format, mais ce n’est pas aussi sophistiqué que du MPEG. Là où je veux en venir, c’est que cette vidéo est lisible de manière fluide sur un Smaky 130, qui correspond à un processeur Motorola 68030 (fixé à 25 MHz pour cet ordinateur). C’est d’un Smaky que j’ai récupéré ce film à l’aide d’un outil que je m’étais amusé à faire http://home.gna.org/fosfat/. La grande partie des logiciels Smaky sont écrits en CALM (en calme aussi), qui est une sorte de langage d’abstraction sur les assembleurs (une notation assembleur indépendante du fabricant). Il y a donc de forte chance que le Smaky utilise un logiciel écrit non pas en C/Pascal, mais en CALM pour arriver à lire cette animation. Ainsi le Flash me donne vraiment mal au ventre quand je le vois suer avec un Athlon X2 64.

Decrapifier

Bref, pour en revenir au « decrapifier », j’ai abordé les fichiers AVI. Ce conteneur développé à l’origine par Microsoft a aujourd’hui des défauts, et malheureusement on le retrouve encore bien trop souvent (on ne peut pas en vouloir à l’AVI, il est vieux). Quoiqu’il en soit, il n’y a pas de méta-donnée « title » la dedans.

Le « decrapifier » va alors se charger d’éliminer tous les caractères peu propice à aider à effectuer des recherches via les « grabbers ». A noter également que lorsqu’un titre est nettoyé, c’est aussi une bonne chose pour l’utilisateur. Le principe est très simple et efficace dans la plupart des cas. Pour l’illustrer, prenons un nom de fichier inutilisable pour une recherche (et bien trop courant).

"{XvID-LOL}.Elephant.-.Dreams.s02e10_(DVDRip)_Etach.avi"
  1. Le nom est nettoyé de tous ses caractères dont le code ASCII est inclus dans les 7 premiers bits (128 premiers caractères). L’ASCII 7 bits à l’avantage d’être présent dans (presque) tous les codages de caractères dont l’unicode. L’idée est de simplement remplacer chacun de ces caractères par un espace, en prenant soins de supprimer l’extension du fichier (sauf exception pour l’apostrophe, les espaces et les caractères alpha-numériques qui sont conservés).
    "XvID LOL  Elephant   Dreams s02e10  DVDRip  Etach"
  2. Une liste noir de mots clefs va permettre d’éliminer tout ce qui ne nous intéresse pas pour la recherche. Les valeurs par défaut avec Enna (fichier ~/.enna/enna.cfg) sont:

    0tv, 1080p, 2hd, 720p, ac3, booya, caph, crimson, ctu, dimension, divx, dot, dsr, dvdrip, dvdscr, e7, etach, fov, fqm, hdq, hdtv, lol, mainevent, notv, pdtv, proper, pushercrew, repack, reseed, screencam, screener, sys, vtv, x264, xor, xvid, SExEP, sSEeEP

    Cette liste est donc éditable à souhait. Il y a deux mots clefs particulier (en fin de liste) que je vais expliquer dans un deuxième temps. Tous les autres mots clefs sont écrits en minuscules car le système n’est pas sensible aux majuscules en temps normal. On peut donc voir le résultat sous la forme suivante:

    "          Elephant   Dreams s02e10               "
  3. Mais le résultat précédent, bien que beaucoup plus intéressant, ne correspond pas à la réalité. Le mot clef « sSEeEP » présent dans la liste va permettre de gérer le s02e10. L’idée est de récupérer également des informations utiles comme la saison et le numéro d’épisode. Le vrai résultat est donc:
    "          Elephant   Dreams                      "
    

    Add new metadata « season », value: « 2″
    Add new metadata « episode », value: « 10″

  4. La dernière étape est de supprimer tous les espaces blancs superflus. Pour finalement avoir une chaîne de caractère sous cette forme:
    "Elephant Dreams"

Les motifs

Deux motifs ont été présentés brièvement. En réalité il y en a trois.

  • NUM (indique un nombre non signé et entier)
  • SE (indique un nombre non signé et entier qui représente une saison)
  • EP (indique un nombre non signé et entier qui représente un épisode)

Il y a quelques règles à prendre en compte lorsqu’on les utilise. Tout d’abord, ce type de mot clef est sensible aux majuscules. Par exemple, SSEEEP n’est pas égale à sSEeEP. Ensuite NUM, ne retournant aucune valeur, il peut être utilisé plusieurs fois dans un même mot clef.

Prenons l’exemple d’Elephant Dreams. Si le mot clef était sNUMeNUM, le résultat final serait exactement le même qu’au point 4. Néanmoins, aucune nouvelle méta-donnée seraient insérées. Cela peut donc être utile dans certains cas de figure où on trouverait des numéros inutiles. Il ne faut pas non plus abuser de ce genre de mots clefs. En voulant supprimer tous les numéros, on risquerait de ne plus avoir de titre (je pense au film « 2012″ par exemple).

Concernant SE et EP, ils ne doivent être utilisés qu’une seule fois par mot clef. Mais il y a qu’en même quelques libertés comme par exemple les mots clefs suivants sont parfaitement possibles: SeasonSE, EpisodeEP. Il n’est donc pas obligatoire d’avoir toujours SE et EP dans le même mot clef. Une autre considération concerne le nombre de saisons et d’épisodes pour un seul et même fichier. Imaginons un autre exemple complètement absurde:

"Mes Vacances (02x100) -s55e10-"

Si les mots clefs SExEP et sSEeEP sont présents dans la liste noir, le résultat final est donc bien:

"Mes Vacances"

Mais en plus il y a 4 nouvelles méta-données :

Add new metadata « season », value: « 2″
Add new metadata « episode », value: « 100″
Add new metadata « season », value: « 55″
Add new metadata « episode », value: « 10″

Pour terminer

Je ne tiens pas à donner des explications techniques sur le fonctionnement. Le gros du travail se fait via la magie de sscanf(). Le choix de NUM, SE et EP n’est pas non plus arbitraire. C’est uniquement par souci de commodité. En interne, le NUM est remplacé par %*u, les SE et EP sont remplacés par %u. Cela évite de devoir jouer l’accordéon avec les chaînes de caractères. Pour des détails, lisez le MAN de sscanf.

A bientôt,

Mathieu SCHROETER

h1

Un (tout petit) tour d’horizon

2009.11.23

Yop,

un tout petit tour d’horizon s’impose. Le travail sur (et autour de) Enna fait son petit bonhomme de chemin. Le design était sans cesse modifié mais depuis quelques semaines on a enfin quelque chose de relativement stable. Personnellement je n’ai pas beaucoup travaillé sur Enna car je continue à me concentrer sur libvalhalla. Il y a encore deux régressions de Enna qui sont dues à l’utilisation de libvalhalla.

Je parle de régressions, bien qu’il n’y a jamais eu de sortie officielle et stable. Il faut comprendre par là qu’avec toutes les modifications qui ont été apportées à Enna, de temps en temps des fonctionnalités disparaissent simplement parce qu’elle devraient être gérées d’une autre manière.

La première régression que je veux souligner est celle de pouvoir reprendre la lecture d’un film à l’endroit où il a été interrompu. Dit comme ça, il n’y a rien d’exceptionnel. Mais « à l’époque », l’enregistrement se faisait à l’aide de Eet qui était également utilisé pour stocker les méta-données des fichiers audio/vidéos. Depuis l’arrivée de libvalhalla et donc d’une base de donnée SQLite, Eet n’avait plus beaucoup de sens et cette partie a été simplement supprimée d’Enna. Mais la lib ne permet pas l’écriture depuis son API publique pour plusieurs raisons. Tout d’abord Valhalla ce n’est pas une bibliothèque pour gérer une base de donnée, mais c’est avant tout un scanner de fichiers, des « parsers » et des « grabbers ». Les données sont récupérées par elle automatiquement. Il est donc possible de les lire depuis l’API publique. Lorsque des données doivent être modifiées, elles le sont depuis les fichiers (tags ID3 par exemple), ou alors depuis les site internets qui fournissent des informations (les grabbers, ImDB, Amazon, etc, …).

Mais pour répondre à certains besoins comme la sauvegarde de la position d’un film, il est nécessaire de reconsidérer cette question sur libvalhalla. J’ai donc commencé à travailler sur cet aspect il y a peu de temps. Il y aura ainsi une information qui permettra d’identifier des données venant de l’extérieur (parser, grabbers, …) et des données venant de l’intérieur. Pour mieux comprendre pourquoi il est nécessaire de faire des séparations, l’exemple d’un champ « playcount » est bien adapté. Le « playcount » (donc le nombre de fois qu’un fichier est lu), dépend uniquement de l’utilisateur d’Enna. Si le fichier en question est modifié (les tags par exemple), ce champ doit rester intacte ce qui n’est pas possible actuellement.

La deuxième régression concerne les « snapshots » (ou « fanarts »). Ils servent à avoir une image illustrant une video. Par exemple les images suivantes présentent les résultats pour quatre « trailers » où des données intéressantes ont pu être récupérées par les « grabbers » (j’ai récupéré les « trailers » sur le site d’Apple, merci au blogger en lien <ici> et <ici> pour l’astuce).


Dans tous ces exemples les images de fond ont pu être téléchargées sur le site TheMovieDB.org. Mais il peut arriver qu’aucune image ne soient disponibles et donc une alternative est nécessaire. Actuellement il y a une image de fond par défaut pour ces cas de figure, mais le but est de créer un « snapshot » directement avec le contenu de la vidéo. Cette fonctionnalité était disponible à l’époque où libvalhalla n’existait pas, et libplayer faisait office d’intermédiaire pour récupérer l’image à 20% de la vidéo. L’idée désormais est donc d’utiliser le « grabber » FFmpeg (ajouté il y a peu de temps) pour extraire l’image. Bien sûr, il ne faut pas l’extraire dans les cas où TheMovieDB contient déjà un « snapshot » de bien meilleur qualité.

Il y bien entendu toujours un panneau d’informations pour les vidéos. Il va encore probablement subir des modifications, et donc rien de ce qui est montré dans ces « screenshots » ne peut être considéré comme définitif.

Il y a encore d’autres choses qui ont évoluées aussi bien au niveau de l’audio que de la vidéo, mais mon objectif  pour ce billet est uniquement de parler des deux régressions. Du côté de libvalhalla, les « grabbers » tels que Amazon, Allocine et LyricWiki re-fonctionnent normalement (espérons que se sois pour encore longtemps). A part ça, une grande partie des modifications sont internes tel que de l’optimisation et des corrections de bugs par exemple.

A bientôt,

Mathieu SCHROETER

h1

Une image pour illustrer libvalhalla

2009.11.05

valhalla

Il manquait une illustration pour libvalhalla. C’est toujours plus sympathique de pouvoir faire référence à une bibliothèque par l’intermédiaire d’images. Ce n’est pas non plus pour rien que je lui ai donné ce nom. Contrairement à celle que j’avais fais pour libplayer, cette fois j’ai choisi un style un peu plus classique et complètement monochrome.

Les personnages représentent des Valkyries à la recherche de vos fichiers multimédias perdus dans les champs de batailles de vos disques durs.

C’est sûrement le billet le plus court que je n’ai jamais publié :-P.

A bientôt,

Mathieu SCHROETER

h1

Force-stop, on demand, events, …

2009.10.11

Hello,

avec les projets GeeXboX parfois j’ai l’impression de ne jamais arriver au bout. Quand quelque chose se termine, il y a toujours autre chose à faire. On ne risque pas de s’ennuyer. Mais il arrive qu’on fasse les choses à double, triple ou pire. Tout ça pour dire que les fonctionnalités présentées dans le billet précédent (« force-stop », « on demand » et « events ») sont implémentées.

Je vous invite à lire l’article à ce lien si vous ne comprenez pas de quoi je parle; reste aussi à être intéressé par ce genre d’article :-).

En quelques phrases

force-stop

Je ne vais pas revenir sur les détails. La réalisation du « force-stop » a introduit de nouvelles tables dans la base de donnée. Une table pour sauvegarder les « grabbers » et une table pour sauvegarder les contextes du « downloader ». En résumé, lorsqu’un fichier a été traité par un « grabber » et que les données ont été sauvegardées, la relation avec ce « grabber » est également introduite dans la base de donnée. Ça évite ainsi au prochain démarrage de « regrabber » les même données. Pour le « downloader » l’idée est un tout petit peu différente. Lorsque Valhalla se termine, il sauvegarde toutes les listes de fichiers à télécharger dans la base de donnée. Au prochain démarrage, quand le scanner tombe sur un fichier où il y avait encore des données à télécharger, il réintroduit les listes dans les structures.

ondemand

Pour le « ondemand » le travail s’est montré un peu plus compliqué que je le pensais. Étant donné que le « ondemand » peut se passer à n’importe quel moment il y a de nombreux cas à considérer. Par exemple, vous faites une requête « ondemand », le fichier de la requête n’a pas été vu par le scanner. Le « ondemand » se met en branle et ce fichier se retrouve dans le mécanisme, puis le scanner voit le fichier et l’introduit presque en même temps dans le mécanisme. Du coup vous vous trouvez avec deux paquets différents pour un même fichier. Il y a ainsi deux-trois astuces pour gérer ce cas de figure ainsi que beaucoup d’autres (je vous épargne le plus tordu sur lequel je suis tombé). Aucune nouvelle table a été introduite pour cette fonctionnalité, uniquement un nouveau champ dans la table des fichiers, pour savoir si le fichier existe dans les chemins du scanner, ou pas.
Grosso modo le « ondemand » se passe ainsi: pause de tous les threads en aval au scanner (donc depuis le DBManager); il attend que tout le monde s’en dort; il cherche dans les queues si le fichier à traiter existe déjà; en fonction de ça il créer un nouveau ou il modifie l’existant; puis il réveille tout le monde. Les paquets « ondemand » ont également une haute priorité et sont donc traités le plus rapidement possible par les différents threads de Valhalla.

events

Concernant les événements, il y a simplement des retours au « front-end » pour lui signaler quelle étapes ont été réalisées, via un callback. Les événements sont possibles uniquement avec les requêtes « ondemand ».

L’architecture

valhalla-internals

Deux threads ont donc fait leur apparition (en rose pâle). L’architecture n’a donc pas été modifiée, mais étendue. Au lieu de n’avoir que le scanner comme intervenant pour l’ajout de fichiers, il y a donc le « ondemand » en parallèle. Les événements sont traités exclusivement par le DBManager.

Comme toujours, ces diagrammes sont un peu simplifiés. Par exemple, les commandes « ondemand » ne sont pas bloquantes. Et donc en réalité il y a une queue devant la flèche entrante du « front-end ». Mais ça n’apportait rien d’intéressant à la lecture de ce diagramme. Il suffit de lire la documentation Doxygen pour connaître ce genre de précision quant a l’utilisation des fonctions publiques.

La base de donnée

valhalla-db

Les modifications sur la base de donnée concernent les tables grabber, dlcontext, file ainsi que la table d’allocation pour les relations (n,n) avec les « grabbers ». Les champs interrupted__ et outofpath__ ont intégrés la table file. Notez bien que les champs terminés par __ sont utilisés uniquement comme données internes pour le bon fonctionnement de Valhalla.

Mais encore …

Comme je le disais au début, on aime faire du travail à double et même à triple. Écrire des « grabbers » dans Enna pour ensuite les porter dans Valhalla. Et le meilleur c’est quand le fournisseur d’un service utilisé par un « grabber » aime se foutre du monde. Amazon par exemple, un jour il décide de dire que toutes les requêtes pour le service doivent être signées HMAC-SHA256 (c’est limite ridicule mais ça n’engage que moi). Ou alors la MPAA/RIAA qui aime emmerder les petits (et dire qu’ils sont payés pour ça) et qui empêche ainsi Lyricwiki de fournir une WebAPI pour les paroles des chansons. Du coup deux « grabbers » cassés, le « grabber » Lyricwiki qui a été fixé d’une autre manière mais qui s’est vu à nouveau être inutilisable (je crois, je ne m’en suis pas occupé).

En ce qui concerne Amazon c’est pas une grosse affaire mais j’ai la flemme. C’est fatiguant de devoir toujours revenir sur ce qui a déjà été fait, encore et encore. Imaginez le jour où il y a une release. Vous aurez deux-trois « grabbers » de morts en à peine quelques semaines.

Finalement avant de fixer des « grabbers » mieux vaut attendre le dernier moment.

Mais encore …, …

Il manque (et oui) des moyens depuis l’API publique pour modifier des méta-données; un exemple: le « play-count ». Vous pouvez imaginez d’autres types de champs dans cette idée. mais qui dit modifier les données dit aussi de considérer deux cas de figure:

  1. Modifier uniquement dans la base de donnée
  2. Modifier également dans le fichier en question (avec FFmpeg)

Selon les méta-données et le type de fichier, seul le cas (1.) est envisageable. Mais pour par exemple un OGG et l’artiste, il peut être bien de pouvoir directement l’écrire dans le fichier. Tout ceci reste encore sujet à réflexion.

A bientôt,

Mathieu SCHROETER

h1

Avancement pour les « grabbers »

2009.08.23

Hello,

Heidrunplusieurs grabbers ont intégrés Valhalla depuis le dernier article, merci à Benjamin pour son travail. Il reste cependant quelques lacunes à combler. Certaines des fonctionnalités manquantes sont mineurs et je ne vais donc pas les aborder ici. Même que je ne compte pas travailler dessus tant que les plus importantes ne seront pas implémentées.

Scan « on-demand »

C’est sûrement une des fonctionnalités la plus importante à faire avant l’intégration dans Enna. Son rôle est de permettre la récupération des méta-données en priorité (sur demande). L’idée est très simple, lorsque vous utilisez Enna et ciblez un fichier en particulier, le rôle du « on-demand » sera de fournir toutes les données le plus rapidement possible pour ce fichier. A moins que ces données soient déjà récupérées et sauvées dans la base de données (ainsi que les fichiers téléchargés tel que les couvertures).

Bien que l’idée soit simple, l’implémentation est bien plus complexe. Valhalla étant un scanner de répertoire, il n’y a actuellement aucun mécanisme pour donner une priorité plus importante à un fichier plutôt qu’un autre. Comme je l’explique dans l’article précédent, les tâches des threads de Valhalla fonctionnent selon le principe du premier arrivé, premier servi. Il faut donc implémenter un moyen de mettre « un paquet » selon un LIFO (Last In, First Out) à la place d’un FIFO. Par exemple un attribut indiquerait que le paquet est prioritaire et les queues seraient utilisées en LIFO.
Les queues dans Valhalla sont héritées de libplayer. Elles se nomment fifo_queue et n’ont pas du tout été prévues pour le LIFO comme le suggère leur nom. Il est peut être temps de les rendre plus polyvalentes. Je n’ai encore rien entrepris en ce sens, je ne fais que des réflexions sur une manière parmi d’autres de gérer le « on-demand ».

Donner plus de priorité dans les queues ce n’est pas l’unique chose à prendre en compte. Le « on-demand » doit aussi pouvoir se faire sur des fichiers qui ne sont pas disponibles avec les chemins scannés par Valhalla. Ce qui induit que ces entrées doivent avoir un statu particulier dans la base de donnée, afin que Valhalla ne les supprime pas lors du prochain scan, croyant que se sont des fichiers effacés car introuvables. Quoi qu’il en soit, avant d’écrire la moindre ligne de code, il est important de faire toutes les considérations nécessaires afin de ne pas introduire des modifications trop intrusives aussi bien au niveau interne de Valhalla qu’au niveau de la structure de la base de donnée.

Le « force-stop »

Depuis la première version de Valhalla, il est possible d’arrêter un scanner (et donc tous les threads en aval) même si le système est en train de travailler sur des fichiers à différents niveaux. Le fait d’avoir ajouté les grabbers à fortement compliqué cette fonctionnalité de « force-stop » qui n’est actuellement pas optimale. J’ai du faire un choix afin de pouvoir commiter le code sur les grabbers même si je n’ai pas terminé cette part du travail.

Mais tout d’abord, pourquoi le « force-stop »? Lorsque vous êtes dans Enna, Valhalla tourne en arrière plan avec une priorité très basse afin de ne pas influencer l’interface et les lectures des fichiers. Si vous quittez Enna, vous voulez le faire immédiatement ce qui semble logique. Par contre pour Valhalla c’est plus compliqué. L’arrêter en plein au milieu de tâches l’oblige à ne pas pouvoir vider les queues des threads. Toutes les données dans ces queues sont donc libérées et perdues. Avant l’ajout des grabbers ce n’était pas du tout un problème, car le système se limitait à une seule étape; le parser. Un thread n’est pas tué avec le « force-stop », mais sa dernière tâche est terminée normalement. La différence est que les tâches suivantes sont ignorées et le thread se termine comme s’il n’y avait rien de plus à faire. Ainsi avant l’ajout des grabbers, au pire les fichiers restant dans les queues n’étaient pas insérés/actualisés dans la base de donnée. Les fichiers qui étaient déjà insérés l’étaient au complet. Ceux qui ne l’étaient pas, ne l’étaient pas du tout. C’était du tout ou rien. Le prochain scan (démarrage d’Enna) permettait de continuer le travail très simplement. Les queues se repeuplaient par le scanner qui ne trouvait pas les fichiers dans la base de données, et ignorait ceux qui existaient déjà (c’est toujours le cas « plus-ou-moins » si vous compilez Valhalla sans les grabbers).

Depuis que les grabbers sont présents, la complexité du « force-stop » a augmenté pour deux raisons. Un fichier peut exister dans plusieurs queues en même temps, et un fichier doit passer par plusieurs étapes avant d’être terminé. Les données d’un fichier dans la base de donnée ne peuvent plus être considérées comme du tout ou rien. Un fichier se retrouve complètement fragmenté quand les threads sont tous terminés en plein au milieu des tâches.

Fonctionnement actuel

J’ai donc fais un choix intermédiaire pour que Valhalla fonctionne aussi en « force-stop » bien que le problème de fragmentation ne soit pas réglé. J’ai décidé de traiter un fichier comme c’est le cas sans le support des grabbers mais à une différence près. Un fichier est considéré comme interrompu tant qu’il n’a pas atteint l’étape ENDING (voir l’article précédent). Donc au prochain scan (redémarrage de Valhalla), ce fichier repassera absolument par toutes les étapes. Le problème est donc que les données déjà grabbées (et sauvées dans la DB) sont re-grabbées ce qui prend beaucoup de temps inutile. Avec des milliers de fichiers et beaucoup de grabbers, il se trouve que Valhalla passe son temps à refaire encore et encore le même travail si on ne le laisse pas se terminer de lui même. Car pour qu’un fichier puisse atteindre l’étape ENDING quand il y a 5-6 grabbers, il faut se lever tôt ;-).
Ce système est provisoire! Il est là en attendant que j’y travail. Et c’est une des raisons qui fait que Valhalla ne doit pas encore être utilisé avec les grabbers dans Enna.

Pourquoi de la fragmentation? Alors ça n’a rien à voir avec votre disque dur, quand je parle de fragmentation c’est dans le sens où toutes les informations qui devraient l’être ne sont pas disponibles dans la base de donnée. Par exemple un fichier est passé par l’étape PARSING et GRABBING (le premier grabber). Les méta-données du parser ont été sauvées dans la DB, ainsi que celle du premier grabber par exemple. Néanmoins il y a peut être encore 4 grabbers avant le downloader et l’ENDING. Le « force-stop » arrête tout, vide les queues, quitte Valhalla. Il se trouve que le fichier en question a une partie des méta-données dans la base de donnée, mais on a aucune certitude qu’elle soient toutes disponibles car ce fichier est indiqué comme étant interrompu.

Fonctionnement futur

Je suis en train de réfléchir à ce problème (sur papier pour le moment). Ce qu’il faut arriver à faire, c’est de sauver le contexte des fichiers interrompus pour pouvoir restaurer leur contexte à la prochaine exécution de Valhalla. Il n’y a donc pas 50 manières de faire. La sauvegarde du contexte doit être réalisée dans la base de donnée. Un mécanisme à l’exécution de Valhalla devra chercher les contextes, les restaurer puis les effacer de la base de données.

Un contexte devra contenir les informations sur quels grabbers ont été traités en entier (ceux où les données sont vraiment sauvées) ainsi que la liste des fichiers qui doivent être téléchargés. Il faut aussi considérer les cas où des contextes ont été sauvés mais que certains grabbers ne sont plus disponibles avec Valhalla, ou même qu’un Valhalla sans le support des grabbers essaient d’utiliser la base de données qui contient ces contextes.

Concernant le downloader c’est un peu différent par rapport aux autres threads. Comme je l’ai dis précédemment, un thread termine sa tâche en cours puis quitte. Pour le downloader une tâche peut être interrompue en plein au mieux. Cela veut simplement dire que pour un fichier en particulier, il y a par exemple un « cover », un « backdrop » et peut être encore autre chose à télécharger. Il se peut que le « cover » soit téléchargé, que le « force-stop » se manifeste et donc le « backdrop » est ignoré. La tâche est à moitié réalisée. Il est donc nécessaire de sauvegarder l’état de cette tâche avant de quitter le thread du downloader.

Finalement ça représente des ajouts de tables dans la base de données pour sauvegarder les contextes, des mécanismes pour gérer les contextes (sauvegarde, restauration et effacement), puis ensuite on pourra réfléchir sérieusement à l’intégration dans Enna.

Les évènements

Le dernier point relativement important est la gestion des évènements pour un scan « on-demand ». Les données étant insérées au fur et à mesure dans la base de donnée, il est intéressant de recevoir une information sur ce qui est disponible afin de savoir quand aller les lire. Simplement, lorsque vous êtes sur un fichier en particulier avec Enna, un « backdrop » va se télécharger à un moment indéterminé. Un évènement sera envoyé par Valhalla à Enna quand le fichier sera sauvé et le « backdrop » s’affichera.

C’est avant tout une question de confort que d’avoir cette fonctionnalité. Dans le cas contraire il serait nécessaire d’aller lire les données que lors de l’accès au fichier, et les images par exemple ne s’afficheraient qu’en changeant de fichier, puis en revenant sur le précédent (pour autant que quelque chose s’est téléchargé entre temps).

Il y a d’autres points mais moins prioritaires. Je compte travailler spécialement sur les trois problèmes présentés dans cet article. J’ai déjà fais un peu de travail sur le « force-stop ». Après je pense régler le « on-demand » puis les évènements.

A bientôt,

Mathieu SCHROETER

h1

Le support des « grabbers » dans la Valhalla

2009.08.11

Les feuilles d’Yggdrasil ne sont plus si inaccessibles pour les braves de la Valhalla. Les « grabbers » auraient pu s’appeler Heidrun mais on se contentera d’un nom plus technique.

Les « grabbers » sont donc enfin supportés dans libvalhalla. Il y a encore du travail avant de pouvoir les utiliser correctement depuis Enna, mais l’essentiel est là. Pour ceux qui n’ont aucune idée de ce qui se cache derrière ce mot barbare « grabber », on pourrait le définir simplement comme étant une manière de s’accaparer (si on traduit directement du mot anglais) ou alors de récupérer des données depuis des moyens extérieurs.
Par exemple, libvalhalla peut récupérer des méta-données provenant directement des fichiers scannés, à l’aide d’FFmpeg (le parser). Mais si on désire retrouver la couverture CD/DVD il n’était pas possible de le faire jusqu’ici. C’est là qu’intervient le « grabber » tel que celui pour Amazon. On peut imaginer plein d’autres types de « grabbers », mais celui d’Amazon est le seul porté sous Valhalla au moment où j’écris ce billet et il permet de « rapatrier » les couvertures CD/DVD.

Architecture

L’architecture de la bibliothèque à été profondément changée. Trois nouveaux threads ont fait leur apparition et sont visibles dans l’image ci-dessous en tant que « Dispatcher », « Grabber » et « Downloader ».

Valhalla Internals

L’image correspond au code, néanmoins elle est un petit peu simplifiée afin d’être suffisamment lisible et compréhensible. Le Dispatcher peut être vu comme un switch réseau. Son but et d’être très réactif (il ne fait donc pas grand chose) et de transmettre les « paquets » au bon endroit en fonction de l’état de celui-ci. Un « paquet » est une analogie au réseau, mais finalement dans le cas de libvalhalla, ce n’est rien de plus qu’une structure qui défini un fichier.

Afin de bien comprendre l’architecture, je vais détailler le chemin effectué par un paquet. Lorsque le scanner trouve un fichier sur le disque, il « l’empacte » et le transmet au DBManager. Le DBManager va interroger la base de donnée pour savoir si ce fichier existe déjà, si oui il vérifie la date de dernière modification et ignore ce paquet si cette date n’a pas changée, autrement (ou si le fichier n’existe pas dans la base), il transmet le paquet au Dispatcher. Un paquet doit suivre 4 étapes avant d’être détruit.

  • Étape 0: PARSING
    Récupération des méta-données à l’aide d’FFmpeg
  • Étape 1: GRABBING
    Récupération de nouvelles données et (ou) fichiers texts (XML) à l’aide d’un ou de plusieurs grabbers
  • Étape 2: DOWNLOADING
    Téléchargement des fichiers indiqués par le(s) grabber(s) (couvertures CD par exemple)
  • Étape 3: ENDING
    Finalisation et destruction du paquet

Le processus pour un paquet se résume donc en:

Scanner -> DBManager -> Dispatcher -> Parser -> Dispatcher -> Grabber -> Dispatcher -> Downloader -> Dispatcher -> DBManager -> Scanner.

Ce qui est faux en réalité, mais l’idée est correcte. L’intérêt de présenter le processus aussi simplement est uniquement là pour permettre de comprendre par la suite comment valhalla fonctionne. Si un paquet suivrait vraiment le processus ci-dessus, le système serait extrêmement lent (j’exagère peut être sur le mot « extrême ») mais il faut garder en tête que travailler avec des grabbers ça ne peut que ralentir, surtout s’il y a beaucoup de grabbers et s’ils font des accès sur internet.

Comme un pipeline

Si le Dispatcher, le Parser, le Grabber et le Downloader sont sur des threads différents ce n’est pas juste pour pouvoir traiter plusieurs paquets en parallèles, mais aussi pour pouvoir traiter un même paquet à plusieurs endroits en même temps. L’idée est donc de récupérer les données « parsées » et « grabbées » aussi vite que possible dans la base de donnée. Ainsi même si un paquet n’a pas finit de suivre toutes les étapes du processus, il est qu’en même possible d’aller chercher les informations dans la base de données.

Pour ceux qui connaissent un peu les architectures des processeurs, ils connaissent également la représentation en pipeline du cycle d’exécution des instructions. J’ai vais donc expliquer le système par un dessin selon ce principe.

Valhalla Pipeline

Ce pipeline présente 4 fichiers traités par deux parsers parallélisés. Il y a chaque fois deux grabbers en série pour chaque fichier, avec un downloader à la fin du processus.

Les tâches des threads de Valhalla fonctionnent en tant que FIFO (à ne pas confondre avec l’ordonnanceur du noyau). Le premier arrivé est donc le premier servi. Chaque fichier de ce pipeline peut être séparé en deux lignes. C’est ce qui arrive lorsqu’un grabber est en marche et que des méta-données doivent être sauvées dans la base de donnée. C’est cela qui permet d’avoir un temps de réaction intéressant. Il est inutile d’attendre que le processus soit terminé pour avoir les méta-données. Par exemple avec le « FILE 0″, le Scanner (jaune) transmet le paquet au DBManager (rose pâle), qui va le transmettre au Dispatcher (gris) afin d’être « parsé » (rose). Puis le paquet revient au Dispatcher et se voit transféré dans deux threads, le grabber (vert) et le DBManager. A ce moment là il se passe deux choses, le DBManager va insérer les méta-données du Parser dans la base de donnée et en même temps le grabber va commencer à traiter les nouvelles méta-données. Puis au « grabbing » suivant, c’est les méta-données du grabber précédent qui sont sauvées quand le deuxième grabber s’apprête à traiter les nouvelles méta-données. Au final c’est lors de l’étape du downloading que les dernières méta-données sont sauvées.
En résumé, chaque case DBManager (de demi-hauteur) correspond à une insertion dans la base de donnée. Dans le cadre de cet exemple, il y a donc trois insertions par fichier (le parser + les deux grabbers).

J’ai omis quelques informations sinon cet article serait 5 fois plus long.

Il faut également prendre le diagramme avec des pincettes car il est impossible de prédire la forme exacte pour plusieurs raisons. Les cases « parser », « grabber » et « downloader » en particulier sont très disproportionnées. Leur temps est une question de plusieurs dizaines de millisecondes à plusieurs secondes. Tout dépend de la taille des fichiers, du demuxer utilisé par FFmpeg, de la vitesse de votre connexion internet, du temps sur les accès aux disque dur, etc, … Le dispatcher se contente de quelques microsecondes, et dans le diagramme il prend autant de temps que le DBManager ce qui est absurde.
Au premier abord on pourrait penser qu’il y a beaucoup de trous dans ce pipeline, mais en réalité les trous sont bien plus grand que ça si vous considérez qu’un parser prend 3 secondes pour un fichier. Néanmoins ce n’est pas du tout un problème (d’ailleurs s’il n’y avait pas de trous alors tous vos CPU seraient constamment à 100%; il ne faut pas oublier non plus que dans certaines étapes il y a des temps morts tel que les accès au disque dur et sur internet).

Si vous regardez bien le diagramme, vous voyez des trous importants après les étapes « parser ». En fait, le scan du disque, le parsing et l’insertion des méta-données des parsers vont se faire très vite. A la même vitesse qu’avant l’ajout des grabbers dans Valhalla. Les grabbers ont aucun impacte sur les parsers car ils sont exécutés après eux. Ce n’est pas plus compliqué que ça.

Il faut également interpréter ce diagramme d’un point de vue plus large. Imaginez le avec plus de 100 lignes (ou plus), ce qui peut arriver sans problème lorsqu’un scanner passe sur un de vos dossiers de musique. Cela donnerait visuellement tous les parsers qui descendraient à gauche en un escalier serré, et les grabbers seraient parsemés (avec des trous importants dans toutes les lignes). Néanmoins il peut y avoir des grabbers non utilisés par certains fichiers, voir même aucun grabber, ce qui complique fortement le diagramme.

Quelques précisions

Il y a de la documentation avec les en-têtes de libvalhalla afin de savoir comment implémenter un grabber, mais je donerais des informations sur ce blog un de ces 4.

L’application test-valhalla permet de tester les grabbers. Il suffit de lire l’aide de la commande.

Valhalla peut être compilé sans le support des grabbers, ainsi les fichiers passent de l’étape 0 PARSING à l’étape 3 ENDING. La bibliothèque réagira exactement comme avant l’ajout des grabbers.

Il manque également  des éléments pour une utilisation dans Enna, le fichier TODO vous en dira plus.

Voilà, je n’ai pas la motivation d’en dire plus aujourd’hui, rien que de dessiner le pipeline ça m’a pris pas mal de temps. Je reviendrais donc sur certains aspects dans un prochain billet.

A bientôt,

Mathieu SCHROETER

h1

La liste de lecture de libplayer

2009.08.02

Hello,

cet été j’ai bossé un peu sur libvalhalla pour ajouter le support des grabbers. Le travail n’est pas terminé et rien n’a encore été commité car les modifications sont très intrusives et forment un tout. Je n’ai encore rien à présenter de concret mais j’espère en terminer avec ça les deux premières semaines d’août. Soit mes deux dernière semaines de congé.

La liste de lecture

libplayerMais ce n’est pas de Valhalla que je désire parler. Mais plutôt de libplayer et de quelques fonctionnalités qui existent depuis bien longtemps et qui ne sont pas forcément connues et qui ne seront même jamais utilisées par Enna. Avant l’entrée en jeu d’Enna dans le monde de GeeXboX il y a eu quelques tentatives de nouveaux GUI tel que MPUI, OMCv1, OMC-SDL puis OMCv2. Aucun de ces projets n’a abouti mais libplayer a été créé en parallèle à OMCv1 (je précise v1 et v2 car il y a eu deux tentatives différentes de créer un GUI basé sur les EFL). Pendant une période, il avait été proposé de laisser la gestion des listes de lecture à libplayer. Au début cette gestion était très rudimentaire et ne permettait que d’ajouter des fichiers dans la liste. J’ai donc pris l’initiative d’étendre la gestion de ces listes, et spécialement avec l’ajout du superviseur (cf. libplayer-et-le-multi-threading) qui date d’une année.

Libplayer contient une playlist interne. Elle n’est pas accessible depuis l’API publique et uniquement une playlist peut exister dans un player à la fois. Cette liste se présente en une structure chaînée dans laquelle il est possible de progresser en avant ou en arrière. Cela implique que l’ordre d’insertion des fichiers défini l’ordre de lecture. Ainsi il existe deux manières de lire la liste. Pour cela il m’est nécessaire de présenter quelques fonctions de l’API publique, ainsi qu’une fonction interne un peu différente.

Chaque entrée dans la playlist est un morceau de la structure chaînée, visible en tant que type opaque nommé mrl_t. Il y a quelques fonctions de base pour travailler avec ces type, tel que mrl_new(), mrl_free() et différents accesseurs. Mais il y a également des fonctions pour assigner un mrl_t à la playlist. Tel que player_mrl_set() et player_mrl_append() qui permettent d’insérer un mrl à l’emplacement courant, respectivement à la suite du mrl courant. Ainsi une série de player_mrl_append() vont permettre de remplir la liste de lecture. L’ordre d’insertion étant directement lié à l’ordre de lecture. Il existe également des fonctions pour supprimer des mrls tel que player_mrl_remove() et player_mrl_remove_all().
Tout ceci n’est qu’une présentation rapide de quelques fonctions de base. Certaines d’entre elles sont utilisées par Enna car il faut bien au moins assigner un mrl_t à libplayer pour qu’un fichier (url, dvd, …) puisse être lu. Mais il existe d’autres fonctionnalités pour effectuer des lectures automatisées de la liste de lecture. Ces fonctions n’ont aucun intérêt pour Enna qui gère déjà ses propres listes de lecture.

Les fonctions de playback

Il y a trois fonctions de playback intéressantes pour la liste de lecture de libplayer.

  • player_set_playback()
    Deux modes peuvent être défini, soit PLAYER_PB_SINGLE ou PLAYER_PB_AUTO. Dans le cas de « single », seul le mrl en cours sera lu lors d’un appel à player_playback_start(). Le mode « auto » va permettre à libplayer de lire toute la liste de lecture sans une intervention de l’extérieur. A chaque morceau qui se termine, libplayer va passer au suivant jusqu’à atteindre la fin de la liste.

Le fait de lire la liste depuis la position courante jusqu’à la fin n’est pas toujours très intéressant, il existe donc deux fonctions qui sont très souvent présentes dans ce type de logiciel.

  • player_set_loop()
    Pour autant que le mode est en « auto », il est possible de dire à libplayer de répéter le même morceau un certain nombre de fois avec PLAYER_LOOP_ELEMENT, ou alors de répéter la liste de lecture avec PLAYER_LOOP_PLAYLIST. Un paramètre de cette fonction permet de dire combien de fois et si ce paramètre est négatif, le bouclage est infini.
  • player_set_shuffle()
    Ici aussi, le mode de playback doit être « auto ». Ainsi la liste de lecture sera mélangée et le morceau lu suivant sera différent de l’ordre d’insertion des morceaux dans la liste de lecture. Chaque activation et désactivation du « shuffle » va réinitialiser la liste de lecture et donc mélanger l’ordre.

Bien sûr il est possible de combiner les fonctionnalités. Si le « loop » est indiqué sur PLAYER_LOOP_PLAYLIST et que le « shuffle » est activé, chaque fois que libplayer arrive à la fin de la liste de lecture, il la mélangera à nouveau et recommencera autant de fois qu’il a été spécifié avec player_set_loop().
Vous vous dites qu’il n’y a rien d’exceptionnel la dedans, vous avez tout à fait raison. Mon but est uniquement de présenter quelques fonctionnalités exploitables partiellement que par un seul outil « test-player » et forcément ignorées d’Enna. Lorsque test-player est lancé, il suffit d’utiliser [#] pour changer de mode, [.] pour changer le bouclage (il y a quelques valeurs prédéfinies) et [,] pour mélanger la liste.

L’automatisation de la lecture

J’ai parlé au début d’une fonction un peu différente. Cette fonction permet de rendre possible la lecture bouclée et mélangée. Mais avant ça un petit peu de théorie sur libplayer. Bien qu’il n’y a à priori rien d’exceptionnel d’automatiser la lecture dans libplayer, il faut qu’en même se poser une question. Le fait d’automatiser la lecture présage l’existence d’une boucle. Ainsi lorsqu’un fichier est lu en entier (donc un événement du type EOF est généré) il faut forcer le passage au morceau suivant d’une manière ou d’une autre.

Imaginons que le playback a été défini en « auto » et que la lecture a été amorcée via player_playback_start(). Cette fonction de start ne doit en aucun cas être bloquée jusqu’à la fin de la lecture. La chaîne d’exécution peut se visualiser ainsi:

démarrage de la lecture -> sélection du morceau -> lecture -> événement de fin de lecture -> sélection du morceau suivant -> lecture -> etc, …

Il y a une rétroaction et ça complique grandement les choses. Le premier démarrage de la lecture est amorcé à l’extérieur via l’utilisateur et cette fonction n’est pas bloquée jusqu’à l’EOF (en réalité elle bloque jusqu’à ce que la lecture soit réellement lancée). Donc le programme qui utilise libplayer peut faire autre chose en même tant que le média se lit. Il est donc nécessaire d’intercepter la fin de la lecture à l’intérieur de libplayer pour que se sois la bibliothèque qui passe au morceau suivant. Le problème n’est peut être pas tout de suite évident, alors détaillons la chaîne.

Lorsque la lecture se termine un événement est créé, il est ensuite envoyé dans un callback interne (pour que libplayer sache que la lecture est terminée) et se callback envoi l’événement à un callback externe (pour que l’utilisateur de la lib soit aussi informé). Si depuis ce callback interne, un nouveau player_playback_start() serait amorcé, le callback serait bloqué tant que la lecture n’a pas réellement commencé. Et dès que celle-ci démarre vraiment, la fonction est relâchée. De ce fait, le callback interne resterait bloqué par le player_playback_start() pendant tout ce temps (si c’est un flux réseau, ça peut prendre quelques secondes). Les événements qui arriveraient entre le EOF et la fin du nouveau start s’empileraient et ne seraient pas envoyés. Ainsi des événements périmés arriveraient à l’utilisateur après le start et ce n’est pas acceptable.

En réalité c’est encore un peu plus compliqué que ça, mais il est inutile d’aller dans les détails.

Le superviseur et le gestionnaire d’événement de libplayer permettent donc de gérer se problème. Lorsqu’un événement est envoyé, il est empilé dans un FIFO. Les événements sont prioritaires et le superviseur est désactivé lorsque l’un d’eux survient. Ainsi il ne peut jamais y avoir d’événements périmés. La deuxième chose concerne le superviseur en lui même. Il met à disposition deux moyens pour exécuter une commande tel que le player_playback_start() par exemple. Le premier est le mode WAIT, et le second NO_WAIT. La différence est que le premier est utilisé pour toutes les fonctions publiques. Cela veut dire que tant que la fonction n’est pas terminée (par exemple tant que le start n’a pas réellement commencé), l’utilisateur est bloqué. Le second mode permet de lancer une fonction mais sans aucune attente. Par exemple le start serait empilé dans le FIFO du superviseur mais n’attendrait pas. Ce mode n’est pas autorisé depuis l’API publique pour plusieurs raisons, dont une très importante. Lorsque l’utilisateur utilise la bibliothèque il doit être sûr que la commande soit effectivement exécutée afin d’avoir un comportement « normal » donc comme s’il n’y avait pas de parallélisme. Quand vous faites par exemple un printf(), vous voulez que celui-ci s’exécute à ce moment là et pas 2 secondes plus tard. Sinon dans le cas de libplayer, vous feriez un start, puis autre chose, un stop, get_property, etc,..  Sans savoir quand est-ce que les fonctions seront réellement exécutées. Dit autrement, les fonctions de l’API publique sont toutes synchrones. L’autre mode est asynchrone et demande quelques considérations. Pour les fonctions qui retournent des valeurs par exemple, si l’API publique était asynchrone, il faudrait pouvoir indiquer un callback pour chaque fonction afin d’avoir un moyen de récupérer les valeurs. Cette fonctionnalité n’est pas disponible actuellement.

Mais revenons en à l’automatisation de la lecture. Pour que libplayer puisse passer au morceau suivant sans bloquer le callback du gestionnaire d’événement il doit travailler de manière asynchrone. C’est donc (à l’heure où j’écris ces lignes) le seul endroit où une fonction est utilisée en NO_WAIT (en vérité ce n’est pas tout à fait vrai, mais je vous épargne les autres cas particuliers). Cette fonction se nomme:

  • player_mrl_next_play()
    Les fonctions player_mrl_next() et player_mrl_previous() à disposition dans l’API publique permettent de progresser dans la liste de lecture par rapport à l’ordre d’insertion des mrls. La fonction next_play est bien différente. Elle va tenir compte du « loop » et du « shuffle » et est donc un peu plus sophistiquée.

Je n’ai pas rendu accessible cette fonction depuis l’API publique pour aucune bonne raison. Elle peut très bien être utilisée de manière synchrone. Elle est utilisée en asynchrone dans libplayer pour libérer le callback le plus vite possible et donc pour respecter la bonne marche des événements. Le fait que les événements sont prioritaires au superviseur indique aussi que la fonction ne pourra jamais se terminer tant que le gestionnaire d’événement ne redonne pas la main au superviseur. Une fonction forcée en WAIT provoquerait irrémédiablement un deadlock. Autrement dit ce serait le serpent qui se mort la queue (ou qui se mort le FIFO).

Conclusion

Le but de cet article est de montrer une facette peu connue de libplayer. Et que cette bibliothèque est bien plus qu’une simple couche d’abstraction sur MPlayer, xine, VLC et GStreamer. Les rôles du superviseur et du gestionnaire d’événement ne se limitent pas qu’aux cas d’utilisations exprimés précédemment. Il y a de nombreuses autres raisons mais ce n’est pas l’objectif de cet article que d’en parler en détail ici.

A bientôt,

Mathieu SCHROETER