Project Icon

mp4-muxer

轻量级JavaScript MP4封装器 支持WebCodecs和多种输出格式

mp4-muxer是一个纯TypeScript实现的MP4封装器,与WebCodecs API无缝集成。支持视频和音频封装,提供Fast Start和分段MP4等内部布局选项。该库性能优异,体积小巧,适用于文件创建和实时流媒体。支持多种主流视频编码(如H.264、H.265、VP9、AV1)和音频编码(AAC、Opus),为开发者提供灵活的媒体处理方案。

mp4-muxer - JavaScript MP4 复用器

WebCodecs API 提供了对媒体编解码器的低级访问,但没有提供将编码后的媒体打包(复用)成可播放文件的方法。本项目用纯 TypeScript 实现了一个 MP4 复用器,具有高质量、快速和体积小的特点,支持视频和音频,以及各种内部布局,如快速启动或分片 MP4。

演示:复用到文件

演示:实时流媒体

**注意:**如果你想创建 WebM 文件,请查看 webm-muxer,它是 mp4-muxer 的姐妹库。

如果你觉得这个库有用并希望支持它,请考虑捐赠 ❤️

快速开始

以下是该库常见用法的示例:

import { Muxer, ArrayBufferTarget } from 'mp4-muxer';

let muxer = new Muxer({
    target: new ArrayBufferTarget(),
    video: {
        codec: 'avc',
        width: 1280,
        height: 720
    },
    fastStart: 'in-memory'
});

let videoEncoder = new VideoEncoder({
    output: (chunk, meta) => muxer.addVideoChunk(chunk, meta),
    error: e => console.error(e)
});
videoEncoder.configure({
    codec: 'avc1.42001f',
    width: 1280,
    height: 720,
    bitrate: 1e6
});

/* 编码一些帧... */

await videoEncoder.flush();
muxer.finalize();

let { buffer } = muxer.target; // buffer 包含最终的 MP4 文件

动机

webm-muxer 因其易用性和与 WebCodecs API 的集成而获得关注后,创建了这个库,以便现在也能创建 MP4 文件,同时保持相同的开发体验。虽然 WebM 是一种更现代的格式,但 MP4 是一个既定标准,在更多设备上得到支持。

安装

使用 NPM,只需安装这个包:

npm install mp4-muxer

你可以像这样导入所有导出的类:

import * as Mp4Muxer from 'mp4-muxer';
// 或者,使用 CommonJS:
const Mp4Muxer = require('mp4-muxer');

或者,你可以简单地在 HTML 中包含该库作为脚本,这将向全局对象添加一个 Mp4Muxer 对象,包含所有导出的类,如下所示:

<script src="build/mp4-muxer.js"></script>

使用方法

初始化

对于每个你想创建的 MP4 文件,像这样创建一个 Muxer 实例:

import { Muxer } from 'mp4-muxer';

let muxer = new Muxer(options);

可用选项由以下接口定义:

interface MuxerOptions {
    target:
        | ArrayBufferTarget
        | StreamTarget
        | FileSystemWritableFileStreamTarget,

    video?: {
        codec: 'avc' | 'hevc' | 'vp9' | 'av1',
        width: number,
        height: number,

        // 向文件添加旋转元数据
        rotation?: 0 | 90 | 180 | 270 | TransformationMatrix
    },

    audio?: {
        codec: 'aac' | 'opus',
        numberOfChannels: number,
        sampleRate: number
    },

    fastStart:
        | false
        | 'in-memory'
        | 'fragmented'
        | { expectedVideoChunks?: number, expectedAudioChunks?: number }

    firstTimestampBehavior?: 'strict' | 'offset' | 'cross-track-offset'
}

该库目前支持的编解码器包括视频的 AVC/H.264、HEVC/H.265、VP9 和 AV1,以及音频的 AAC 和 Opus。

target(必需)

