h1

Valhalla: les nouvelles fonctions de sélection

2009.05.31

Hello,

depuis peu, les modifications évoquées dans le billet précédent ont été réalisées. L’API a été modifiée pour toutes les fonctions qui permettent d’effectuer des sélections sur la base de donnée. Ce blog étant aussi en quelque sorte un carnet de laboratoire, je vais donc y expliquer le fonctionnement de ces fonctions ainsi que la manière de les utiliser. A terme, ces informations figureront directement dans la documentation (en anglais) de libvalhalla. Il est judicieux de consulter la documentation Doxygen déjà présente dans l’en-tête public (valhalla.h) qui peut être généré via un paramètre du script “configure”.

Cet article est long et très spécifique à libvalhalla du côté développeur uniquement. Si vous êtes un simple utilisateur d’Enna, vous ne verrez pas vraiment de différence par rapport à avant car les modifications sont majoritairement internes.

Il y a trois fonctions publiques et chacune est complémentaire aux autres.


int valhalla_db_metalist_get (
  valhalla_t *handle,
  valhalla_db_item_t *search,
  valhalla_db_restrict_t *restriction,
  int (*result_cb) (void *data,
                    valhalla_db_metares_t *res),
  void *data);

int valhalla_db_filelist_get (
  valhalla_t *handle,
  valhalla_file_type_t filetype,
  valhalla_db_restrict_t *restriction,
  int (*result_cb) (void *data,
                    valhalla_db_fileres_t *res),
  void *data);

int valhalla_db_file_get (
  valhalla_t *handle,
  int64_t id, const char *path,
  valhalla_db_restrict_t *restriction,
  valhalla_db_filemeta_t **res);

bird

valhalla_db_metalist_get

Utilisation simple

Elle permet de récupérer une liste de metadata. Cela veut dire, par exemple une liste d’albums, d’auteurs, d’artistes, de genres; peu importe le nom, vous pouvez récupérer n’importe quelle liste. Le résultat se présente toujours sous la forme du nom du champ (comme “author”) avec une valeur et un groupe. Sous la forme d’un tableau, cela donnerait quelque chose comme:

meta id meta name data id data value group
2 author 1665 Dido ENTITIES
2 author 456 Ennio Morricone ENTITIES
2 author 1257 John Williams ENTITIES
2 author 1224 Linkin Park ENTITIES

Dans cet exemple, uniquement les auteurs sont présents, mais il est également possible de demander une liste de metadata selon un groupe et non selon un nom de meta ou un id. Vous pouvez typiquement demander la liste de toutes les metadata qui appartiennent au group ENTITIES. De ce fait, vous trouverez la liste de tous les auteurs, artistes, compositeurs, studios, etc,…

Sans rentrer encore dans les détails du code, une telle liste se récupère de cette manière:


valhalla_db_item_t s = VALHALLA_DB_SEARCH_TEXT ("author", ENTITIES);
valhalla_db_metalist_get (handle, &s, NULL, callback, NULL);

Et sans expliquer en détail la fonction, l’argument “handle” correspond au pointeur sur votre instance de valhalla, “s”  (pour search) défini ce que vous cherchez, le troisième argument permet de spécifier des restrictions (qui sont expliquées dans le chapitre suivant), le callback est la fonction dans laquelle chaque ligne du résultat est envoyée et le dernier argument est un pointeur void qui peut être récupéré dans le callback.

Les macros  VALHALLA_DB_SEARCH_XXX sont présentes pour simplifier l’utilisation de libvalhalla. Elles ne sont pas indispensables. En réalité, elle ne font qu’attribuer les valeurs au bon endroit dans la structure valhalla_db_item_t. Actuellement il existe trois macros différentes:

VALHALLA_DB_SEARCH_ID(meta_id, group)
VALHALLA_DB_SEARCH_TEXT(meta_text, group)
VALHALLA_DB_SEARCH_GRP(group)

Vous pouvez donc chercher une liste de meta en fonction de l’id, du texte, ou alors vous pouvez récupérer plusieurs noms de metadata différents dans le cas d’une recherche selon un groupe.

Utilisation étendue

Récupérer une liste comme expliqué dans le chapitre précédent est la fonctionnalité la plus simple offerte par cette fonction. Il va de soit qu’il faut pouvoir donner quelques restrictions sur la liste retournée. Il est courant de ne pas vouloir une liste complète. Un exemple parlant concerne la metadata “album”. Il est intéressant de pouvoir récupérer tous les albums d’un auteur en particulier. Et c’est ici qu’interviennent les restrictions. Elles permettent d’inclure ou d’exclure certains résultats. D’un point de vue SQL, se sont des sous-requêtes qui limitent les résultats à leur ensemble, ou alors à tous les résultats différents de leur ensemble.

Voici un premier exemple. Vous voulez tous les albums de Dido:


valhalla_db_item_t s = VALHALLA_DB_SEARCH_TEXT ("album", TITLES);
valhalla_db_restrict_t r1 = VALHALLA_DB_RESTRICT_STR (IN, "author", "Dido");
valhalla_db_metalist_get (handle, &s, &r1, callback, NULL);

Le résultat sera un tableau de la même forme que vue précédemment, mais qui contiendra uniquement les albums de Dido.

meta id meta name data id data value group
3 album 1666 Life For Rent TITLES
3 album 1669 No Angel TITLES
3 album 1725 Safe Trip Home TITLES

Cette requête est déjà bien plus sophistiquée, car vous pouvez imaginer de donner ce genre de restriction pour n’importe quel metadata. Un autre exemple, vous pourriez lister tous les albums d’une certaine année. Il n’y a aucune limite sur les choix des restrictions. Mais ceci n’est pas suffisant, et il y a donc des possibilités un peu plus complexes afin d’avoir des résultats plus spécifiques.

Tout d’abord, dans l’exemple précédent, la macro VALHALLA_DB_RESTRICT est utilisée avec un premier argument “IN”. Il est possible d’inverser la logique en y inscrivant “NOTIN” à la place. Le résultat deviendrait alors la liste de tous les albums qui existent dans la base de donnée, sauf ceux de Dido.

Utilisation avancée

L’utilisation étendue permet déjà d’effectuer de nombreuse recherches intéressantes. Néanmoins pour les cas les plus précis, elle ne suffit pas. Il est nécessaire parfois de cumuler les restrictions pour affiner le résultats, ou tout simplement, pour avoir des résultats un peu plus exotiques. Ainsi les restrictions sont des listes chaînées, et il est possible de les lier très facilement. Commençons par un exemple, nous désirons la liste de tous les albums de Dido qui ne sont pas de 2003.


valhalla_db_item_t s = VALHALLA_DB_SEARCH_TEXT ("album", TITLES);
valhalla_db_restrict_t r1 = VALHALLA_DB_RESTRICT_STR (IN, "author", "Dido");
valhalla_db_restrict_t r2 = VALHALLA_DB_RESTRICT_STR (NOTIN, "year", "2003");
VALHALLA_DB_RESTRICT_LINK (r2, r1);
valhalla_db_metalist_get (handle, &s, &r1, callback, NULL);

Une nouvelle macro est donc introduite. Elle permet de lier très simplement les restrictions ensemble. La macro doit être comprise de cette manière. “link from r2 to r1″, soit lier de r2 à r1. Ainsi c’est r1 qui doit être spécifié dans la fonction valhalla_db_metalist_get(). Il n’y a aucune limite sur le nombre de liens possibles (mise à part qu’une requête ne doit pas dépasser 4096 caractères), néanmoins il ne faut pas tenter de lier toutes les restrictions sur la même car seul le dernier lien sera considéré. Avec trois restrictions, la bonne manière de procéder est donc:


valhalla_db_item_t s = VALHALLA_DB_SEARCH_TEXT ("album", TITLES);
valhalla_db_restrict_t r1 = VALHALLA_DB_RESTRICT_STR (IN, "author", "Linkin Park");
valhalla_db_restrict_t r2 = VALHALLA_DB_RESTRICT_STR (NOTIN, "year", "2003");
valhalla_db_restrict_t r3 = VALHALLA_DB_RESTRICT_STR (NOTIN, "genre", "Alternative");
VALHALLA_DB_RESTRICT_LINK (r3, r2);
VALHALLA_DB_RESTRICT_LINK (r2, r1);
valhalla_db_metalist_get (handle, &s, &r1, callback, NULL);

Ici, la liste concernera donc tous les albums de Linkin Park, qui ne sont pas de 2003 et qui ne sont pas du rock/metal alternatif. Okay, la liste ne risque pas d’être bien longue avec cet exemple :-P, mais ce qui compte c’est de montrer le principe. Les restrictions sont liées de la manière suivante: de r3 à r2 et de r2 à r1. En code ça donnerait donc:


r2.next = &r3;
r1.next = &r2;

Le code ci-dessus peut très bien remplacer la macro si vous préférez.

Il y a encore une macro que je n’ai pas vraiment présenté. Pour l’argument de recherche de la fonction, il est possible d’indiquer uniquement un groupe (comme je l’ai mentionné plus haut dans ce billet). Si le groupe est mit à NIL, il y aura toutes les metadata qui seront listées, autrement il n’y aura que les metadata liées au groupe.

Un dernier exemple qui retourne absolument toutes les metadata liées à Dido pour son album Life For Rent.


