4

I have a program, mpg123, which has an interactive mode that allows keyboard commands from stdin to do useful things such as control volume. I am trying to start up mpg123 so that it reads commands from a named pipe; that way I can have other programs interact with it.

In one terminal I do the following:

mkfifo pipe
tail -n1 -f pipe | mpg123 -vC /some/song.mp3

And in another terminal I do the following:

cat > pipe
-

Now, I was expecting that the - would be sent to the mpg123 program exactly the same as if I was sitting in that terminal smacking the - key, but that is not what happens. Can anyone please tell me what I am doing incorrectly?

4

It seems that -C causes mpg123 to read from the terminal, not from stdin. I see this, however, in my version of mpg123's man page:

-R, --remote
       Activate  generic  control interface.  mpg123 will then read and
       execute commands from stdin. Basic usage is ``load <filename> ''
       to  play some file and the obvious ``pause'', ``command.  ``jump
       <frame>'' will jump/seek to a given point (MPEG  frame  number).
       Issue ``help'' to get a full list of commands and syntax.

This may be what you are looking for; try mpg123 -vR <pipe. The interaction in your example would become something like the following (this sets the volume to 30%):

$ cat >pipe
load /some/song.mp3
volume 30

But then, what does -C do that -R doesn't that results in the former mode failing to read from stdin when a named pipe, rather than a terminal, is connected?

A quick look at the mpg123 source code indicates that it uses the termios facilities to read keypresses from the terminal, using tcsetattr to put it in the so-called "non-canonical mode", where keypresses are transmitted to the reader without further processing (in particular, without waiting for a complete line to have been typed):

struct termios tio = *pattern;
(...)

tio.c_lflag &= ~(ICANON|ECHO);
(...)

return tcsetattr(0,TCSANOW,&tio);

(This is the same as the GNU libc code sample.)

Then, in a loop, a function get_key is called, which uses select to tell whether file descriptor 0 (stdin) has data available and, if so, reads one byte from it (read(0,val,1)). But this still doesn't explain why a terminal works but a pipe doesn't! The answer lies in the terminal initialization code:

/* initialze terminal */
void term_init(void)
{
    debug("term_init");

    term_enable = 0;

    if(tcgetattr(0,&old_tio) < 0)
    {
        fprintf(stderr,"Can't get terminal attributes\n");
        return;
    }
    if(term_setup(&old_tio) < 0)
    {
        fprintf(stderr,"Can't set terminal attributes\n");
        return;
    }

    term_enable = 1;
}

Note that if either tcgetattr or term_setup fails, then term_enable is set to 0. (The function to read keys from the terminal starts with if(!term_enable) return 0;.) And, indeed, when stdin isn't a terminal, tcgetattr fails, the corresponding error message is printed, and the keypress-handling code is skipped:

$ mpg123 -C ~/input.mp3 <pipe
(...)
Can't get terminal attributes

This explains why attempting to send commands by piping into mpg123 -C fails. That's a debatable choice by the implementors; presumably by simply allowing tcgetattr / tcsetattr to fail (perhaps by using a switch for that purpose), instead of disabling the keypress-handling code handling, your attempt would have worked.

3
  • 1
    Thanks for that, but I am still unclear how reading from the terminal is different from reading from stdin. Is there then a /dev/tty device somewhere that I can pipe commands to and have the "mpg123 -C" see that input? (I am trying to understand what is happening better so I can apply this to future, non-mpg123 problems) Thanks!
    – virosa
    Apr 29 '15 at 1:27
  • stdin from a program is by default connected to the tty, but they don't have to be the same. You can read/write to the tty as discussed unix.stackexchange.com/a/138390/109842 Apr 29 '15 at 2:15
  • @virosa: I will edit my answer to add more information. Long story short, mpg123 expects an actual terminal when -C is specified, and just ignores commands if something else (such as a pipe) is connected to stdin.
    – dhag
    Apr 29 '15 at 2:21

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.