ffmpeg进程优雅退出

需求

有时我们会在程序中通过fork/execl方式调用ffmpeg进程执行某些命令,这通常比调用API更简单。然后我们等待这些命令执行完毕后ffmpeg进程自动就会结束,但如果我们的任务是需要长时间运行比如拉取rtsp流保存为mp4,我们希望这个工作可以随时终止,终止的时候ffmpeg要优雅的退出,即ffmpeg要可以在退出时执行写入mp4的元数据、正常关闭文件等清理工作,使他看起来就像是正常退出一样,ffmpeg可以吗?

实现

ffmpeg是可以的。

一旦我们以额外进程的方式运行ffmpeg,那么通知ffmpeg进程最简单的方式就是发送信号,ffmpeg进程优雅退出的也是这么实现的,即监听某些信号,在信号回调函数中终止正在进行的操作,进行优雅退出。我们知道ffmpg这个可执行程序对应的代码是由ffmpeg.c,下面我们就跟踪下代码看起其具体实现。

在ffmpeg.c的main函数中会调用ffmpeg_parse_options函数,ffmpeg_parse_options中会调用SIGNAL函数对SIGINT和SIGTERM这两个信号进行注册,回调函数为sigterm_handler

 由下面sigterm_handler的实现可知,当这两个信号触发时,会将信号值赋值给received_sigterm,然后进行计算,如果接收的信号次数大于3则直接调用exit()进行强制退出了,而ffmpeg就是通过对received_sigterm的判断实现的优雅退出。

 由ffmpeg.c代码可知其命令的执行都是通过main函数调用transcode实现的,通过下面的transcode代码我们知道其主要包括两部分:通过一个循环不断的调用transcode_step执行任务,之后进行flush解码器、写文件尾这样的清理工作,而调用transcode_step的循环条件就是判断received_sigterm,所以收到信号后就不会再进行额外的transcode_step,转而去进行优雅的退出。

static int transcode(int *err_rate_exceeded)
{
    int ret = 0, i;
    InputStream *ist;
    int64_t timer_start;

    print_stream_maps();

    *err_rate_exceeded = 0;
    atomic_store(&transcode_init_done, 1);

    if (stdin_interaction) {
        av_log(NULL, AV_LOG_INFO, "Press [q] to stop, [?] for help\n");
    }

    timer_start = av_gettime_relative();

    while (!received_sigterm) {
        OutputStream *ost;
        int64_t cur_time= av_gettime_relative();

        /* if 'q' pressed, exits */
        if (stdin_interaction)
            if (check_keyboard_interaction(cur_time) < 0)
                break;

        ret = choose_output(&ost);
        if (ret == AVERROR(EAGAIN)) {
            reset_eagain();
            av_usleep(10000);
            ret = 0;
            continue;
        } else if (ret < 0) {
            av_log(NULL, AV_LOG_VERBOSE, "No more output streams to write to, finishing.\n");
            ret = 0;
            break;
        }

        ret = transcode_step(ost);
        if (ret < 0 && ret != AVERROR_EOF) {
            av_log(NULL, AV_LOG_ERROR, "Error while filtering: %s\n", av_err2str(ret));
            break;
        }

        /* dump report by using the output first video and audio streams */
        print_report(0, timer_start, cur_time);
    }

    /* at the end of stream, we must flush the decoder buffers */
    for (ist = ist_iter(NULL); ist; ist = ist_iter(ist)) {
        float err_rate;

        if (!input_files[ist->file_index]->eof_reached) {
            int err = process_input_packet(ist, NULL, 0);
            ret = err_merge(ret, err);
        }

        err_rate = (ist->frames_decoded || ist->decode_errors) ?
                   ist->decode_errors / (ist->frames_decoded + ist->decode_errors) : 0.f;
        if (err_rate > max_error_rate) {
            av_log(ist, AV_LOG_FATAL, "Decode error rate %g exceeds maximum %g\n",
                   err_rate, max_error_rate);
            *err_rate_exceeded = 1;
        } else if (err_rate)
            av_log(ist, AV_LOG_VERBOSE, "Decode error rate %g\n", err_rate);
    }
    ret = err_merge(ret, enc_flush());

    term_exit();

    /* write the trailer if needed */
    for (i = 0; i < nb_output_files; i++) {
        int err = of_write_trailer(output_files[i]);
        ret = err_merge(ret, err);
    }

    /* dump report by using the first video and audio streams */
    print_report(1, timer_start, av_gettime_relative());

    return ret;
}

代码示例

下面写一个linux示例代码演示如果让ffmpeg优雅的终止(不包含错误处理),编译方式:g++ main.cpp -o main

#include <stdlib.h>
#include <sys/wait.h>

int main() {
    pid_t pid = fork();

    if (pid == 0) {
        // 子进程
        // 使用execl调用ffmpeg命令,请提供完整的路径,例如:/usr/bin/ffmpeg
        execl("/usr/bin/ffmpeg", "ffmpeg", "-y", "-rtsp_transport", "tcp", "-i", "rtsp://example.com/stream", "-c", "copy", "-an", "output.mp4", NULL);

        exit(0);
    } else {
        // 父进程
        // 主线程休眠5秒
        sleep(5);

        // 向子进程发送SIGINT信号
        kill(pid, SIGINT);

        // 等待子进程终止
        int status;
        waitpid(pid, &status, 0);
    }

    return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值