valhalla_db_item_t s = VALHALLA_DB_SEARCH_GRP (NIL);
valhalla_db_restrict_t r1 = VALHALLA_DB_RESTRICT_STR (IN, "author", "Dido");
valhalla_db_restrict_t r2 = VALHALLA_DB_RESTRICT_STR (IN, "album", "Life For Rent");
VALHALLA_DB_RESTRICT_LINK (r2, r1);
valhalla_db_metalist_get (handle, &s, &r1, callback, NULL);
meta id meta name data id data value group
6 track 19 1 ORGANIZATIONAL
6 track 225 10 ORGANIZATIONAL
6 track 229 11 ORGANIZATIONAL
6 track 242 12 ORGANIZATIONAL
6 track 92 2 ORGANIZATIONAL
5 year 50 2003 TEMPORAL
6 track 202 3 ORGANIZATIONAL
6 track 101 4 ORGANIZATIONAL
6 track 223 5 ORGANIZATIONAL
6 track 96 6 ORGANIZATIONAL
6 track 231 7 ORGANIZATIONAL
6 track 196 8 ORGANIZATIONAL
6 track 187 9 ORGANIZATIONAL
1 title 1673 BonusTrack TITLES
2 author 1665 Dido ENTITIES
1 title 1668 Do You Have A Little Time TITLES
1 title 1672 Dont Leave Home TITLES
1 title 1666 Life For Rent TITLES
3 album 1666 Life For Rent TITLES
1 title 1678 Marys In India TITLES
4 genre 104 Pop CLASSIFICATION
1 title 1670 Sand In My Shoes TITLES
1 title 1669 See The Sun TITLES
1 title 1674 See You When Youre 40 TITLES
1 title 1675 Stoned TITLES
1 title 1671 This Land Is Mine TITLES
1 title 1664 White Flag TITLES
1 title 1667 Who Makes You Feel TITLES

Et d’un point de vue SQL

La requête SQL qui effectue les sélections est créée en fonction des arguments de la fonction. Elle se construit comme des lego qui s’imbriquent les uns dans les autres. D’une manière général, sa forme est:


SELECT meta.meta_id, data.data_id, meta.meta_name, data.data_value
FROM (
 data INNER JOIN assoc_file_metadata AS assoc
 ON data.data_id = assoc.data_id
) INNER JOIN meta
ON assoc.meta_id = meta.meta_id
% Les conditions sont optionnelles
WHERE
  % Une sous-requête apparait par le biais des restrictions.
  % Il y a autant de sous-requêtes que de restrictions du type IN, NOT IN.
  assoc.file_id <IN|NOT IN> (
    SELECT assoc.file_id
    FROM (
      data INNER JOIN assoc_file_metadata AS assoc
      ON data.data_id = assoc.data_id
    ) INNER JOIN meta
    ON assoc.meta_id = meta.meta_id
    % A choix, il peut y avoir un ID ou du texte.
    WHERE meta.<meta_id|meta_name> = <ID|"TEXT"> AND data.<data_id|data_value> = <ID|"TEXT">
  )
  % L'argument search indique quel meta et/ou quel groupe doit être listé
  AND meta.<meta_id|meta_name> = <ID|"TEXT">
  AND assoc._grp_id = <ID>
GROUP BY assoc.meta_id, assoc.data_id
ORDER BY data.data_value;

Sa longueur varie fortement selon le nombre de conditions. Il est possible de visualiser les requêtes générées par Valhalla en utilisant la verbosité maximale de la bibliothèque.

bird

valhalla_db_filelist_get

Utilisation simple

La récupération d’une liste de fichiers ressemble à celle pour les metadata. La différence vient principalement du deuxième argument qui fait référence au type de fichier (audio, video, etc, …). Ce qui limite ensuite la liste se sont les restrictions. L’utilisation étant exactement la même, je vais aller droit au but avec des exemples.

Pour lister la totalité des fichiers référencés dans la base de donnée, rien de plus simple:


valhalla_db_filelist_get (handle, VALHALLA_FILE_TYPE_NULL, NULL, callback, NULL);

Le fait d’indiquer un type de fichier NULL, permet d’ignorer le type. Le résultat de cette requête correspond à un tableau de ce genre qui contiendrait la totalité de l’arborescence:

id path filetype
101 /home/foo/bar/dido/life_for_rent/sand_in_my_shoes.m4a AUDIO
102 /home/foo/bar/dido/life_for_rent/white_flag.m4a AUDIO
103 /home/foo/bar/dido/life_for_rent/who_makes_you_feel.m4a AUDIO

Avec les restrictions

Il est ensuite facile de faire du tri avec les restrictions, de la même manière que pour la fonction précédente. Par exemple, pour lister tous les fichiers d’Ennio Morricone qui ne sont référencés dans aucun album.


valhalla_db_restrict_t r1 = VALHALLA_DB_RESTRICT_STR (IN, "author", "Ennio Morricone");
valhalla_db_restrict_t r2 = VALHALLA_DB_RESTRICT_STR (NOTIN, "album", NULL);
VALHALLA_DB_RESTRICT_LINK (r2, r1);
valhalla_db_filelist_get (handle, VALHALLA_FILE_TYPE_NULL, &r1, callback, NULL);

Et ci-dessous, un autre exemple qui correspond à la catégorie “Non Classé” dans Enna. Tous les fichiers qui ne sont ni dans un album, ni lié à un author et dont le genre est indéfini.


valhalla_db_restrict_t r1 = VALHALLA_DB_RESTRICT_STR (NOTIN, "album", NULL);
valhalla_db_restrict_t r2 = VALHALLA_DB_RESTRICT_STR (NOTIN, "author", NULL);
valhalla_db_restrict_t r3 = VALHALLA_DB_RESTRICT_STR (NOTIN, "genre", NULL);
VALHALLA_DB_RESTRICT_LINK (r3, r2);
VALHALLA_DB_RESTRICT_LINK (r2, r1);
valhalla_db_filelist_get (handle, VALHALLA_FILE_TYPE_NULL, &r1, callback, NULL);

Facile, non?

A noter que ce n’est pas encore totalement optimal, car le groupe ne peut pas être donné avec les restrictions. Ainsi n’importe quel metadata de n’importe quel groupe est considérée dans l’inclusion comme l’exclusion.

Et d’un point de vue SQL

La aussi, la requête ressemble beaucoup à la précédente avec des différences au niveau de ce qui est sélectionné et dans quelle table.


SELECT file_id, file_path, _type_id
FROM file AS assoc
% Les conditions sont optionnelles
WHERE
  % Une sous-requête apparait par le biais des restrictions.
  % Il y a autant de sous-requêtes que de restrictions du type IN, NOT IN.
  assoc.file_id <IN|NOT IN> (
    SELECT assoc.file_id
    FROM (
      data INNER JOIN assoc_file_metadata AS assoc
      ON data.data_id = assoc.data_id
    ) INNER JOIN meta
    ON assoc.meta_id = meta.meta_id
    % A choix, il peut y avoir un ID ou du texte.
    WHERE meta.<meta_id|meta_name> = <ID|"TEXT"> AND data.<data_id|data_value> = <ID|"TEXT">
  )
  % Le type n'est pas inscrit dans la requête si la valeur est NULL.
  AND _type_id = <ID>
ORDER BY file_id;

Si dans la clause FROM, la table “file” créer un alias “assoc”, c’est uniquement pour une question de factorisation dans le code par rapport à la sous-requête.

bird

valhalla_db_file_get

Maintenant que l’on a vu comment récupérer des listes de metadata ainsi que des listes de fichiers, il est important de pouvoir aussi récupérer des metadata en fonction d’un fichier spécifique. Cette possibilité n’existait pas avec l’ancien modèle car les fichiers étaient toujours retournés dans une liste. Ici il est question d’un seul fichier à la fois. Il faut également être conscient que cette fonction est liée uniquement à la base de donnée, si un fichier non référencé dans la base mais existant quelque part est donné en argument, aucun résultat ne sera retourné (cette possibilité existera quand j’implémenterai le “scan on-demand”).

Les arguments sont donc l’id du fichier, le chemin du fichier, des restrictions et le pointeur sur la structure de résultat. Il n’y a pas de callback pour l’utilisateur car le résultat est limité à un seul fichier (en réalité il y a un callback interne qui récupère toutes les metadata car le vrai résultat est sur plusieurs lignes).

Les metadata sont disponibles dans une structure chaînée. Je dois également introduire une nouvelle possibilité des restrictions. Mais tout d’abord, voici un exemple:


valhalla_db_filemeta_t *metadata = NULL;
valhalla_db_restrict_t r1 = VALHALLA_DB_RESTRICT_STR (EQUAL, "track", NULL);
valhalla_db_restrict_t r2 = VALHALLA_DB_RESTRICT_STR (EQUAL, "title", NULL);
VALHALLA_DB_RESTRICT_LINK (r2, r1);
valhalla_db_file_get (handle, 0, "/home/foo/bar/dido/life_for_rent/white_flag.m4a", &r1, &metadata);
/* ... */
VALHALLA_DB_FILEMETA_FREE (metadata);

L’argument EQUAL dans la macro VALHALLA_DB_RESTRICT sert à limiter les résultats à des metadata précises. Sans les restrictions, la structure metadata contiendrait absolument toutes les metadata. Mais dans cet exemple, la structure contient uniquement le track et le titre. A noter qu’utiliser IN ou NOTIN ici ça n’a aucun sens. De même qu’utiliser EQUAL pour les deux fonctions précédentes (voir la conclusion).

Il faut être conscient que plusieurs tracks et plusieurs titres peuvent être retournés pour un même fichier. Car rien n’exclus que certains noms de metadata n’existent pas dans différents groupes. Et comme je l’ai dis plus haut, il n’est pour le moment pas possible de limiter une restriction à un groupe. Néanmoins le groupe est un champ de la structure valhalla_db_filemeta_t, il est donc possible de faire un tri à postériori.

Après utilisation des metadata, il ne faut pas oublier de les libérer avec VALHALLA_DB_FILEMETA_FREE().

Et d’un point de vue SQL


