123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389 |
- /**
- * 基于WebGL的视频裁切处理器
- * @param {Array} configs 配置数组,每个元素为一个配置对象,包含以下属性:
- * - type: 裁切类型,默认值为"none"
- * - x: 裁切区域的起始横坐标百分比(左上角),范围介于0到1之间,默认值为0
- * - y: 裁切区域的起始纵坐标百分比(左上角),范围介于0到1之间,默认值为0
- * - w: 裁切区域的宽度百分比,范围介于0到1之间,默认值为1
- * - h: 裁切区域的高度百分比,范围介于0到1之间,默认值为1
- *
- * 使用方式:
- * 1. 创建一个VideoProcessor实例,传入适当的配置数组。
- * 2. 调用initializeById方法,传入源canvas的ID和目标canvas ID数组。
- * 这会初始化源和目标canvas,并将源canvas内容处理后绘制到目标canvas。
- * 3. 在需要更新帧时(例如,源视频播放新的一帧时),调用drawFrame方法。
- * 这会将当前源canvas中的帧复制并按照给定配置处理,然后绘制到目标canvas。
- *
- * 示例代码:
- * ```js
- * const configs = [
- * { type: 'none', x: 0, y: 0, w: 1, h: 1 },
- * { type: "scaleOnly", x: 2/3, y: 0.0, w: 1/3, h: 4/9 },
- * { type: "threePart", x: 0.0, y: 22/27, w: 1, h: 4/27 },
- * ];
- * const videoProcessor = new VideoProcessor(configs);
- * videoProcessor.initializeById("video1", ["camera1", "camera2", "camera3"]);
- *
- * // 每当源视频播放新的一帧时执行以下操作
- * videoProcessor.drawFrame();
- * ```
- */
- class VideoProcessor {
- constructor(configs = []) {
- this.configs = configs;
- this.vertexShaderSource = this._createVertexShaderSource();
- }
- initialize(sourceCanvas, targetCanvasArray) {
- this.canvasSource = sourceCanvas;
- this.canvasTargets = targetCanvasArray;
- this.glSource = this.canvasSource.getContext('webgl');
- // 初始化帧缓冲区和纹理数组
- this.frameBuffers = [];
- this.textures = [];
- this.programs = [];
- this.canvasTargets.forEach((targetCanvas, index) => {
- targetCanvas.width = this.canvasSource.width;
- targetCanvas.height = this.canvasSource.height;
- const config = this.configs[index] || {
- type: 'none',
- x: 0,
- y: 0,
- w: 1,
- h: 1,
- };
- this.fragmentShaderSources = ['' * this.configs.length];
- this.fragmentShaderSources[index] =
- this._createFragmentShaderSource(config);
- const glTarget = this.canvasTargets[index].getContext('webgl');
- // 创建并初始化帧缓冲区和纹理
- this._initWebGLCanvas(glTarget, index);
- });
- }
- initializeById(sourceCanvasId, targetCanvasIds) {
- const sourceCanvas = document.getElementById(sourceCanvasId);
- const targetCanvasArray = targetCanvasIds.map((id) =>
- document.getElementById(id),
- );
- this.initialize(sourceCanvas, targetCanvasArray);
- }
- drawFrame() {
- this.canvasTargets.forEach((_, index) => {
- const glTarget = this.canvasTargets[index].getContext('webgl');
- this._copyOnVideoFrame(glTarget, index);
- });
- }
- _initWebGLCanvas(glTarget, index) {
- console.log(`开始初始化 webgl-${index}`);
- // 创建纹理
- const texture = glTarget.createTexture();
- this.textures.push(texture);
- glTarget.bindTexture(glTarget.TEXTURE_2D, this.textures[index]);
- glTarget.texParameteri(
- glTarget.TEXTURE_2D,
- glTarget.TEXTURE_MIN_FILTER,
- glTarget.LINEAR,
- );
- glTarget.texParameteri(
- glTarget.TEXTURE_2D,
- glTarget.TEXTURE_WRAP_S,
- glTarget.CLAMP_TO_EDGE,
- );
- glTarget.texParameteri(
- glTarget.TEXTURE_2D,
- glTarget.TEXTURE_WRAP_T,
- glTarget.CLAMP_TO_EDGE,
- );
- glTarget.texImage2D(
- glTarget.TEXTURE_2D,
- 0,
- glTarget.RGBA,
- this.canvasSource.width,
- this.canvasSource.height,
- 0,
- glTarget.RGBA,
- glTarget.UNSIGNED_BYTE,
- null,
- );
- // 创建一个帧缓冲区
- const frameBuffer = glTarget.createFramebuffer();
- this.frameBuffers.push(frameBuffer);
- glTarget.bindFramebuffer(glTarget.FRAMEBUFFER, this.frameBuffers[index]);
- glTarget.framebufferTexture2D(
- glTarget.FRAMEBUFFER,
- glTarget.COLOR_ATTACHMENT0,
- glTarget.TEXTURE_2D,
- this.textures[index],
- 0,
- );
- // 检查状态
- var status = glTarget.checkFramebufferStatus(glTarget.FRAMEBUFFER);
- if (status == glTarget.FRAMEBUFFER_COMPLETE) {
- console.log('Framebuffer is complete.');
- } else {
- console.error('Framebuffer is incomplete:', status);
- }
- // 创建着色器程序
- var vertexShader = this._createShader(
- glTarget,
- glTarget.VERTEX_SHADER,
- this.vertexShaderSource,
- );
- // 使用对应的片段着色器源创建并链接不同的着色器程序
- var fragmentShader = this._createShader(
- glTarget,
- glTarget.FRAGMENT_SHADER,
- this.fragmentShaderSources[index],
- );
- const program = glTarget.createProgram();
- this.programs.push(program);
- glTarget.attachShader(this.programs[index], vertexShader);
- glTarget.attachShader(this.programs[index], fragmentShader);
- glTarget.linkProgram(this.programs[index]);
- glTarget.useProgram(this.programs[index]);
- var success = glTarget.getProgramParameter(
- this.programs[index],
- glTarget.LINK_STATUS,
- );
- if (success) {
- console.log('Program linked successfully.');
- } else {
- console.error(glTarget.getProgramInfoLog(this.programs[index]));
- glTarget.deleteProgram(this.programs[index]);
- }
- // 创建顶点缓冲区,并设置 WebGL 的属性与通道(Channels)
- var positionLocation = glTarget.getAttribLocation(
- this.programs[index],
- 'a_position',
- );
- var texCoordLocation = glTarget.getAttribLocation(
- this.programs[index],
- 'a_texCoord',
- );
- var positionBuffer = glTarget.createBuffer();
- glTarget.bindBuffer(glTarget.ARRAY_BUFFER, positionBuffer);
- // 顶点坐标信息
- var positions = [-1, -1, 1, -1, -1, 1, 1, 1];
- glTarget.bufferData(
- glTarget.ARRAY_BUFFER,
- new Float32Array(positions),
- glTarget.STATIC_DRAW,
- );
- glTarget.enableVertexAttribArray(positionLocation);
- glTarget.vertexAttribPointer(
- positionLocation,
- 2,
- glTarget.FLOAT,
- false,
- 0,
- 0,
- );
- var texCoordBuffer = glTarget.createBuffer();
- glTarget.bindBuffer(glTarget.ARRAY_BUFFER, texCoordBuffer);
- // 纹理坐标信息
- var texCoords = [0, 0, 1, 0, 0, 1, 1, 1];
- glTarget.bufferData(
- glTarget.ARRAY_BUFFER,
- new Float32Array(texCoords),
- glTarget.STATIC_DRAW,
- );
- glTarget.enableVertexAttribArray(texCoordLocation);
- glTarget.vertexAttribPointer(
- texCoordLocation,
- 2,
- glTarget.FLOAT,
- false,
- 0,
- 0,
- );
- console.log(`初始化 webgl-${index} 完成`);
- console.log('--------------------');
- }
- _copyOnVideoFrame(glTarget, index) {
- var errorCode = glTarget.getError();
- if (errorCode !== glTarget.NO_ERROR) {
- console.error('WebGL error:', errorCode);
- }
- // 根据索引绑定相应的帧缓冲区
- glTarget.bindFramebuffer(glTarget.FRAMEBUFFER, this.frameBuffers[index]);
- glTarget.viewport(0, 0, this.canvasSource.width, this.canvasSource.height);
- // 从源canvas复制当前帧到纹理中
- glTarget.texImage2D(
- glTarget.TEXTURE_2D,
- 0,
- glTarget.RGBA,
- glTarget.RGBA,
- glTarget.UNSIGNED_BYTE,
- this.canvasSource,
- );
- // 切换到目标canvas的帧缓冲区并绘制纹理
- glTarget.bindFramebuffer(glTarget.FRAMEBUFFER, null);
- glTarget.viewport(
- 0,
- 0,
- this.canvasTargets[index].width,
- this.canvasTargets[index].height,
- );
- this._drawTextureToTargetCanvas(glTarget, index); // 此处调用内部方法
- errorCode = glTarget.getError();
- if (errorCode !== glTarget.NO_ERROR) {
- console.error('WebGL error:', errorCode);
- }
- }
- _drawTextureToTargetCanvas(glTarget, index) {
- glTarget.clearColor(0, 0, 0, 1);
- glTarget.clear(glTarget.COLOR_BUFFER_BIT);
- glTarget.activeTexture(glTarget.TEXTURE0);
- glTarget.bindTexture(glTarget.TEXTURE_2D, this.textures[index]);
- glTarget.uniform1i(
- glTarget.getUniformLocation(this.programs[index], 'u_texture'),
- 0,
- );
- glTarget.drawArrays(glTarget.TRIANGLE_STRIP, 0, 4);
- }
- _createShader(glTarget, type, source) {
- var shader = glTarget.createShader(type);
- glTarget.shaderSource(shader, source);
- glTarget.compileShader(shader);
- var success = glTarget.getShaderParameter(shader, glTarget.COMPILE_STATUS);
- if (success) {
- return shader;
- }
- console.error(glTarget.getShaderInfoLog(shader));
- glTarget.deleteShader(shader);
- }
- _createVertexShaderSource() {
- return `
- attribute vec4 a_position;
- attribute vec2 a_texCoord;
- varying vec2 v_texCoord;
- void main() {
- gl_Position = a_position;
- v_texCoord = a_texCoord;
- }
- `;
- }
- /**
- * 裁图模式
- * [scaleOnly: 只缩放,不裁剪]
- * [threePart: 三等分切割]
- * [camera720: 两等分切割]
- */
- _createFragmentShaderSource(config) {
- const { type, x, y, w, h } = config;
- const width = this._formatShaderNumber(w);
- const height = this._formatShaderNumber(h);
- const offsetX = this._formatShaderNumber(x);
- const offsetY = this._formatShaderNumber(y);
- switch (type) {
- case 'scaleOnly':
- return `
- precision mediump float;
- varying vec2 v_texCoord;
- uniform sampler2D u_texture;
- void main() {
- vec2 scaledTexCoord = vec2(v_texCoord.x * ${width}, v_texCoord.y * ${height});
- scaledTexCoord.x += ${offsetX};
- scaledTexCoord.y += ${offsetY};
- gl_FragColor = texture2D(u_texture, scaledTexCoord);
- }
- `;
- case 'threePart':
- return `
- precision mediump float;
- varying vec2 v_texCoord;
- uniform sampler2D u_texture;
- void main() {
- if (v_texCoord.y < (1.0 / 3.0)) {
- vec2 newTexCoord = vec2(v_texCoord.x / 3.0, v_texCoord.y * 3.0 * ${height} + ${offsetY});
- gl_FragColor = texture2D(u_texture, newTexCoord);
- } else if (v_texCoord.y < (2.0 / 3.0)) {
- vec2 newTexCoord = vec2(v_texCoord.x / 3.0 + 1.0 / 3.0, v_texCoord.y * 3.0 * ${height} + ${offsetY} - ${height});
- gl_FragColor = texture2D(u_texture, newTexCoord);
- } else {
- vec2 newTexCoord = vec2(v_texCoord.x / 3.0 + 2.0 / 3.0, v_texCoord.y * 3.0 * ${height} + ${offsetY} - 2.0 * ${height});
- gl_FragColor = texture2D(u_texture, newTexCoord);
- }
- }
- `;
- case "camera720":
- // + 1.0 / 720.0 解决拼接黑线问题
- return `
- precision mediump float;
- varying vec2 v_texCoord;
- uniform sampler2D u_texture;
-
- void main() {
- if (v_texCoord.y < (1.0 / 2.0)) {
- vec2 newTexCoord = vec2(v_texCoord.x / 2.0, v_texCoord.y * 2.0 * ${height} + ${offsetY});
- gl_FragColor = texture2D(u_texture, newTexCoord);
- } else {
- vec2 newTexCoord = vec2(v_texCoord.x / 2.0 + 1.0 / 2.0, v_texCoord.y * 2.0 * ${height} + ${offsetY} - 1.0 * ${height} + 1.0 / 720.0 );
- gl_FragColor = texture2D(u_texture, newTexCoord);
- }
- }
- `;
- case "oldTwoPart":
- return `
- precision mediump float;
- varying vec2 v_texCoord;
- uniform sampler2D u_texture;
- void main() {
- vec2 newTexCoord = vec2(v_texCoord.x / 2.0, v_texCoord.y * ${height} + ${offsetY});
- gl_FragColor = texture2D(u_texture, newTexCoord);
- }
- `;
- case "oldThreePart":
- return `
- precision mediump float;
- varying vec2 v_texCoord;
- uniform sampler2D u_texture;
- void main() {
- vec2 newTexCoord = vec2(v_texCoord.x / 3.0, v_texCoord.y * ${height} + ${offsetY});
- gl_FragColor = texture2D(u_texture, newTexCoord);
- }
- `;
- default:
- return `
- precision mediump float;
- varying vec2 v_texCoord;
- uniform sampler2D u_texture;
- void main() {
- gl_FragColor = texture2D(u_texture, v_texCoord);
- }
- `;
- }
- }
- _formatShaderNumber(number) {
- const clampedNumber = Math.max(0, Math.min(1, number));
- return clampedNumber.toFixed(6);
- }
- }
|