yuv-render
一个基于 WebGL 的 YUV 视频帧渲染器,提供对 I420 格式视频数据的渲染能力,包含纹理管理、画布控制和资源释放功能。
功能特性
- 支持 HTMLCanvasElement 容器绑定
- 自动初始化 WebGL 渲染环境
- 支持动态尺寸调整
- 实现 YUV 三通道纹理分离渲染
- 提供资源清理接口
类结构
typescript
class YUVRender {
// 属性
el: HTMLCanvasElement
private webglContext: WebGLRenderingContext | null
private yTexture: WebGLTexture | null
private uTexture: WebGLTexture | null
private vTexture: WebGLTexture | null
// 构造方法
constructor(el: HTMLCanvasElement)
// 访问器
get height(): number
get width(): number
// 方法
setDimension(width: number, height: number): void
render(data: Uint8Array): void
clear(): void
dispose(): void
}API 文档
constructor(el: HTMLCanvasElement)
初始化渲染器,创建 WebGL 上下文并配置着色器程序
- 参数:
el: 目标 canvas 元素
- 初始化流程:
- 获取 WebGL 上下文
- 创建着色器程序
- 初始化缓冲区
- 创建 Y/U/V 三个纹理通道
get width(): number
获取画布当前宽度
- 返回值:
number类型的 canvas 宽度值
get height(): number
获取画布当前高度
- 返回值:
number类型的 canvas 高度值
setDimension(width: number, height: number): void
设置画布显示尺寸
- 参数:
width: 画布宽度(像素)height: 画布高度(像素)
- 行为说明:
- 直接修改 canvas.width/canvas.height 属性
- 会触发 WebGL viewport 的重置
render(data: Uint8Array): void
渲染 YUV 数据到 canvas
- 参数:
data: I420 格式视频数据(Uint8Array)
- 数据处理流程:
- 注意事项:
- 数据必须符合 I420 格式规范
- 宽高需预先通过 setDimension 设置
- 每次调用会重新上传纹理数据
clear(): void
清除画布内容
- 行为说明:
- 调用 WebGL 的 clear(COLOR_BUFFER_BIT) 方法
- 用于清除当前帧的渲染结果
dispose(): void
资源清理方法
- 执行动作:
- 清除画布内容(调用 clear())
- 主动释放 WebGL 上下文(WEBGL_lose_context)
- 移除 canvas 元素
- 置空所有引用
- 注意事项:
- 应在组件销毁时调用
- 调用后实例不可复用
使用示例
点击查看代码
vue
<script setup lang="ts">
import { useTemplateRef, onMounted, shallowReactive } from 'vue';
import { VPButton } from 'vitepress/theme';
import { YUVRender } from 'yuv-render';
const scope = shallowReactive({
waiting: false
});
const canvas = useTemplateRef<HTMLCanvasElement>('canvas');
let yuv: YUVRender;
onMounted(()=> {
const el = canvas.value!;
yuv = new YUVRender(el);
yuv.setDimension(768, 320);
});
const start = () => {
scope.waiting = true;
fetch(`${import.meta.env.BASE_URL}frame.json`)
.then((res: Response) => res.json())
.then((frameArray: ArrayBuffer) => {
scope.waiting = false;
yuv.render(new Uint8Array(frameArray));
});
};
</script>
<template>
<div class="buttons">
<VPButton
text="Render"
:disabled="scope.waiting"
@click="start()"
/>
</div>
<div :class="{ 'yuv-render': true, 'v-waiting': scope.waiting }"
>
<canvas ref="canvas" />
<div className="v-loading-spinner" />
</div>
</template>
<style scoped lang="stylus">
.buttons {
margin-bottom: 16px;
}
.yuv-render
display block
box-sizing border-box
background-color #000
position relative
padding 0
width 768px
height 320px
canvas
position absolute
width 100%
height 100%
top 0
left 0
$primary-background-color = #2B333F
$primary-background-transparency = 0.7
$secondary-background-color = lighten($primary-background-color, 33%)
$secondary-background-transparency = 0.5
.v-loading-spinner
display none
position absolute
top 50%
left 50%
margin -25px 0 0 -25px
opacity 0.85
// Need to fix centered page layouts
text-align left
border 6px solid rgba($primary-background-color, $primary-background-transparency)
// border: 6px solid rgba(43, 51, 63, 0.5);
box-sizing border-box
background-clip padding-box
width 50px
height 50px
border-radius 25px
visibility hidden
.v-seeking .v-loading-spinner, .v-waiting .v-loading-spinner
display block
// add a delay before actual show the spinner
animation v-spinner-show 0s linear 0.3s forwards
.v-loading-spinner:before, .v-loading-spinner:after
content ''
position absolute
margin -6px
box-sizing inherit
width inherit
height inherit
border-radius inherit
// Keep 100% opacity so they don't show through each other
opacity 1
border inherit
border-color transparent
border-top-color white
// only animate when showing because it can be processor heavy
.v-seeking .v-loading-spinner:before, .v-seeking .v-loading-spinner:after, .v-waiting .v-loading-spinner:before, .v-waiting .v-loading-spinner:after
-webkit-animation v-spinner-spin 1.1s cubic-bezier(0.6, 0.2, 0, 0.8) infinite, v-spinner-fade 1.1s linear infinite
animation v-spinner-spin 1.1s cubic-bezier(0.6, 0.2, 0, 0.8) infinite, v-spinner-fade 1.1s linear infinite
.v-seeking .v-loading-spinner:before, .v-waiting .v-loading-spinner:before
border-top-color rgb(255, 255, 255)
.v-seeking .v-loading-spinner:after, .v-waiting .v-loading-spinner:after
border-top-color rgb(255, 255, 255)
-webkit-animation-delay 0.44s
animation-delay 0.44s
@keyframes v-spinner-show
to
visibility visible
@keyframes v-spinner-show
to
visibility visible
@keyframes v-spinner-spin
100%
transform rotate(360deg)
@keyframes v-spinner-spin
100%
-webkit-transform rotate(360deg)
@keyframes v-spinner-fade
0%
border-top-color $secondary-background-color
20%
border-top-color $secondary-background-color
35%
border-top-color white
60%
border-top-color $secondary-background-color
100%
border-top-color $secondary-background-color
@keyframes v-spinner-fade
0%
border-top-color $secondary-background-color
20%
border-top-color $secondary-background-color
35%
border-top-color white
60%
border-top-color $secondary-background-color
100%
border-top-color $secondary-background-color
</style>