web-dev-qa-db-ja.com

Node.jsを使用してffmpegでMP4ファイルをストリーミングするにはどうすればよいですか?

私はこの問題を数日間解決しようとしてきましたが、この件について何か助けていただければ幸いです。

ファイルの場所を文字列として渡し、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()

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

答え

上記の「再度更新された」というタイトルのコードは、フラッシュを使用せずに、mp4ファイルをs3からNode.jsサーバー経由でブラウザーにストリーミングします。ファイル内のメタデータがファイルの末尾から先頭に移動されるように、ファイルをNode.jsサーバーに一時的に保存する必要があります。一時ファイルを保存せずにストリーミングするには、最初にS3でファイルを実際に変更し、このメタデータを変更する必要があります。 S3でこのようにファイルを変更した場合は、「updated again」というタイトルのコードを変更して、S3の結果がNode.jsサーバーのファイルストリームではなく、ffmpegコンストラクターに直接パイプされるようにすることができます。 、次に、コードが現在行っているように、そのファイルの場所をffmepgに提供します。最後の「pipe」コマンドを「save(location)」に変更して、メタデータを前面に移動した状態でmp4ファイルのバージョンをローカルで取得できます。次に、その新しいバージョンのファイルをS3にアップロードして、エンドツーエンドのストリーミングを試すことができます。個人的には、最初にs3にアップロードされるときに、この方法でファイルを変更するタスクを作成します。これにより、Node.jsサーバーに一時ファイルをトランスコードしたり保存したりせずに、mp4で記録およびストリーミングできます。

12
LaserJesus

Blockquote最初にファイルとしてローカルに保存せずにs3からmp4ファイルをストリーミングするにはどうすればよいですか?ファイルをトランスコードせずにffmpegでこれを行うにはどうすればよいですか?以下は、私が現在持っている、機能していないコードです。 s3ファイルをストリームとしてffmpegに渡そうとし、それをmp3にトランスコードしていることに注意してください。これは私がしたくないことです。

AFAIK-moov atomがメディアファイルの適切な場所にある場合、S3でホストされているmp4の場合、httpに依存できるため、ストリーミングに特別なことは何も必要ありません。クライアントの要求が「チャンク」の場合「それをエンコードすると、それだけが得られます。チャンク化されたストリームは、以下に示す「END-OF」 マーカー で終了します。

0\r\n
\r\n 

チャンクヘッダーを含めることで、クライアントは「ストリームが欲しい」と言っています。隠れて、S3は単なるnginxですか、それともApacheではありませんか?どちらもヘッダーを尊重します。

クライアントとしてcurlCLIを使用してテストします...

> User-Agent: curl/7.28.1-DEV
> Host: S3.domain
> Accept: */*
> Transfer-Encoding: chunked
> Content-Type: video/mp4
> Expect: 100-continue

「Content-Type:」ヘッダーにコーデックを追加してみてください。私は知りませんが、このタイプのストリーミングに必要になるとは思いません(atomはそのようなものを解決します)

3
Robert Rowntree

ここでの主な問題の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});
6
technicallyjosh

S3ファイルオブジェクトからのファイルストリームのバッファリングで問題が発生しました。 s3ファイルストリームには正しいヘッダーが設定されておらず、パイピングが正しく実装されていないようです。

より良い解決策は、s3-streamsと呼ばれるこのnodejsモジュールを使用することだと思います。ストリームを出力応答ソケットに正しくパイプできるように、正しいヘッダーを設定し、出力をバッファリングします。これにより、ファイルストリームを再ストリーミングする前に、最初にローカルに保存する必要がなくなります。

0
Phillip Ochola