概述
在golang中调用FFmpeg,一种方法是使用cgo来使用FFmpeg,这样就可以在go程序里面编写转码语句,进行错误处理等, 但是这样的方法貌似有点复杂,或许得不偿失。
这里使用CMD来直接调用FFmpeg进行转码,在使用较多的复杂参数的情况下,可能反而简单许多。
使用记录
调用:
1 | cmd := exec.Command("/bin/bash", "-c", param) |
这里的param
就是整个FFmpeg转码语句,例如/usr/bin/ffmpeg -i 1.mp4 1.flv
。
这里使用Setpgid
来设置进程组pid,这样在后面kill时直接杀掉整个进程组,无残留。
另外cmd.start()
表示非阻塞,转码开始后就放在后台(因为直播相关,所以会一直转码),等待结束命令。
转码日志记录:
1 | stdout, err := os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY, 0644) |
注意ffmpeg使用的日志输出是Stderr,err…
停止:
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
}
1 | func Exec(param, logFile string) (*exec.Cmd, error) { |
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无限阻塞。