SELECT file.file_id, assoc._grp_id, meta.meta_id, data.data_id, meta.meta_name, data.data_value
FROM ((
    file INNER JOIN assoc_file_metadata AS assoc
    ON file.file_id = assoc.file_id
  ) INNER JOIN data
  ON data.data_id = assoc.data_id
) INNER JOIN meta
ON assoc.meta_id = meta.meta_id
% Les conditions sont optionnelles
WHERE
  % Une condition apparait par le biais des restrictions.
  % Il y a autant de conditions que de restrictions du type EQUAL.
  % A choix, il peut y avoir un ID ou du texte. Chaque restriction
  % est séparée par un OR.
  (
    (meta.<meta_id|meta_name> = <ID|"TEXT"> AND data.<data_id|data_value> = <ID|"TEXT">)
    <OR> ...
  )
  % Il faut l'id d'un fichier ou un path
  AND file.<file_id|file_path> = <ID|"PATH">
;

Cette requête retourne autant de lignes qu’il y a de metadata. Mais pour l’utilisateur, tout est transparent car les résultats sont tous retournés dans une structure chaînée.

En conclusion

Les possibilités de ce nouveau système sont très larges par rapport à l’ancien. Bien qu’il y ait encore quelques manques pour pouvoir spécifier des groupes avec les restrictions, il est possible de retrouver les données avec peu de lignes de code et simplement, tout en ayant des requêtes relativement complexes qui se créer en interne. Il y aura forcément des modifications à l’avenir, je tâcherais de garder à jour cet article.

Et pour en revenir aux restrictions et à l’argument qui peut prendre la valeur IN, NOTIN ou EQUAL, il faut comprendre que c’est avant tout une question de factorisation en interne. IN et NOTIN devraient être utilisés uniquement avec les deux premières fonctions présentées qui récupèrent des listes, et EQUAL devrait être utilisé uniquement pour la récupération des metadata d’un fichier. Je vais continuer à travailler sur ces éléments dans les jours à venir afin d’éviter des confusions.

A bientôt,

Mathieu SCHROETER

h1

Quoi de neuf dans la Valhalla

2009.05.09

OdinAu pays du dieu Odin, il est temps de ne plus limiter l’entrée de la Valhalla uniquement aux guerriers valeureux. Mais soyons fous et acceptons tout le monde, et peu importe leurs caractéristiques (cf. Valhalla Scanner).

Je travail ainsi à la refonte complète de la structure de la base de donnée. Pour rappel, le problème majeur vient du manque de souplesse à l’insertion des méta-données. Il n’est pas possible d’y insérer des informations autres que le titre, l’année, le numéro de piste, l’auteur, l’album et le genre. De nombreux fichiers offrants bien d’autres informations il est donc indispensable de revoir le modèle complètement. Mais qui dit nouveau modèle, dit aussi nouveau code et nouvelle API. La conséquence de cette réécriture provoquera indéniablement une fracture avec le module actuellement présent dans Enna et avec les bases de données générées par le système actuel. En date de cet article, je n’ai pas encore écris les modifications mais j’ai néanmoins fais les réflexions nécessaires. Et se sont ces réflexions que je vais partager ici “en partie :-)”.

Nouveau modèle

L’idée est de se détacher des noms donnés au méta-données. Tel que “genre”, “author” et “album” par exemple. Ce ne sont finalement que des noms et devraient donc être traités comme de simple donnée dans la base de donnée. Il n’y aura donc plus de table nommée “genre”, etc. Mais une table regroupant les noms, et une seconde table regroupant les valeurs et gardant indirectement une relation entre le nom et la valeur.

Un tel système permet d’insérer une infinité de noms différents de méta-données et d’éviter en même temps les duplications. Il est important d’assurer un minimum de redondance. Si un genre est inséré, il ne faudrait pas qu’il soit réinséré trois fois si trois nouvelles données nommées “genre” seraient envoyées par le scanner. De même pour les valeurs, la valeur “rock” par exemple devrait apparaitre qu’une seule fois dans la base de donnée. Ainsi tous les fichiers qui feraient référence à “genre”->”rock” seraient tous liés à la même information. Ce qui est le cas avec l’ancien modèle, et il est important de conserver cette caractéristique pour une question de bon-sens et de qualité.

Mais rendre le modèle complètement générique apporte un nouveau problème. Il devient difficile de retrouver les informations qui concernent un même thème. Imaginez un fichier qui utilise le nom “genre” et un autre type de fichier qui utilise le nom “style”. Peut être que ces deux fichiers sont de la musique et que les deux contiennent la valeur “rock”. Mais une fois nous avons “genre”->”rock” et une fois nous avons “style”->”rock”.
Pour y remédier, il peut être judicieux de créer une troisième table qui permettrait de regrouper les paires nom->valeur sous des noms d’ensemble. Cette table se nommerait donc “group” et contiendrait une liste (pouvant être étendue si nécessaire) de différents groupes possibles. Voici une première liste de groupes (qui va encore évoluer):

Miscellaneous, Entity, Title, Classification, Personal, Temporal, Spacial, Commercial, Legal, Technical, Identifier, Musical, Contact, Organizational

Typiquement, par rapport à l’exemple précédent pour “genre” et “style”, il serait judicieux de regrouper ces paires dans le groupe “Classification”. Ainsi il serait facile de ressortir toutes les paires par groupe pour créer des listes de genre et de style en étant sûr que le nom “genre” ne fait pas référence à autre chose. On pourrait très bien imaginer que pour certains formats de fichier, ce soit des noms français et que ça ferait référence au genre (sexe) et non au style de musique. Il faudrait donc pouvoir assigner un autre groupe pour ces particularités. Ainsi, lors de l’insertion des méta-données d’un fichier, il sera obligatoire de chaque fois spécifier le groupe. Mais dans les cas “inconnu”, il sera toujours possible d’utiliser le groupe “Miscellaneous”.

Ça sera donc aux parsers de dire quel paire va dans quel groupe. Cela peut paraître un peu contraignant, car ça demande quelques réflexions sur le fonctionnement des parsers. Mais afin de pouvoir récupérer de la manière la plus exhaustives possible des informations depuis l’extérieur (comme Enna), il est indispensable de les regrouper correctement. Avec l’ancien système, ce n’est pas possible et c’est donc un plus que j’estime intéressant. Sans oublier que le nom des méta-données pourra également être récupéré depuis l’extérieur, et donc un tri pourra se faire sur les “genre” et sur les “style”, etc,.. si nécessaire. Et bien sûr, il sera qu’en même possible de récupérer tous les “genre” sans tenir compte des groupes.
(Quand je parle de “l’extérieur”, je parle donc des accès effectués depuis l’API et des valeurs récupérées. Je parle également toujours du genre, mais c’est un exemple.. l’idée est la même pour toutes les méta-données.)

Diagramme Entité/Relation

Le diagramme E/R ci-dessous résume assez bien à quoi va ressembler la base de donnée. Il se peut qu’il évolue encore, mais la forme générale (les relations) ne devraient pas trop bouger.

Valhalla E/R

D’habitude je préfère travailler avec des diagrammes MERISE, mais le logiciel Dia est mieux adapté pour du E/R. Pour les non-adeptes des E/R, le rectangle/losange “is defined by” correspond à une table d’allocation ayant pour clef primaire (file_id, meta_id et data_id).

But final

Cette petite analyse du modèle est un premier pas vers une future modification de Valhalla, dans laquelle il sera possible d’y introduire directement les grabbers d’Enna (les grabbers sont les modules qui permettent de récupérer des informations et des images via Allocine, IMdB, etc,..).

Il y a quelques indications dans ce document (voir aussi ici), mais j’en parlerais plus en détail dans un nouvel article, dès que j’en aurais terminé avec le nouveau modèle et son implémentation.

A bientôt,

Mathieu SCHROETER

h1

Vim & l’indentation

2009.04.04

Hello,

S’il y a bien quelque chose qui me dérange dans les codes sources se sont les mélanges entre les tabulations et les espaces avec l’indentation. Une des justification de ne pas utiliser les deux façons de faire en même temps, est tout simplement le résultat d’un `diff`. La première colonne du `diff –unified` est réservée à un espace, un ‘-’ ou alors un ‘+’. Ainsi lorsqu’un patch est lu et que les tabulations se mélangent avec les espaces, les éléments ne sont plus alignés comme dans cet exemple:


 void foobar (void)
 {
-        printf ("Hello ");
+	printf ("World!\n");
 }

Le premier printf utilisant 8 espaces et le second une tabulation. (à noter que l’affichage dans WordPress est faussé, à moins de regarder le code en cliquant sur le lien “view plain”)

Souvent dans les sources avec une indentation de 4 espaces, les tabulations sont utilisées pour chaque 8 espaces. Il y a donc régulièrement des lignes qui mélangent les deux en commençant par une tabulation, puis 4 espaces afin d’atteindre le 3ème niveau, et ainsi de suite.

Alors je n’ai rien contre les tabulations, mais seulement si elles sont utilisées pour représenter une indentation de 8 colonnes tel que c’est le cas dans le noyau Linux. Il y a même parfois pire. Certaines personnes changent la configuration du TAB afin qu’il fasse 4 colonnes au lieu de 8 (qui est la valeur par défaut). Ainsi pour les autres développeurs, la lecture du code est très difficile. Chose qui ne peut en aucun cas arriver avec des espaces.

Vim

VimMon but n’est pas d’ouvrir un troll entre Vi(m) et Emacs. En ce qui me concerne, j’utilise Vim pour une raison “historique”. C’est tout simplement le premier éditeur de texte évolué que j’ai utilisé sous Linux et je n’ai jamais eu l’envie de changer mes habitudes, celui-ci me donne entière satisfaction.

Afin de pouvoir travailler dans de bonnes conditions pour le développement, voici ma configuration actuelle de Vim (~/.vimrc).


" Global settings
:set nocompatible
:syntax on
:set hlsearch
:set shiftwidth=2
:set background=dark
:set cursorline

" Show line number
:set number
:highlight LineNr term=bold ctermfg=darkgray guifg=darkgray