此选项指定复用器创建的数据将被写入的位置。选项包括:

  • ArrayBufferTarget:文件数据将被写入一个大的缓冲区,然后存储在目标中。

    import { Muxer, ArrayBufferTarget } from 'mp4-muxer';
    
    let muxer = new Muxer({
        target: new ArrayBufferTarget(),
        fastStart: 'in-memory',
        // ...
    });
    
    // ...
    
    muxer.finalize();
    let { buffer } = muxer.target;
    
  • StreamTarget:此目标定义了在有新数据可用时将被调用的回调 - 如果你想流式传输数据,例如将其传输到其他地方,这很有用。构造函数具有以下签名:

    constructor(options: {
        onData?: (data: Uint8Array, position: number) => void,
        chunked?: boolean,
        chunkSize?: number
    });
    

    onData 为每个新的可用数据块调用。position 参数指定数据必须写入的字节偏移量。由于复用器写入的数据并不总是连续的,请确保遵守此参数

    当使用 chunked: true 时,复用器创建的数据将首先累积,只有在达到足够大小时才会写出。这有助于减少总写入次数,但代价是增加延迟。它使用默认的 16 MiB 块大小,可以通过手动设置 chunkSize 为所需的字节长度来覆盖。

    如果你想将此目标用于实时流媒体,即在复用完成之前进行播放,你还需要设置 fastStart: 'fragmented'

    使用示例:

    import { Muxer, StreamTarget } from 'mp4-muxer';
    
    let muxer = new Muxer({
        target: new StreamTarget({
            onData: (data, position) => { /* 处理数据 */ }
        }),
        fastStart: false,
        // ...
    });
    
  • FileSystemWritableFileStreamTarget:这本质上是一个分块 StreamTarget 的包装器,旨在简化该库与文件系统访问 API 的使用。将文件直接写入磁盘(在创建过程中)带来许多好处,例如创建远大于可用 RAM 的文件。

    你可以选择覆盖默认的 16 MiB chunkSize

    constructor(
        stream: FileSystemWritableFileStream,
        options?: { chunkSize?: number }
    );
    

    使用示例:

    import { Muxer, FileSystemWritableFileStreamTarget } from 'mp4-muxer';
    
    let fileHandle = await window.showSaveFilePicker({
        suggestedName: `video.mp4`,
        types: [{
            description: 'Video File',
            accept: { 'video/mp4': ['.mp4'] }
        }],
    });
    let fileStream = await fileHandle.createWritable();
    let muxer = new Muxer({
        target: new FileSystemWritableFileStreamTarget(fileStream),
        fastStart: false,
        // ...
    });
    
    // ...
    
    muxer.finalize();
    await fileStream.close(); // 确保关闭流
    

fastStart(必需)

默认情况下,MP4 元数据(轨道信息、采样时间等)存储在文件末尾 - 这使得文件写入更快、更容易。然而,将这些元数据放在文件的_开头_(称为"快速启动")提供了某些好处:文件更容易通过网络流式传输而无需范围请求,像 YouTube 这样的网站可以在上传时就开始处理视频。通过将 fastStart 设置为以下选项之一,该库提供了对元数据放置的完全控制:

  • false:禁用快速启动,将所有元数据放在文件末尾。这个选项是最快的,使用最少的内存。建议用于直接流式传输到磁盘的大型、无界文件。

  • 'in-memory':通过将所有媒体块保存在内存中直到文件完成,生成具有快速启动功能的文件。这个选项以更昂贵的完成步骤和更高的内存需求为代价,产生最紧凑的输出。当使用 ArrayBufferTarget 时,这是首选选项,因为它将产生更高质量的输出,而不会改变内存占用。

  • 'fragmented':生成一个_分片 MP4 (fMP4)_ 文件,通过将采样元数据分组到"片段"(短媒体段)中,均匀地将其放置在整个文件中,同时将通用元数据放在文件开头。分片文件非常适合流式传输,因为它们针对随机访问进行了优化,几乎不需要寻找。此外,无论文件变得多大,它们的创建都保持轻量级,因为它们不需要长时间将媒体保存在内存中。虽然分片文件不像常规 MP4 文件那样广泛支持,但这个选项提供了强大的好处,几乎没有什么缺点。更多详情见此

  • object:通过在复用开始时为元数据保留空间,生成具有快速启动功能的文件。为了知道需要保留多少字节才能安全,你需要提供以下数据:

    {
        expectedVideoChunks?: number,
        expectedAudioChunks?: number
    }
    

    注意,如果你有视频轨道,expectedVideoChunks 属性是_必需的_ - 音频也是如此。设置此选项后,你不能复用比指定数量更多的块(但少于指定数量是可以的)。

    这个选项比 'in-memory' 更快,不使用额外内存,但会产生稍大的输出,当你想将文件流式传输到磁盘同时保留快速启动功能时,这个选项很有用。

