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

Las*_*sus 11 streaming ffmpeg amazon-s3 node.js fluent-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});
    });
});
Run Code Online (Sandbox Code Playgroud)

这是在调用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});
Run Code Online (Sandbox Code Playgroud)

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

更新

我将mp3文件放在同一个s3存储桶中,我在这里使用的代码能够将文件流式传输到浏览器而不存储本地副本.所以我面临的流媒体问题与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});
    });
});
Run Code Online (Sandbox Code Playgroud)

在接收方我只是在一个空的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()
Run Code Online (Sandbox Code Playgroud)

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

答案

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

tec*_*osh 6

这里的一个主要问题是你不能在管道流上寻找.所以你必须先存储文件.但是,如果您只想从头开始流式传输,则可以使用略有不同的构造和管道.这是一个最直接的方法的例子.

// 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});
Run Code Online (Sandbox Code Playgroud)


Rob*_*ree 3

Blockquote 如何从 s3 流式传输 mp4 文件,而不先将其存储为本地文件?如何让 ffmpeg 执行此操作而不对文件进行转码?以下是我目前无法正常工作的代码。请注意,它尝试将 s3 文件作为流传递给 ffmpeg,并且还将其转码为 mp3,我不想这样做。

AFAIK - 如果 moov 原子位于媒体文件中的正确位置,则对于 S3 托管的 mp4,流式传输不需要任何特殊内容,因为您可以依靠 http 来实现这一点。如果客户端请求“分块”编码,它将得到一个由“END-OF”标记终止的分块流,如下所示。

0\r\n
\r\n 
Run Code Online (Sandbox Code Playgroud)

通过包含分块标头,客户端表示“我想要一个流”。在幕后,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
Run Code Online (Sandbox Code Playgroud)

可能想尝试将编解码器添加到“Content-Type:”标头中。我不知道,但不认为这种类型的流媒体需要它(原子解决了这个问题)