你如何使用 Node.js 通过 ffmpeg 流式传输 MP4 文件?

How do you use Node.js to stream an MP4 file with ffmpeg?

几天来我一直在尝试解决这个问题,非常感谢有关此主题的任何帮助。

通过将文件位置作为字符串传递并将其转码为 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});

=====================================

已更新

我将一个 mp3 文件放在同一个 s3 存储桶中,我在这里使用的代码可以工作,并且能够将文件流式传输到浏览器,而无需存储本地副本。所以我遇到的流媒体问题与 mp4/aac container/encoder 格式有关。

我仍然对将 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()

=====================================

答案

上面标题 'updated again' 下的代码将通过 Node.js 服务器从 s3 流式传输 mp4 文件到浏览器,而不使用 flash。它确实需要将文件临时存储在 Node.js 服务器上,以便将文件中的元数据从文件末尾移动到前面。为了在不存储临时文件的情况下进行流式传输,您需要先实际修改 S3 上的文件并更改此元数据。如果您在 S3 上以这种方式更改了文件,那么您可以修改标题 'updated again' 下的代码,以便将 S3 的结果直接通过管道传输到 ffmpeg 构造函数中,而不是通过 Node.js 上的文件流=] 服务器,然后将该文件位置提供给 ffmepg,就像代码现在所做的那样。您可以将最终的 'pipe' 命令更改为 'save(location)' 以在本地获取 mp4 文件的版本,并将元数据移至前面。然后您可以将该文件的新版本上传到 S3 并尝试端到端流式传输。就我个人而言,我现在要创建一个任务,在文件首先上传到 s3 时以这种方式修改文件。这允许我在 mp4 中录制和流式传输,而无需转码或在 Node.js 服务器上存储临时文件。

这里的主要问题之一是您无法在管道流上进行搜索。所以你必须先存储文件。但是,如果您只想从头开始流式传输,您可以使用稍微不同的结构和管道。这是最直接的方法示例。

// 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});

Blockquote How can I stream the mp4 file from s3, without storing it locally as a file first? How do I get ffmpeg to do this without transcoding the file too? The following is the code I have at the moment which isn't working. Note that it attempts to pass the s3 file as a stream to ffmpeg and it's also transcoding it into an mp3, which I'd prefer not to do.

AFAIK - 如果 moov atom 在媒体文件中的正确位置,对于 S3 托管的 mp4,流式传输不需要什么特别的,因为您可以依赖 http。如果客户端请求 "chunked" 编码,它将得到一个由 "END-OF" marker 终止的分块流,如下所示。

0\r\n
\r\n 

通过包含分块 header,客户会说“我想要一个流”。在幕后,S3 只是 nginx 或 apache,不是吗?他们都尊重 header。

使用 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:" header。我不知道,但不认为这种类型的流式传输需要它(原子解决了这些问题)

我在缓冲来自 S3 文件 object 的文件流时遇到问题。 s3 文件流没有正确的 headers 设置,它似乎没有正确实现管道。

我认为更好的解决方案是使用这个名为 s3-streams 的 nodejs 模块。它设置正确的 headers 并缓冲输出,以便流可以正确地通过管道传输到输出响应套接字。它使您无需在重新流式传输之前先在本地保存文件流。