Forum Programmation.c Gestion STDIN + pilotage Mplayer

Posté par  .
Étiquettes : aucune
0
6
juin
2007
Bonjour à tous, amis linuxiens,

J'essaie de réaliser un programme sous linux, et je suis dans l'embarras.

Mon programme se lance.

Il fork pour lancer une video Vboucle en boucle dans mplayer. Cette vidéo qui tourne à l'infini est celle qui sert de video d'"attente".

En effet, j'ai un lecteur de codes barres sur port usb. Lorsque j'appuie sur le bouton du lecteur/douchette, le code en caractères correspondant au code barre exposé au lecteur est transmis à STDIN, exactement comme si je l'avais tapé manuellement au clavier. Mon programme doit récupérer la chaine de caractères donnée par le lecteur de code barre.

En fonction du code barre reçu. Une autre vidéo (Vrésultat) est lancée dans mplayer (l'ancien mplayer est alors killé): pas en boucle cette fois, elle est jouée une fois et une seule, puis c'est reparti pour la vidéo d'attente en boucle.
Pendant que la vidéo Vrésultat est en cours de lecture, je compte inhiber le lecteur de code barre (ça je sais pas encore comment), et le réactiver une fois que Vboucle tourne à nouveau.

Mon gros problème:
A partir du moment ou je fork et que j'execl mplayer pour lire la video en boucle, c'est mplayer qui récupère les pressions au clavier, et lorsque je lis un codebarre avec ma douchette, c'est mplayer qui récupère tout, et donc ça va pas du tout du tout (c'est comme si lors d'une lecture de vidéo dans mplayer, vous appuyiez un peu partout sur le clavier dont certaines touches sont affectées à des fonctions diverses).

J'ai consulté de l'aide ça et là, on m'a dit que:
- le processus enfant (qui spawn mplayer par la suite) hérite du stdin de son père (mon programme). On m'a conseillé dans un premier temps de rediriger STDIN du processus fils vers /dev/null à l'aide de dup2. Pas concluant du tout, mplayer continue à récupérer les évènements clavier (et donc le code barre).
- on m'a conseiller d'essayer les paramètres -noconsoleinputs (ou un truc du genre, j'ai pas mon code sous les yeux au moment où je vous écris) --> pas concluant non plus.
- on m'a conseiller d'essayer le mode -slave. --> pas concluant non plus.

Lorsque dans Gnome, je lance mplayer depuis un shell en ligne de commande, je remarque que d'une part il écrit dans le terminal. Mais que d'autre part une fenetre graphique est ouverte, celle-là même dans laquelle est affichée la vidéo. Cette fenêtre graphique récupère le focus de la part de X (donc clavier + souris), et je me demande si ça viendrait pas de là.

Me souvenant que mplayer n'a pas forcément besoin du serveur X pour pouvoir afficher de la video plein écran, j'ai récupéré le fichier de configuration mplayer.conf de GeexBox, pour lancer par la suite mon programme depuis TTY1 (ctrl+alt+F1) en root, et voir ce que ça donne avec un mplayer qui n'ouvre plus une fenetre de window manager, mais écrit directement dans le framebuffer. Manque de pot, sous ubuntu, avec mes drivers ATI proprio, il parait que j'ai pas de framebuffer (on m'a fait lancer un ptit programme qui écrit directement en FBO,et ça m'affiche des "A" rouge plein l'écran).

Très alambiqué tout cela. Mon problème se résume à vouloir lancer un programme d'une part réceptionne des codesbarres de STDIN, et d'autre part pilote mplayer, SANS que mplayer soit sensible à aucun évènement clavier.

Ah oui, dernière chose, on peut effectivement configurer mplayer pour mapper les fonctions qu'on veut, aux touches qu'on veut. j'ai essayé de virer tous les mappages, mais même sans mapping, les touches sont interceptées quand même.

J'aimerai que mon programme garde en somme l'"exclusivité" sur STDIN. Non de dieu!!!

Merci d'avance de votre aide!
  • # Sémantique, architecture, toussa ...

    Posté par  . Évalué à 4.

    Si tu te perds, c'est parce que tu essaies de résorber les symptômes plutôt que de résoudre le problème de fond.

    D'abord, comme tous les programmes, ton application et le mplayer que tu lances derrière héritent tout deux de la console de ton terminal X.

    Ensuite, mplayer, en plus d'être doté d'un jeu de handlers fichier stdout,stdin, stderr comme tout le monde, ouvre une connexion vers le serveur X, qui est complètement indépendante de sa console, et qui peut très bien atteindre une machine distincte. Et c'est depuis les événements X que mplayer traite les commandes de navigation de l'utilisateur, pas depuis stdin.

    Avec cela, en environnement graphique, l'application qui reçoit les événements de la console, c'est le serveur X lui-même (et en fait ce n'est même plus tout-à-fait vrai car Xorg ou Xfree86 gèrent eux-même le clavier et les périphs d'entrées). C'est aussi eux qui les traduisent en événements dans l'envrionnement graphique et qui les distribuent aux applications concernées (en fonction du focus et avec la complicité du WM).

    D'autre part, à priori, ta douchette est vue comme un clavier, et est gérée comme tel par le système lui-même. C'est ce qui fait que tu n'a rien à configurer lorsque tu branches un clavier USB supplémentaire. Donc, au départ, tes applications ne savent pas du tout que tu es en train d'utiliser une douchette.

    Ton problème, donc, c'est qu'il te faudrait deux focus. L'un provenant du clavier et ciblant la fenêtre vidéo de mplayer et l'autre, provenant de la douchette, sur la fenêtre du x-term qui fait tourner ton programme.


    En conséquence, ton premier souci consiste à être parfaitement sûr de ce que tu veux faire :

    - Soit tu souhaites conserver le fonctionnement normal de tes applications. Dans ce cas, c'est à toi de cliquer manuellement sur la fenêtre du X-term lorsque tu veux utiliser la douchette et sur celle de mplayer quand tu veux naviguer. C'est très fastidieux, mais c'est ce que tu ferais naturellement quand même si tu rentrais tes codes barre à la main ... Ca te permet également d'aller balancer la sortie de ta douchette dans un notepad au besoin, sans qu'elle soit interceptée par ton application ...

    Tu peux aussi rentrer en mode slave pour ne pas avoir à faire du ping/pong mais cela t'oblige à réimplémenter TOUTES les commandes clavier de mplayer et à jouer avec termios pour récupérer l'état des touches avant d'appuyer sur return, quand c'est possible.

    - Soit tu cherches à faire en sorte que la douchette ne serve qu'à zapper les vidéos lorsqu'elles tournent et dans ce cas, cela signifie implicitement qu'aucun autre programme ne doit recevoir ses caractères pendant que ton application tourne. Et dans ce cas, il y a donc une formalité à accomplir au préalable. Demander au système l'exclusivité sur la douchette.

    Ca se gère donc au niveau de la gestion des HID par le noyau Linux. C'est normal que cela se trouve à ce niveau, d'ailleurs : il existe d'autres douchettes qui se branchent "en série" sur le cordon PS/2 en s'insérant entre le clavier et l'unité centrale. Dans un tel cas de figure, il t'est impossible de distinguer la douchette du clavier.

    Dès lors, la meilleure solution consiste à écrire un daemon qui surveille silencieusement l'activité du clavier et reconnaît les trames susceptibles d'être envoyées par la douchette et qui, le cas échéant, prévient les programmes concernés via un canal de communication propre (un signal, un segment de RAM partagée ou , de préférence, un socket UNIX).
    • [^] # Re: Sémantique, architecture, toussa ...

      Posté par  . Évalué à 1.

      Très intéressante réponse que voici. Tout d'abord, un grand merci pour avoir pris le temps de me répondre et m'expliquer autant de choses.

      Pour clarifier ma situation et ce que je souhaite faire:
      Je réalise une sorte de borne ou on file un ticket avec un code barre, et en fonction du code barre, on gagne ou on perd.

      La borne est un truc interactif grand public. En permanence à l'écran, il faut qu'il y ait une vidéo d'affichée. Que ce soit la vidéo d'attente tournant en boucle, pendant qu'on attend qu'une personne viennent passer son ticket, ou que ce soit, ponctuellement, un vidéo annonçant un grand gagnant, ou un perdant.

      S'il y a un truc que j'apprécie avec unix, c'est l'approche terre à terre, simple, rationnelle, qui consiste à dire: ya des outils qui font très bien un boulot : pas la peine de réinventer la roue, on utilise ce qui éxiste déjà.
      C'est à partir de là que je me suis dit: pour l'affichage, puisqu'il y a en permanence de la vidéo affichée en plein écran, autant utiliser un logiciel qui le fait bien et qui semble offrir pas mal d'options niveau pilotage par un programme tiers: Mplayer (et plutôt tout-terrain niveau formats en entrée qui plus est).

      Donc mon utilisation de Mplayer si tu veux, c'est juste pour balancer de la vidéo à l'écran, je n'ai besoin d'absolument aucune des fonctions d'interaction utilisateur qu'offre mplayer pour une utilisation disons "classique".

      Mon but est donc d'avoir MON PROGRAMME, qui écoute STDIN pour des codes barres, fait sa petite mayonnaise, et pilote mplayer pour afficher telle ou telle vidéo.

      En permanence, l'écran de l'ordinateur doit être occupé par une vidéo plein écran. Pas de bureau visible, pas de terminal visible, c'est pour une exposition en public.

      J'ai donc un autre problème: arriver à mettre mplayer en plein écran (pour le moment, lorsque je switch en plein écran, j'ai la vidéo tjs à 100% de sa taille, elle est juste centrée avec du noir pour remplir le reste de l'écran et non pas maximisée....).

      Je compte sur toi pour m'éclairer !

      Merci beaucoup d'avance.

      Cdt.
      • [^] # Taille vidéo mplayer

        Posté par  (site web personnel) . Évalué à 1.

        Pour la taille de la vidéo en plein écran, utilise -vo xv ou vo=xv dans le fichier de conf de mplayer.
      • [^] # Re: Sémantique, architecture, toussa ...

        Posté par  . Évalué à 1.

        je pense que dans ton cas (une borne interactif), le plus simple ca serait vraiment de virer le serveur X et de passer en framebuffer.
        en framebuffer, mplayer devrait se contente de stdin pour le pilotage et donc si tu fermes stdin pour mplayer, il ne captera plus les caracteres envoyes par la douchette. tu pourrais meme piloter mplayer depuis ton application et eviter de le killer/relancer a chaque fois en utilisant un pipe.
        te reste plus qu'a changer de carte graphique!
  • # Piloter stdin et stdout d'un fils

    Posté par  (site web personnel) . Évalué à 1.

    Pour piloter le stdin et le stdout d'un fils, il faut, juste après le fork(), fermer stdin et stdout et faire un dup() de deux descripteurs de fichier au préalable défini sur respectivement stdin et stdout. Les deux descripteurs de fichier étant connu du père, en écrivant dessus, on pilote mplayer (avec de préférence -slave mais pas obligé).

    Exemple de code repompé d'un d'un de mes vieux projets (le programme à lancer s'appelait apt, rien à voir avec Debian) par flemme de chercher dans Google:

    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h> // exit()
    #include <string.h> // memset(), strcat()...
    
    #include "apt.h"
    #include "msg.h"
    #include "init.h"
    #include "parse.h"
    
    #define STDIN 0
    #define STDOUT 1
    #define STDERR 2
    
    
    /* The size should be around 500, because of the mvts answer */
    #define APT_OUTPUT_LINE_MAX_LENGTH 1000
    
    extern config* current_configuration;
    
    int APTIN_K,APTOUT_K;
    FILE* STREAM_IN;
    FILE* STREAM_OUT;
    
    void APTlaunch(const char *apt_path,int* aptin_k,int* aptout_k){
    
      int childPid;
      int pipefdin[2],pipefdout[2];
    
      /* Pipes init */  
      if( pipe(pipefdin) == -1 || pipe(pipefdout) == -1 ){
        perror("Creating apt communication pipes");
        exit(1);
      }
    
      childPid = fork();
      
      if( !childPid ){
    
        // son, so we launch apt and we connect
        // its standards channels to pipes
    
        // close unused channels
        close(pipefdin[1]);
        close(pipefdout[0]);
    
        // Duplicate stdin to our pipe
        close(STDIN);
        if( dup(pipefdin[0]) == -1 ){
          perror("duplicating apt stdin");
          exit(1);
        }
    
        // Duplicate stdout to our pipe
        close(STDOUT);
        if( dup(pipefdout[1]) == -1 ){
          perror("duplicating apt stdout");
          exit(1);
        }
    
        // Duplicate stderr to the same pipe as stdout
        close(STDERR);
        if( dup(pipefdout[1]) == -1 ){
          perror("duplicating apt stderr");
          exit(1);
        }
    
        // Launch apt
        if( execl(apt_path,apt_path,NULL) == -1 ){
          perror("can't find apt");
          exit(1);
        }
    
      }else{
    
        // father, this process, continues execution
    
        // Close unusable channels
        close(pipefdin[0]);
        close(pipefdout[1]);
    
        // Return the right values to control the apt
        *aptin_k = pipefdin[1];
        *aptout_k = pipefdout[0];
    
      }
    
    }
    
    void APTstart(const char* apt_path){
    
      // Launch the apt upon the connection
      APTlaunch(apt_path,&APTIN_K,&APTOUT_K);
    
      // Use stdio to manipulate the commands
      STREAM_IN = fdopen(APTIN_K,"w");
      STREAM_OUT = fdopen(APTOUT_K,"r");
    
    }
    
    void APTstop(){
      write(APTIN_K,"q\n",2);
    }
    
    int APTloadDataFile(const char* filename){
      int written;
      
      written = fprintf(STREAM_IN,"load %s%s\n",current_configuration->apt_data_path
    ,filename);
      fflush(STREAM_IN);
    
      return written;
    }
    
    int APTsendCommand(const char* cmd){
      int written;
      
      written = fprintf(STREAM_IN,"%s\n",cmd);
      fflush(STREAM_IN);
    
      return written;
    }
    
    char* APTgetNextLine(){
      char* line;
      line = (char*)malloc(APT_OUTPUT_LINE_MAX_LENGTH*sizeof(char));
    
      fgets(line,APT_OUTPUT_LINE_MAX_LENGTH,STREAM_OUT);
    
      // Careful, the \n is still in line
      return line;
    }
    
    char* APTgetOutputTillEmptyLine(){
      char* buffer;
      lineList* acftList = (lineList*)malloc(sizeof(lineList));
      lineList* otherLines;
      int byteCounter = 0;
      
      acftList->line = APTgetNextLine();
      acftList->nextLines = NULL;
      otherLines = acftList;
    
      if( acftList->line[0] != '\n' ){
        buffer = APTgetNextLine();
    
        while( buffer[0] != '\n' ){
          byteCounter += strlen(buffer);
          otherLines = addToLineList(otherLines,buffer);
          buffer = APTgetNextLine();
        }
      }
    
      buffer = lineList2string(acftList,byteCounter);
      freeLineList(acftList);
    
      return buffer;
    }
    
    void APTflushOutput(){
      char* line = APTgetNextLine();
    
      while( strcmp(line,"\n") ){
        free(line);
        line = APTgetNextLine();
      }
      free(line);
    }
    
    • [^] # Re: Piloter stdin et stdout d'un fils

      Posté par  . Évalué à 1.

      Je ne cherche pas vraiment à "piloter" mplayer, tout ce que je souhaite, c'est lancer une vidéo en plein écran:
      en forkant/execl on dit à mplayer de lire la vidéo en boucle, dans quel cas suffit d'envoyer un signal à mplayer pour le faire taire par la suite. Pendant ce temps je veux pouvoir faire ma petite cuisine avec la douchette code barre sur STDIN. Je veux que MPLAYER ME FOUTE LA PAIX, Qu'il ne soit PAS sensible à aucune pression clavier. Je me sers juste de Mplayer comme "afficheur de vidéo", that's all.

      L'autre cas plus simple est la lecture d'une vidéo une seule fois, dans quel cas, mon programme fait juste un waitpid() pour attendre que mplayer quitte à la fin de la lecture.

      Dans tous les cas, mon problème est que Mplayer me laisse tranquille et que mon programme garde la main sur STDIN.

      SVP, vous là, oui, vous, je suis sûr que vous savez comment résoudre mon problème. Ayez pitié :°)
      • [^] # Re: Piloter stdin et stdout d'un fils

        Posté par  (site web personnel) . Évalué à 1.

        Et pourquoi donc ne fermes-tu pas stdin après avoir forké vers mplayer (comme fait dans l'exemple que je te donne)?
        • [^] # Re: Piloter stdin et stdout d'un fils

          Posté par  . Évalué à 1.

          Salut,

          Je le fais déjà en fait. Mais cela ne résoud pas le problème. Lorsqu'on lance mplayer, utilise non seulement stdin/stdout, mais il ouvre également sa propre fenêtre pour afficher la vidéo. Lorsque cette fenêtre est créée, elle prend le focus dans X qui la branche sur les évènements clavier+souris (!=stdin). Même en atomisant stdin de mplayer, la fenêtre affichant la vidéo reste encore et toujours sensible aux pressions clavier (& donc à la lecture d'un code barre).... :/

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.