firstTimestampBehavior(可选)

指定如何处理每个轨道的第一个块具有非零时间戳的情况。在默认的严格模式下,时间戳必须从 0 开始以确保正确播放。然而,当直接将 MediaTrackStream 的视频帧或音频数据传输到编码器然后到复用器时,时间戳通常相对于文档的年龄或计算机的时钟,这通常不是我们想要的。必须明确设置这些时间戳的处理:

  • 使用 'offset' 将每个轨道的时间戳偏移该轨道第一个块的时间戳。这样,它从 0 开始。
  • 使用 'cross-track-offset' 将每个轨道的时间戳偏移_所有轨道第一个块时间戳的最小值_。这类似于 'offset',但应在所有轨道使用相同时钟时使用。

复用媒体块

然后,设置好 VideoEncoder 和 AudioEncoder 后,使用以下方法将编码的块发送到复用器:

addVideoChunk(
    chunk: EncodedVideoChunk,
    meta?: EncodedVideoChunkMetadata,
    timestamp?: number,
    compositionTimeOffset?: number
): void;

addAudioChunk(
    chunk: EncodedAudioChunk,
    meta?: EncodedAudioChunkMetadata,
    timestamp?: number
): void;

这两个方法都接受一个可选的第三个参数 timestamp(微秒),如果指定,它将覆盖传入块的 timestamp 属性。

元数据来自传递给 VideoEncoder 或 AudioEncoder 构造函数的 output 回调的第二个参数,需要传递给复用器,如下所示:

let videoEncoder = new VideoEncoder({
    output: (chunk, meta) => muxer.addVideoChunk(chunk, meta),
    error: e => console.error(e)
});
videoEncoder.configure(/* ... */);

可选字段 compositionTimeOffset 可以在数据块的解码时间不等于其呈现时间时使用;这种情况出现在存在 B帧 时。使用 WebCodecs API 进行编码时不会出现 B 帧。解码时间通过从 timestamp 中减去 compositionTimeOffset 来计算,这意味着 timestamp 决定了呈现时间。

如果您从 WebCodecs API 以外的来源获得编码媒体数据,可以使用以下方法将原始数据直接发送到复用器:

addVideoChunkRaw(
    data: Uint8Array,
    type: 'key' | 'delta',
    timestamp: number, // 以微秒为单位
    duration: number, // 以微秒为单位
    meta?: EncodedVideoChunkMetadata,
    compositionTimeOffset?: number // 以微秒为单位
): void;

addAudioChunkRaw(
    data: Uint8Array,
    type: 'key' | 'delta',
    timestamp: number, // 以微秒为单位
    duration: number, // 以微秒为单位
    meta?: EncodedAudioChunkMetadata
): void;

完成

编码完成且所有编码器都已刷新后,在 Muxer 实例上调用 finalize 来完成 MP4 文件:

muxer.finalize();

使用 ArrayBufferTarget 时,最终的缓冲区可通过以下方式访问:

let { buffer } = muxer.target;

使用 FileSystemWritableFileStreamTarget 时,确保在调用 finalize 后关闭流:

await fileStream.close();

详细信息

可变帧率

MP4 文件支持可变帧率,但观察到一些播放器(如 QuickTime)在时间戳不规则时表现不佳。因此,尽可能尝试使用固定帧率。

关于分段 MP4 文件的其他说明