" Special configuration for development
:filetype on
:autocmd FileType c,cpp,cxx,h set cindent|set tabstop=8|set softtabstop=2|set expandtab
:autocmd FileType make setlocal noexpandtab

" Special highlighting for Doxygen
:let g:load_doxygen_syntax=1

" Show when a line exceeds 80 chars
:au BufWinEnter * let w:m1=matchadd('ErrorMsg', '\%>80v.\+', -1)

" Highlight Tabs and Spaces
:highlight Tab ctermbg=darkgray guibg=darkgray
:highlight Space ctermbg=darkblue guibg=darkblue
:au BufWinEnter * let w:m2=matchadd('Tab', '\t', -1)
:au BufWinEnter * let w:m3=matchadd('Space', '\s\+$\| \+\ze\t', -1)
:set list listchars=tab:»·,trail:·

En plus de la coloration syntaxique, il y a quelques paramètres pour éviter des bavures dans le code. L’un d’eux permet de montrer explicitement si une ligne dépasse les 80 caractères. Elles apparaissent en rouge mais ne sont pas coupées. Parfois il est voulu de dépasser cette limite. Deux autres paramètres mettent en évidence les espaces non désirés (à la fin d’une ligne par exemple), ainsi que les tabulations.

Il y a également des paramètres pour faciliter l’indentation. Celle-ci est définie pour 2 espaces tout en affichant les tabulations sur 8 colonnes. Il faut aussi être conscient que les “highlights” ont un prix et qu’ils ralentissent l’affichage de manière significative.

Vous pouvez voir ci-dessous un “screenshot” de Vim avec une tabulation et des espaces non-désirés (qui utilisent la configuration susmentionnée).

vim_highlight

