Files
tk-ws-scrcpy/vendor/tinyh264/YUVWebGLCanvas.ts
2025-07-30 13:39:32 +08:00

67 lines
2.7 KiB
TypeScript

/**
* based on tinyh264 demo: https://github.com/udevbe/tinyh264/tree/master/demo
*/
import YUVSurfaceShader from './YUVSurfaceShader';
import Texture from '../h264-live-player/Texture';
import Canvas from './Canvas';
export default class YUVWebGLCanvas extends Canvas {
private yTexture: Texture;
private uTexture: Texture;
private vTexture: Texture;
private yuvSurfaceShader: YUVSurfaceShader;
constructor(canvas: HTMLCanvasElement) {
super(canvas);
const gl = canvas.getContext('experimental-webgl', {
preserveDrawingBuffer: true,
}) as WebGLRenderingContext | null;
if (!gl) {
throw new Error('Unable to initialize WebGL. Your browser may not support it.');
}
this.yuvSurfaceShader = YUVSurfaceShader.create(gl);
this.yTexture = Texture.create(gl, gl.LUMINANCE);
this.uTexture = Texture.create(gl, gl.LUMINANCE);
this.vTexture = Texture.create(gl, gl.LUMINANCE);
}
decode(buffer: Uint8Array, width: number, height: number): void {
this.canvas.width = width;
this.canvas.height = height;
// the width & height returned are actually padded, so we have to use the frame size to get the real image dimension
// when uploading to texture
const stride = width; // stride
// height is padded with filler rows
// if we knew the size of the video before encoding, we could cut out the black filler pixels. We don't, so just set
// it to the size after encoding
const sourceWidth = width;
const sourceHeight = height;
const maxXTexCoord = sourceWidth / stride;
const maxYTexCoord = sourceHeight / height;
const lumaSize = stride * height;
const chromaSize = lumaSize >> 2;
const yBuffer = buffer.subarray(0, lumaSize);
const uBuffer = buffer.subarray(lumaSize, lumaSize + chromaSize);
const vBuffer = buffer.subarray(lumaSize + chromaSize, lumaSize + 2 * chromaSize);
const chromaHeight = height >> 1;
const chromaStride = stride >> 1;
// we upload the entire image, including stride padding & filler rows. The actual visible image will be mapped
// from texture coordinates as to crop out stride padding & filler rows using maxXTexCoord and maxYTexCoord.
this.yTexture.image2dBuffer(yBuffer, stride, height);
this.uTexture.image2dBuffer(uBuffer, chromaStride, chromaHeight);
this.vTexture.image2dBuffer(vBuffer, chromaStride, chromaHeight);
this.yuvSurfaceShader.setTexture(this.yTexture, this.uTexture, this.vTexture);
this.yuvSurfaceShader.updateShaderData({ w: width, h: height }, { maxXTexCoord, maxYTexCoord });
this.yuvSurfaceShader.draw();
}
}