Mon objectif est de pouvoir lire des vidéos en stéréo 3d sous Linux avec différents types de lunettes à obturation LCD sur des écrans supportant une fréquence de balayage élevée (idéalement 120 Hz).

J'écris ces quelques lignes pour avoir une trace de mes idées, il y a probablement pas mal de choses erronées ou incohérentes dans ce qui suit, mais ça m'aide à essayer d'y voir un peu plus clair.

Formats vidéo

Pour mes premiers tests je suis parti sur une modification de MPlayer en essayant de me concentrer sur le format side-by-side en pleine résolution dans un premier temps. D'autres formats doivent pouvoir être facilement supportés par la suite : side-by-side half et above-and-below half et full. À priori MPlayer ne sait pas gérer le dualstream et aucune implémentation ne permet encore de décoder le format Blu-ray 3D (ssif), je mets ça de côté pour plus tard.

Types de lunettes

Ça ne « fonctionne » pour l'instant qu'avec des lunettes SimuleyesVR en mode WLC (White Line Code) ou des lunettes eDimensional avec l'ED activator en mode page flipping sous Wine. À terme le but est de supporter le maximum de types de lunettes à obturation (WLC/BLC, VGA DDC, 3D Vision, etc.).

J'ai déjà quelques bouts de code qui permettent d'envoyer un signal DDC avec certaines cartes graphiques NVIDIA (NV40 et NV50), mais rien pour l'instant pour d'autres types de lunettes. Quand j'aurais fait plus de progrès sur ce sujet, je collerai le tout dans une librairie.

Types d'écrans

Je fais les tests sur un écran CRT (ViewSonic P90f) connecté en VGA qui supporte du 120 Hz en 1024x768. À priori cette application ne devrait être destinée qu'à des écrans ou projecteurs supportant nativement des fréquences élevées, les autres types d'écrans (TV 3D) pouvant supporter nativement des formats side-by-side. Ça devrait limiter l'usage de l'application aux écrans CRT et aux projecteurs DLP.

État des lieux

Pour l'instant, l'application consiste en une modification du driver xv de MPlayer et est capable d'afficher les trames gauche et droite l'une après l'autre en mode fenêtré ou plein écran. Je ne peux pas lire de vidéos en 1080p (3840x1080) car elles dépassent la taille maximale d'une image X Video (2048x2048), mais ce problème doit pouvoir se régler en créant deux images séparées avant de les envoyer au driver xv.

Côté performances, la synchronisation avec le rafraîchissement de l'écran (120 Hz) « marchotte » pour des vidéos en 360p avec quelques sauts de trames, mon PC n'est pas assez puissant pour des résolutions supérieures (Athlon XP 2400+). J'ai modifié la fonction de la touche pause pour inverser les yeux quand ces sauts se produisent.

La boucle d'affichage est un hack assez moche qui consiste pour l'instant en 5 appels successifs à la fonction d'affichage (XvShmPutImage) avec des XSync intercalés.

J'envisage de faire les modifications suivantes pour améliorer la synchronisation et la vitesse de lecture :

  • décoder une trame vers deux images de type Xvimage (gauche et droite)
  • copier chaque image vers un pixmap en mémoire vidéo (avec XvShmPutImage)
  • utiliser un thread d'affichage qui affiche successivement ces deux pixmaps en synchronisation avec le retour vertical (avec des XCopyArea ou des XRenderComposite)

Pour ce faire, il faut régler les problèmes suivants :

  • modifier X.org pour que XvShmPutImage puisse écrire vers un Drawable de type Pixmap. Pour l'instant il n'est capable de le faire que vers un Drawable de type Window. Des patches ont été proposés pour corriger le problème mais ils n'ont pas encore été intégrés et il n'est pas sûr qu'ils soient assez complets pour gérer ce problème correctement.
  • vérifier que le driver NVIDIA est capable de gérer l'écriture vers un Pixmap, sinon il faudra sans doute essayer avec le driver libre Nouveau et le modifier si besoin est.
  • trouver une méthode de synchronisation avec le retour vertical. Je pars sur l'idée d'utiliser les fonctions glXGetVideoSyncSGI et glXWaitVideoSyncSGI qui nécessitent un contexte OpenGL courant. Voir aussi XF86VidSetViewPort, XSync ne semblant pas être très adapté pour cette tâche (round-trip complet de et vers le serveur). Ça semble fonctionner correctement selon mes premiers tests, même avec une charge CPU annexe assez importante et sans avoir de busy-wait.
  • vérifier que l'on peut effectivement utiliser un thread pour la partie rendu, X Window n'étant pas réentrant il y aura probablement des choses à gérer à ce niveau, probablement en utilisant les fonctions XInitThreads, XLockDisplay et XUnlockDisplay.
  • faire en sorte que le thread ait une priorité suffisamment haute et qu'il utiliser un scheduler adapté pour ne pas rater de trames.
  • trouver un moyen de détecter les sauts de trames, probablement avec un mix de glXGetVideoSyncSGI et d'un timer.
  • voir si l'utilisation de DBE (Double Buffer Extension) pourrait apporter quelque chose.
  • en cas de difficulté avec la synchronisation, étudier X Synchronization Extension et le KMS page flip ioctl récemment introduit dans le kernel (2.6.33).

Pistes écartées

J'ai déjà testé plusieurs pistes que j'ai écartées par la suite :

  • utilisation d'OpenGL : ça nécessite de convertir les trames de la vidéo qui sont en YV12 vers du RGB pour l'affichage. Il est possible d'implémenter cette conversion avec un fragment shader mais c'est beaucoup plus lent que X Video. C'est la technique qu'utilise sView (en utilisant libswscale) mais ça va à deux à l'heure sur mon PC.
  • utilisation de deux fenêtres : j'ai essayé d'envoyer des images YV12 vers deux fenêtres indépendantes en modifiant leur ordre d'affichage avec des XRaiseWindow/XLowerWindow et XRestackWindows mais je n'ai pas réussi à synchroniser avec le retour vertical. Et je ne pense pas que ce soit une solution assez bas niveau pour obtenir des résultats concluants (passe par le window manager).

Voilà, c'est tout pour aujourd'hui, maintenant dodo... :)