ffmpeg重复回听音频流,时长叠加问题

一、问题原因

使用ffmpeg回听流时,相同流地址生成相同的id,并且输出的文件是相同的,会将音频流数据追加到输出文件(如果指定了输出文件)或者在内存中持续累积音频数据,而不是重新开始处理音频流。这就导致了每次播放都会在前一次的基础上增加时长,而不是从音频流的开头重新开始计算时长。

二、解决

  1. 每次使用新的输出文件或临时文件

    • 在每次执行 FFmpeg 命令回听流地址时,指定一个不同的输出文件名。这样每次播放的结果都会保存到独立的文件中,避免了时长叠加的问题。例如,可以在文件名中添加时间戳或随机数来确保每次文件名的唯一性。
    • 如果不想保存为文件,而是在内存中处理音频数据(如实时播放),可以考虑在每次播放前清空相关的音频数据缓冲区或临时存储区域,具体操作取决于你使用 FFmpeg 的方式和环境。
  2. 正确控制 FFmpeg 的播放逻辑

    • 如果你希望在同一个输出中重复播放音频流,但每次都从开头开始,需要在 FFmpeg 命令中添加适当的参数来控制播放位置。例如,可以在播放前先将播放位置设置为音频流的开头,然后再开始播放。在 FFmpeg 中,可以使用-ss参数来指定开始时间,将其设置为0即可从音频流的起始位置开始播放。
    • 对于实时流,可能需要根据流的特性和协议,使用特定的方法来重置播放状态,确保每次播放都是从最新的音频数据开始,而不是累积之前的播放数据。这可能涉及到对流的连接、缓冲和播放控制等方面的精细调整,具体方法会因流的类型(如 RTMP、RTSP 等)和使用场景而异。
  3. 检查和优化流的处理方式

    • 确保你的 FFmpeg 命令正确地处理了音频流的边界和播放结束的情况。有些情况下,如果流处理不正确,可能会导致音频数据在结束后没有被正确清理,从而影响下一次播放。例如,在处理某些实时流时,需要正确处理流的结束标记或信号,以便在播放结束时能够正确重置相关状态。
    • 检查 FFmpeg 的版本和配置,确保没有配置错误或版本相关的问题导致异常的音频数据处理行为。有时候,升级到最新版本的 FFmpeg 或者调整特定的配置参数(如缓冲区大小、音频格式处理选项等)可以解决一些与音频流处理相关的问题,包括时长叠加问题。

三、实现

回听流参数:

ini 复制代码
    const dateTime = new Date().getTime(); // 当前时间戳
    const id = generateId(streamUrl);
    const hlsOptions = [
        "-ss",
        "0",
        "-c:v",
        "libx264",
        "-c:a",
        "aac",
        "-preset",
        "veryfast",
        "-crf",
        "23",
        "-f",
        "hls",
        "-hls_time",
        "10",
        "-hls_list_size",
        "100",
        "-hls_flags",
        "append_list+discont_start",
        "-start_number",
        "0",
        "-hls_init_time",
        "10",
        "-hls_segment_filename",
        `public/hls/${id}/${dateTime}/%03d.ts`,
      ];
javascript 复制代码
// 根据传入的流地址生成 ID,流地址相同则 ID 相同,避免重复使用 ffmpeg 处理同一个流
function generateId(streamUrl) {
  const hash = crypto.createHash("sha256");
  hash.update(streamUrl);
  return hash.digest("hex").slice(0, 10); // 取哈希的前 10 个字符作为 ID
}

每次回听前先清除缓存数据并销毁Hls实例

scss 复制代码
// 清理数据
function clearData(id) {
  if (streams[id]) {
    const { ffmpeg } = streams[id];
    // 停止 FFmpeg 转流服务
    ffmpeg.kill("SIGKILL");
  }
  const hlsFolderPath = path.join("public", "hls", id);
  deleteFolderRecursive(hlsFolderPath);
  // 从 streams 对象中移除条目
  delete streams[id];

  // 从 activeRequests 对象中移除条目
  delete activeRequests[id];
}

// 删除文件夹及其子文件夹
function deleteFolderRecursive(folderPath) {
  if (fs.existsSync(folderPath)) {
    fs.readdirSync(folderPath).forEach((file) => {
      const curPath = path.join(folderPath, file);
      if (fs.lstatSync(curPath).isDirectory()) {
        deleteFolderRecursive(curPath);
      } else {
        fs.unlinkSync(curPath);
      }
    });
    fs.rmdirSync(folderPath);
  }
}

// 销毁实例
closePlayer() {
  if (this.currentHls) this.currentHls.destroy()
}

输出文件

javascript 复制代码
      // 回听
      if ((outputType === "hls" || outputType === "both") && !isLive) {
        hlsUrl = `/hls/${id}/${dateTime}/index.m3u8`;

        // 创建 HLS 目录
        if (!fs.existsSync(`public/hls/${id}/${dateTime}`)) {
          fs.mkdirSync(`public/hls/${id}/${dateTime}`, { recursive: true });
        }

        // HLS output
        ffmpegInstance
          .output(`public/hls/${id}/${dateTime}/index.m3u8`)
          .outputOptions(hlsOptions);
      }
相关推荐
大怪v9 小时前
AI抢饭?前端佬:我要验牌!
前端·人工智能·程序员
新酱爱学习9 小时前
字节外包一年,我的技术成长之路
前端·程序员·年终总结
小兵张健9 小时前
开源 playwright-pool 会话池来了
前端·javascript·github
IT_陈寒12 小时前
Python开发者必知的5大性能陷阱:90%的人都踩过的坑!
前端·人工智能·后端
codingWhat13 小时前
介绍一个手势识别库——AlloyFinger
前端·javascript·vue.js
代码老中医13 小时前
2026年CSS彻底疯了:这6个新特性让我删掉了三分之一JS代码
前端
不会敲代码113 小时前
Zustand:轻量级状态管理,从入门到实践
前端·typescript
踩着两条虫13 小时前
VTJ.PRO 双向代码转换原理揭秘
前端·vue.js·人工智能
扉川川13 小时前
OpenClaw 架构解析:一个生产级 AI Agent 是如何设计的
前端·人工智能
远山枫谷13 小时前
一文理清页面/组件通信与 Store 全局状态管理
前端·微信小程序