
音视频视频处理音频处理【免费下载链接】mediabunnyPure TypeScript media toolkit for reading, writing, and converting video and audio files, directly in the browser.项目地址https://gitcode.com/gh_mirrors/me/mediabunny点击查看免费下载mediabunny/server是 Mediabunny 官方扩展包它借助 NodeAV基于 FFmpeg C API 的 N-API 绑定为 Node、Bun、Deno 等服务端环境补齐浏览器专属的编解码与视频处理能力。注册之后你就能在 TypeScript 服务端代码中直接使用 Mediabunny 完整特性做解码、编码、转码、帧变换与流式处理而无需再调用 FFmpeg CLI。读完本文你将掌握它的安装配置、hardwareContext硬件加速选项、与 NodeAVAVFrame的零拷贝互操作方式以及如何搭建一个内存占用 O(1) 的流式媒体压缩服务。为什么服务端需要单独一个扩展包Mediabunny 的核心库默认依赖浏览器环境完整的解码器、编码器与视频处理能力建立在 WebCodecs、Canvas 2D 等浏览器 API 之上。在 Node、Bun、Deno 这类服务端运行时中这些 API 并不存在因此核心包的功能会受限。mediabunny/server解决了这个问题它通过 NodeAV一个提供 FFmpeg C API 的 N-API 绑定的现代 Node.js 库把服务端缺失的能力“polyfill”回 Mediabunny使你在服务端也能使用全部 Mediabunny 特性。相比“用子进程去调 FFmpeg CLI 再解析输出”的传统做法这套 API 原生融入 TypeScript 类型系统没有进程启动开销、没有文本协议解析也没有临时文件周转。该扩展包新增的能力包括视频解码与编码AVCH.264、HEVCH.265、VP8、VP9、AV1 与 ProRes支持长度前缀length-prefixed与 Annex B 两种 AVC/HEVC 格式支持通过 VP9 与 ProRes 处理透明视频。音频解码与编码AAC、MP3、Vorbis、Opus、FLAC、AC-3、E-AC-3 与 DTSAAC 同时支持 AAC 与 ADTS 两种封装格式。视频帧变换缩放resize、旋转rotate、翻转flip、裁剪crop。自动硬件加速在 macOS、Linux、Windows 上自动检测并启用可用的硬件编解码器。内建多线程所有解码器与编码器自动运行在独立线程上主线程不被阻塞。零拷贝解码与编码路径视频帧与音频样本数据默认不会从 FFmpeg 侧复制进 JavaScript。安装mediabunny/server以 Mediabunny 为 peer 依赖需要同时安装两者npm install mediabunny mediabunny/server从 packages/server/package.json 可以看到该包还直接依赖node-av^6.0.0与同版本的mediabunny/prores用于 ProRes 解码见下文实现细节并且声明mediabunny^1.45.0为 peer 依赖——也就是说它要求宿主项目已经安装了对应版本范围的 Mediabunny 核心库。包本身遵循与 Mediabunny 一致的 MPL-2.0 许可。快速开始一行注册全量能力就位使用方式极其简单只需在项目入口处调用registerMediabunnyServer()import { registerMediabunnyServer } from mediabunny/server; registerMediabunnyServer();调用之后你就拥有了服务端上的完整 Mediabunny 特性集。从 注册实现 可以看到这一步实际完成的工作是注册视频解码器NodeAvVideoDecoder基于libavcodec支持 AVC/HEVC/VP8/VP9/AV1注册 ProRes 解码器registerProresDecoder()源码注释说明它比 FFmpeg 更快因此优先使用注册视频编码器NodeAvVideoEncoder额外支持 ProRes注册音频解码器与编码器NodeAvAudioDecoder/NodeAvAudioEncoderAAC/MP3/Vorbis/Opus/FLAC/AC-3/E-AC-3/DTS注册基于libavfilter的VideoSample变换器transformVideoSample使缩放、旋转、翻转、裁剪可用。另外两点值得注意必须在与 Mediabunny 交互之前调用官方文档明确要求先注册再使用否则解码/编码会落到缺省实现上。防重复加载注册函数内部使用一个全局Symbol.for(mediabunny/server loaded)标记检测重复加载。如果包被加载了两次例如多个依赖分别引入了不同版本会输出一条显式警告提示你检查依赖重复或打包配置。测试代码 test/node/server-extension.test.ts 也演示了这一标准用法在beforeAll中调用registerMediabunnyServer()随后即可直接构造NodeAvVideoDecoder等类并驱动完整的解码器/编码器生命周期init → decode/encode → flush → close。配置选项hardwareContextregisterMediabunnyServer接受一个可选的options参数目前包含一个关键配置项hardwareContext用于控制硬件加速上下文。指定具体的硬件渲染设备import { registerMediabunnyServer } from mediabunny/server; import * as NodeAv from node-av; registerMediabunnyServer({ // Use a specific hardware rendering device: hardwareContext: NodeAv.HardwareContext.create( NodeAv.AV_HWDEVICE_TYPE_VAAPI, /dev/dri/renderD128, ), });上面这个例子在 Linux 上显式指定 VAAPI 设备节点/dev/dri/renderD128常见于 Intel/AMD 集成显卡环境。同理也可以用AV_HWDEVICE_TYPE_VIDEOTOOLBOXmacOS或AV_HWDEVICE_TYPE_D3D11VA/AV_HWDEVICE_TYPE_CUDAWindows等平台类型。hardwareContext 的三种形态从 options 类型定义 可以看到hardwareContext支持三种取值NodeAv.HardwareContext实例直接使用该上下文null显式禁用硬件加速(codecId: NodeAv.AVCodecID) MaybePromiseNodeAv.HardwareContext | null函数每次解析硬件解码器/编码器时都会调用该函数无缓存按 codec ID 返回该编解码器应使用的上下文返回null表示该编解码器不使用硬件加速。当hardwareContext未提供时misc.ts 中的 getHardwareContext 会在首次使用时调用NodeAv.HardwareContext.auto()自动探测可用设备并缓存结果供后续复用。注册函数还会对传入的options做严格校验不是对象、或hardwareContext既非HardwareContext实例又非函数时会抛出TypeError。在编解码配置层面关闭硬件加速除了注册时的hardwareContext每个解码器/编码器实例本身也遵循 Mediabunny 的hardwareAcceleration配置。在 video-decoder.ts 与 video-encoder.ts 中可以看到当配置为prefer-software时直接回退到NodeAv.Codec.findDecoder(codecId)/findEncoder(codecId)的软件实现。这也是官方性能章节提到的“除非显式用hardwareAcceleration: prefer-software关闭”的落点。实战一个内存占用 O(1) 的上传压缩服务下面这个完整示例来自官方文档它实现了一个简单的 Node.js 媒体压缩服务器客户端请求体以流的方式喂给 Mediabunny媒体被处理后输出直接以流的方式落到磁盘。得益于流水线pipelining内存占用为 O(1)同时由于流背压backpressure上传过快时网络层会被自动降速不会把内存打爆。import { ALL_FORMATS, Conversion, FilePathTarget, Input, Mp4OutputFormat, Output, Quality, ReadableStreamSource } from mediabunny; import { registerMediabunnyServer } from mediabunny/server; import { Readable } from node:stream; import http from node:http; registerMediabunnyServer(); const server http.createServer(async (req, res) { // Read the request body as a stream const stream Readable.toWeb(req) as ReadableStreamUint8Array; const input new Input({ source: new ReadableStreamSource(stream), formats: ALL_FORMATS, }); // Stream the output directly to the disk, could also stream to S3 etc. const output new Output({ format: new Mp4OutputFormat(), target: new FilePathTarget(./converted-${crypto.randomUUID()}.mp4), }); try { const conversion await Conversion.init({ input, output, video: async track ({ codec: avc, height: Math.min(720, await track.getDisplayHeight()), quality: new Quality(medium), }), }); await conversion.execute(); res.statusCode 204; res.end(); } catch (error) { res.statusCode 500; res.end(); console.error(Error processing media:, error); } }); server.listen(3000);几个关键点的拆解输入ReadableStreamSource把 Node 的Readable流包装成 Mediabunny 可消费的ReadableStreamUint8Arrayformats: ALL_FORMATS让输入探测支持全部容器格式。输出Mp4OutputFormat指定输出封装为 MP4FilePathTarget把结果流式写入磁盘文件换成 S3、对象存储等任意 target 同样可行。转码策略video回调返回一个对象声明输出视频用 AVC 编码、高度不超过 720p取原视频显示高度与 720 的较小值、质量等级Quality(medium)。Mediabunny 会根据这些参数解析出具体的编码器配置分辨率、帧率、码率、像素格式等。整体流程Conversion.init完成输入/输出管线装配conversion.execute()驱动全流水线执行。因为解码器、变换器、编码器都运行在独立线程且数据以流的形式逐段推进所以整体内存占用恒定与媒体文件总大小无关。这段代码也可以直接对应到仓库里的 hls-transcoding 与 file-compression 等示例所演示的同一套ConversionAPI 上。性能特征mediabunny/server的性能根基在于它的架构是“薄包装”两层薄封装它只是 NodeAV 的薄包装而 NodeAV 本身又是 FFmpeg C API 的薄包装。相比在 Node 里调 FFmpeg CLI 子进程避免了进程创建、标准输入输出传输、文本/文件格式解析等开销。多线程所有解码器和编码器自动运行在独立线程主线程事件循环不被阻塞适合高并发服务。硬件加速自动启用在支持的操作系统上自动检测并使用可用硬件编解码器macOS、Linux、Windows除非显式配置hardwareAcceleration: prefer-software。默认零拷贝视频帧与音频样本数据不会从 FFmpeg 复制到 JavaScript除非你显式调用VideoSample.copyTo()或AudioSample.copyTo()。GPU 解码 → 编码的零拷贝路径会在可行时自动启用。以上均为架构层面的可验证事实见 packages/server/src 下各编解码器实现与官方文档的 Performance 章节具体吞吐数字取决于硬件、编解码器与媒体参数这里不做杜撰。与 NodeAV 深度集成AVFrame 零拷贝互操作mediabunny/server导出了两个自定义样本资源类让你能在 Mediabunny 的世界与 NodeAV也就是 FFmpeg的世界之间零拷贝地穿梭AvFrameVideoSampleResource一个由 NodeAVFrame其底层是 FFmpegAVFrame直接支撑的VideoSampleResource。用它构造的VideoSample不经过任何数据复制由于AVFrame本身可以由 GPU 内存支撑这也正是零拷贝硬件加速编解码路径的来源。AvFrameAudioSampleResource同理用于音频的AudioSampleResource。两个资源类的构造器都会校验传入Frame的媒体类型视频/音频并接管Frame的所有权——资源关闭时会调用frame.free()。如果你想保留一份自用拷贝请先 clone 再传入。双向转换的完整示例import { VideoSample, AudioSample } from mediabunny; import { AvFrameVideoSampleResource, AvFrameAudioSampleResource, toAvFrame } from mediabunny/server; // Frame - VideoSample new VideoSample(new AvFrameVideoSampleResource(frame), { timestamp }); // Frame - AudioSample new AudioSample(new AvFrameAudioSampleResource(frame)); // (uses the timestamp in the frame) // VideoSample - Frame await toAvFrame(videoSample, frame); // AudioSample - Frame await toAvFrame(audioSample, frame);toAvFrame的语义值得展开见 toAvFrame 实现若样本本身由AvFrameVideoSampleResource/AvFrameAudioSampleResource支撑则对目标Frame执行unref()ref()——即引用同一个AVFrame完全零拷贝否则执行真实的像素/采样数据复制视频走copyVideoSampleToAvFrame音频走copyAudioSampleToAvFrame视频帧的时间基统一设为1/1000000微秒精度并把样本的微秒级 timestamp/duration 写入frame.pts/frame.duration音频帧的时间基设为1/sampleRatepts 由timestamp * sampleRate换算而来。Electron 示例GPU 帧直采不进 CPU一个非常典型的零拷贝场景是 Electron 应用自捕获。下面的示例在 macOS 上通过 VideoToolbox 硬件上下文与SharedTexture把渲染进程 GPU 上的纹理直接导入为 Mediabunny 的VideoSample——视频数据自始至终没有离开 GPUimport { VideoSample } from mediabunny; import { AvFrameVideoSampleResource } from mediabunny/server; import { HardwareContext, SharedTexture, AV_HWDEVICE_TYPE_VIDEOTOOLBOX } from node-av; // Create hardware context (platform-specific) const hw HardwareContext.create(AV_HWDEVICE_TYPE_VIDEOTOOLBOX); using sharedTexture SharedTexture.create(hw); // In Electron paint event with offscreen rendering offscreen.webContents.on(paint, (event) { const texture event.texture; if (!texture?.textureInfo) { return; } // Import as hardware frame (zero-copy) const frame sharedTexture.importTexture(texture.textureInfo, { pts: 0n }); const sample new VideoSample(new AvFrameVideoSampleResource(frame), { timestamp: 0, duration: 0, }); texture.release(); });拿到sample之后你可以直接把它喂给 Mediabunny 的编码器编码器同样能走av_frame_ref零拷贝路径见 video-encoder.ts 的 encode 方法实现 GPU 级屏幕录制/推流。麦克风录音示例NodeAV 的 Device API 提供了访问用户麦克风的能力配合AvFrameAudioSampleResource可以无缝接入 Mediabunny 的音频样本管线。下面示例还演示了把时间戳归零减去首帧时间戳import { AudioSample } from mediabunny; import { AvFrameAudioSampleResource } from mediabunny/server; import { DeviceAPI, Decoder } from node-av; await using mic await DeviceAPI.openMicrophone(); const audioStream mic.audio()!; using decoder await Decoder.create(audioStream); let firstTimestamp: number | null null; for await (const frame of decoder.frames(mic.packets(audioStream.index))) { if (!frame) { break; } const sample new AudioSample(new AvFrameAudioSampleResource(frame)); if (firstTimestamp null) { firstTimestamp sample.timestamp; } // Offset timestamps so they start at 0 sample.setTimestamp(sample.timestamp - firstTimestamp); // Do something with the sample now, like passing it to an AudioSampleSource // ... }视频与音频处理浏览器环境有 Canvas 2D 这类天然适合视频帧处理的 API且与 Mediabunny 集成良好服务端没有这些 API因此需要其他方案。mediabunny/server提供了三个层次的替代路径。VideoSample.transform()开箱即用的帧变换VideoSample上有一个transform()方法在注册了mediabunny/server后即可使用用于简单的几何变换const transformed await sample.transform({ width: 640, height: 360, fit: cover, });它的底层是 transformVideoSample 所构建的一个libavfilter滤镜图其执行顺序固定为正方形像素归一化square-pixel normalize→ 旋转 → 翻转 → 裁剪 → 按 fit 策略缩放。其中旋转 90/180/270 度分别映射为transpose1、两次transpose1、transpose2flip对应hflip裁剪通过cropw:h:x:y实现fit三种取值语义清晰fill直接scale到目标尺寸可能拉伸变形contain用force_original_aspect_ratiodecrease缩放后用透明黑底pad补边cover用force_original_aspect_ratioincrease放大后居中裁剪保证铺满且不变形。滤镜图通过 NodeAV 的FilterGraph装配buffer源滤镜接收输入帧、buffersink汇滤镜吐出结果帧单帧推入后立即冲刷flush最终产出一个携带原始 timestamp/duration、旋转与翻转信息已被“烘焙”为 0 的新VideoSample。输入若已是AvFrameVideoSampleResource支撑的帧则直接复用其AVFrame否则先复制成AVFrame。NodeAV filter graphs释放 libavfilter 的全部威力FFmpeg 的libavfilter是一个极其强大且通用的媒体处理库通过 NodeAV 可以直接访问它的全部能力视频与音频数据都适用。例如把 Mediabunny 的 Conversion API 与滤镜图组合给视频做灰度化import { Conversion } from mediabunny; import { AvFrameVideoSampleResource, toAvFrame } from mediabunny/server; import { Frame, FilterAPI } from node-av; async function* one(f: Frame) { yield f; } const conversion await Conversion.init({ // ... video: { process: async (sample) { // VideoSample - Frame using inFrame new Frame(); inFrame.alloc(); await toAvFrame(sample, inFrame); // Frame - filter - AvFrameVideoSampleResource using filter FilterAPI.create(formatgray); for await (const outFrame of filter.frames(one(inFrame))) { return outFrame new AvFrameVideoSampleResource(outFrame); } return null; }, }, // ... }); await conversion.execute();这里的process回调是 Mediabunny 提供的“在管线中插入自定义处理”的钩子每帧先经toAvFrame变成AVFrame通过FilterAPI.create(formatgray)的滤镜处理后再包装成AvFrameVideoSampleResource返回给编码器——全程没有像素数据拷贝进 JavaScript。Canvas API polyfills把 Skia Canvas 接入管线如果你需要完整的 Canvas 2D API比如复杂绘图、水印、合成可以使用 Skia Canvas 这类 GPU 加速的 Canvas polyfill。与 Mediabunny 结合只需“进 Canvas、出 Canvas”两步转换const width videoSample.displayWidth; const height videoSample.displayHeight; const canvas new Canvas(width, height); const ctx canvas.getContext(2d); // Copy data from VideoSample const imageData ctx.createImageData(width, height); await videoSample.copyTo(imageData.data, { format: RGBA }); ctx.putImageData(imageData, 0, 0); // Issue draw commands ctx.fillStyle red; ctx.fillRect(20, 20, 100, 60); // Convert to VideoSample again const pixels ctx.getImageData(0, 0, width, height).data; return new VideoSample(pixels, { format: RGBA, codedWidth: width, codedHeight: height, timestamp: videoSample.timestamp, duration: videoSample.duration, });注意videoSample.copyTo(imageData.data, { format: RGBA })这一步是有意的显式拷贝Canvas 需要把像素搬到自己的缓冲区属于上节所说“除非显式请求”才发生的复制。控制日志输出命令行应用通常不希望 Mediabunny 的输出污染 stdout/stderr。Mediabunny 通过Logging单例提供日志级别的精细控制。日志级别定义在 src/logging.ts枚举值从静默到详尽共四档高等级包含低等级import { Logging, LogLevel } from mediabunny; // The default: Mediabunny can log errors, warnings, and information messages. Logging.level LogLevel.Info; // Only log warnings and errors. Logging.level LogLevel.Warnings; // Only log errors. Logging.level LogLevel.Errors; // Dont log anything at all. Logging.level LogLevel.Silent;如果你想接管日志而不是打印到控制台还可以订阅日志事件事件参数即对应日志调用传入的参数数组Logging.on(error, (args: unknown[]) { // Handle error message }); Logging.on(warn, (args: unknown[]) { // Handle warning message }); Logging.on(info, (args: unknown[]) { // Handle info message });此外注册mediabunny/server时会把 NodeAV 自身的日志级别压到AV_LOG_ERROR见 index.ts避免 FFmpeg 侧的海量调试信息刷屏。实现原理mediabunny/server的核心实现思路是用 NodeAV 提供的 N-API 绑定直接驱动libavcodec与libavfilter并通过 Mediabunny 的自定义编解码器custom coderAPI 接入核心库。编码路径见 video-encoder.ts 与 audio-encoder.ts把 Mediabunny 的VideoSample/AudioSample转换成AVFrame支持零拷贝ref或经像素/采样数据复制按需做格式转换——视频侧用SoftwareScaleContext缩放器统一像素格式与分辨率video-encoder.ts音频侧用SoftwareResampleContext重采样器处理采样率/声道/采样格式差异及编码器定长帧要求帧送入正确编码器产出的 packet 再被规范化为 WebCodecs 与 Mediabunny Codec Registry 期望的格式AVC/HEVC 输出按需转为长度前缀格式并剥除 SPS/PPS/VPS NAL 单元或保持 Annex B取决于avc.format/hevc.format配置AAC 在adts格式下逐帧生成并前置 ADTS 头FLAC 的 extradata 被包装成fLaC打头的 WebCodecs 规范形态。解码路径见 video-decoder.ts 与 audio-decoder.ts把 packet 与解码器元数据extraData、尺寸、采样率、声道等送入对应解码器解码出的AVFrame被 clone 后包装成VideoSample/AudioSample实例输出解码器内部维护一份preciseTimings簿记最多 128 条用输入 packet 的原始时间戳/时长对解码输出的微秒级时间做精确回填恢复比微秒更精确的时序信息B 帧重排场景下尤为重要测试 server-extension.test.ts 对1/25帧时长与排序后的时间戳都有断言。视频帧变换resize/rotate/flip/crop统一用libavfilterAPI 实现见 video-sample.ts滤镜链的语义与顺序如上文VideoSample.transform()一节所述。硬件加速解码器/编码器在初始化时通过getHardwareDecoderCodec/getHardwareEncoderCodecmisc.ts从当前硬件上下文解析可用的硬件编解码器并缓存按 codec ID 的结果找不到则回退软件实现。AV1 解码在源码层面默认走软件路径注释引用了 opencv 的相关已知问题ProRes 编码则优先选择prores_ks编码器。零拷贝只要可能AVFrame就不会被复制进 JavaScript除非显式copyTo()从而形成零拷贝 decode → transform → encode的完整链路。整条管线的代码分布在 packages/server/src 下感兴趣的读者可以从 index.ts 的注册入口开始追读。总结mediabunny/server把 Mediabunny 的“浏览器优先”能力平移到服务端一次registerMediabunnyServer()注册即可在 Node、Bun、Deno 中获得覆盖 AVC/HEVC/VP8/VP9/AV1/ProRes 与 AAC/MP3/Vorbis/Opus/FLAC/AC-3/E-AC-3/DTS 的全量编解码能力、基于 libavfilter 的帧变换、全平台自动硬件加速与零拷贝管线。结合Conversion流式 API你可以用纯 TypeScript 搭建出内存占用 O(1) 的转码服务配合AvFrameVideoSampleResource/AvFrameAudioSampleResource/toAvFrame还能与 NodeAV 生态无缝互操作把 FFmpeg 的全部能力编织进 Mediabunny 的类型化管线之中。赞分享音视频视频处理音频处理【免费下载链接】mediabunnyPure TypeScript media toolkit for reading, writing, and converting video and audio files, directly in the browser.项目地址https://gitcode.com/gh_mirrors/me/mediabunny点击查看免费下载相关推荐mediabunny/server 实战指南在 Node.js、Bun 与 Deno 中为 Mediabunny 补齐 FFmpeg 级编解码能力mediabunny/server 实战指南在 Node.js、Bun 与 Deno 中为 Mediabunny 补齐 FFmpeg 级编解码能力 med音视频视频处理音频处理Mediabunny MP3 编码扩展 mediabunny/mp3-encoder基于 LAME WASM 的浏览器与服务器端 MP3 编码方案Mediabunny MP3 编码扩展 mediabunny/mp3 encoder基于 LAME WASM 的浏览器与服务器端 MP3 编码方案 Medi音视频视频处理音频处理vite-plugin-qiankun 三步接入乾坤子应用Vite 微前端完整指南vite plugin qiankun 三步接入乾坤子应用Vite 微前端完整指南 微前端场景里最让人头疼的是每个子应用各打一份框架依赖页面加载一次比一次音视频视频处理音频处理上一篇Arduino Audio Tools 嵌入式音频开发构建专业级音频应用的模块化方案下一篇5分钟掌握FFmpeg批量视频转换工具告别重复操作效率提升10倍创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考