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

标签 c linux video raspberry-pi execve

你好。

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

注意:该代码只是一个 execve 调用,我知道它是正确的,因为在 X 中它工作得很好。

最佳答案

execve() call 为执行的程序提供了一个新的环境。对于能够访问 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,如果 fork() 失败,则返回 (pid_t)-1

许多实现,包括 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 秒。

关于c - 在 C 程序上通过 execve/l 执行 omxplayer 不会在 fork() 之后在子进程的非 X 控制台上输出视频,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17400954/

相关文章:

linux - 获取其他线程的回溯

c - 如何在 Linux 中正确安装 gsl 库?

c - Arduino Cryposuite 和 C 编程语法

c++ - Intel Xeon X5550 上的 Linux 下 __rdtscp 校准不稳定

batch-file - 在PAL模式下通过持续时间值计算视频的帧数

javascript - 使用 Easel.js 将视频添加到 HTML 5 Canvas

php - 使用正则表达式和 ffmpeg 获取视频的尺寸

C:检查最低有效字节和最高有效字节是否为 0 或 1

c - 如何在 C 中制作 !default 大小写

c - 数据传输时禁用 ARP