在C程序上执行omxplayer,通过execve / l将不会在fork()之后在子进程的非X控制台上输出视频

时间:2013-07-01 09:16:33

标签: c linux video raspberry-pi execve

HY。

我正在尝试通过execve或execl函数在C fork()之后在Raspberry Pi上执行omxplayer(http://elinux.org/Omxplayer),以便我可以为视频播放过程保存PID(因此System不会做的工作)。如果我在X控制台/终端上执行该程序,它可以工作,但如果它通过标准终端(不启动X),它将运行,但如果在子进程上调用execve,则不会将视频输出到屏幕。顺便说一句,在控制台中通过“omxplayer ...”commnad执行播放器将播放视频并输出到屏幕。我对这类事情有点新意,所以这是一个我无法解决或找不到答案的情况。这里的任何人都有一个关于如何解决这个问题的想法或一个让我找到可能的解决方案的方向吗?

注意:代码只是一个我知道是正确的execve调用,因为在X中它完美无缺。

1 个答案:

答案 0 :(得分:4)

execve()调用为执行的程序提供了一个新环境。要使程序能够访问X显示,您需要至少保留某些环境变量 - DISPLAY。您是否无意中从新环境中省略了DISPLAY

要让OMXPlayer在没有X的情况下工作,它必须能够访问视频设备本身(/dev/video,在这种情况下;有关详细信息,请参阅OMXPlayer builds页面)。它通常配置为允许video组的所有成员访问它。

您可以在程序中使用popen("id -Gn", "r")来运行列出当前组成员身份的id -Gn命令。 (从文件句柄中读取列表作为字符串,然后使用pclose()关闭它。)如果列表不包含video,则问题是运行原始文件的用户的权限程序不包括对视频设备的访问权限。修复很简单:将video添加到用户所属的组中。


以下是一个示例程序run.c,用于说明execvp()的基本用法:

#include <unistd.h>

/* For the example main(): */
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>

/* Try executing a command in a child process.
 * Returns the PID of the child process,
 * but does not tell whether the execution was
 * successful or not.
 * Returns (pid_t)-1 with errno set if fork() fails.
*/
pid_t run(char *const command[])
{
    pid_t   child;

    child = fork();
    if (child == (pid_t)-1)
        return (pid_t)-1;

    if (!child) {
        execvp(command[0], command);
        _exit(127);
    }

    return child;
}

int main(int argc, char *argv[])
{
    pid_t child, p;
    int   status;

    if (argc < 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
        fprintf(stderr, "\n");
        fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
        fprintf(stderr, "       %s COMMAND [ ARGUMENTS .. ]\n", argv[0]);
        fprintf(stderr, "\n");
        return 1;
    }

    child = run(argv + 1);
    if (child == (pid_t)-1) {
        fprintf(stderr, "%s: %s.\n", argv[1], strerror(errno));
        return 1;
    }

    fprintf(stderr, "(%s: PID %d)\n", argv[1], (int)child);
    fflush(stderr);

    do {
        p = waitpid(child, &status, 0);
        if (p == (pid_t)-1 && errno == EINTR)
            continue;
    } while (p != child && p != (pid_t)-1);
    if (p == (pid_t)-1) {
        fprintf(stderr, "(%s: %s.)\n", argv[1], strerror(errno));
        return 1;
    }

    if (WIFEXITED(status)) {
        if (WEXITSTATUS(status) == 127)
            fprintf(stderr, "(%s: Could not execute command.)\n", argv[1]);
        else
        if (WEXITSTATUS(status) == 0)
            fprintf(stderr, "(%s: Exited successfully.)\n", argv[1]);
        else
            fprintf(stderr, "(%s: Exited with error %d.)\n", argv[1], WEXITSTATUS(status));
    } else
    if (WIFSIGNALED(status))
        fprintf(stderr, "(%s: Killed by %s.)\n", argv[1], strsignal(WTERMSIG(status)));
    else
        fprintf(stderr, "(%s: Died from unknown causes.)\n", argv[1]);

    return status;
}

您可以使用例如

编译和测试它
gcc -W -Wall -O3 run.c -o run
./run date --utc

请注意run()函数不会尝试检查命令是否实际执行;它只返回子进程PID,如果(pid_t)-1失败,则返回fork()

许多实现(包括GNU C库popen())使用127退出状态作为执行失败的指示。也就是说,它不应该由应该执行的命令返回,而是由子进程返回,因为命令执行失败。上述run()也是如此。


您可以在run()函数中的父进程和子进程之间使用close-on-exec管道,让父进程知道子进程是否成功启动了所需的命令,如果没有,为什么不。然后,父进程也可以立即获得已解散的子进程。如果出现错误,这会给调用者留下额外的麻烦,所以我个人强烈推荐这种方法。以下是一个示例实现:

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <errno.h>

/* Helper function: Close file descriptor, without modifying errno.
 * Returns 0 if successful, otherwise the errno reported by close().
*/
static int closefd(const int fd)
{
    int saved_errno, result;

    /* Invalid descriptor? */
    if (fd == -1)
        return EINVAL;

    /* Save errno. It's thread-local, so as long as we restore
     * it before returning, no-one will notice any change in it. */
    saved_errno = errno;

    /* Close descriptor, and save errno (or 0) in result. */
    do {
        result = close(fd);
    } while (result == -1 && errno == EINTR);
    if (result == -1)
        result = errno;
    else
        result = 0;

    /* Restore errno. Done. */
    errno = saved_errno;

    return result;
}

/* Helper function: Create a close-on-exec pipe.
 * Return 0 if success, errno otherwise.
*/
int close_on_exec_pipe(int fds[2])
{
    int result;

    result = pipe(fds);
    if (result == -1) {
        fds[0] = -1;
        fds[1] = -1;
        return errno;
    }

    do {

        do {
            result = fcntl(fds[0], F_SETFD, FD_CLOEXEC);
        } while (result == -1 && errno == EINTR);
        if (result == -1)
            break;

        do {
            result = fcntl(fds[1], F_SETFD, FD_CLOEXEC);
        } while (result == -1 && errno == EINTR);
        if (result == -1)
            break;

        /* Success. */
        return 0;

    } while (0);

    /* Failed. */
    closefd(fds[0]);
    closefd(fds[1]);
    fds[0] = -1;
    fds[1] = -1;

    return errno;
}

/* Run an external command in a child process.
 * command[0] is the path or name of the command,
 * and the array must be terminated with a NULL.
 *
 * If successful, this function will return the PID
 * of the child process. Otherwise, it will return
 * (pid_t)-1, with errno indicating the error.
*/
pid_t run(char *const command[])
{
    pid_t   child, p;
    int     commfd[2], errcode;

    /* Create a close-on-exec pipe between the parent and child. */
    if (close_on_exec_pipe(commfd))
        return (pid_t)-1;

    /* Fork the new child process. */
    child = fork();
    if (child == (pid_t)-1) {
        closefd(commfd[0]);
        closefd(commfd[1]);
        return (pid_t)-1;
    }

    if (!child) {
        /* Child process: */

        /* Close the read/parent end of the pipe. */
        closefd(commfd[0]);

        /* In case of C library bugs, prepare errno. */
        errno = EINVAL;

        /* Execute the desired command. */
        execvp(command[0], command);

        /* Failed. errno describes why. */
        errcode = errno;

        /* Send it to the parent via the pipe. */
        {
            const char       *p = (char *)&errcode;
            const char *const q = (char *)&errcode + sizeof errcode;
            ssize_t           n;

            while (p < q) {
                n = write(commfd[1], p, (size_t)(q - p));
                if (n > (ssize_t)0)
                    p += n;
                else
                if (n != (ssize_t)-1)
                    break;
                else
                if (errno != EINTR)
                    break;
            }
        }

        /* Close write/child end of the pipe. */
        closefd(commfd[1]);

        /* Exit with a failure (127). */
        _exit(127);
    }

    /* Parent: */

    /* Close the write/child end of the pipe. */
    closefd(commfd[1]);

    /* Try to read the execution error. */
    {
        char       *p = (char *)&errcode;
        char *const q = (char *)&errcode + sizeof errcode;
        ssize_t     n;

        errcode = 0;

        while (p < q) {
            n = read(commfd[0], p, (size_t)(q - p));
            if (n > (ssize_t)0)
                p += n;
            else
            if (n != (ssize_t)-1)
                break; /* n == 0 is pipe closed */
            else
            if (errno != EINTR)
                break;
        }

        /* Close the read/parent end of the pipe. */
        closefd(commfd[0]);

        /* Pipe closed (on exec), no data read? */
        if (n == (ssize_t)0 && p == (char *)&errcode) {
            /* Yes, success! */
            errno = 0;
            return child;
        }

        /* Execution failed.
         * If we didn't get the reason, use EINVAL. */
        if (!errcode || p != q)
            errcode = EINVAL;
    }

    /* Reap the child process. */
    do {
        p = waitpid(child, NULL, 0);
        if (p == (pid_t)-1) {
            if (errno == EINTR)
                continue;
            else
                break;
        }
    } while (p != child);

    /* Return with failure. */
    errno = errcode;
    return (pid_t)-1;
}

在我看来,这种方法的唯一缺点是父进程中使用了额外的两个描述符,尽管只是暂时的。在几乎所有情况下,这都是无关紧要的,但是如果你有一个使用大量文件描述符的服务器类型应用程序,那么你应该知道这一点。


Phidg​​ets库使用线程。执行回调的线程不同于说,等待RFID Phidg​​ets示例中的按键。一种选择是使用posix_spawn()来执行播放器(来自非主线程)。

但是,一般来说,最好让主线程监控播放器使用waitpid(child, &status, WNOHANG)检查播放器是否退出,以及处理任何新的RFID事件,根据需要启动播放器(杀死一个现有实例,如果是新的RFID),甚至在RFID被移动到阅读器范围之外时杀死了播放器。

这需要一个简单的线程事件队列:

#define  _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <pthread.h>
#include <stdio.h>
#include <errno.h>

/* RFID tag event types: tag read, tag lost.
*/
typedef enum {
    RFID_TAG_LOST = 0,
    RFID_TAG_READ
} rfid_event_type_t;

/* Structure describing all possible RFID tag events.
*/
typedef struct rfid_event_st  rfid_event_t;
struct rfid_event_st {
    struct rfid_event_st     *next;
    rfid_event_type_t         type;
    CPhidgetRFIDHandle        rfid;
    CPhidgetRFID_Protocol     protocol;
    void                     *userptr;
    char                      tag[];
};

static pthread_mutex_t  event_lock = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t   event_wait = PTHREAD_COND_INITIALIZER;
static rfid_event_t    *event_queue = NULL;

/* Add event to event queue.
*/
static int add_event(const CPhidgetRFIDHandle rfid,
                     const CPhidgetRFID_Protocol protocol,
                     const rfid_event_type_t type,
                     const char *const tag,
                     void *const userptr)
{
    const size_t  taglen = (tag) ? strlen(tag) : 0;
    rfid_event_t *ev;

    /* Allocate memory for a new event. */
    ev = malloc(sizeof (rfid_event_t) + taglen + 1);
    if (!ev)
        return errno = ENOMEM;

    /* Fill in the fields. */
    ev->next = NULL;
    ev->type = type;
    ev->rfid = rfid;
    ev->protocol = protocol;
    ev->userptr = userptr;
    if (taglen > 0)
        memcpy(ev->tag, tag, taglen);
    ev->tag[taglen] = '\0';

    /* Lock event queue. */
    pthread_mutex_lock(&event_lock);

    /* Append to the event queue. */
    if (event_queue) {
        rfid_event_t *prev = event_queue;
        while (prev->next)
            prev = prev->next;
        prev->next = ev;
    } else
        event_queue = ev;

    /* Signal and unlock. */
    pthread_cond_signal(&event_wait);
    pthread_mutex_unlock(&event_lock);

    return 0;
}

/* Get next event, waiting at most 'maxwait' seconds.
*/
static rfid_event_t *get_event(const long maxwait)
{
    struct timespec until;
    rfid_event_t   *ev;

    pthread_mutex_lock(&event_lock);

    /* Event already in the queue? */
    if (event_queue) {
        ev = event_queue;
        event_queue = ev->next;
        ev->next = NULL;
        pthread_mutex_unlock(&event_lock);
        return ev;
    }

    /* No waiting requested? */
    if (maxwait <= 0L) {
        pthread_mutex_unlock(&event_lock);
        return NULL;
    }

    /* Get current wall clock time, */
    clock_gettime(CLOCK_REALTIME, &until);
    /* and add maxwait seconds. */
    until.tv_sec += maxwait;

    /* Wait for a signal. */
    pthread_cond_timedwait(&event_wait, &event_lock, &until);

    /* Event arrived in the queue? */
    if (event_queue) {
        ev = event_queue;
        event_queue = ev->next;
        ev->next = NULL;
        pthread_mutex_unlock(&event_lock);
        return ev;
    }

    /* No event; timed out. */
    pthread_mutex_unlock(&event_lock);
    return NULL;
}

根据Phidg​​ets RFID示例,标记和标记丢失处理程序

int CCONV TagHandler(CPhidgetRFIDHandle RFID, void *usrptr, char *TagVal, CPhidgetRFID_Protocol proto)
{
    return add_event(RFID, proto, RFID_TAG_READ, TagVal, usrptr);
}

int CCONV TagLostHandler(CPhidgetRFIDHandle RFID, void *usrptr, char *TagVal, CPhidgetRFID_Protocol proto)
{
    return add_event(RFID, proto, RFID_TAG_LOST, TagVal, usrptr);
}

在设置完所有内容后,您不必等待按键,而是创建一个循环,例如

    pid_t         child = (pid_t)-1; /* Not running */
    pid_t         p;
    rfid_event_t *event;

    /* Infinite loop */
    while (1) {

        /* Do we have a player child process? */
        if (child != (pid_t)-1) {

            /* Yes. Has it exited yet? */
            p = waitpid(child, NULL, WNOHANG);
            if (p == child) {
                /* Yes. No more player. */
                child == (pid_t)-1;
            }
        }

        /* Check for a new event.
         * If we have a child, only wait one second only
         * for the event; otherwise, wait up to 30 secs.
        */
        if (child == (pid_t)-1)
            event = get_event(30L);
        else
            event = get_event(1L);

        /* If no event yet, start at the beginning of the loop. */
        if (!event)
            continue;

        /*
         * TODO: Handle the event.
         *       You can stop the existing player via e.g.
         *       if (child != (pid_t)-1)
         *           kill(child, SIGKILL);
         *       and then start a new one.
        */

        /* Discard the event. It's dynamically allocated. */
        free(event);
    }

如果你启动播放器,上面的循环检测到它在一秒钟内没有播放。如果没有播放器运行,那么循环可以等待RFID信号,只要它想要 - 我用了30秒。