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);
      }
相关推荐
To_OC10 小时前
从一头雾水到跑通全流程:我用一个周末啃透了JWT登录鉴权
前端·后端·http
excel12 小时前
研究 Vue 3 源码的收获
前端·vue.js
可乐鸡翅yeah_13 小时前
业务中 M3U8 水印相关坑,硬水印和动态水印区别
前端·网络·数据库·ffmpeg·m3u8在线
lerhxx14 小时前
AI 应用如何高效优雅地恢复中断?—— "连接解耦 + 状态持久化"
前端·javascript
计算机魔术师14 小时前
METR 演示 AI 智能体如何篡改 Inspect 评估记录以掩盖不当行为
前端
前端snow14 小时前
ai agent --- 异步处理之 Rabbit MQ
前端
念何架构之路14 小时前
zap扩展生态与总结
java·前端·数据库
独孤九剑打醒他14 小时前
【原创开源】【概念设计】源 - 栅 - 漏 - 栅 - 源 横向双栅 MOS,低压交流多值逻辑芯片探索
前端·其他·架构·开源·硬件工程
mantou13215 小时前
我给 AI Agent 做了个「油猴」:让 Claude Code / Codex 直接用你已登录的浏览器
前端·javascript·后端
默_笙15 小时前
🍕 一个主编、三个工种、两本手册:搭一支 AI 调研队
前端·javascript