agora-rte-extension-ng
v1.0.3
Published
Agora RTE Extension — useExtension 模式的视频/音频插件开发框架
Maintainers
Readme
Agora RTE Extension v2
简介
Agora RTE Extension v2 是新一代插件 SDK,为 Agora Web SDK 提供 useExtension 模式的视频/音频插件开发能力,完全替代 v1 的 pipe 模式。
与 v1 不同,新框架由 SDK 统一管理媒体数据的获取、转换和输出,插件只需实现核心处理逻辑。视频插件在共享的 WebGL2 context 上直接操作 GPU texture,避免不必要的 CPU↔GPU 拷贝;音频插件通过 AudioWorklet 在实时音频线程中处理数据,需要更大帧长或 WASM 资源的算法由 worklet 侧插件自行缓冲和管理。
安装
npm install agora-rte-extension-ng@^1.0.0架构概览
视频插件
摄像头 Track → texImage2D → inputTexture (GPU)
→ ext1.applyEffect({input:A, output:B}) → swap A↔B
→ ext2.applyEffect({input:B, output:A}) → swap A↔B
→ blitFramebuffer → canvas → 输出 Track框架创建共享的 WebGL2 context,将视频帧上传为 GPU texture,然后按 useExtension 顺序依次调用每个 enabled 插件的 applyEffect,通过 ping-pong 交换 input/output texture。最终结果 blit 到 canvas 输出。
音频插件
麦克风 Track → AudioWorkletNode (框架)
→ 128 采样 quantum → AudioWorklet 侧插件 processAudioFrame(input)
→ 输出 Track框架拥有 AudioWorklet 管线。主线程上的 AudioExtension 只承载控制面状态和生命周期;实时处理类在 AudioWorklet 模块中通过 registerAudioWorkletExtension() 注册,框架按 registrationName 实例化后,在 process() 回调中同步调用其 processAudioFrame()。
快速开始
视频插件
import { VideoExtension } from "agora-rte-extension-ng";
import type { VideoExtensionContext, Frame } from "agora-rte-extension-ng";
class MyVideoExtension extends VideoExtension {
public name = "MyVideoExtension";
public init(context: VideoExtensionContext): void {
super.init(context);
// context.gl — 共享的 WebGL2RenderingContext
// context.canvas — 共享的 HTMLCanvasElement
// 在此初始化 WASM、shader 等资源
}
public destroy(): void {
// 释放资源
super.destroy();
}
public async applyEffect(frame: Frame): Promise<void> {
if (frame.kind === "cpu") {
// CPU 模式:frame.videoFrame 是 VideoFrame
return;
}
// GPU 模式:
// frame.input — 当前帧输入纹理(只读)
// frame.output — 必须将处理结果写入此纹理
// frame.width / height — 帧尺寸
// 示例:直接拷贝(直通)
this.blitTexture(frame.input, frame.output, frame.width, frame.height);
}
}使用:
const ext = localVideoTrack.useExtension(MyVideoExtension);
await ext.disable(); // 框架跳过该插件处理,开销很低
await ext.enable(); // 重新启用
ext.destroy(); // 移除并销毁音频插件
// my-audio-extension.ts(主线程控制面)
import { AudioExtension } from "agora-rte-extension-ng";
import type { AudioExtensionContext } from "agora-rte-extension-ng";
export class MyAudioExtension extends AudioExtension {
public static extensionDefinition = {
moduleURL: new URL("./my-audio-worklet.js", import.meta.url).href,
registrationName: "my-audio-extension",
};
public name = "MyAudioExtension";
public init(context: AudioExtensionContext): void {
super.init(context);
// context.audioContext — AudioContext
// context.sampleRate — 采样率
}
}// my-audio-worklet.ts(AudioWorklet 侧实时处理)
import { registerAudioWorkletExtension } from "agora-rte-extension-ng";
import type {
AudioWorkletExtensionContext,
IAudioWorkletExtension,
} from "agora-rte-extension-ng";
class MyAudioWorkletExtension implements IAudioWorkletExtension {
public init(context: AudioWorkletExtensionContext): void {
// context.sampleRate — 采样率
// context.extensionOptions — useExtension() 传入的初始配置
}
/**
* 框架在 AudioWorklet 的 process() 中调用此方法。
* 传入 128 采样的 Float32Array,必须同步返回处理后的 128 采样。
* 如果算法帧长大于 128,在 worklet 侧维护缓冲,并同步返回本 quantum 的输出。
*/
public processAudioFrame(input: Float32Array): Float32Array {
// 示例:直通
return input;
}
}
registerAudioWorkletExtension("my-audio-extension", MyAudioWorkletExtension);API 参考
VideoExtension(视频插件基类)
| 属性/方法 | 说明 |
|-----------|------|
| name: string | 插件名称(必须实现) |
| enabled: boolean | 当前启用状态(只读) |
| mode: VideoExtensionMode | 处理模式,默认 TEXTURE |
| supportsCPUMode: boolean | 是否支持 CPU 回退模式,默认 false |
| init(context) | 初始化,子类必须调用 super.init(context) |
| destroy(options?) | 销毁,子类必须调用 super.destroy(options)。框架在回退/恢复过程中可能传入 { reason: "context-loss" }。 |
| enable() | 启用插件 |
| disable() | 禁用插件(框架跳过该插件处理,开销很低) |
| applyEffect(frame) | TEXTURE 模式处理方法(子类覆写)。接收 Frame,kind: "gpu" 或 kind: "cpu" |
| processVideoFrame(frame) | VIDEO_FRAME 模式处理方法(子类覆写) |
| setOptions(options) | 声明式配置入口(唯一入站通道)。子类覆写以应用配置;框架在 init() 后用初始 extensionOptions 自动调用一次。options 中的图片 / ArrayBuffer 会自动跨 worker 边界 transfer。 |
| on(event, handler) / off(event, handler) | 监听插件通过 emit(event, data) 推送出的数据(唯一出站通道),例如逐帧 face landmark。 |
受保护的 GL 工具方法
| 方法 | 说明 |
|------|------|
| blitTexture(src, dst, w, h) | 通过 blitFramebuffer 拷贝 texture |
| drawTextureToFbo(tex, fbo, w, h) | 使用直通 shader 渲染到 FBO |
| createTexture(w, h) | 创建 RGBA texture |
| createFbo(texture) | 创建绑定到 texture 的 FBO |
| compileProgram(vert, frag) | 编译链接 shader program |
| pixelToGL(px, py, w, h) | 像素坐标转 GL NDC 坐标(框架 Y 翻转约定) |
AudioExtension(音频插件基类)
| 属性/方法 | 说明 |
|-----------|------|
| name: string | 插件名称(必须实现) |
| enabled: boolean | 当前启用状态(只读) |
| init(context) | 初始化主线程控制面,子类必须调用 super.init(context) |
| destroy(options?) | 销毁主线程控制面资源,子类必须调用 super.destroy(options)。 |
| enable() | 启用插件 |
| disable() | 禁用插件 |
AudioWorklet 侧插件
| API | 说明 |
|-----|------|
| AudioExtension.extensionDefinition(静态字段) | 声明 moduleURL / registrationName,供框架加载 worklet 模块 |
| registerAudioWorkletExtension(name, ctor) | 在 AudioWorklet 模块中注册实时处理类 |
| IAudioWorkletExtension.init(context) | 初始化 worklet 侧插件,可读取 sampleRate 与初始 extensionOptions |
| IAudioWorkletExtension.processAudioFrame(input, referenceInputs?) | 实时音频处理方法;每次接收 128 采样,必须同步、低分配地返回 128 采样 |
VideoExtensionMode(枚举)
| 值 | 说明 |
|----|------|
| TEXTURE | GPU Texture 模式(默认,推荐) |
| VIDEO_FRAME | VideoFrame 模式(旧插件兼容) |
Frame(类型)
type Frame =
| { kind: "gpu"; input: WebGLTexture; output: WebGLTexture; width: number; height: number }
| { kind: "cpu"; width: number; height: number; videoFrame: VideoFrame };VideoExtensionContext / AudioExtensionContext(类型)
type VideoExtensionContext = {
backend: "gpu" | "cpu";
threadMode: "worker" | "main";
canvas?: HTMLCanvasElement | OffscreenCanvas;
gl?: WebGL2RenderingContext;
};
type AudioExtensionContext = {
audioContext: AudioContext;
sampleRate: number;
extensionOptions?: Record<string, unknown>;
};
type AudioWorkletExtensionContext = {
sampleRate: number;
extensionOptions?: Record<string, unknown>;
};视频路径说明
帧尺寸偶数对齐
框架会自动将帧尺寸向下对齐到偶数(如 641→640, 481→480)。这是因为视频编解码器使用的 YUV 色度子采样(NV12/I420)要求宽高为偶数,奇数尺寸会导致编解码链路中出现绿屏。插件收到的 frame.width / frame.height 始终是偶数对齐后的值,与 texture 尺寸一致。
WebGL (GPU) 主路径
直接读 input texture,通过 shader 或 WASM 处理,写 output texture。常规 GPU 插件推荐使用这一路径。
适用:超分辨率、水印叠加、色彩调整等以 GPU 为主的处理。
CPU 数据兼容路径
如插件确实需要 CPU 侧像素数据,可使用 VIDEO_FRAME 模式(声明 mode = VideoExtensionMode.VIDEO_FRAME),框架会自动做 texture ↔ VideoFrame 转换。或者在 applyEffect 中处理 frame.kind === "cpu" 分支。
新插件应优先使用 WebGL (GPU) 主路径,仅在确有需要时使用 VIDEO_FRAME 模式。
与 v1 pipe 模式的区别
| | v1 pipe 模式 | v2 useExtension 模式 | |---|---|---| | 数据流 | Processor 链,每个 Processor 自行管理 MediaStreamTrack | 框架统一管理 GPU texture,插件只操作 texture | | GL context | 每个 Processor 自建 | 框架创建共享 context | | CPU↔GPU 拷贝 | 每个 Processor 至少一次 | WebGL (GPU) 主路径通常无额外 CPU 拷贝;CPU 数据兼容路径会增加一次 readPixels | | 生命周期 | registerExtension → createProcessor → pipe → unpipe | useExtension → enable/disable → destroy | | 互斥 | 不能与 useExtension 同时使用 | 不能与 pipe 同时使用 |
构建
npm run build输出到 dist/ 目录:
AgoraRTC_N-extension.js— UMDAgoraRTC_N-extension.esm.mjs— ESMextension.d.ts— TypeScript 类型声明
测试
npm test运行当前提交的单元测试,覆盖 SDK 基类、管理器/运行时行为以及 WebGL context loss/restore 处理。
