如何使用 Node.js 通过 ffmpeg 流式传输 MP4 文件?
Posted
技术标签:
【中文标题】如何使用 Node.js 通过 ffmpeg 流式传输 MP4 文件?【英文标题】:How do you use Node.js to stream an MP4 file with ffmpeg? 【发布时间】:2016-02-16 23:47:42 【问题描述】:我已经尝试解决这个问题好几天了,非常感谢任何关于这个问题的帮助。
通过将文件的位置作为字符串传递并将其转码为 mp3,我能够使用 fluent-ffmpeg 成功地流式传输存储在 Node.js 服务器上的 mp4 音频文件。如果我从同一个文件创建文件流并将其传递给 fluent-ffmpeg,则它适用于 mp3 输入文件,但不适用于 mp4 文件。在 mp4 文件的情况下,不会引发错误,它声称流已成功完成,但浏览器中没有播放任何内容。我猜这与存储在 mp4 文件末尾的元数据有关,但我不知道如何编写代码。当它的位置被传递给 ffmpeg 而不是流时,这是完全相同的文件。当我尝试将流传递给 s3 上的 mp4 文件时,再次没有抛出错误,但没有流到浏览器。这并不奇怪,因为 ffmpeg 无法将文件作为流本地处理,因此期望它处理来自 s3 的流是一厢情愿的想法。
如何从 s3 流式传输 mp4 文件,而不先将其作为文件存储在本地?如何让 ffmpeg 在不对文件进行转码的情况下执行此操作?以下是我目前无法使用的代码。请注意,它尝试将 s3 文件作为流传递给 ffmpeg,并且还将其转码为 mp3,我不希望这样做。
.get(function(req,res)
aws.s3(s3Bucket).getFile(s3Path, function (err, result)
if (err)
return next(err);
var proc = new ffmpeg(result)
.withAudioCodec('libmp3lame')
.format('mp3')
.on('error', function (err, stdout, stderr)
console.log('an error happened: ' + err.message);
console.log('ffmpeg stdout: ' + stdout);
console.log('ffmpeg stderr: ' + stderr);
)
.on('end', function ()
console.log('Processing finished !');
)
.on('progress', function (progress)
console.log('Processing: ' + progress.percent + '% done');
)
.pipe(res, end: true);
);
);
这是在调用 aws.s3 时使用 knox 库...我也尝试使用 Node.js 的标准 aws sdk 编写它,如下所示,但我得到的结果与上面相同。
var AWS = require('aws-sdk');
var s3 = new AWS.S3(
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_KEY,
region: process.env.AWS_REGION_ID
);
var fileStream = s3.getObject(
Bucket: s3Bucket,
Key: s3Key
).createReadStream();
var proc = new ffmpeg(fileStream)
.withAudioCodec('libmp3lame')
.format('mp3')
.on('error', function (err, stdout, stderr)
console.log('an error happened: ' + err.message);
console.log('ffmpeg stdout: ' + stdout);
console.log('ffmpeg stderr: ' + stderr);
)
.on('end', function ()
console.log('Processing finished !');
)
.on('progress', function (progress)
console.log('Processing: ' + progress.percent + '% done');
)
.pipe(res, end: true);
======================================
更新
我在同一个 s3 存储桶中放置了一个 mp3 文件,我在这里的代码可以工作,并且能够将文件流式传输到浏览器,而无需存储本地副本。所以我面临的流媒体问题与 mp4/aac 容器/编码器格式有关。
我仍然对将 m4a 文件从 s3 完全下载到 Node.js 服务器的方法感兴趣,然后将其传递给 ffmpeg 进行流式传输,而无需将文件实际存储在本地文件系统中。
======================================
再次更新
我已经设法让服务器将文件以 mp4 格式直接传输到浏览器。这一半回答了我原来的问题。我现在唯一的问题是我必须先将文件下载到本地商店,然后才能流式传输。我仍然想找到一种无需临时文件即可从 s3 流式传输的方法。
aws.s3(s3Bucket).getFile(s3Path, function(err, result)
result.pipe(fs.createWriteStream(file_location));
result.on('end', function()
console.log('File Downloaded!');
var proc = new ffmpeg(file_location)
.outputOptions(['-movflags isml+frag_keyframe'])
.toFormat('mp4')
.withAudioCodec('copy')
.seekInput(offset)
.on('error', function(err,stdout,stderr)
console.log('an error happened: ' + err.message);
console.log('ffmpeg stdout: ' + stdout);
console.log('ffmpeg stderr: ' + stderr);
)
.on('end', function()
console.log('Processing finished !');
)
.on('progress', function(progress)
console.log('Processing: ' + progress.percent + '% done');
)
.pipe(res, end: true);
);
);
在接收端,我在一个空的 html 页面中只有以下 javascript:
window.AudioContext = window.AudioContext || window.webkitAudioContext;
context = new AudioContext();
function process(Data)
source = context.createBufferSource(); // Create Sound Source
context.decodeAudioData(Data, function(buffer)
source.buffer = buffer;
source.connect(context.destination);
source.start(context.currentTime);
);
;
function loadSound()
var request = new XMLHttpRequest();
request.open("GET", "/stream/<audio_identifier>", true);
request.responseType = "arraybuffer";
request.onload = function()
var Data = request.response;
process(Data);
;
request.send();
;
loadSound()
======================================
答案
上面标题为“再次更新”的代码将通过 Node.js 服务器将 mp4 文件从 s3 流式传输到浏览器,而无需使用 flash。它确实需要将文件临时存储在 Node.js 服务器上,以便将文件中的元数据从文件末尾移动到前面。为了在不存储临时文件的情况下进行流式传输,您需要先在 S3 上实际修改文件并更改此元数据。如果您在 S3 上以这种方式更改了文件,那么您可以修改标题为“再次更新”下的代码,以便将 S3 的结果直接通过管道传输到 ffmpeg 构造函数,而不是传输到 Node.js 服务器上的文件流,然后将该文件位置提供给 ffmepg,就像现在的代码一样。您可以将最终的“管道”命令更改为“保存(位置)”以在本地获取 mp4 文件的版本,并将元数据移到前面。然后,您可以将该文件的新版本上传到 S3 并尝试端到端流式传输。就我个人而言,我现在将创建一个任务,在文件首先上传到 s3 时以这种方式修改文件。这允许我在 mp4 中录制和流式传输,而无需在 Node.js 服务器上转码或存储临时文件。
【问题讨论】:
文件的编解码器是什么?此外,我还读到 html 5 不支持流式传输某些 m4a 文件,如果那是接收端的内容。另外,您是否尝试过不指定格式和编解码器?示例here 如果您删除了这些,它只会让客户端确定类型是什么,因为您只是将文件流传递给响应。 编解码器是: 音频:aac (LC) (mp4a / 0x6134706D), 8000 Hz, mono, fltp, 16 kb/s (default) 接收端是一个webkit音频上下文。我将在一秒钟内更新我的问题以显示代码。我几乎已经回答了我自己的问题。我现在无需转码即可将 mp4 流式传输到客户端。我只是想找出一种方法来做到这一点,而无需将其临时存储在 Node.js 服务器上。 google 'moov atom' 并查看 ffmpeg 文档以将索引放在文件的前面。 “-movflags” 感谢罗伯特,我已将其包含在我的帖子的最后更新中(代码示例接近结尾)。这允许从 Node.js 流式传输 MP4,但我仍然必须先将文件从 s3 下载到节点服务器。它无法一直通过管道。 【参考方案1】:这里的一个主要问题是您无法在管道流上搜索。所以你必须先存储文件。但是,如果您只想从头开始流式传输,则可以使用稍微不同的结构和管道。这是一个最直接的方法示例。
// can just create an in-memory read stream without saving
var stream = aws.s3(s3Bucket).getObject(s3Path).createReadStream();
// fluent-ffmpeg supports a readstream as an arg in constructor
var proc = new ffmpeg(stream)
.outputOptions(['-movflags isml+frag_keyframe'])
.toFormat('mp4')
.withAudioCodec('copy')
//.seekInput(offset) this is a problem with piping
.on('error', function(err,stdout,stderr)
console.log('an error happened: ' + err.message);
console.log('ffmpeg stdout: ' + stdout);
console.log('ffmpeg stderr: ' + stderr);
)
.on('end', function()
console.log('Processing finished !');
)
.on('progress', function(progress)
console.log('Processing: ' + progress.percent + '% done');
)
.pipe(res, end: true);
【讨论】:
似乎应该可以进行搜索,因为 S3 GET 确实支持范围请求:docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/…(js aws-sdk)和docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectGET.html(基本 API)。应该可以处理来自浏览器的范围请求并从范围 S3 请求创建一个流。 @jeffreypriebe 很好的发现。这将是对流的整体功能的一个很好的补充,但是offset
以秒为单位而不是字节。我想可能根据字节范围计算时间。我主要是在回答他的问题,以使其在不先保存的情况下正确流式传输。
我没有看到那个偏移量。我在考虑浏览器的范围标头,它以字节为单位,然后您可以通过它。在该设置中,我认为您不会在 ffmpeg 中寻找,因为您将在请求范围开始时在流中进行管道传输。 (基本上:对 S3 的范围请求,永远不要在 ffmpeg 中搜索。)
@technicallyjosh - 感谢代码示例,但不幸的是它似乎不起作用。如果从 s3 读取的源文件是 mp4 格式,我听不到任何声音。如果我改为从 s3 读取 .ogg 文件,它会完美运行。因此,如果您的原始来源类似于 ogg,这会将音频流式传输为 mp4。在我的情况下,原始来源是 mp4。 (是的,没有 offset 参数)
@LaserJesus 你试过我的例子没有 toFormat() 和 withAudioCodec() 或任何变体?另外,s3中的文件存储的mime类型是什么?【参考方案2】:
块引用 如何从 s3 流式传输 mp4 文件,而不先将其作为文件存储在本地?如何让 ffmpeg 在不对文件进行转码的情况下执行此操作?以下是我目前无法使用的代码。请注意,它尝试将 s3 文件作为流传递给 ffmpeg,并且还将其转码为 mp3,我不希望这样做。
AFAIK - 如果 moov atom 在媒体文件中的正确位置,对于 S3 托管的 mp4,流媒体不需要什么特别的,因为您可以依赖 http。如果客户端请求“分块”编码,它将得到一个分块流,由“END-OF”marker 终止,如下所示。
0\r\n
\r\n
通过包含分块标头,客户端说“我想要一个流”。在幕后,S3 只是 nginx 或 apache,不是吗?他们都尊重标题。
使用 curl CLI 作为您的客户端对其进行测试...
> User-Agent: curl/7.28.1-DEV
> Host: S3.domain
> Accept: */*
> Transfer-Encoding: chunked
> Content-Type: video/mp4
> Expect: 100-continue
可能想尝试将编解码器添加到“Content-Type:”标头。我不知道,但不认为这种类型的流媒体需要它(原子解决了那些东西)
【讨论】:
感谢罗伯特的回答。您在 s3 上移动原始文件中的 moov 原子的建议适用于我现有的代码。这仍然不是我所希望的理想状态,因为我现在需要设置一个任务来为所有上传的内容重新定位 moov atom(或更改上传内容的设备)。有没有办法从 s3 中获取文件,在 Node.js 中更改 moov 位置以进行蒸汽处理,然后在不先存储文件副本的情况下对其进行流式传输(假设我不能事先更改 s3 上的 moov atom 位置)? 如果 S3 上 mp4 媒体的主要用途是流式传输,那么您需要存储可以使用的文件,而不是放错 atom 的文件。你可以考虑一个 fs 监听器,它会运行一个进程来改变原子位置,只要写一个新的 mp4。 我已经为您提供了答案和赏金,因为在 S3 上重新混合文件最终使我的代码正常工作。感谢您的帮助,我希望再发表评论,并附上您提到的 fs 侦听器模式的链接。 嗯。如果它是您想要的 node.js 链接,您可以转到任何“gulp.serve”项目任务。例如关于如何监听变化和生成事件。 OnNew,您必须使用将原子放在前面的开关来处理 ffmpeg。当我有机会时,我会尝试包含更多代码。 vfs.watch 示例....github.com/c9/vfs/blob/master/example/vfs-watch-example.js【参考方案3】:我在缓冲来自 S3 文件对象的文件流时遇到问题。 s3 文件流没有设置正确的标头,它似乎没有正确实现管道。
我认为更好的解决方案是使用这个名为 s3-streams 的 nodejs 模块。它设置正确的标头并缓冲输出,以便流可以正确地通过管道传输到输出响应套接字。它使您无需在重新流式传输之前先在本地保存文件流。
【讨论】:
以上是关于如何使用 Node.js 通过 ffmpeg 流式传输 MP4 文件?的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 node.js 和 socket.io 通过 WebSockets 流式传输 MP3 数据?