Vim (cliquez sur l'image pour la voir à sa taille original)

A bientôt,

Mathieu SCHROETER

h1

Valhalla Scanner

2009.03.14

Hello,

Un petit nouveau a fait son entrée dans les dépôts de GeeXboX. C’est un projet qui est né suite à quelques expériences négatives avec une autre bibliothèque. Elle m’avait alors motivé à en écrire une afin d’y apporter une vision un peu différente. Le but de Valhalla (ou libvalhalla), est très simple, elle va scanner des emplacements sur votre disque dur (par exemple) afin d’y trouver des fichiers audio/video pour les référencer dans une base de données. Il est alors facile ensuite d’y effectuer des requêtes permettant de générer des listes d’artistes, d’albums, etc,…

Mais tout d’abord.. pourquoi Valhalla?

Dans la mythologie nordique, la Valhöll (ou Walhalla, Valhalla, Valhalle), demeure-des-occis, est le lieu où les guerriers valeureux sont amenés. C’est sur les champs de bataille que des vierges guerrières (pour les celts) ou des Valkyries (pour les germains), cherchent et récupèrent les hommes les plus braves et les plus valeureux afin de les ramener dans Ásgard, où Odin les attend pour les préparer à la bataille finale, le Ragnarök.
Dans la Valhöll(palais de Thor), les guerriers alors nommés Einherjar sont heureux : ils combattent, se tuent, renaissent pour encore se pourfendre dans un champ clos.

Valhöll. (2009, mars 10). Wikipédia, l’encyclopédie libre. Page consultée le 17:42, mars 12, 2009 à partir de http://fr.wikipedia.org/w/index.php?title=Valh%C3%B6ll&oldid=38802776.

Je cherchais un nom qui sort de l’ordinaire tout en gardant un sens par rapport à la fonctionnalité de cette bibliothèque. Si la définition de La Valhalla ci-dessus ne vous a pas convaincu, imaginez que les scanners sont les Valkyries, que les braves sont les fichiers audio/video, et qu’elles les emmènent à la Valhalla (dans la base de donnée). Ceux-ci mourant et renaissant sans cesse dans un champ clos (les fichiers pouvant être rescannés en boucle afin d’y repérer les modifications).

(A noter que Valgrind est la porte qui mène à La Valhalla)

Fonctionnement

SQLite

Small. Fast. Reliable.

Valhalla se base sur deux bibliothèques principales. Tout d’abord il y a la libavformat (de FFmpeg) qui permet de récupérer les méta-données, puis SQLite qui s’occupe de gérer la base de donnée. Si ces libs ont été choisies c’est pour de bonnes raisons. Comme je l’ai dis plus haut, libvalhalla est née suit à des expériences avec une autre lib. En premier lieu, cela concerne le moyen pour récupérer les méta-données. Sans rentrer dans des détails inutiles, pour se faire il est nécessaire de passer par des démuxeurs et en créer peut avoir un avantage certain sur la rapidité. Néanmoins beaucoup de travail serait nécessaire pour gérer la plupart des formats de fichier, et c’est ici qu’intervient FFmpeg. Cette bibliothèque étant la plus aboutie, elle supporte énormément de formats. Il est donc facile de récupérer les méta-données sans se soucier du “comment”. La deuxième raison est purement technique. Certains aspects dans la construction de l’autre lib ne me plaisaient pas (à tord ou à raison).
Concernant SQLite, c’est un excellent outil (et très léger et rapide) de gestion de base de données.

Architecture

Avant d’aborder l’architecture en elle-même j’aimerais expliquer mes choix. Tout d’abord Valhalla repose sur les threads. Parce que j’aime travailler avec les threads même si de nombreuses personnes estiment que les “threads are evil”. Ensuite il y a une raison liée à l’aspect pratique et à l’aspect vitesse. D’un point de vue pratique, les threads sont un moyen facile de créer des tâches parallèles et aussi de créer des tâches de fond. Il est intéressant dans le cas d’Enna, de pouvoir l’utiliser sans être bloqué par Valhalla.

J’entends certaines personnes me parler des fork(), alors oui c’est également un moyen.. mais franchement, communiquer entre des sous-processus c’est pénible et nettement moins efficace que de le faire directement entre deux threads qui partagent le même espace mémoire. Les fork() doivent être utilisés si le contexte le demande. Tel que c’est le cas dans libplayer pour travailler avec MPlayer.

L’aspect vitesse est également intéressant car aujourd’hui il est courant de trouver des ordinateurs avec au moins 2 cores. Alors quitte à faire travailler les démuxeurs sur les deux (ou plus), je vous montrerais des petites statistiques plus loin.

La figure ci-dessous présente une vision simplifiée de l’architecture.

Il y a trois parties principales. En vert, le scanner s’occupe de chercher tous les fichiers qui respectent certains critères relatifs à leur nom. Puis chaque référence aux fichiers est envoyée à la partie jaune (Database) qui va vérifier si le fichier existe déjà dans la base de donnée ou s’il est nouveau. Si des modifications sont nécessaires, alors il est envoyé à la partie orange (Parser) afin que les méta-données soient récupérées. Le parser renvoit les données à la Database qui va alors insérer les modifications adéquates dans la base de données à travers SQLite. Chaque bloc représenté dans la figure est un thread. La partie orange peut contenir plusieurs parsers et donc plusieurs threads.

FFmpeg

FFmpeg

A complete, cross-platform solution for audio and video.

Les parsers utilisent la bibliothèque libavformat d’FFmpeg pour réaliser l’extraction des méta-données. Il y a cependant un problème de vitesse. libavformat fait appel à des fonctions de “probe” afin d’identifier le format du fichier pour lui assigner le bon démuxeur. Et dans certains cas (fichiers altérés?), le “probe” prend beaucoup de temps car il y a beaucoup de démuxeurs et qu’il est parfois nécessaire de tester plusieurs fois un même fichier (en avançant dans les données) avant que la lib puisse deviner lequel est le bon.

L’idée est donc de proposer un format directement à libavformat au lieu de le laisser procéder tout seul à la détection. Le moyen utilisé est très simple et efficace. Il est rare que le suffixe d’un fichier ne correspond pas au bon format. La première étape est donc de récupérer le nom du format qui est lié au suffixe (à l’extension). Typiquement, si les fichiers sont des .mp3, le nom est tout simplement “mp3″. Voir les détails dans les sources.


name = suffix_fmt_guess (file);
if (name)
  fmt = av_find_input_format (name);

La deuxième étape est un petit peu plus compliquée. Bien que l’on a désormais une idée sur le démuxeur, il est exclus de l’utiliser tel quel. Si un format est forcé à l’ouverture du fichier, aucun test n’est réalisé par libavformat. Ainsi, une fonction de probe() a été écrite pour tester le démuxeur. L’avantage cette fois vient du fait qu’un seul démuxeur est testé, et non plus tous les démuxeurs de libavformat. Le gain en vitesse est impressionnant (j’ai constaté un gain de plus d’un facteur dix avec cette technique et un peu plus de 1000 MP3/OGG).


if (fmt)
{
  int score = parser_probe (fmt, file);
  if (!score) /* Bad score? */
    fmt = NULL;
}

Si vous êtes intéressé par la fonction parser_probe(), je vous invite à directement la consulter dans les sources. En résumé, la fonction est inspirée de libavformat/utils.c::av_open_input_file(), et dépend fortement de l’implémentation interne de libavformat en ce qui concerne les scores retournés par les fonctions de probe().

Les priorités

Si vous désirez définir des priorités non temps réelles à des threads, voici l’astuce:


#include <unistd.h>
#include <sys/resource.h>
#include <sys/syscall.h>

static inline void
my_setpriority (int prio)
{
  pid_t pid = syscall (__NR_gettid);
  setpriority (PRIO_PROCESS, pid, prio);
}

La fonction gettid() n’existe pas dans la glibc, il est donc obligatoire de passer par un appel système. Et sans utiliser setpriority(), il n’est pas possible à ma connaissance de baisser la priorité en étant un simple utilisateur.

Défaut de jeunesse

Malheureusement un défaut relativement important a été soulevé par Aurélien (un des leader du projet GeeXboX et développeur de FFmpeg). Les méta-données disponibles dans les fichiers audio/video peuvent être très différentes d’un format à l’autre. Valhalla est limité dans le nombre de meta-données supportées. Une future mise à jour majeure devrait apporter beaucoup plus de souplesse pour la gestion des méta-données. A priori cela ne semble pas si compliqué, mais si l’on considère le fait que la base de données suit un modèle relationnel, il est difficile d’être complètement générique.

Je n’ai pas encore pris le temps d’étudier toutes les possibilités pour y remédier. Mais ça fait partie de mes priorités pour Valhalla.

Enna

Mon but principal est d’utiliser Valhalla dans Enna. Ainsi un nouveau module à fait son apparition et permet de retrouver de manière thématique les albums, les artistes, les genres ainsi que les fichiers non-classés. Le module est également paramétrable depuis le fichier ~/.enna/enna.cfg pour surtout y indiquer les répertoires à scanner.

[valhalla]
path=file:///home/foo/music
path=file:///home/bar/music
verbosity=info
parser_number=2
commit_interval=128
# <=0 for infinite
scan_loop=-1
# time [sec] for sleeping between loops
scan_sleep=900
# 0: normal, -20: higher, 19 lower
scan_priority=19

Quelques paramètres méritent certaines explications. Tout d’abord vous pouvez indiquer autant de “path” que vous le désirez. Les paths ne sont pas scannés en parallèle, mais séquentiellement. Il n’y a pas beaucoup d’avantage de scanner  plusieurs répertoires en parallèle car les accès au disque ne seront pas plus rapide.
Vous pouvez bien sûr changer la verbosité de la bibliothèque. L’intérêt est limité pour de simples utilisateurs, mais ça peut être utile dans des cas de debuggage.

Nombre de parsers

L’option “parser_number” indique le nombre de parsers à créer. Par défaut, le nombre maximum est de 8. Ce chiffre peut être changé à la compilation de Valhalla en passant le define PARSER_NB_MAX=# dans le CFLAGS. Mais il faut garder à l’esprit qu’il est inutile de définir beaucoup de parsers car ça n’augmentera en rien la vitesse.

Intervalle entre les commits

L’option “commit_interval” permet de dire chaque combien de fichiers, les données sont réellement inscrites dans la base de données. Moins cette valeur est grande, et moins le scan sera rapide car SQLite augmentera le nombre d’écritures sur le disque dur. J’ai constaté un gain de ~20 en passant d’une valeur de 1 à une valeur de 128. Si vous définissez une valeur plus grande, le gain ne sera plus tellement visible et les données s’accumuleront dans la RAM. Le fait de mettre une petite valeur est également plus sûr, car en cas de plantage, plus de données seront écrites. C’est donc aussi une question de bon sens.

Bouclage du scan et temps entre les scans

L’option “scan_loop” indique le nombre de bouclage qui doivent être effectués pendant qu’Enna est exécuté. L’intérêt est de détecter les modifications et les nouveaux fichiers. En mettant une valeur de 1, le scan se fera uniquement au démarrage d’Enna. Par contre avec une valeur plus petite ou égale à 0, le scan se fera chaque “scan_sleep” secondes. Vous pouvez bien sûr donner une valeur supérieure tel que 2, 3, etc,.. pour définir exactement le nombre de bouclages désiré.

Priorité du scan

L’option “scan_priority” permet de garantir que le scanner, le gestionnaire de la base de données ainsi que les parsers, ne puissent pas monopoliser les ressources de votre système. Le fait de donner une valeur de 19, garanti que Valhalla aura la priorité la plus basse et son comportement sera bien une tâche de fond. Par défaut un utilisateur à une priorité de 0, et tous les programmes exécutés ont une priorité de 0 (vous ne pouvez donc donner que des priorités inférieures (valeurs positives ou égales)). Si vous n’avez pas les droits appropriés et que vous donnez une priorité de -1 à -20, Valhalla gardera une priorité de 0. Par contre, si vous êtes root, alors vous pouvez mettre n’importe quel priorité. (Il n’est pas recommandé de mettre une priorité supérieure ou égale à Enna à moins d’affecter directement la lecture des fichiers et l’interface.)

La suite

Comme je l’ai expliqué plus haut, il reste beaucoup de travail pour rendre la bibliothèque plus générique envers les méta-données. Mais il reste également du travail au niveau de l’API, en ce qui concerne les fonctions pour sélectionner les données dans la base de données, et ces fonctions dépendent directement des méta-données.

Bien sûr, il est possible d’effectuer des requêtes sur la base de données sans passer par Valhalla. Mais dans ce cas il faut être conscient qu’il peut y avoir des problèmes de parallélisme. SQLite utilise fcntl() pour bloquer l’accès au fichier le temps des modifications. Mais sur certains systèmes de fichier, cela est buggé (voir les explications  ici).

Quelques modifications mineures sont également prévues, comme l’ajout de filtres sur les paths, pour par exemple ignorer les fichiers et dossiers cachés. Et également un moyen de récupérer l’information si un fichier contient des flux audio, video ou audio+video.

Une mise à jour concernera également l’ajout d’informations sur la version du fichier de base de données et la version de Valhalla. Car actuellement, si des modifications structurelles sont effectuées sur la base de données, Valhalla ne fait aucun contrôle sur le fichier qui a été créé par une ancienne version. Ce qui peut provoquer des erreurs plus ou moins imprévisibles lors de l’exécution d’un scan. Le temps que j’implémente cette partie, il est judicieux d’effacer le fichier ~/.enna/valhalla_music.db lors d’une mise à jour de Valhalla.

Télécharger

Pour télécharger Valhalla, rien de plus simple:

hg clone http://hg.geexbox.org/libvalhalla

La compilation et l’installation se fait comme pour n’importe quel logiciel.

./configure && make && sudo make install

Pensez aussi à faire un `sudo ldconfig` pour être sûr que la lib soit référencée pour le chargeur de programme.

Il est également important (indispensable) d’utiliser une version récente d’FFmpeg!

Quelques statistiques

Comme promis, voici quelques statistiques liées à la vitesse du scan. Le premier scan est toujours le plus long. La vitesse dépend principalement de la vitesse d’accès du média sur lequel se trouve les fichiers. Puis en second lieu, elle dépend également du cache effectué par le système de fichier. Mes quelques tests ont été fait sur une partitions EXT3 avec principalement des fichiers MP3 (dont des corrompus), des fichiers OGG, WMA et M4A. (le fichier de base de données est bien sûr effacé entre chaque essai). Il y a 1090 fichiers et mon système est dual-core.

Premier scan (sans cache)

Run: parser=2 loop=1 wait=0 priority=19 commit-int=128
Time elapsing: 24675.591000 msec, 24.675591 sec

Quelques scans (même config + cache)

Time elapsing: 1049.802000 msec, 1.049802 sec
Time elapsing: 1183.110000 msec, 1.183110 sec
Time elapsing: 1066.583000 msec, 1.066583 sec

La vitesse a énormément changée et pourtant la bibliothèque à fait exactement le même job. Le cache du système de fichier a un effet très important.

Avec un seul parser (1 parser + cache)

Run: parser=1 loop=1 wait=0 priority=19 commit-int=128
Time elapsing: 1500.876000 msec, 1.500876 sec
Time elapsing: 1436.085000 msec, 1.436085 sec
Time elapsing: 1637.296000 msec, 1.637296 sec

On note en moyenne 1.10 sec pour 2 parsers et 1.53 sec pour 1 parser. Vous me direz que ça n’a rien d’exceptionnel et vous avez tout a fait raison. La différence est de 430 ms entre les deux moyennes. Mais en réfléchissant un peu, cela représente qu’en même un gain de ~40%.
Ces statistiques ne sont pas très exhaustives car 1090 fichiers ce n’est pas grand chose. Il faudrait également effectuer beaucoup plus de tests pour avoir une statistique relevante. Mon but était juste de vous montrer que le nombre de parsers ne fait pas de miracle. Et qu’avoir plus d’un parser lors du premier scan ne changera quasiment rien. Mais si vous avez beaucoup plus de fichiers que moi, (des dizaines de milliers) l’impacte sera bien plus grand.

Avec un commit-int à 1 (+ cache)

Run: parser=2 loop=1 wait=0 priority=19 commit-int=1
Time elapsing: 20299.520000 msec, 20.299520 sec
Time elapsing: 18831.318000 msec, 18.831318 sec
Time elapsing: 19063.652000 msec, 19.063652 sec

Voyez comme l’intervalle entre les commits effectués par SQLite sont très important. Passer de 1.5 sec à 20 sec n’est pas du tout négligeable.

Avec un commit-int à 1024 (+ cache)

Run: parser=2 loop=1 wait=0 priority=19 commit-int=1024
Time elapsing: 935.655000 msec, 0.935655 sec
Time elapsing: 1018.052000 msec, 1.018052 sec
Time elapsing: 953.042000 msec, 0.953042 sec

Il est intéressant de noter qu’avec un intervalle de 1024, Valhalla arrive à descendre en dessous de la seconde. Maintenant vous comprendrez qu’il est difficile de trouver une configuration parfaite pour tout le monde. Mais le gain entre un intervalle de 128 ou de 1024 n’a plus beaucoup d’impact. Sans compter que 128 est plus sûr dans le cas d’un crash.

(Tous les tests ont été effectué avec l’outil test-valhalla directement disponible depuis les sources.)

Conclusion

Il reste beaucoup de travail à faire, mais vous pouvez utiliser cette bibliothèque correctement dans l’état actuel. Les performances sont à mon avis très bonnes et le nombre de parsers sera sûrement plus significatif dans le futur, car certaines améliorations auront forcément un impact sur la vitesse de récupération des méta-données. Même sur un système mono-processeur (mono-core), il est judicieux de garder 2 parsers. Parfois certains fichiers sont relativement altérés et difficiles à parser. Le fait de garder un deuxième parser assure un bon enchaînement si l’un d’eux perd du temps.

A bientôt,

Mathieu SCHROETER

h1

GeeXboX 1.2 – Broadcast simultaneously one year in the future

2009.02.01

EDIT (2009.02.11):

Bogue corrigé dans la version 1.2.1!

Bug fixed in version 1.2.1!

EDIT (2009.02.07):

Il y a un bug important qui nous a malheureusement échappé jusqu’à la release et il concerne l’installateur. Lorsqu’une installation est effectuée sur un périphérique USB, la MBR du disque principal peut être modifiée et ainsi rendre le boot impossible sur le PC. Dans le cas de Window$, il suffit d’exécuter un fixmbr depuis le CD d’installation avec la console de récupération pour que tout rentre dans l’ordre. En attendant je vous conseil de ne pas installer GeeXboX sur une mémoire USB si vous ne savez pas comment restaurer la MBR. Ce bug a été fixé en devel uniquement.

There is a major bug with the current release and the installator. When a installation is done on a USB removable, the MBR can be changed on the main harddisk and the boot will be impossible on the PC. In the case of Window$, you must use fixmbr located on the recovery console of the installation CD to restore the MBR. I suggest to not install GeeXboX on a USB removable if you don’t know how to fix the MBR. This bug is fixed only in the devel.

Cette fois ça y est..

Les derniers problèmes majeurs ont été corrigés et il était temps d’en finir avec ces versions bêta. La GeeXboX 1.2 est alors diponible en téléchargement directement sur cette page pour les architectures x86_32, x86_64 et PowerPC. Pour ceux qui trouverait ce langage un peu vulgaire, les versions x86 sont compatibles PC et MacIntel et la version PowerPC vise spécialement les “anciens” Macintosh. A noter que seul la version x86_32 permet l’installation sur disque dur (la raison étant avant tout technique).

Les quelques modifications depuis la bêta3 sont directement disponible ici. Je rappel également les utilisateurs de Window$, qu’il est possible d’installer GeeXboX sans passer par l’installateur de la distribution. Un outil nommé “Installateur GeeXboX pour Win32″ est disponible sur la même page de téléchargement que pour les images ISO et les générateurs.

Pour conclure, on espère dans quelques semaines publier une première version (alpha) de la futur GeeXboX 2.0.

gx-v2-photo-full

Enna Media Center

A bientôt,

Mathieu SCHROETER

h1

Enna/libplayer – Problèmes typiques

2009.01.20

Hello,

je reçois de temps en temps des emails de personnes qui ont des problèmes avec Enna. Comme par exemple le fait qu’ils ont des avertissements qui s’affichent tel que [mplayer] Warn: [hack] slave command ’stop’, les snapshots des vidéos qui ne sont pas créés ou alors d’autres choses. Cette article a donc pour but de donner quelques précisions sur le fonctionnement général de Enna/libplayer et pourquoi certains messages apparaissent et comment utiliser libplayer correctement.

Les “backends”

Enna est une interface graphique et donc le décodage et la sortie audio/video sont confiés à un backend. Il existe actuellement deux backends dont l’un est lié aux EFLs en utilisant Emotion (donc GStreamer) et le second backend étant bien entendu libplayer. Une question légitime peut être de se demander pourquoi ne pas utiliser uniquement Emotion? L’avantage principale est son intégration avec les EFLs, il est donc facile de manipuler la fenêtre vidéo, de lui appliquer des effets, etc,.. Mais cette solution à aussi un désavantage, Evas étant un “canvas RGB” le sortie vidéo ne peut plus être accélérée à l’aide de Xv, XvMC, VIDIX (en VESA) et bientôt VDPAU (par exemple). Ainsi d’un point de vue performance c’est un désavantage et c’est là qu’intervient libplayer.
Je ne vais pas y revenir en détail, un article précédent explique déjà comment libplayer affiche la vidéo à travers Enna.

libplayer/MPlayer

Le titre de cet article aurait plutôt dû s’appeler “Enna/libplayer/MPlayer” mais vous conviendrez que c’est pas très esthétique. Le wrapper de MPlayer étant le plus avancé dans libplayer la raison est suffisante pour ne pas aborder les autres possibilités de libplayer tel que xine, VLC et GStreamer (en tout cas pour l’instant).

Ainsi lorsque vous utilisez Enna/libplayer vous avez besoin de MPlayer. Dans le cas contraire vous aurez droit à un joli message d’erreur en rouge vous avertissant que MPlayer est manquant, ou que vous n’avez pas compilé libplayer avec le support de MPlayer.

Les problèmes typiques

problem_solved

Problème I.

Vous avez tout compilé et installé mais lorsque vous exécutez Enna un message d’erreur apparait:

[player] Error: no wrapper registered

Le wrapper MPlayer n’a pas été compilé dans libplayer. La raison principale est que vous avez effectué un simple `./configure` en compilant libplayer. Vous n’avez pas prêté attention aux informations retournées par le script configure qui a choisi de ne pas compiler le wrapper MPlayer. Le script ignore ce wrapper s’il ne trouve pas le binaire ‘mplayer’ sur votre système. Il est possible de forcer le wrapper avec `./configure --enable-mplayer` ou alors en installant MPlayer avant de compiler libplayer.

Problème II.

Vous êtes sûr d’avoir compilé et installé libplayer correctement mais lorsque vous exécutez Enna et tentez de lire un fichier audio/video rien ne se passe. Dans les messages vous pouvez voir:

[Enna] [module.c:nnn] Warn: Unable to load module libplayer

C’est un problème classique qui arrive lorsque tout est installé mais la bibliothèque libplayer n’a pas été référencée pour ld.so (run-time linker). Il suffit alors de faire `sudo ldconfig` pour que les liens soient remis à jour. Je pense que c’est le problème le plus répandu et le plus simple de cette liste. Même que de nombreux développeurs se font avoir.

D’une fois que la librairie est référencée, il est inutile de re-exécuter cette commande, à moins d’avoir changé de --prefix.

Problème III.

Tout fonctionne mais lorsque vous lisez des fichiers audio/video des avertissements apparaissent dans la console:

[mplayer] Warn: [hack] slave command 'stop'

Ce message apparait uniquement avec les versions relativement anciennes de MPlayer. Il faut savoir que la dernière version largement “packagée” à ce jour par les distributions Linux est mplayer-rc2 (version packagée par Ubuntu dans Intrepid Ibex par exemple). Cette version date de presque un an et demi et aucune nouvelle RC n’a été publiée par le team MPlayer. Néanmoins quelques fonctionnalités nécessaires au bon fonctionnement de libplayer n’existaient pas encore. Un article précédent aborde justement la commande ’stop’. La solution est donc de compiler une version récente de MPlayer.

Problème IV.

Vous pouvez écouter de la musique et regarder des vidéos, mais les snapshots ne s’affichent pas (les snapshots étant les images de fond capturées dans la video depuis le module video d’Enna).

Il peut y avoir plusieurs raisons à ce problème. Tout d’abord assurez-vous d’avoir la propriété use_snapshots=1 dans votre fichier ~/.enna/enna.cfg. Si le problème persiste, vérifiez qu’il n’y a pas de message d’avertissement venant de libplayer comme quoi l’image du snapshot n’a pas pu être copiée. Si c’est le cas vous devez compiler une version plus récente d’MPlayer car les snapshots PNG ne sont possibles que depuis quelques mois.

Depuis peu c’est le module metadata/libplayer qui s’occupe des snapshots et donc le backend n’est pas concerné. Par défaut ce module devrait être compilé, dans le cas contraire vérifiez s’il est bien notifié comme actif dans les messages retournés par le script configure (généré par autogen.sh) de Enna.

Problème V.

Lorsque vous fermez Enna (ou suite à une erreur), des instances de MPlayer sont toujours visibles dans la liste des processus.

Lorsque vous quittez Enna, vous devez voir des messages qui informent que les modules sont désactivés comme par exemple [Enna] [module.c:nn] Info: disable module : backend_libplayer. Dans le cas contraire cela veut dire qu’Enna n’a pas été fermé correctement et donc libplayer n’a pas été “uninitialisé”. Les processus enfants de libplayer restent alors en vie et il est donc nécessaire de les tuer manuellement (notez bien que se ne sont pas des processus zombies comme certains peuvent le penser, ils sont bien vivants mais sont en mode ‘-idle -slave‘ et attendent donc des ordres sur leur stdin).

Il y a deux conditions classiques où ce problème apparaît. Par exemple Enna c’est fait tuer tout bêtement, typiquement suite à une erreur de segmentation. Le second cas très courant est que vous avez fermé Enna en cliquant sur la croix de la fenêtre. Ce bug avait été corrigé mais est réapparut depuis quelques temps. La solution est donc de quitter Enna avec la touche d’échappement (Esc).

Problème VI.

Vous voyez tout le temps deux instances de MPlayer dans la liste des processus et même parfois plus.

Ce n’est pas un bug car il y a plusieurs “player” qui sont créés au chargement d’Enna. Tout d’abord il y a le backend qui créer une instance pour pouvoir lire les fichiers audio/video. Ensuite il y a le module metadata/libplayer qui créer également une instance. Le but étant d’avoir des modules indépendants. Vous pouvez très bien utiliser le backend libplayer sans compiler le module metadata/libplayer et inversement.

De temps en temps vous pouvez voir un troisième processus enfant mplayer. Celui-ci apparait que brièvement lorsque des meta-données sont recherchées ou lorsque des snapshots sont créés. Il faut comprendre que pour analyser des metadata, libplayer doit utiliser l’argument ‘-identify‘ d’MPlayer. Ceci ne peut se faire qu’au lancement de MPlayer pour un MRL précis. Les MPlayer lancés en mode ‘-idle -slave‘ ne peuvent en aucun cas identifier toutes les données.

Problème VII.

Vous avez un message d’erreur concernant X11, mais libplayer est correctement installé:

[mplayer] Err: auto-detection for videoout is not enabled
               without X11 support

Il y a deux possibilités.. Vous voulez utiliser Enna uniquement en Framebuffer et vous avez compilé libplayer sans le support de X11. Dans ce cas vous devez explicitement indiquer le video_out dans votre fichier ~/.enna/enna.cfg pour libplayer. L’auto-détection ne fonctionne pas sans le support de X11 pour une raison technique que je ne vais pas aborder ici. Si vous désirez garder l’auto-détection en Framebuffer, compilez libplayer également avec le support de X11.

Vous avez lancé Enna depuis X11, alors vous n’avez pas compilé libplayer avec le support de X11. Soit parce que vous avez explicitement indiqué ‘--disable-x11‘ au configure, soit parce que le configure n’a pas trouvé les en-têtes adéquates pour X11 et à donc décidé de désactiver son support. Pensez donc à installer la libX11-dev (ou équivalent). libplayer dépend de Xlib pour gérer les fenêtres X11.

Quelques remarques

Avant de poser des questions, lisez bien les messages d’erreurs retournés par Enna et libplayer. Les textes affichés dans la console ne sont pas là pour la remplir bêtement en la décorant avec des couleurs, mais ils sont là pour suivre ce qui se passe dans Enna. Les couleurs sont très importantes car elles reflètent l’importance de l’information. De manière simplifiée, les messages bleus sont insignifiants, les verts sont informatifs, les jaunes demandent à ce qu’on leurs prête plus d’attention mais ils n’indiquent pas de dysfonctionnement.
Par contre si vous voyez des messages rouges (Error) ou même pire, des messages sur fond rouge (Critical), alors il y a un problème sérieux.

Je vous conseil vivement de ne pas changer le niveau de verbosité par défaut de Enna et de libplayer. Enna est configuré en mode “info” et n’affichera pas les messages bleus. libplayer est configuré en mode “warning” et n’affichera pas les messages bleus et verts.

Si vous rencontrez un problème que je n’ai pas décrit plus haut et que vous nous contactez, veillez à nous transmettre le log complet retourné par Enna avec au moins les verbosités par défaut (ou alors avec plus de verbosité).

A bientôt,

Mathieu SCHROETER

h1

GeeXboX 1.2-beta3

2008.12.28

Une dernière news avant 2009!

Et oui, encore une version beta pour cette 1.2.  On espère que ça sera la dernière avant la version finale. Quoi qu’il en soit, de nombreuses corrections ont été apportées à l’installateur. Avec les versions précédentes il était presque impossible d’avoir un système fonctionnel (autre qu’en LiveCD) mais les défauts étaient connus. L’installation sur disque dur devrait dorénavant fonctionner correctement, tout comme l’installation sur des mémoires flash tel que les sticks USB. A noter tout de même que le boot sur USB dépend en grande partie du BIOS de votre machine, des problèmes peuvent donc encore survenir.

Un deuxième problème majeur concernait le montage automatique des périphériques tel que les disque-durs et les lecteurs DVD. Un bug apparaissait typiquement lors de la lecture d’un film DVD car il s’interrompait avant la fin. Ce problème venait du montage du disque en lui même. Il était monté en iso9660 au lieu de l’UDF et MPlayer stoppait la lecture lorsqu’il atteignait 4GB de données. Ainsi un nouvel outil basé sur D-BUS/HAL a fait son apparition (automountd) et permet de monter correctement tous les médias de stockage.

Et quelques nouveautés (non implémentées dans l’interface du générateur d’ISO, il faut donc les paramétrer à la main dans les fichiers de config).

  • La possibilité d’avoir un serveur Samba.
  • La possibilité de pouvoir monter des images CD/DVD autre que des simples ISO à l’aide de Fuse.

http://www.geexbox.org/fr/index.html#geexbox_1.2_beta3

EDIT (2009.01.04): je vous invite à essayer plutôt l’ISO de développement disponible sur cette page, de nombreuses corrections ont été apportées par rapport à la beta3.

Pour télécharger, etc,..

Rendez-vous sur la page du site officiel:
http://www.geexbox.org/fr/downloads.html

Des questions? pensez alors à faire un tour dans nos forums:
http://www.geexbox.org/forum/

menu

Pour les “bidouilleurs”

Il y a eu quelque ajouts dans le toolchain de GeeXboX (non supporté dans les versions officiels).

  • Le support du bluetooth (mais relativement incomplet actuellement, BLUETOOTH=yes)

(il y a également d’autres éléments non intégrés officiellement depuis pas mal de temps)

  • Le support de VDR et de ses plugins (VDR=yes)
  • Le support pour les drivers vidéo propriétaire de NVidia (NVIDIA=yes)

Mais tout ceci concerne uniquement ceux qui compilent leur propre GeeXboX (et attention, je ne parle pas de compilation du point de vue du générateur d’ISO, mais de compilation depuis les sources de la distribution).

A bientôt pour 2009!

Mathieu SCHROETER

h1

GeeXboX 1.2-beta2

2008.12.14

Hello,

GeeXboXla seconde beta est dehors avec sa série de corrections. A part des mises à jours de paquetages tel que le noyau Linux ou encore uClibc. Il faut noter le retour du support pour l’architecture PowerPC, le support d’HAL qui aide particulièrement X.Org au niveau de l’auto-détection et également des modifications pour les règles UDEV afin de corriger un certains nombre de problèmes pour le chargement des modules PATA. L’interface web a été corrigée afin de gérer correctement les fichiers comportant des caractères spéciaux, précédemment de tels noms de fichier pouvaient provoquer des malformations au niveau des tags XHTML, ce qui rendait la navigation impossible.

Une liste plus complète se trouve sur la page principale du site :
http://www.geexbox.org/fr/index.html#geexbox_1.2_beta2

Vous remarquerez l’absence de fix au niveau de l’installateur. En réalité il y a eu quelques corrections tel que la suppression de la méthode “fastboot” qui était complètement buggé, et des binaires mal installés ou mal compilés tel que makebootfat qui était introuvable et grub qui provoquait des erreurs de segmentations. Concrètement, le script d’installation n’a pas subit plus de modifications depuis la beta1. L’installation sur clef USB ne permet donc toujours pas de pouvoir repartitionner comme d’antan.

Au niveau du générateur d’ISO il y a eu quelques corrections qui concernent principalement le support du 64 bits. Ndiswrapper était accessible avec cette architecture ce qui n’avait aucun sens car seule la GeeXboX 32 bits le supporte (bien qu’il serait théoriquement possible d’utiliser des drivers Win64 avec un Ndiswrapper 64 bits, mais je n’ai pas fais la moindre recherche le concernant, quoi qu’il en soit il n’est pas compilé avec cette architecture). L’option VESA était également disponible en 64 avec la beta1, ce qui n’a pas de sens et a donc été supprimé. Il y a eu également une correction au niveau de l’isolinux quand le générateur réécrit les arguments pour le kernel, mais rien de bien important.

Pour télécharger et tester, rendez-vous ici :
http://www.geexbox.org/fr/downloads.html

A bientôt,

Mathieu SCHROETER

h1

GeeXboX 1.2-beta1

2008.11.02

Youhouhouh…

La HDTV étant à la “mode”, une version supportant ce type d’écran se devait de voir le jour. La branche 1.x de GeeXboX s’enrichit donc d’une beta qui ne demande plus qu’à être testée de fond en comble.

Attention, qu’on se comprenne bien. La 1.2 contient toujours l’interface OSD de MPlayer. Ne cherchez pas Enna car aucune version de GeeXboX n’existe à ce jour avec ce GUI. Seul les futurs versions 2.x en seront équipées!

Ceci étant dit, quoi de neuf entre la version 1.1 et 1.2? Et bien la grande différence vient tout simplement du support pour les écrans HDTV. Techniquement cela a demandé l’ajout du serveur graphique X.Org. Pour les non habitués, en effet, les précédentes versions de GeeXboX ne comportent pas de serveur graphique. Le sortie vidéo se fait tout simplement à travers le framebuffer en VESA (tout en exploitant l’accélération vidéo VIDIX).

GOOM en action

GOOM en action

Le VESA ne permettant pas de supporter des écrans avec des définitions exotiques tel que la HD, le seul moyen réaliste était donc d’intégrer X.Org. Les habitués sont déjà au courant de tout cela car de nombreuses versions de développements ont été publiées dans nos forums. C’est principalement pour cette raison que la taille de l’image ISO à presque doublé depuis la 1.1.

Bien que X.Org soit présent, vous pouvez bien entendu revenir sur la version VESA (sauf en 64 bits); soit par le menu de boot de l’ISO, soit par l’intermédiaire du générateur. Si vous utilisez une sortie TV analogique, il est inutile de tester le mode HDTV qui n’intègre pas ce support (tout du moins, pas officiellement).

Où télécharger?

C’est par la bas que ça se passe (images ISO et générateurs): http://www.geexbox.org/fr/downloads.html

Quelques autres nouveautés

  • Le support du 64 bits pour les processeurs x86.
  • Visualisation GOOM pendant la lecture de fichiers audio.
  • Une nette amélioration du support pour les menus DVD.
  • Une méthode de boot rapide pour l’installation sur disque dur.
  • La possibilité de modifier la configuration d’une installation existante (sans réinstaller).
  • Un support amélioré des partages réseaux Samba (Windows) et la possibilité de créer des partages statiques.
  • Ajout de nombreux nouveaux drivers pour divers périphériques (DVB, Wifi, Ethernet, …)
  • Optimisations pour EasyGate et EeePC.
  • Etc, …

Je vous invite à consulter la news du site pour de plus amples informations: http://www.geexbox.org/fr/index.html#geexbox_1.2_beta1

Les nouveaux onglets du générateur d’ISO

HDTV (X.Org)

Le support HDTV est activé par défaut, vous pouvez bien entendu le désactiver via la case à cocher ad-hoc. Il est recommandé de laisser la case à cocher “Auto settings” activée. Le serveur X devrait s’en sortir très bien tout seul. Dans le cas contraire, modifiez manuellement les paramètres nécessaires.

GOOM

GOOM est un “gadget” s’animant pendant la lecture de musique et par rapport à celle-ci. Il est relativement gourmand en puissance de calcul (accéléré uniquement via les instructions MMX), vous pouvez donc le désactiver ou modifier quelque peu ses paramètres d’affichage si nécessaire.

NFS (partages UNIX)

Le support de NFS n’est pas quelque chose de nouveau. Mais auparavant il était uniquement possible de paramétrer les partages via le fichier de configuration. Désormais vous pouvez facilement ajouter et supprimer des partages depuis le générateur.

Samba (partages Windows)

De même pour les partages Samba (Windows), toutes les modifications peuvent se faire depuis le générateur. Les montages statiques créer explicitement l’accès aux partages depuis GeeXboX comme avec NFS. Les partages dynamiques n’ont pas évolués (mise à part la corrections de bugs), et GeeXboX cherchera toujours automatiquement à les monter en respectant le “Username” et le “Password” indiqués.

La suite?

Et bien nous comptons sur vous pour nous rapporter tous les bugs que vous rencontreriez. GeeXboX supporte une large palette de matériels différents, mais il nous est impossible de nous assurer que tout fonctionne correctement mise à part, par rapport à nos propres machines.

Idéalement, si vous êtes développeur, n’hésitez pas à nous envoyer des patchs correcteurs en plus de votre rapport de bug ;-).

