一、问题原因
使用ffmpeg回听流时,相同流地址生成相同的id,并且输出的文件是相同的,会将音频流数据追加到输出文件(如果指定了输出文件)或者在内存中持续累积音频数据,而不是重新开始处理音频流。这就导致了每次播放都会在前一次的基础上增加时长,而不是从音频流的开头重新开始计算时长。
二、解决
-
每次使用新的输出文件或临时文件
- 在每次执行 FFmpeg 命令回听流地址时,指定一个不同的输出文件名。这样每次播放的结果都会保存到独立的文件中,避免了时长叠加的问题。例如,可以在文件名中添加时间戳或随机数来确保每次文件名的唯一性。
- 如果不想保存为文件,而是在内存中处理音频数据(如实时播放),可以考虑在每次播放前清空相关的音频数据缓冲区或临时存储区域,具体操作取决于你使用 FFmpeg 的方式和环境。
-
正确控制 FFmpeg 的播放逻辑
- 如果你希望在同一个输出中重复播放音频流,但每次都从开头开始,需要在 FFmpeg 命令中添加适当的参数来控制播放位置。例如,可以在播放前先将播放位置设置为音频流的开头,然后再开始播放。在 FFmpeg 中,可以使用
-ss
参数来指定开始时间,将其设置为0
即可从音频流的起始位置开始播放。 - 对于实时流,可能需要根据流的特性和协议,使用特定的方法来重置播放状态,确保每次播放都是从最新的音频数据开始,而不是累积之前的播放数据。这可能涉及到对流的连接、缓冲和播放控制等方面的精细调整,具体方法会因流的类型(如 RTMP、RTSP 等)和使用场景而异。
- 如果你希望在同一个输出中重复播放音频流,但每次都从开头开始,需要在 FFmpeg 命令中添加适当的参数来控制播放位置。例如,可以在播放前先将播放位置设置为音频流的开头,然后再开始播放。在 FFmpeg 中,可以使用
-
检查和优化流的处理方式
- 确保你的 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);
}