mini2440上实现网络电台播放

上次说到要实现一个无线鼠标控制的网络电台播放程序(远程控制:)),今天有空就实现了。主要的难点就是读取鼠标数据,昨天花了一天时间看了linux kernel的driver/input/mousedev.c源代码,看懂了数据格式和控制原理,就很简单了:)。

鼠标的设备文件是/dev/input/mice,默认读取的是PS2数据,分3个字节,分别是3个按键、X、Y坐标。不过要注意的是XY坐标都是相对坐标(不超过127,有符号),如果要获得滚轮数据需要打开im扩展,直接向mice写数据串{ 0xf3, 200, 0xf3, 100, 0xf3, 80 }就行了,这个在mousedev.c写的很清楚了,PS2格式参考看这里

解决了鼠标控制,具体播放就开个进程放mplayer,把所有电台URL放在playlist里就行了,mplayer支持从标准输入的按键控制,0和9控制音量,<和>对应前进和后退,p是暂停。进程间通信就用pipe,不过建pipe,绑定stdin有点麻烦,有现成的API做了这些了,popen和pclose就可以搞定这些。最后用鼠标左键前翻,右键后翻,中键暂停,滚轮控制音量。

Talk is cheap, show me the code: github

// @author: lbzhung
// @brief:  just a network radio player and control by mouse.
//          using mplayer as backend, left button previous, right button forward and middle button pause.
// @see  linux kernel drivers/input/mousedev.c
//       http://www.computer-engineering.org/ps2mouse/
//       
#include <cstdio>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>

typedef unsigned char BYTE;

#pragma pack(1)
struct imps2_data
{
    BYTE btn_left:1;
    BYTE btn_right:1;
    BYTE btn_middle:1;
    BYTE NONE:1;
    BYTE x_sign:1;  // x offset sign
    BYTE y_sign:1;  // y offset sign
    BYTE x_overflow:1; // x offset is overflow
    BYTE y_overflow:1; // y offset is overflow

    // x/y movement offset relative to its position
    signed char x;
    signed char y;
    signed char z;
};
#pragma pack()