Bon tests et à bientôt,

Mathieu SCHROETER

h1

Ecrire un programme basé sur libplayer

2008.09.26

Hello,

Cet article présente un exemple de l’utilisation de libplayer en C. Cette exemple se veut très simple et n’aborde qu’une petite partie des possibilités offertes par la librairie. Le but ici étant de vous montrer comment initialiser libplayer, comment lui donner quelque chose à lire pour également récupérer quelques informations et puis attendre la fin de la lecture pour quitter proprement. Il est vivement recommandé de consulter le fichier d’en-tête player.h pour une description de chaque fonction.

Initialisation d’un player

Avant toute chose, créons un fichier exemple.c avec la fonction principale main() et les éléments nécessaires pour utiliser libplayer.


static int
event_cb (player_event_t e, void *data)
{
  return 0;
}

int
main (int argc, char **argv)
{
  player_t *player;

  player = player_init (PLAYER_TYPE_MPLAYER,
                        PLAYER_AO_AUTO,
                        PLAYER_VO_AUTO,
                        PLAYER_MSG_WARNING,
                        0, event_cb);
  if (!player)
    return -1;

  /* ... */

  player_uninit (player);
  return 0;
}

‘argv’ nous sera utile pour spécifier à libplayer le fichier audio/video à lire. Le type player_t représente un lecteur audio/video, sans lui vous ne pourrez rien faire du tout. La fonction player_init() va nous permettre de créer une instance de player_t. Il est possible d’utiliser un callback pour récupérer des évènements venant de libplayer, la fonction event_cb() jouera donc ce rôle. player_uninit() va libérer correctement tout ce qui doit l’être. Ne quittez jamais une application sans “uninitialiser” le player!

