Как вы используете Node.js для потоковой передачи файла MP4 с помощью ffmpeg?

Я пытаюсь решить эту проблему уже несколько дней и буду очень признателен за любую помощь по этому поводу.

Я могу успешно транслировать аудиофайл mp4, хранящийся на сервере Node.js, с помощью fluent-ffmpeg, передав расположение файла в виде строки и перекодировав его в mp3. Если я создаю файловый поток из того же файла и передаю его в fluent-ffmpeg, вместо этого он работает с входным файлом mp3, но не с файлом mp4. В случае с файлом mp4 ошибка не выдается, и он утверждает, что поток завершен успешно, но в браузере ничего не воспроизводится. Я предполагаю, что это связано с метаданными, хранящимися в конце файла mp4, но я не знаю, как это исправить. Это тот же самый файл, который работает правильно, когда его местоположение передается в ffmpeg, а не в поток. Когда я пытаюсь передать поток в файл mp4 на s3, снова не возникает никаких ошибок, но в браузер ничего не передается. Это неудивительно, поскольку ffmpeg не будет работать с файлом локально как с потоком, поэтому ожидать, что он будет обрабатывать поток от s3, - это принятие желаемого за действительное.

Как я могу транслировать файл mp4 из s3, не сохраняя его локально как файл сначала? Как мне заставить 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});
    });
});

Это использует библиотеку knox, когда она вызывает aws.s3 ... Я также пробовал писать ее, используя стандартный aws sdk для Node.js, как показано ниже, но я получаю тот же результат, что и выше.

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.

Меня все еще интересует способ передать файл 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});
    });
});

На принимающей стороне у меня просто следующий javascript на пустой странице html:

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 в браузер без использования flash. Это действительно требует, чтобы файл был временно сохранен на сервере Node.js, чтобы метаданные в файле перемещались из конца файла в начало. Чтобы осуществлять потоковую передачу без сохранения временного файла, вам необходимо сначала фактически изменить файл на S3 и изменить эти метаданные. Если вы изменили файл таким образом на S3, вы можете изменить код под заголовком «обновлено снова», чтобы результат S3 передавался прямо в конструктор ffmpeg, а не в файловый поток на сервере Node.js. , а затем предоставив это местоположение файла для ffmepg, как сейчас делает код. Вы можете изменить последнюю команду 'pipe' на 'save (location)', чтобы получить версию файла mp4 локально с перемещением метаданных на передний план. Затем вы можете загрузить эту новую версию файла на S3 и опробовать непрерывную потоковую передачу. Лично я сейчас собираюсь создать задачу, которая изменяет файлы таким образом, поскольку они в первую очередь загружаются в s3. Это позволяет мне записывать и транслировать в mp4 без перекодирования или хранения временного файла на сервере Node.js.


person LaserJesus    schedule 15.11.2015    source источник
comment
Какой кодек для файла? Также я читал, что потоковая передача некоторых файлов m4a не поддерживается для html 5, если это то, что находится на принимающей стороне. Кроме того, вы пробовали не указывать формат и кодек? Примеры здесь Если вы удалите их, это просто позволит клиенту определить тип, поскольку вы просто передаете поток файла в ответ.   -  person technicallyjosh    schedule 19.11.2015
comment
Кодек: Audio: aac (LC) (mp4a / 0x6134706D), 8000 Гц, моно, fltp, 16 кбит / с (по умолчанию). Принимающая сторона - это аудиоконтекст webkit. Я обновлю свой вопрос через секунду, чтобы показать код. Я почти ответил на свой вопрос. У меня сейчас есть потоковая передача mp4 клиенту без перекодирования. Я просто хотел бы найти способ сделать это без временного хранения на сервере Node.js.   -  person LaserJesus    schedule 20.11.2015
comment
погуглите "moov atom" и посмотрите документацию ffmpeg, чтобы разместить индекс в ПЕРЕДНЕЙ ЧАСТИ файла. -movflags   -  person Robert Rowntree    schedule 22.11.2015
comment
Спасибо, Роберт, я включил это в последнее обновление своего поста (пример кода ближе к концу). Это позволяет потоковую передачу MP4 из Node.js, но мне все равно нужно сначала загрузить файл с s3 на сервер узла. Он не мог протянуть его до конца.   -  person LaserJesus    schedule 25.11.2015


Ответы (3)


Blockquote Как я могу транслировать файл mp4 из s3, не сохраняя его сначала локально как файл? Как мне заставить ffmpeg делать это без перекодирования файла? Ниже приведен код, который у меня сейчас не работает. Обратите внимание, что он пытается передать файл s3 в виде потока в ffmpeg, а также перекодирует его в mp3, чего я бы предпочел не делать.

AFAIK - если атом moov находится в нужном месте в медиафайле, для mp4, размещенного на S3, для потоковой передачи ничего особенного не требуется, потому что для этого вы можете положиться на http. Если клиентский запрос "фрагментирован" кодировкой, он получит именно это, фрагментированный поток, завершенный "END-OF" маркер, показанный ниже.

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 :. Я не знаю, но не думаю, что это потребуется для такого типа потоковой передачи (атом разрешает это)

