0%

golang通过CMD来调用FFmpeg


概述

在golang中调用FFmpeg,一种方法是使用cgo来使用FFmpeg,这样就可以在go程序里面编写转码语句,进行错误处理等, 但是这样的方法貌似有点复杂,或许得不偿失。

这里使用CMD来直接调用FFmpeg进行转码,在使用较多的复杂参数的情况下,可能反而简单许多。


使用记录

调用:

1
2
3
cmd := exec.Command("/bin/bash", "-c", param)
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
cmd.start()

这里的param就是整个FFmpeg转码语句,例如/usr/bin/ffmpeg -i 1.mp4 1.flv

这里使用Setpgid来设置进程组pid,这样在后面kill时直接杀掉整个进程组,无残留。

另外cmd.start()表示非阻塞,转码开始后就放在后台(因为直播相关,所以会一直转码),等待结束命令。

转码日志记录:

1
2
stdout, err := os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY, 0644)
cmd.Stderr = stdout

注意ffmpeg使用的日志输出是Stderrerr

停止:

1
err := syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)

加上一个负号,直接杀掉整个进程组。

PS:一定要在kill之后调用cmd.Wait(),否则会变成僵尸进程。


完整代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
func Exec(param, logFile string) (*exec.Cmd, error) {
cmd := exec.Command("/bin/bash", "-c", param)
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}

logOut, err := os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return nil, err
}
// 将标准输出和标准错误都写到log中
cmd.Stdout = logOut
cmd.Stderr = logOut

log.Info("Exce cmd: %s", param)

if err := cmd.Start(); err != nil {
_ = logOut.Close()
return nil, err
}

return cmd, nil
}

func KillCmd(cmd *exec.Cmd) error {
if cmd == nil || cmd.Process == nil {
return errors.New("process not found or already stopped")
}

log.Info("Kill CMD. pid: %d", cmd.Process.Pid)

err := syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
if err != nil {
return err
}

// 如果不wait,则会产生僵尸进程
go cmd.Wait() // 为了不阻塞,另外起一个线程去Wait

return nil
}

2019-12-30修改记录

实际使用的时候发现出现了defunct进程,也就是僵尸进程,重新看了一下cmd.Start()的使用方法, 发现自己没有调用cmd.Wait()


2020-01-20修改记录

最近在使用FFmpeg来将rtmp流录制成ts文件,发现了一系列问题:

  • 首先,在FFmpeg进程运行过程中,如果使用SIGKILL(也就是-9信号)来将其杀死,那么这就相当于FFmpeg异常结束,它在结束时不会进行任何后处理。 那么FFmpeg所录制的最后一片ts(由于只录制了一部分,没有达到设置的ts时长),就不能得到正确的处理,m3u8文件中也不会对他进行记录。 这里正确的做法是使用SIGTERM信号(或者SIGINT)来使FFmpeg进程停止,FFmpeg收到这个信号,会将最后一片时长不足的ts片也保存下来,并写进m3u8文件中。
  • 第二,在拉取rtmp流的时候,我是先停止rtmp流,然后再发送SIGTERM信号给FFmpeg,但是我发现FFmpeg进程并没有结束!这个问题困扰了很久, 终于在一瞬间想到了,原来是FFmpeg因为拉不到新数据而阻塞了!所以连信号都接收不到了!为了让FFmpeg不会无限阻塞,可以设置一个rw_timeout参数, 来让FFmpeg阻塞一段时间后超时,从而接收到信号。

所以这里总结一下就是:1.使用SIGTERM信号来使得FFmpeg正常结束。2.使用rw_timeout参数来防止FFmpeg无限阻塞。


参考

Go语言中Kill子进程的正确姿势