You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Golang中如何在不同函数终止Exec启动的FFmpeg进程

解决方案:终止Go中通过exec启动的FFmpeg录屏进程

问题分析

你的现有代码存在两个核心问题:

  1. RoomCreate中启动录制后立刻调用cancelFn(),导致录制刚启动就被终止。
  2. 没有存储录制任务的上下文和FFmpeg进程引用,stopRecording无法定位并终止目标进程。

核心解决思路

  • 用并发安全的存储结构(如sync.Map)关联每个房间ID与对应的录制任务(上下文取消函数、FFmpeg进程)。
  • 给FFmpeg发送q命令实现优雅停止,避免直接杀死进程导致视频文件损坏。
  • 分离进程启动与阻塞等待,用Start()替代Run(),让录制在后台运行并监听上下文取消信号。

完整修改代码示例

1. 定义全局录制任务存储

import (
    "context"
    "os/exec"
    "sync"
    "fmt"

    "github.com/gofiber/fiber/v2"
    "github.com/google/uuid"
)

type RecordingTask struct {
    CancelFn context.CancelFunc
    Process  *exec.Cmd
}

var recordingTasks sync.Map // key: roomID string, value: *RecordingTask

2. 修改RoomCreate处理函数

func RoomCreate(c *fiber.Ctx) error {
    fileName := "out.mp4"
    if len(os.Args) > 1 {
        fileName = os.Args[1]
    }

    roomID := uuid.NewString()
    ctx, cancelFn := context.WithCancel(context.Background())
    
    // 启动录制并获取FFmpeg进程
    ffmpegCmd, err := startRecording(ctx, fileName)
    if err != nil {
        cancelFn()
        return c.Status(fiber.StatusInternalServerError).SendString("启动录制失败")
    }

    // 将任务存储到全局Map
    recordingTasks.Store(roomID, &RecordingTask{
        CancelFn: cancelFn,
        Process:  ffmpegCmd,
    })

    return c.Redirect(fmt.Sprintf("/room/%s", roomID))
}

3. 重写startRecording函数

func startRecording(ctx context.Context, fileName string) (*exec.Cmd, error) {
    ffmpeg := exec.Command("ffmpeg",
        "-f", "gdigrab",
        "-framerate", "30",
        "-i", "desktop",
        "-f", "mp4",
        fileName,
    )

    // 获取stdin管道,用于发送停止命令
    stdin, err := ffmpeg.StdinPipe()
    if err != nil {
        return nil, err
    }

    // 启动进程(非阻塞)
    if err := ffmpeg.Start(); err != nil {
        stdin.Close()
        return nil, err
    }

    // 监听上下文取消信号,优雅停止FFmpeg
    go func() {
        <-ctx.Done()
        // 发送"q"命令让FFmpeg正常收尾
        _, _ = stdin.Write([]byte("q"))
        stdin.Close()
        // 等待进程完全退出
        _ = ffmpeg.Wait()
    }()

    return ffmpeg, nil
}

4. 实现stopRecording函数

func stopRecording(c *fiber.Ctx) error {
    roomID := c.Params("roomID")
    if roomID == "" {
        return c.Status(fiber.StatusBadRequest).SendString("缺少房间ID")
    }

    // 从Map中删除并获取任务
    task, ok := recordingTasks.LoadAndDelete(roomID)
    if !ok {
        return c.Status(fiber.StatusNotFound).SendString("未找到录制任务")
    }

    recordingTask := task.(*RecordingTask)
    // 触发上下文取消,执行优雅停止逻辑
    recordingTask.CancelFn()

    return c.SendString("录制已停止")
}

关键注意事项

  • 优雅停止:直接调用Process.Kill()会导致FFmpeg来不及写入视频文件的元数据,生成的MP4可能无法播放。发送q命令是FFmpeg官方推荐的停止方式。
  • 并发安全:使用sync.Map而非普通map,避免多goroutine操作时出现竞态问题。
  • 资源清理:停止录制后从recordingTasks中删除任务,避免内存泄漏。

内容的提问来源于stack exchange,提问作者Tammam

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.24 19:09:24