Paramètres de player_init()

C’est ici que vous allez pouvoir donner quelques paramètres pour créer l’instance. Pour notre exemple on va choisir d’utiliser le wrapper MPlayer. Vous pouvez utiliser PLAYER_TYPE_XINE si vous préférez, mais n’utilisez pas GSTREAMER ou VLC qui ne sont pas encore suffisamment exploitables. Les deux paramètres suivant indiquent la sortie audio et la sortie vidéo à utiliser. Si vous avez défini MPlayer, je vous conseil de laisser en AUTO. Néanmoins avec xine il est nécessaire de les spécifier, un bon choix est PLAYER_AO_ALSA pour la sortie audio et PLAYER_VO_XV pour la sortie vidéo.
Le paramètre qui suit indique le niveau de verbosité. Le meilleur choix est le niveau “warning”, qui permet de se rendre compte des potentiels problèmes sans pour autant remplir le terminal de messages.
L’avant dernier argument permet d’indiquer dans quel fenêtre X11 la vidéo sera affichée. Dans notre cas on en a aucune, alors la valeur 0 indiquera à libplayer de créer une fenêtre en plein écran (désactivez Compiz si nécessaire, les fenêtres créées de cette manière posent un problème majeur avec ce gestionnaire).
Le dernier paramètre permet de transmettre l’adresse de notre callback. Il est optionnel, passez NULL si vous ne voulez pas l’utiliser. Par contre il ne sera plus possible de récupérer l’information de fin de lecture.