int main(int argc, char *argv[])
{
    BYTE mousedev_imps_seq[] = { 0xf3, 200, 0xf3, 100, 0xf3, 80 };
    int mice_fd = open("/dev/input/mice", O_RDWR|O_NONBLOCK);
    if (mice_fd == -1)
    {
        fprintf(stderr, "Open mice fail");
        return 1;
    }

    // set mice mode, so can read rolling wheels
    int nRet = write(mice_fd, mousedev_imps_seq, sizeof(mousedev_imps_seq));
    if (nRet < 0)
    {
        fprintf(stderr, "set mice imps2 fail\n");
        return 1;
    }


    FILE *stream = popen("mplayer -quiet -softvol -softvol-max 300 -playlist channel.txt", "w");
    if (stream == NULL || stream < 0)
    {
        fprintf(stderr, "popen mplayer fail\n");
        return 1;
    }

    while (true)
    {
        fd_set fdset;
        //struct timeval tv;
        //tv.tv_sec = 0;
        //tv.tv_usec = 200000;

        FD_ZERO(&fdset);
        FD_SET(mice_fd, &fdset);

        int ret = select(mice_fd+1, &fdset, NULL, NULL, NULL);
        if (ret < 0)
        {
            fprintf(stderr, "select return error\n");
            return 1;
        }
        else
        {
            if (FD_ISSET(mice_fd, &fdset))
            {
                imps2_data data;
                while (true)
                {
                    int nLen = read(mice_fd, &data, sizeof(data));
                    if (nLen == 0)
                    {
                        fprintf(stderr, "End of file \n");
                        return 1;
                    }
                    else if (nLen == -1)
                    {
                        if (errno != EAGAIN)
                        {
                            fprintf(stderr, "read fail\n");
                            return 1;
                        }
                        break;
                    }
                    else
                    {
                        // nLen = 1 is ack
                        if (nLen == sizeof(data))
                        {
                            //printf("nLen:%d, left:%d right:%d middle:%d X:%d Y:%d Z:%d\n",
                            //        nLen, data.btn_left, data.btn_right, data.btn_middle, data.x, data.y, data.z);
                            if (data.btn_left)
                            {
                                if (data.btn_right)
                                {
                                    // OK quit
                                    close(mice_fd);

                                    fprintf(stream, "q");
                                    fflush(stream);

                                    // wait process exit
                                    pclose(stream);
                                    return 0;
                                }

                                // preivous
                                fprintf(stream, "<");
                                fflush(stream);
                            }
                            else if (data.btn_right)
                            {
                                // forword
                                fprintf(stream, ">");
                                fflush(stream);
                            }
                            else if (data.btn_middle)
                            {
                                // pause
                                fprintf(stream, "p");
                                fflush(stream);
                            }
                            else if (data.z != 0)
                            {
                                if (data.z > 0)
                                {
                                    // rolling down
                                    fprintf(stream, "9");
                                    fflush(stream);
                                }
                                else
                                {
                                    // rolling up
                                    fprintf(stream, "0");
                                    fflush(stream);
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    return 0;
}

Linux pipe读写问题

之前的那个mplayer播放mms电台需要一个控制接口,mplayer只支持控制台输入命令,所以要控制mplayer只能用管道控制标准输入stdin。为了灵活就用FIFO文件作为mplayer的输入端。

mkfifo radio_pipe
cat radio_pipe | mplayer mms://xxx
echo -n p > radio_pipe

这里有个问题是cat radio_pipe读到文件EOF会退出,导致mplayer退出,所以每次只能控制一次:(。

前几天看mini2440附带的led-player程序,突然意识到可以自己写个pipe_read程序忽略文件EOF,这样就解决了mplayer的控制。记得之前写TCP程序时知道read返回0说明到文件EOF了,TCP就代表关闭了。我只要忽略掉read返回0的状态就行了(之后证明大错特错-_-!!!),错误是因为select认为文件EOF是读信号,除非你退出否则一直触发。最后的解决方法是这一句

int null_pipe_write_rd = open(pstrPipe, O_WRONLY | O_NONBLOCK);

再打开一个不操作的pipe读fd,这样才不至返回文件EOF,具体原因不是很明白:(,不过达到了我要的效果:)。

mkfifo radio_pipe
./pipe_read radio_pipe | mplayer mms://xxx
echo -n p > radio_pipe

这样就不会退出了,不过就如我们CEO说的一句话:解决了一个问题,往往带来了其他问题,这样pipe_read永远也不会退出了,尽管mplayer已经退出了-_-!!!。

Talk is cheap, show me the code:

#include <cstdio>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>

void usage(const char *pstrProgram)
{
    printf("Usage:\n"
           "  %s pipefile\n", pstrProgram);
}


int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        usage(argv[0]);
        return 1;
    }

    bool bExit = false;
    const char *pstrPipe = argv[1];

    int pipe_rd = open(pstrPipe, O_RDONLY | O_NONBLOCK);
    if (pipe_rd == -1)
    {
        fprintf(stderr, "open pipe fail\n");
        return 1;
    }

    int null_pipe_write_rd = open(pstrPipe, O_WRONLY | O_NONBLOCK);

    while (!bExit)
    {
        fd_set fdset;
        //struct timeval tv;

        FD_ZERO(&fdset);
        FD_SET(pipe_rd, &fdset);

        int ret = select(pipe_rd+1, &fdset, NULL, NULL, NULL);

        if (ret < 0)
        {
            fprintf(stderr, "select return error\n");
            return 1;
        }
        else
        {
            if (FD_ISSET(pipe_rd, &fdset))
            {
                char buffer[128];
                while (true)
                {
                    int nLen = read(pipe_rd, buffer, sizeof(buffer));
                    if (nLen == 0)
                    {
                        fprintf(stderr, "End of file\n");
                        break;
                    }
                    else if (nLen == -1)
                    {
                        if (errno == EAGAIN)
                        {
                            fprintf(stderr, "nonblocking read fail\n");
                        }
                        else
                        {
                            fprintf(stderr, "read fail\n");
                            return 1;
                        }
                        break;
                    }
                    else
                    {
                        // write stdout
                        int ret = write(STDOUT_FILENO, buffer, nLen);
                        if (ret == -1 && errno != EAGAIN)
                        {
                            fprintf(stderr, "write fail\n");
                            return 1;
                        }
                    }
                }
            }
        }
    }

    return 0;
}

mplayer在线收音机

最近搬家后一直苦于没有电视看(想念以前的IPTV,看不完的电影和电视:)),所以我一直想在mini2440板子上做一个在线收音机弥补一下,正好有个音响空着。一开始用的方案是这里

wget -q -O -http://example.com/mystream | madplay -Q --no-tty-control-

可惜的是只支持mp3类型的广播(这个只有国外的shoutcast,而且貌似都是私人的,中文的很少,只听到台湾一哥们几年前的录音-_-!!!)。madplay里面是MAD定点解码库只支持mp3之类的,要想播放mms需要微软的解码库,这个估计没戏。而国内的网络电台清一色的mms,没办法继续找。

好不容易找到mplayer,花了大力气交叉编译好,发现居然有15M--!!!。好吧,随便找个中国之声的电台测试,在板子上接上音响,发现效果很好,以后吃饭时候听广播就靠它了。现在的问题是播放只能远程登陆到mini2440上启动mplayer,下一步是做到远程控制。初步的方案是用无线鼠标HID控制,切换电台和控制声音(之前还想到过用人声音控制,例如拍手切换电台,这个估计不现实吧--!!!)。

这里记几个mplayer的命令:q退出,9和0控制声音,p Pause。

BTW:最近这段时间天天加班,这个要什么时候能弄完啊!!!,之前想写的FTP服务器还没写呢:(