需求
有时我们会在程序中通过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;
}