Que fait player_init()? Et bien il se passe beaucoup de choses, je vais donc résumer les étapes. Cette fonction va créer une instance de player_t puis va initialiser trois éléments importants. Tout d’abord libplayer étant MT-Safe, il y a un superviseur créé pour chaque instance, qui va sérialiser les actions venant de l’utilisateur de la librairie pour les transmettre au wrapper.
Il y a un gestionnaire d’évènement qui a pour tâche de transmettre certaines informations venant du wrapper ou de l’interne à libplayer, pour l’utilisateur via le callback (event_cb() dans notre cas).
La 3ème étapes importante étant d’initialiser le wrapper, donc MPlayer (fork du process), xine, GStreamer ou VLC.

Initialisation d’un mrl

Un MRL dans libplayer représente un média. Cela peut être un fichier, un DVD, un CD audio, etc,… Dans notre exemple on se contentera des fichiers. Tout d’abord il faut créer une instance pour un MRL puis l’assigner à un player.
En fonction du type de MRL, les arguments peuvent changer. Dans notre cas, il nous faut ceux qui définissent un fichier. Il nous faut également un pointeur pour notre MRL.


mrl_resource_local_args_t *args;
mrl_t *mrl;

Créons ces arguments dynamiquement pour les paramétrer (args sera automatiquement libéré par libplayer en temps voulu).


args = calloc (1, sizeof (mrl_resource_local_args_t));

Et enfin, donnons les arguments et créons l’instance de notre MRL. Si mrl_new() a pu créer l’instance sans erreur, il ne faut surtout pas libérer args!


args->location = strdup (argv[1]);
mrl = mrl_new (player, MRL_RESOURCE_FILE, args);

Détails de mrl_new()

Cette fonction va créer une instance en fonction de la ressource (FILE dans notre cas) puis interroger le player pour récupérer quelques informations. Il n’est pas nécessaire de spécifier si le fichier est uniquement audio, ou vidéo. libplayer devinera automatiquement en fonction des propriétés retournées par le wrapper (MPlayer par exemple).

Assigner le mrl au player

Maintenant que nous avons un mrl de prêt, nous pouvons l’assigner au player puis lancer la lecture.


player_mrl_set (player, mrl);
title = mrl_get_metadata (player, NULL,
                          MRL_METADATA_TITLE);
if (title)
  printf ("Title: %sn", title);
player_playback_start (player);

Quand une instance est assignée au player, elle est automatiquement libérée dés l’appel à player_uninit(). En ce qui concerne mrl_get_metadata(), le fait de passer un mrl NULL indique que nous voulons récupérer l’information depuis le MRL actuellement défini dans player (on aurait pu mettre ‘mrl’ à la place, ce qui reviendrait au même).

Attendre la fin de la lecture

Il y a deux moyens d’attendre la fin de la lecture. Par attente active ou par attente passive. Afin de faire les choses de manière élégante, je vous propose de mettre en œuvre une attente passive via un semaphore initialisé à 0.

Exemple complet


#include <semaphore.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include <player.h>

static sem_t sem;

static int
event_cb (player_event_t e, void *data)
{
  if (e == PLAYER_EVENT_PLAYBACK_FINISHED)
    sem_post (&sem);

  return 0;
}

int
main (int argc, char **argv)
{
  player_t *player;
  mrl_resource_local_args_t *args;
  mrl_t *mrl;
  int res = 0;
  char *title;

  if (argc < 2)
    return -1;

  player = player_init (PLAYER_TYPE_MPLAYER,
                        PLAYER_AO_AUTO,
                        PLAYER_VO_AUTO,
                        PLAYER_MSG_WARNING,
                        0, event_cb);
  if (!player)
    return -1;

  sem_init (&sem, 0, 0);

  args = calloc (1, sizeof (mrl_resource_local_args_t));
  if (!args)
  {
    res = -1;
    goto out;
  }

  args->location = strdup (argv[1]);
  mrl = mrl_new (player, MRL_RESOURCE_FILE, args);
  if (!mrl)
  {
    free (args);
    free (args->location);
    res = -1;
    goto out;
  }

  player_mrl_set (player, mrl);
  title = mrl_get_metadata (player,
                            NULL, MRL_METADATA_TITLE);
  if (title)
  {
    printf ("Title: %s\n", title);
    free (title);
  }
  player_playback_start (player);

  sem_wait (&sem);

 out:
  player_uninit (player);
  sem_destroy (&sem);
  return res;
}

Il est bien clair que cet exemple n’est pas parfait (et pas totalement protégé). Si la lecture ne se termine jamais correctement, le processus reste en attente indéfiniment. Si cela devrait arriver (un fichier illisible par exemple), un kill lui règlera son compte, mais n’oubliez pas de killer également le processus fils “mplayer”. Celui-ci n’étant jamais terminé sans un appel à player_uninit().

Pour compiler cet exemple:

  gcc `pkg-config libplayer --cflags --libs` exemple.c -o exemple

A bientôt,

Mathieu SCHROETER