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);
      }
相关推荐
沉默璇年31 分钟前
react中useMemo的使用场景
前端·react.js·前端框架
yqcoder37 分钟前
reactflow 中 useNodesState 模块作用
开发语言·前端·javascript
2401_882727571 小时前
BY组态-低代码web可视化组件
前端·后端·物联网·低代码·数学建模·前端框架
会发光的猪。1 小时前
css使用弹性盒,让每个子元素平均等分父元素的4/1大小
前端·javascript·vue.js
天下代码客2 小时前
【vue】vue中.sync修饰符如何使用--详细代码对比
前端·javascript·vue.js
猫爪笔记2 小时前
前端:HTML (学习笔记)【1】
前端·笔记·学习·html
前端李易安2 小时前
Webpack 热更新(HMR)详解:原理与实现
前端·webpack·node.js
红绿鲤鱼2 小时前
React-自定义Hook与逻辑共享
前端·react.js·前端框架
Domain-zhuo2 小时前
什么是JavaScript原型链?
开发语言·前端·javascript·jvm·ecmascript·原型模式
小丁爱养花2 小时前
前端三剑客(三):JavaScript
开发语言·前端·javascript