通过将媒体和相关元数据分解成小片段,fMP4 文件优化了随机访问,非常适合流媒体传输,同时即使对于长文件也能保持低成本写入。但是,您应该记住以下几点:

  • 媒体数据块缓冲: 在复用包含视频和音频轨道的文件时,复用器需要等待两种媒体的数据块来完成任何给定的片段。换句话说,如果另一种媒体尚未编码到该时间戳的数据块,它必须缓冲一种媒体的数据块。例如,如果您先编码所有视频帧,然后再编码音频,复用器将不得不将所有这些视频帧保存在内存中,直到音频数据块开始输入。如果您的视频很长,这可能会导致内存耗尽。当只有一个媒体轨道时,不会出现这个问题。因此,在复用多媒体文件时,请确保文件大小有一定限制,或者数据块以某种交错方式编码(如实时媒体的情况)。这将使内存使用保持在恒定的低水平。

  • 视频关键帧频率: 为了能够在不知道前面片段的情况下播放某个片段,每个轨道在片段中的第一个样本必须是关键帧。然而,这意味着复用器需要等待视频关键帧来开始一个新的片段。如果这些关键帧太不频繁,片段会变得太大,影响随机访问。因此,每 5-10 秒,您应该强制生成一个视频关键帧,如下所示:

    videoEncoder.encode(frame, { keyFrame: true });
    

实现与开发

MP4 文件基于 ISO 基本媒体格式,该格式将文件结构化为盒子(或原子)的层次结构。用于实现此库的标准包括 ISO/IEC 14496-1ISO/IEC 14496-12ISO/IEC 14496-14。此外,QuickTime MP4 规范 也是一个非常有用的资源。

对于开发,克隆此存储库,使用 npm install 安装所有内容,然后运行 npm run watch 将代码打包到 build 目录中。运行 npm run check 以运行 TypeScript 类型检查器,运行 npm run lint 以运行 ESLint。

项目侧边栏1项目侧边栏2
推荐项目
Project Cover

豆包MarsCode

豆包 MarsCode 是一款革命性的编程助手,通过AI技术提供代码补全、单测生成、代码解释和智能问答等功能,支持100+编程语言,与主流编辑器无缝集成,显著提升开发效率和代码质量。

Project Cover

AI写歌

Suno AI是一个革命性的AI音乐创作平台,能在短短30秒内帮助用户创作出一首完整的歌曲。无论是寻找创作灵感还是需要快速制作音乐,Suno AI都是音乐爱好者和专业人士的理想选择。

Project Cover

有言AI

有言平台提供一站式AIGC视频创作解决方案,通过智能技术简化视频制作流程。无论是企业宣传还是个人分享,有言都能帮助用户快速、轻松地制作出专业级别的视频内容。

Project Cover

Kimi

Kimi AI助手提供多语言对话支持,能够阅读和理解用户上传的文件内容,解析网页信息,并结合搜索结果为用户提供详尽的答案。无论是日常咨询还是专业问题,Kimi都能以友好、专业的方式提供帮助。

Project Cover

阿里绘蛙

绘蛙是阿里巴巴集团推出的革命性AI电商营销平台。利用尖端人工智能技术,为商家提供一键生成商品图和营销文案的服务,显著提升内容创作效率和营销效果。适用于淘宝、天猫等电商平台,让商品第一时间被种草。

Project Cover

吐司

探索Tensor.Art平台的独特AI模型,免费访问各种图像生成与AI训练工具,从Stable Diffusion等基础模型开始,轻松实现创新图像生成。体验前沿的AI技术,推动个人和企业的创新发展。

Project Cover

SubCat字幕猫

SubCat字幕猫APP是一款创新的视频播放器,它将改变您观看视频的方式!SubCat结合了先进的人工智能技术,为您提供即时视频字幕翻译,无论是本地视频还是网络流媒体,让您轻松享受各种语言的内容。

Project Cover

美间AI

美间AI创意设计平台,利用前沿AI技术,为设计师和营销人员提供一站式设计解决方案。从智能海报到3D效果图,再到文案生成,美间让创意设计更简单、更高效。

Project Cover

AIWritePaper论文写作

AIWritePaper论文写作是一站式AI论文写作辅助工具,简化了选题、文献检索至论文撰写的整个过程。通过简单设定,平台可快速生成高质量论文大纲和全文,配合图表、参考文献等一应俱全,同时提供开题报告和答辩PPT等增值服务,保障数据安全,有效提升写作效率和论文质量。

投诉举报邮箱: service@vectorlightyear.com
@2024 懂AI·鲁ICP备2024100362号-6·鲁公网安备37021002001498号