使用 Node JS 将 h264 文件转换或包装为 mp4

Convert or wrap h264 file to mp4 with Node JS

我正在 Raspberry Pi 上拍摄视频,并希望将原始 h264 文件转换为 mp4 文件,或者可能将其包装在 mp4 中,就像使用命令 line/Python 完成的那样。然而,我希望在 NodeJS 中做到这一点。似乎有许多节点 JS 库在 npm 上使用 Raspberry Pi 的 mp4-box 库。但是,其中 none 个具有适当的文档或似乎适合我的项目的需要。我不知道我是否遗漏了什么或者这是不可能的。

了解 mp4(如 mkv)是一个容器非常重要。您可以向这些容器添加视频、音频、字幕 "layer"。而且H.264已经是一种压缩格式,不是原始视频格式。

没有一种直接的方法可以将 H.264 编码文件嵌入到 MP4 容器中,而无需从头开始实际构建整个文件结构。 可行的,但为了做到这一点,您需要了解 mp4 容器格式(它在很大程度上基于 Quicktime MOV 容器)并使用 TypedArrays 构建它,结果你可以保存为 MP4 文件(我创建了一个 paste here 描述容器文件结构)。

另一种方法是从 Node.js 生成 FFmpeg(或直接使用该软件)并提供 H.264 作为输入并将其保存为 MP4 文件。这很简单。该命令类似于:

ffmpeg -i yourH264encodedFileHere -c:v copy mp4FileContainer.mp4

到 运行 从 Node 可以使用 spawn(参见示例)。

这种基本方法的替代方法是安装和使用 fluent-ffmpeg NPM module 来完成所有繁重的工作。

例子

var ffmpeg = require("fluent-ffmpeg");
var inFilename = "video.h264";
var outFilename = "video.mp4";

ffmpeg(inFilename)
  .outputOptions("-c:v", "copy") // this will copy the data instead or reencode it
  .save(outFilename);

一些注意事项:

  • fluent 可以对文件名(空格等)挑剔。
  • FFmpeg 需要预先安装并在全局路径中可用。如果你不想这样,你可以使用 ffmpeg.setFfmpegPath(pathToFFmpegBin) 代替。
  • 要在 RPI 上安装 FFmpeg,this resource 可能会有用。

尝试在我的 pi 零上安装 ffmpeg 花费了两个多小时并以错误结束,所以这是包装 mp4 的替代方法:

  1. 安装 MP4Box
sudo apt install -y gpac
  1. 在您的 Node 脚本中,使用 exec 调用 MP4Box 命令,就像 Raspivid docs 所说的那样
import raspivid from 'raspivid';
import { createWriteStream, unlinkSync } from 'fs'
import { exec } from 'child_process'
import internal from 'stream'


// Helper: promisify streaming to a file
const streamToFile = (inputStream: internal.Readable, filePath: string) => {
    return new Promise((resolve, reject) => {
        const fileWriteStream = createWriteStream(filePath)

        inputStream
            .pipe(fileWriteStream)
            .on('finish', resolve)
            .on('error', reject)
    })
}

// Helper: promisify executing shell command
const execPromise = (command: string) => {
        return new Promise((resolve, reject) => {
            exec(command, (error, stdout, stderr) => {
                doLogInfo(stdout)
                if (error) {
                    console.error(error.message)
                    reject(error)
                }
                if (stderr) {
                    console.error(stderr)
                    resolve(stderr)
                }
                resolve()
            })
        })
    }

// Driver
const getMp4FromRaspivid = async () => {
    // Record vid w raspivid
    const h264Path = './myVid.h264'
    const readVideoStream = await raspivid(VIDEO_OPTIONS) as internal.Readable;
    await streamToFile(readVideoStream, h264Path)

    // Wrap in mp4
    const mp4Path = './myVid.mp4'
    const execCommand = ['MP4Box', '-add', h264Path, mp4Path].join(' ')
    await execPromise(execCommand)

    // Delete h264
    unlinkSync(h264Path)

}