person Robert Rowntree    schedule 22.11.2015
comment
Спасибо за ответ Роберт. Ваше предложение переместить атом moov в исходный файл на s3 сработало с моим существующим кодом. Это все еще не совсем то, на что я надеялся, поскольку теперь мне нужно будет настроить задачу для изменения положения атома moov для всего загруженного контента (или изменить устройства, загружающие контент). Есть ли способ захватить файл из s3, изменить позицию moov для обработки в Node.js, а затем передать его, не сохраняя сначала копию файла (при условии, что я не могу изменить местоположение атома moov на s3 заранее)? - person LaserJesus; 25.11.2015
comment
Если основное использование мультимедиа mp4 на S3 будет потоковой передачей, вам необходимо хранить файлы, которые вы можете использовать, а не файлы с неуместным атомом. Вы могли бы рассмотреть прослушиватель fs, который будет запускать процесс для изменения местоположения атома всякий раз, когда записывается новый mp4. - person Robert Rowntree; 25.11.2015
comment
Я наградил вас ответом и наградой за это, так как re_muxing файла на S3 в конечном итоге заставил мой код заработать. Спасибо за вашу помощь, и я хотел бы получить еще один комментарий со ссылкой на шаблон прослушивателя fs, который вы упомянули. - person LaserJesus; 26.11.2015
comment
Умм. Если вам нужна ссылка на node.js, вы можете перейти к любой задаче проекта gulp.serve. Например, о том, как прислушиваться к изменениям и генерировать событие. OnNew вам нужно будет обработать ffmpeg с помощью переключателя Поместить атом впереди. Когда у меня будет возможность, я постараюсь добавить больше кода. - person Robert Rowntree; 26.11.2015
comment
Пример vfs.watch .... github.com/ c9 / vfs / blob / master / example / vfs-watch-example.js. - person Robert Rowntree; 28.11.2015

Одна из основных проблем здесь заключается в том, что вы не можете искать в конвейерном потоке. Поэтому сначала вам нужно сохранить файл. Однако, если вы хотите просто транслировать с самого начала, вы можете использовать немного другую конструкцию и канал. Вот пример наиболее простого способа сделать это.

// 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});
person technicallyjosh    schedule 20.11.2015
comment
Похоже, поиск должен быть возможен, поскольку S3 GET поддерживает запрос диапазона: docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/ (js aws-sdk) и docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectGET.html (базовый API). Должна быть возможность обработать запрос диапазона от браузера и создать поток из запроса S3 с диапазоном. - person jeffreypriebe; 20.11.2015
comment
@jeffreypriebe Хорошая находка. Это было бы хорошим дополнением к общей функции потоковой передачи, однако offset указывается в секундах, а не в байтах. Я полагаю, что возможно вычислить время на основе диапазона байтов. Я в основном отвечал на его вопрос, чтобы получить его должным образом без предварительного сохранения. - person technicallyjosh; 20.11.2015
comment
Я не видел этого смещения. Я думал о заголовке диапазона браузера, который находится в байтах, и затем вы можете передать его. В этой настройке я бы подумал, что вы не будете искать в ffmpeg, так как вы будете подключаться к потоку в начале диапазона запроса. (В основном: запрос диапазона до S3, никогда не ищите в ffmpeg.) - person jeffreypriebe; 20.11.2015
comment
@technicallyjosh - Спасибо за пример кода, но, к сожалению, он не работает. Если исходный файл, читаемый из s3, находится в формате mp4, я не слышу звука. Если я вместо этого читаю файл .ogg из s3, он отлично работает. Таким образом, это будет передавать аудио в формате mp4, если ваш исходный источник похож на ogg. В моем случае исходный код - mp4. (и да, это без аргумента смещения) - person LaserJesus; 25.11.2015
comment
@LaserJesus вы пробовали мой пример без toFormat () и withAudioCodec () или каких-либо его вариаций? Кроме того, какой тип MIME хранится в файле в s3? - person technicallyjosh; 25.11.2015
comment
Код выдает ошибку, если toFormat не указан. Если кодек не указан, ошибка не возникает, но снова нет звуковых потоков. Я не уверен, что это за тип mime для файла, кроме того, что это файл только со звуком, mp4, aac. - person LaserJesus; 26.11.2015
comment
@technicallyjosh удалось ли вам искать в канальном потоке S3? Та же проблема с файлами mp3. stackoverflow.com/questions/67407961/ - person Kukula Mula; 06.05.2021

У меня возникла проблема с буферизацией файловых потоков из файлового объекта S3. Файловый поток s3 не имеет правильных заголовков, и кажется, что он неправильно реализует конвейер.

Я думаю, что лучшим решением будет использование этого модуля nodejs, называемого s3-streams. Он устанавливает правильные заголовки и буферизует вывод, чтобы поток можно было правильно передать по конвейеру в сокет выходного ответа. Это избавляет вас от необходимости сначала сохранять файловый поток локально перед его повторной потоковой передачей.

person Phillip Ochola    schedule 02.11.2016