abundant-magician-17307
12/23/2025, 8:47 AMtall-airline-16756
12/23/2025, 8:47 AMtall-airline-16756
12/23/2025, 8:48 AMegress test-template 在本地复现并调优模板渲染,有助于定位是渲染瓶颈还是后端编码瓶颈。
为更准确定位问题,请提供以下信息:
1. 模板实现技术栈(例如 React + DOM/CSS、Canvas、WebGL、或第三方播放器)。
2. 卡顿出现的位置:是在浏览器渲染(页面播放/预览)阶段,还是在 Egress 编码输出(录制/流)阶段?你能否复现并描述时间点(如页面加载、多人视频播放、画面切换时)?
3. 目标输出分辨率与帧率(如 1080p/30fps、720p/15fps 等)。
4. 模板是否对多个媒体轨道(如多路视频、共享屏幕)同时渲染?
5. 是否已有性能数据或截图(CPU/GPU 利用率、Chrome 无头日志、或 egress test-template 的输出)?
6. 你希望实现的锐化强度和应用方式(实时全帧、只在关键帧、离线后处理等)。
如果你把上面几项信息发给我,我会基于你的回答:
• 推荐优先级最低到最高的优化步骤(例如降分辨率、合并图层、延迟锐化到后处理等);
• 提供一份可测试的最小化改动建议(若你用 Canvas 或 WebGL,我可以给具体实现要点);
• 指出如何用 egress test-template 本地复现来验证改动效果。
想先从哪几项信息开始?
Sources: Custom recording templates | LiveKit docs | Universal Egress | Universal Egressabundant-magician-17307
12/23/2025, 8:57 AMtall-airline-16756
12/23/2025, 8:58 AMegress test-template 本地测试改动并监测 CPU/GPU 利用率和帧丢失。
如果你想继续,我可以:
• 提供一个基于 WebGL 的最小示例着色器用于锐化;或
• 给出一个 Canvas(2D)或 CSS 的快速示例供你在模板里试验;或
• 帮你制定一个测试步骤清单(如何用 egress test-template 测量并比较性能)。
你希望我先给哪个示例或测试清单?abundant-magician-17307
12/23/2025, 9:05 AMtall-airline-16756
12/23/2025, 9:05 AM<!-- 最小化示例:把 <video id="src"> 的帧通过 WebGL 做锐化,渲染到 <canvas id="out"> -->
<video id="src" autoplay playsinline muted></video>
<canvas id="out"></canvas>
<script type="text/javascript">
const vsSource = `#version 300 es
in vec2 a_position;
in vec2 a_texcoord;
out vec2 v_texcoord;
void main() {
v_texcoord = a_texcoord;
gl_Position = vec4(a_position, 0.0, 1.0);
}
`;
// 简单 3x3 锐化卷积核(可调节中心权重)
const fsSource = `#version 300 es
precision highp float;
in vec2 v_texcoord;
uniform sampler2D u_texture;
uniform vec2 u_texelSize; // 1.0 / textureSize
uniform float u_strength; // e.g., 1.0
out vec4 outColor;
void main() {
vec2 off[9];
off[0] = v_texcoord + vec2(-u_texelSize.x, -u_texelSize.y);
off[1] = v_texcoord + vec2(0.0, -u_texelSize.y);
off[2] = v_texcoord + vec2(u_texelSize.x, -u_texelSize.y);
off[3] = v_texcoord + vec2(-u_texelSize.x, 0.0);
off[4] = v_texcoord;
off[5] = v_texcoord + vec2(u_texelSize.x, 0.0);
off[6] = v_texcoord + vec2(-u_texelSize.x, u_texelSize.y);
off[7] = v_texcoord + vec2(0.0, u_texelSize.y);
off[8] = v_texcoord + vec2(u_texelSize.x, u_texelSize.y);
float kernel[9];
kernel[0] = 0.0; kernel[1] = -1.0; kernel[2] = 0.0;
kernel[3] = -1.0; kernel[4] = 5.0; kernel[5] = -1.0;
kernel[6] = 0.0; kernel[7] = -1.0; kernel[8] = 0.0;
// mix identity kernel with sharpening by u_strength
float center = kernel[4];
kernel[4] = mix(1.0, center, u_strength);
vec3 sum = vec3(0.0);
for (int i = 0; i < 9; ++i) {
vec4 c = texture(u_texture, off[i]);
sum += c.rgb * kernel[i];
}
outColor = vec4(clamp(sum, 0.0, 1.0), 1.0);
}
`;
function createShader(gl, type, source) {
const s = gl.createShader(type);
gl.shaderSource(s, source);
gl.compileShader(s);
if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) {
console.error(gl.getShaderInfoLog(s));
gl.deleteShader(s);
return null;
}
return s;
}
function createProgram(gl, vsSrc, fsSrc) {
const vs = createShader(gl, gl.VERTEX_SHADER, vsSrc);
const fs = createShader(gl, gl.FRAGMENT_SHADER, fsSrc);
const p = gl.createProgram();
gl.attachShader(p, vs);
gl.attachShader(p, fs);
gl.linkProgram(p);
if (!gl.getProgramParameter(p, gl.LINK_STATUS)) {
console.error(gl.getProgramInfoLog(p));
gl.deleteProgram(p);
return null;
}
return p;
}
(async function init() {
const video = document.getElementById('src');
const canvas = document.getElementById('out');
// 获取屏幕共享流示例:你可能已经有 rtc track,这里只是示例
const stream = await navigator.mediaDevices.getDisplayMedia({ video: true });
video.srcObject = stream;
await video.play();
// 设置 canvas 大小与视频一致(或按需降采样)
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
const gl = canvas.getContext('webgl2');
const program = createProgram(gl, vsSource, fsSource);
gl.useProgram(program);
// 顶点数据(全屏四边形)
const posBuf = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, posBuf);
const positions = new Float32Array([
-1, -1, 0, 0,
1, -1, 1, 0,
-1, 1, 0, 1,
1, 1, 1, 1,
]);
gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);
const vao = gl.createVertexArray();
gl.bindVertexArray(vao);
const a_position = gl.getAttribLocation(program, 'a_position');
const a_texcoord = gl.getAttribLocation(program, 'a_texcoord');
gl.enableVertexAttribArray(a_position);
gl.vertexAttribPointer(a_position, 2, gl.FLOAT, false, 16, 0);
gl.enableVertexAttribArray(a_texcoord);
gl.vertexAttribPointer(a_texcoord, 2, gl.FLOAT, false, 16, 8);
// 创建视频纹理
const tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
const u_texture = gl.getUniformLocation(program, 'u_texture');
const u_texelSize = gl.getUniformLocation(program, 'u_texelSize');
const u_strength = gl.getUniformLocation(program, 'u_strength');
function render() {
if (video.readyState >= 2) {
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, video);
gl.viewport(0, 0, canvas.width, canvas.height);
gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.uniform1i(u_texture, 0);
gl.uniform2f(u_texelSize, 1.0 / canvas.width, 1.0 / canvas.height);
gl.uniform1f(u_strength, 1.0); // 0.0 = no sharp, 1.0 = full kernel
gl.bindVertexArray(vao);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
}
requestAnimationFrame(render);
}
requestAnimationFrame(render);
})();
</script>
整合与性能要点(简短)
• 优先使用 `webgl2`,用着色器在 GPU 做卷积,比用 Canvas 2D 更省 CPU。
• 在 Egress 模板中直接启用全帧 1080p@30 的实时锐化会显著增加 GPU 负载,可能加剧卡顿。建议先在本地用 egress test-template 复现并测量(监控 CPU/GPU 使用率)。
• 若 GPU 成为瓶颈,可考虑:降低处理分辨率(例如先在 720p 做锐化再 upscale)、降低 `u_strength`,或仅对感兴趣区域(ROI)做锐化而非整帧。
• 注意纹理上传开销:调用 texImage2D 每帧会有成本,确保使用合适的像素格式并避免不必要的像素转换。
• 在无头 Chrome 里,确保启用了 GPU 加速(容器/环境需允许 GPU),否则着色器仍会在 CPU 上运行,性能劣化明显。
如果你需要,我可以:
• 把这个示例改为在渲染前降采样然后再上采样的版本;或
• 提供一个更高效的边缘增强着色器(比如使用 separable kernel 或不同算子)以减少采样次数。
想要哪种进一步的示例?abundant-magician-17307
12/23/2025, 9:09 AMtall-airline-16756
12/23/2025, 9:09 AMegress test-template 来复现与测量性能。
集成步骤(按序)
1. 准备自定义模板目录
• 在本地建立模板文件夹(比如 `my-egress-template/`),包含 `index.html`、静态资源与脚本。
• 你可以基于默认模板复制并修改,也可以从空白模板开始。
1. 在模板中嵌入 WebGL canvas 与 video 元素
• 把之前的示例 HTML 中的 <video id="src"> 和 <canvas id="out"> 放入模板的适当位置。
• 如果模板已有用于展示远端/屏幕的 video 元素,可以直接复用该元素的 MediaStream(或把 track attach 到示例 video 上)。
示例片段(index.html 的核心部分)
<body>
<!-- 复用或新建 video,用于接收屏幕共享或远端 track -->
<video id="src" autoplay playsinline muted style="display:none;"></video>
<!-- 最终展示/被录制的 canvas -->
<canvas id="out" width="1920" height="1080"></canvas>
<script src="./sharpen.js"></script>
</body>
1. 在脚本中获取 LiveKit 的媒体并绑定到 video
• 如果模板通过 LiveKit SDK 注入已有 track(例如 Egress 模板常见做法是把 track attach 到 DOM),直接把对应的 MediaStream 或 MediaStreamTrack 绑定到 `video.srcObject`。
• 常见方式:
◦ 如果你拿到的是 `MediaStream`:`video.srcObject = stream;`
◦ 如果你拿到的是 `MediaStreamTrack`:先创建 const s = new MediaStream([track]); video.srcObject = s;
示例(在 sharpen.js 中)
• 在页面初始化时等待 LiveKit 的 track attach 或查找已有 video,然后调用着色器初始化函数并启动 render loop。
• 如果模板用的是某个 front-end 框架(React/Vue),把初始化放到组件挂载后。
1. 把前面提供的 WebGL 着色器逻辑放到 sharpen.js
• 将 VS/FS、着色器编译、纹理上传与 requestAnimationFrame 渲染循环代码放到 `sharpen.js`。
• 在每帧上传视频帧到纹理时,注意捕获异常并在 video 尺寸变化时调整 canvas 大小。
1. 性能优化建议(集成时必须考虑)
• 启用 GPU:确保无头 Chrome 运行环境允许 GPU 渲染(容器/CI 环境需映射 GPU 或使用支持 GPU 的运行时)。
• 降采样策略:如果 1080p 全帧锐化太慢,可先把 canvas 设置为 1280x720、在 shader 中处理后再 upscale 到 1080p(或让 Egress 输出降为 720p)。
• 局部锐化:只对活跃区域(ROI)进行卷积,避免全帧计算。
• 减少纹理上传开销:如果可能,使用 `texSubImage2D`(在支持情况下)或避免格式转换。
• 控制调用频率:如果场景允许,尝试以 20–25fps 做锐化而不是强制 30fps。
1. 在本地用 egress test-template 验证
• 使用 LiveKit CLI 的 `egress test-template`(或已有命令)在本地启动无头 Chrome 并加载你的模板,观察是否复现卡顿。
• 同时监控 CPU/GPU 使用率与无头 Chrome 日志,调整 `u_strength`、分辨率或是否对 ROI 处理。
简短示例:把 video track 绑定到示例 video(伪代码)
// 假设 liveTrack 是你从 LiveKit 模板环境获取到的 MediaStreamTrack
const video = document.getElementById('src');
const ms = new MediaStream([liveTrack]);
video.srcObject = ms;
await video.play();
// 然后调用你的 WebGL 初始化逻辑,canvas 会渲染并显示结果
测试与回归
• 先在本地用屏幕共享或录制视频文件测试;确认无头 Chrome 在你的环境下启用了 GPU。
• 逐步增加锐化强度与分辨率,观察帧率与系统资源,找到可接受的平衡点。
• 若无法启用 GPU,在无头环境中做复杂 shader 会非常慢,应改为后处理或降采样策略。
如果你希望,我可以:
• 把完整的 index.html + sharpen.js 替你整理为一个压缩包结构,方便直接替换模板;或
• 给出一个降采样后再锐化并上采样的改进版 shader 示例以降低采样次数。
你想先要哪种文件示例?我可以直接输出完整文件内容以便你拷贝。abundant-magician-17307
12/23/2025, 9:20 AMtall-airline-16756
12/23/2025, 9:21 AMabundant-magician-17307
12/23/2025, 9:28 AMtall-airline-16756
12/23/2025, 9:29 AMegress test-template 复现并对比 Canvas vs WebGL 性能(CPU/GPU 占用、帧率、延迟)。
如果你愿意,我可以:
• 帮你把之前的 WebGL 锐化示例改成与默认模板更容易集成的版本;或
• 给出一个同时支持 Canvas 2D 和 WebGL 的切换示例,方便在无头环境下做对比测试。
你想先要哪个示例?
Sources: Composite and web recordings | LiveKit docs | Custom recording templates | LiveKit docs | Universal Egressabundant-magician-17307
12/23/2025, 9:31 AMtall-airline-16756
12/23/2025, 9:31 AMindex.html + sharpen.js 文件,便于把模板上传并用 egress test-template 验证;或
• 给出在 Canvas 2D 上做轻量锐化的示例并说明性能差异。
你想先要哪一个?
Sources: client-sdk-unity-web | livekit/livekit_composite | DeepWikiabundant-magician-17307
12/23/2025, 9:39 AMtall-airline-16756
12/23/2025, 9:40 AMegress test-template 验证并监控 CPU。
• 若性能不足,优先采用降采样、仅对 ROI 处理或改用 WebGL(GPU 加速)。
• 尽量复用缓冲区并可把计算移到 WebWorker(仍然会用到 CPU)。
示例代码(可直接嵌入模板的 index.html)
<video id="src" autoplay playsinline muted style="display:none;"></video>
<canvas id="out" width="1920" height="1080"></canvas>
<script>
// 轻量 3x3 锐化卷积(中心权重可调)
const K = [
0, -1, 0,
-1, 5, -1,
0, -1, 0
];
const video = document.getElementById('src');
const canvas = document.getElementById('out');
const ctx = canvas.getContext('2d');
let tmpOut = null; // 复用输出缓冲
async function initWithStream(stream) {
video.srcObject = stream;
await video.play();
canvas.width = video.videoWidth || canvas.width;
canvas.height = video.videoHeight || canvas.height;
tmpOut = new Uint8ClampedArray(canvas.width * canvas.height * 4);
requestAnimationFrame(render);
}
// 每帧卷积(在主线程,适合轻量或降采样后处理)
function render() {
if (video.readyState >= 2) {
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
const img = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = img.data;
const w = canvas.width, h = canvas.height;
// step = 1 为全帧;改为 2 可降低开销(每隔像素处理)
const step = 1;
if (step === 1) {
const out = tmpOut;
out.fill(0);
let kIdx;
for (let y = 0; y < h; y++) {
for (let x = 0; x < w; x++) {
const idx = (y * w + x) * 4;
let r = 0, g = 0, b = 0;
kIdx = 0;
for (let ky = -1; ky <= 1; ky++) {
const yy = Math.min(h - 1, Math.max(0, y + ky));
for (let kx = -1; kx <= 1; kx++) {
const xx = Math.min(w - 1, Math.max(0, x + kx));
const kpix = (yy * w + xx) * 4;
const kval = K[kIdx++];
if (kval === 0) continue;
r += data[kpix] * kval;
g += data[kpix + 1] * kval;
b += data[kpix + 2] * kval;
}
}
out[idx] = Math.min(255, Math.max(0, r));
out[idx + 1] = Math.min(255, Math.max(0, g));
out[idx + 2] = Math.min(255, Math.max(0, b));
out[idx + 3] = data[idx + 3];
}
}
img.data.set(out);
ctx.putImageData(img, 0, 0);
} else {
// 降采样处理思路(更快):
// 1) 在小尺寸临时 canvas 上绘制并获取像素
// 2) 对小图做卷积
// 3) 将小图放大回主 canvas(ctx.drawImage)
// 具体实现可按需提供。
}
}
requestAnimationFrame(render);
}
// 模板里把 LiveKit 提供的 MediaStream 或 MediaStreamTrack 传入 initWithStream
// 例如: const ms = new MediaStream([mediaTrack]); initWithStream(ms);
// 本地调试可用:屏幕共享
// navigator.mediaDevices.getDisplayMedia({video:true}).then(initWithStream);
</script>
可选优化建议(简短)
• 降采样处理:先把视频画到小 canvas(如 720p),处理后再放大回主 canvas。
• ROI 处理:只对感兴趣区域做卷积而非整帧。
• WebWorker:把像素卷积移到 Worker,主线程只做 drawImage/putImageData(仍有数据复制开销)。
• 若需要持续高质量且稳定的全帧锐化,优先改用 WebGL 着色器(GPU 加速)。
要我把示例改为哪种版本:
• 完整的降采样→锐化→上采样实现;或
• 把卷积移到 WebWorker 的示例;或
• 直接给出可替换默认模板的完整文件(index.html + sharpen.js)?