Une petite rétrospective de libplayer

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


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

Stopper la lecture

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

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

Détecter la fin de la lecture

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

Mais encore…

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

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

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

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

Maximum: 80

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

A bientôt,

Mathieu SCHROETER

Advertisement

One thought on “Une petite rétrospective de libplayer

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s