Skywalker13

Diary of an ex-GeeXboX developer…

Quoi de neuf sur les libs ~ 🇫🇷

Posted at — Jan 3, 2010

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()][4] 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 libplayer-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:

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