@yume-chan/scrcpy-decoder-webcodecs 0.0.0-20240714132542
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +75 -0
- package/LICENSE +21 -0
- package/README.md +49 -0
- package/esm/index.d.ts +2 -0
- package/esm/index.d.ts.map +1 -0
- package/esm/index.js +2 -0
- package/esm/index.js.map +1 -0
- package/esm/video/codec/av1.d.ts +8 -0
- package/esm/video/codec/av1.d.ts.map +1 -0
- package/esm/video/codec/av1.js +71 -0
- package/esm/video/codec/av1.js.map +1 -0
- package/esm/video/codec/h264.d.ts +7 -0
- package/esm/video/codec/h264.d.ts.map +1 -0
- package/esm/video/codec/h264.js +27 -0
- package/esm/video/codec/h264.js.map +1 -0
- package/esm/video/codec/h265.d.ts +7 -0
- package/esm/video/codec/h265.d.ts.map +1 -0
- package/esm/video/codec/h265.js +30 -0
- package/esm/video/codec/h265.js.map +1 -0
- package/esm/video/codec/h26x.d.ts +9 -0
- package/esm/video/codec/h26x.d.ts.map +1 -0
- package/esm/video/codec/h26x.js +35 -0
- package/esm/video/codec/h26x.js.map +1 -0
- package/esm/video/codec/index.d.ts +7 -0
- package/esm/video/codec/index.d.ts.map +1 -0
- package/esm/video/codec/index.js +7 -0
- package/esm/video/codec/index.js.map +1 -0
- package/esm/video/codec/type.d.ts +8 -0
- package/esm/video/codec/type.d.ts.map +1 -0
- package/esm/video/codec/type.js +2 -0
- package/esm/video/codec/type.js.map +1 -0
- package/esm/video/codec/utils.d.ts +4 -0
- package/esm/video/codec/utils.d.ts.map +1 -0
- package/esm/video/codec/utils.js +10 -0
- package/esm/video/codec/utils.js.map +1 -0
- package/esm/video/decoder.d.ts +35 -0
- package/esm/video/decoder.d.ts.map +1 -0
- package/esm/video/decoder.js +144 -0
- package/esm/video/decoder.js.map +1 -0
- package/esm/video/index.d.ts +4 -0
- package/esm/video/index.d.ts.map +1 -0
- package/esm/video/index.js +4 -0
- package/esm/video/index.js.map +1 -0
- package/esm/video/render/bitmap.d.ts +7 -0
- package/esm/video/render/bitmap.d.ts.map +1 -0
- package/esm/video/render/bitmap.js +17 -0
- package/esm/video/render/bitmap.js.map +1 -0
- package/esm/video/render/index.d.ts +4 -0
- package/esm/video/render/index.d.ts.map +1 -0
- package/esm/video/render/index.js +4 -0
- package/esm/video/render/index.js.map +1 -0
- package/esm/video/render/type.d.ts +4 -0
- package/esm/video/render/type.d.ts.map +1 -0
- package/esm/video/render/type.js +2 -0
- package/esm/video/render/type.js.map +1 -0
- package/esm/video/render/webgl.d.ts +16 -0
- package/esm/video/render/webgl.d.ts.map +1 -0
- package/esm/video/render/webgl.js +90 -0
- package/esm/video/render/webgl.js.map +1 -0
- package/package.json +52 -0
- package/src/index.ts +1 -0
- package/src/video/codec/av1.ts +101 -0
- package/src/video/codec/h264.ts +42 -0
- package/src/video/codec/h265.ts +47 -0
- package/src/video/codec/h26x.ts +45 -0
- package/src/video/codec/index.ts +6 -0
- package/src/video/codec/type.ts +12 -0
- package/src/video/codec/utils.ts +11 -0
- package/src/video/decoder.ts +197 -0
- package/src/video/index.ts +3 -0
- package/src/video/render/bitmap.ts +24 -0
- package/src/video/render/index.ts +3 -0
- package/src/video/render/type.ts +3 -0
- package/src/video/render/webgl.ts +118 -0
- package/tsconfig.build.json +21 -0
- package/tsconfig.build.tsbuildinfo +1 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { EventEmitter } from "@yume-chan/event";
|
|
2
|
+
import { ScrcpyVideoCodecId } from "@yume-chan/scrcpy";
|
|
3
|
+
import { createCanvas } from "@yume-chan/scrcpy-decoder-tinyh264";
|
|
4
|
+
import { WritableStream } from "@yume-chan/stream-extra";
|
|
5
|
+
import { Av1Codec, H264Decoder, H265Decoder } from "./codec/index.js";
|
|
6
|
+
import { BitmapFrameSink, WebGLFrameSink } from "./render/index.js";
|
|
7
|
+
export class WebCodecsVideoDecoder {
|
|
8
|
+
static isSupported() {
|
|
9
|
+
return typeof globalThis.VideoDecoder !== "undefined";
|
|
10
|
+
}
|
|
11
|
+
static capabilities = {
|
|
12
|
+
h264: {},
|
|
13
|
+
h265: {},
|
|
14
|
+
};
|
|
15
|
+
#codec;
|
|
16
|
+
get codec() {
|
|
17
|
+
return this.#codec;
|
|
18
|
+
}
|
|
19
|
+
#codecDecoder;
|
|
20
|
+
#writable;
|
|
21
|
+
get writable() {
|
|
22
|
+
return this.#writable;
|
|
23
|
+
}
|
|
24
|
+
#renderer;
|
|
25
|
+
get renderer() {
|
|
26
|
+
return this.#renderer;
|
|
27
|
+
}
|
|
28
|
+
#frameRendered = 0;
|
|
29
|
+
get framesRendered() {
|
|
30
|
+
return this.#frameRendered;
|
|
31
|
+
}
|
|
32
|
+
#frameSkipped = 0;
|
|
33
|
+
get framesSkipped() {
|
|
34
|
+
return this.#frameSkipped;
|
|
35
|
+
}
|
|
36
|
+
#sizeChanged = new EventEmitter();
|
|
37
|
+
get sizeChanged() {
|
|
38
|
+
return this.#sizeChanged.event;
|
|
39
|
+
}
|
|
40
|
+
#decoder;
|
|
41
|
+
#frameSink;
|
|
42
|
+
#currentFrameRendered = false;
|
|
43
|
+
#animationFrameId = 0;
|
|
44
|
+
/**
|
|
45
|
+
* Create a new WebCodecs video decoder.
|
|
46
|
+
*/
|
|
47
|
+
constructor({ codec, canvas, enableCapture }) {
|
|
48
|
+
this.#codec = codec;
|
|
49
|
+
if (canvas) {
|
|
50
|
+
this.#renderer = canvas;
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
this.#renderer = createCanvas();
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
this.#frameSink = new WebGLFrameSink(this.#renderer, !!enableCapture);
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
this.#frameSink = new BitmapFrameSink(this.#renderer);
|
|
60
|
+
}
|
|
61
|
+
this.#decoder = new VideoDecoder({
|
|
62
|
+
output: (frame) => {
|
|
63
|
+
if (this.#currentFrameRendered) {
|
|
64
|
+
this.#frameRendered += 1;
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
this.#frameSkipped += 1;
|
|
68
|
+
}
|
|
69
|
+
this.#currentFrameRendered = false;
|
|
70
|
+
// PERF: Draw every frame to minimize latency at cost of performance.
|
|
71
|
+
// When multiple frames are drawn in one vertical sync interval,
|
|
72
|
+
// only the last one is visible to users.
|
|
73
|
+
// But this ensures users can always see the most up-to-date screen.
|
|
74
|
+
// This is also the behavior of official Scrcpy client.
|
|
75
|
+
// https://github.com/Genymobile/scrcpy/issues/3679
|
|
76
|
+
this.#updateSize(frame.displayWidth, frame.displayHeight);
|
|
77
|
+
this.#frameSink.draw(frame);
|
|
78
|
+
},
|
|
79
|
+
error(e) {
|
|
80
|
+
if (controller) {
|
|
81
|
+
try {
|
|
82
|
+
controller.error(e);
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
// ignore
|
|
86
|
+
// `controller` may already in error state
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
error = e;
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
switch (this.#codec) {
|
|
95
|
+
case ScrcpyVideoCodecId.H264:
|
|
96
|
+
this.#codecDecoder = new H264Decoder(this.#decoder, this.#updateSize);
|
|
97
|
+
break;
|
|
98
|
+
case ScrcpyVideoCodecId.H265:
|
|
99
|
+
this.#codecDecoder = new H265Decoder(this.#decoder, this.#updateSize);
|
|
100
|
+
break;
|
|
101
|
+
case ScrcpyVideoCodecId.AV1:
|
|
102
|
+
this.#codecDecoder = new Av1Codec(this.#decoder, this.#updateSize);
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
let error;
|
|
106
|
+
let controller;
|
|
107
|
+
this.#writable = new WritableStream({
|
|
108
|
+
start: (_controller) => {
|
|
109
|
+
if (error) {
|
|
110
|
+
_controller.error(error);
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
controller = _controller;
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
write: (packet) => {
|
|
117
|
+
this.#codecDecoder.decode(packet);
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
this.#onFramePresented();
|
|
121
|
+
}
|
|
122
|
+
#updateSize = (width, height) => {
|
|
123
|
+
if (width !== this.#renderer.width ||
|
|
124
|
+
height !== this.#renderer.height) {
|
|
125
|
+
this.#renderer.width = width;
|
|
126
|
+
this.#renderer.height = height;
|
|
127
|
+
this.#sizeChanged.fire({
|
|
128
|
+
width: width,
|
|
129
|
+
height: height,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
#onFramePresented = () => {
|
|
134
|
+
this.#currentFrameRendered = true;
|
|
135
|
+
this.#animationFrameId = requestAnimationFrame(this.#onFramePresented);
|
|
136
|
+
};
|
|
137
|
+
dispose() {
|
|
138
|
+
cancelAnimationFrame(this.#animationFrameId);
|
|
139
|
+
if (this.#decoder.state !== "closed") {
|
|
140
|
+
this.#decoder.close();
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=decoder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decoder.js","sourceRoot":"","sources":["../../src/video/decoder.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/video/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,mBAAmB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/video/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,mBAAmB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bitmap.d.ts","sourceRoot":"","sources":["../../../src/video/render/bitmap.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAE3C,qBAAa,eAAgB,YAAW,SAAS;;gBAGjC,MAAM,EAAE,iBAAiB,GAAG,eAAe;IAIvD,IAAI,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI;CAchC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export class BitmapFrameSink {
|
|
2
|
+
#context;
|
|
3
|
+
constructor(canvas) {
|
|
4
|
+
this.#context = canvas.getContext("bitmaprenderer", { alpha: false });
|
|
5
|
+
}
|
|
6
|
+
draw(frame) {
|
|
7
|
+
createImageBitmap(frame)
|
|
8
|
+
.then((bitmap) => {
|
|
9
|
+
this.#context.transferFromImageBitmap(bitmap);
|
|
10
|
+
frame.close();
|
|
11
|
+
})
|
|
12
|
+
.catch((e) => {
|
|
13
|
+
console.warn("[@yume-chan/scrcpy-decoder-webcodecs]", "VideoDecoder error", e);
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=bitmap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bitmap.js","sourceRoot":"","sources":["../../../src/video/render/bitmap.ts"],"names":[],"mappings":"AAEA,MAAM,OAAO,eAAe;IACxB,QAAQ,CAA8B;IAEtC,YAAY,MAA2C;QACnD,IAAI,CAAC,QAAQ,GAAG,MAAM,CAAC,UAAU,CAAC,gBAAgB,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAE,CAAC;IAC3E,CAAC;IAED,IAAI,CAAC,KAAiB;QAClB,iBAAiB,CAAC,KAAK,CAAC;aACnB,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE;YACb,IAAI,CAAC,QAAQ,CAAC,uBAAuB,CAAC,MAAM,CAAC,CAAC;YAC9C,KAAK,CAAC,KAAK,EAAE,CAAC;QAClB,CAAC,CAAC;aACD,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE;YACT,OAAO,CAAC,IAAI,CACR,uCAAuC,EACvC,oBAAoB,EACpB,CAAC,CACJ,CAAC;QACN,CAAC,CAAC,CAAC;IACX,CAAC;CACJ"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/video/render/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/video/render/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"type.d.ts","sourceRoot":"","sources":["../../../src/video/render/type.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,SAAS;IACtB,IAAI,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;CACjC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"type.js","sourceRoot":"","sources":["../../../src/video/render/type.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { FrameSink } from "./type.js";
|
|
2
|
+
export declare class WebGLFrameSink implements FrameSink {
|
|
3
|
+
#private;
|
|
4
|
+
static vertexShaderSource: string;
|
|
5
|
+
static fragmentShaderSource: string;
|
|
6
|
+
/**
|
|
7
|
+
* Create a new WebGL frame renderer.
|
|
8
|
+
* @param canvas The canvas to render frames to.
|
|
9
|
+
* @param enableCapture
|
|
10
|
+
* Whether to allow capturing the canvas content using APIs like `readPixels` and `toDataURL`.
|
|
11
|
+
* Enable this option may reduce performance.
|
|
12
|
+
*/
|
|
13
|
+
constructor(canvas: HTMLCanvasElement | OffscreenCanvas, enableCapture: boolean);
|
|
14
|
+
draw(frame: VideoFrame): void;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=webgl.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webgl.d.ts","sourceRoot":"","sources":["../../../src/video/render/webgl.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAE3C,qBAAa,cAAe,YAAW,SAAS;;IAC5C,MAAM,CAAC,kBAAkB,SAW3B;IAEE,MAAM,CAAC,oBAAoB,SAQ7B;IAIE;;;;;;OAMG;gBAEC,MAAM,EAAE,iBAAiB,GAAG,eAAe,EAC3C,aAAa,EAAE,OAAO;IAiE1B,IAAI,CAAC,KAAK,EAAE,UAAU;CAezB"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
export class WebGLFrameSink {
|
|
2
|
+
static vertexShaderSource = `
|
|
3
|
+
attribute vec2 xy;
|
|
4
|
+
|
|
5
|
+
varying highp vec2 uv;
|
|
6
|
+
|
|
7
|
+
void main(void) {
|
|
8
|
+
gl_Position = vec4(xy, 0.0, 1.0);
|
|
9
|
+
// Map vertex coordinates (-1 to +1) to UV coordinates (0 to 1).
|
|
10
|
+
// UV coordinates are Y-flipped relative to vertex coordinates.
|
|
11
|
+
uv = vec2((1.0 + xy.x) / 2.0, (1.0 - xy.y) / 2.0);
|
|
12
|
+
}
|
|
13
|
+
`;
|
|
14
|
+
static fragmentShaderSource = `
|
|
15
|
+
varying highp vec2 uv;
|
|
16
|
+
|
|
17
|
+
uniform sampler2D texture;
|
|
18
|
+
|
|
19
|
+
void main(void) {
|
|
20
|
+
gl_FragColor = texture2D(texture, uv);
|
|
21
|
+
}
|
|
22
|
+
`;
|
|
23
|
+
#context;
|
|
24
|
+
/**
|
|
25
|
+
* Create a new WebGL frame renderer.
|
|
26
|
+
* @param canvas The canvas to render frames to.
|
|
27
|
+
* @param enableCapture
|
|
28
|
+
* Whether to allow capturing the canvas content using APIs like `readPixels` and `toDataURL`.
|
|
29
|
+
* Enable this option may reduce performance.
|
|
30
|
+
*/
|
|
31
|
+
constructor(canvas, enableCapture) {
|
|
32
|
+
const attributes = {
|
|
33
|
+
// Low-power GPU should be enough for video rendering.
|
|
34
|
+
powerPreference: "low-power",
|
|
35
|
+
alpha: false,
|
|
36
|
+
// Disallow software rendering.
|
|
37
|
+
// Other rendering methods are faster than software-based WebGL.
|
|
38
|
+
failIfMajorPerformanceCaveat: true,
|
|
39
|
+
preserveDrawingBuffer: enableCapture,
|
|
40
|
+
};
|
|
41
|
+
const gl = canvas.getContext("webgl2", attributes) ||
|
|
42
|
+
canvas.getContext("webgl", attributes);
|
|
43
|
+
if (!gl) {
|
|
44
|
+
throw new Error("WebGL not supported");
|
|
45
|
+
}
|
|
46
|
+
this.#context = gl;
|
|
47
|
+
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
|
|
48
|
+
gl.shaderSource(vertexShader, WebGLFrameSink.vertexShaderSource);
|
|
49
|
+
gl.compileShader(vertexShader);
|
|
50
|
+
if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
|
|
51
|
+
throw new Error(gl.getShaderInfoLog(vertexShader));
|
|
52
|
+
}
|
|
53
|
+
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
|
|
54
|
+
gl.shaderSource(fragmentShader, WebGLFrameSink.fragmentShaderSource);
|
|
55
|
+
gl.compileShader(fragmentShader);
|
|
56
|
+
if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
|
|
57
|
+
throw new Error(gl.getShaderInfoLog(fragmentShader));
|
|
58
|
+
}
|
|
59
|
+
const shaderProgram = gl.createProgram();
|
|
60
|
+
gl.attachShader(shaderProgram, vertexShader);
|
|
61
|
+
gl.attachShader(shaderProgram, fragmentShader);
|
|
62
|
+
gl.linkProgram(shaderProgram);
|
|
63
|
+
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
|
|
64
|
+
throw new Error(gl.getProgramInfoLog(shaderProgram));
|
|
65
|
+
}
|
|
66
|
+
gl.useProgram(shaderProgram);
|
|
67
|
+
// Vertex coordinates, clockwise from bottom-left.
|
|
68
|
+
const vertexBuffer = gl.createBuffer();
|
|
69
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
|
|
70
|
+
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1.0, -1.0, -1.0, +1.0, +1.0, +1.0, +1.0, -1.0]), gl.STATIC_DRAW);
|
|
71
|
+
const xyLocation = gl.getAttribLocation(shaderProgram, "xy");
|
|
72
|
+
gl.vertexAttribPointer(xyLocation, 2, gl.FLOAT, false, 0, 0);
|
|
73
|
+
gl.enableVertexAttribArray(xyLocation);
|
|
74
|
+
// Create one texture to upload frames to.
|
|
75
|
+
const texture = gl.createTexture();
|
|
76
|
+
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
77
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
78
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
79
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
80
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
81
|
+
}
|
|
82
|
+
draw(frame) {
|
|
83
|
+
const gl = this.#context;
|
|
84
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, frame);
|
|
85
|
+
frame.close();
|
|
86
|
+
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
|
|
87
|
+
gl.drawArrays(gl.TRIANGLE_FAN, 0, 4);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=webgl.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webgl.js","sourceRoot":"","sources":["../../../src/video/render/webgl.ts"],"names":[],"mappings":"AAEA,MAAM,OAAO,cAAc;IACvB,MAAM,CAAC,kBAAkB,GAAG;;;;;;;;;;;CAW/B,CAAC;IAEE,MAAM,CAAC,oBAAoB,GAAG;;;;;;;;CAQjC,CAAC;IAEE,QAAQ,CAAwB;IAEhC;;;;;;OAMG;IACH,YACI,MAA2C,EAC3C,aAAsB;QAEtB,MAAM,UAAU,GAA2B;YACvC,sDAAsD;YACtD,eAAe,EAAE,WAAW;YAC5B,KAAK,EAAE,KAAK;YACZ,+BAA+B;YAC/B,gEAAgE;YAChE,4BAA4B,EAAE,IAAI;YAClC,qBAAqB,EAAE,aAAa;SACvC,CAAC;QAEF,MAAM,EAAE,GACJ,MAAM,CAAC,UAAU,CAAC,QAAQ,EAAE,UAAU,CAAC;YACvC,MAAM,CAAC,UAAU,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;QAC3C,IAAI,CAAC,EAAE,EAAE,CAAC;YACN,MAAM,IAAI,KAAK,CAAC,qBAAqB,CAAC,CAAC;QAC3C,CAAC;QACD,IAAI,CAAC,QAAQ,GAAG,EAAE,CAAC;QAEnB,MAAM,YAAY,GAAG,EAAE,CAAC,YAAY,CAAC,EAAE,CAAC,aAAa,CAAE,CAAC;QACxD,EAAE,CAAC,YAAY,CAAC,YAAY,EAAE,cAAc,CAAC,kBAAkB,CAAC,CAAC;QACjE,EAAE,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC;QAC/B,IAAI,CAAC,EAAE,CAAC,kBAAkB,CAAC,YAAY,EAAE,EAAE,CAAC,cAAc,CAAC,EAAE,CAAC;YAC1D,MAAM,IAAI,KAAK,CAAC,EAAE,CAAC,gBAAgB,CAAC,YAAY,CAAE,CAAC,CAAC;QACxD,CAAC;QAED,MAAM,cAAc,GAAG,EAAE,CAAC,YAAY,CAAC,EAAE,CAAC,eAAe,CAAE,CAAC;QAC5D,EAAE,CAAC,YAAY,CAAC,cAAc,EAAE,cAAc,CAAC,oBAAoB,CAAC,CAAC;QACrE,EAAE,CAAC,aAAa,CAAC,cAAc,CAAC,CAAC;QACjC,IAAI,CAAC,EAAE,CAAC,kBAAkB,CAAC,cAAc,EAAE,EAAE,CAAC,cAAc,CAAC,EAAE,CAAC;YAC5D,MAAM,IAAI,KAAK,CAAC,EAAE,CAAC,gBAAgB,CAAC,cAAc,CAAE,CAAC,CAAC;QAC1D,CAAC;QAED,MAAM,aAAa,GAAG,EAAE,CAAC,aAAa,EAAG,CAAC;QAC1C,EAAE,CAAC,YAAY,CAAC,aAAa,EAAE,YAAY,CAAC,CAAC;QAC7C,EAAE,CAAC,YAAY,CAAC,aAAa,EAAE,cAAc,CAAC,CAAC;QAC/C,EAAE,CAAC,WAAW,CAAC,aAAa,CAAC,CAAC;QAC9B,IAAI,CAAC,EAAE,CAAC,mBAAmB,CAAC,aAAa,EAAE,EAAE,CAAC,WAAW,CAAC,EAAE,CAAC;YACzD,MAAM,IAAI,KAAK,CAAC,EAAE,CAAC,iBAAiB,CAAC,aAAa,CAAE,CAAC,CAAC;QAC1D,CAAC;QACD,EAAE,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;QAE7B,kDAAkD;QAClD,MAAM,YAAY,GAAG,EAAE,CAAC,YAAY,EAAE,CAAC;QACvC,EAAE,CAAC,UAAU,CAAC,EAAE,CAAC,YAAY,EAAE,YAAY,CAAC,CAAC;QAC7C,EAAE,CAAC,UAAU,CACT,EAAE,CAAC,YAAY,EACf,IAAI,YAAY,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,EAClE,EAAE,CAAC,WAAW,CACjB,CAAC;QAEF,MAAM,UAAU,GAAG,EAAE,CAAC,iBAAiB,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC;QAC7D,EAAE,CAAC,mBAAmB,CAAC,UAAU,EAAE,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QAC7D,EAAE,CAAC,uBAAuB,CAAC,UAAU,CAAC,CAAC;QAEvC,0CAA0C;QAC1C,MAAM,OAAO,GAAG,EAAE,CAAC,aAAa,EAAE,CAAC;QACnC,EAAE,CAAC,WAAW,CAAC,EAAE,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;QACvC,EAAE,CAAC,aAAa,CAAC,EAAE,CAAC,UAAU,EAAE,EAAE,CAAC,kBAAkB,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC;QACnE,EAAE,CAAC,aAAa,CAAC,EAAE,CAAC,UAAU,EAAE,EAAE,CAAC,kBAAkB,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC;QACnE,EAAE,CAAC,aAAa,CAAC,EAAE,CAAC,UAAU,EAAE,EAAE,CAAC,cAAc,EAAE,EAAE,CAAC,aAAa,CAAC,CAAC;QACrE,EAAE,CAAC,aAAa,CAAC,EAAE,CAAC,UAAU,EAAE,EAAE,CAAC,cAAc,EAAE,EAAE,CAAC,aAAa,CAAC,CAAC;IACzE,CAAC;IAED,IAAI,CAAC,KAAiB;QAClB,MAAM,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC;QACzB,EAAE,CAAC,UAAU,CACT,EAAE,CAAC,UAAU,EACb,CAAC,EACD,EAAE,CAAC,IAAI,EACP,EAAE,CAAC,IAAI,EACP,EAAE,CAAC,aAAa,EAChB,KAAK,CACR,CAAC;QACF,KAAK,CAAC,KAAK,EAAE,CAAC;QAEd,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,kBAAkB,EAAE,EAAE,CAAC,mBAAmB,CAAC,CAAC;QACjE,EAAE,CAAC,UAAU,CAAC,EAAE,CAAC,YAAY,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IACzC,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@yume-chan/scrcpy-decoder-webcodecs",
|
|
3
|
+
"version": "0.0.0-20240714132542",
|
|
4
|
+
"description": "Raw H.264 stream decoder and renderer using WebCodecs API (requires modern browser).",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"adb",
|
|
7
|
+
"android-phone",
|
|
8
|
+
"scrcpy",
|
|
9
|
+
"scrcpy-decoder"
|
|
10
|
+
],
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"author": {
|
|
13
|
+
"name": "Simon Chan",
|
|
14
|
+
"email": "cnsimonchan@live.com",
|
|
15
|
+
"url": "https://chensi.moe/blog"
|
|
16
|
+
},
|
|
17
|
+
"homepage": "https://github.com/yume-chan/ya-webadb/tree/main/packages/scrcpy-decoder-webcodecs#readme",
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/yume-chan/ya-webadb.git",
|
|
21
|
+
"directory": "packages/scrcpy-decoder-webcodecs"
|
|
22
|
+
},
|
|
23
|
+
"bugs": {
|
|
24
|
+
"url": "https://github.com/yume-chan/ya-webadb/issues"
|
|
25
|
+
},
|
|
26
|
+
"type": "module",
|
|
27
|
+
"main": "esm/index.js",
|
|
28
|
+
"types": "esm/index.d.ts",
|
|
29
|
+
"sideEffects": false,
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@yume-chan/event": "0.0.0-20240714132542",
|
|
32
|
+
"@yume-chan/no-data-view": "0.0.0-20240714132542",
|
|
33
|
+
"@yume-chan/scrcpy": "0.0.0-20240714132542",
|
|
34
|
+
"@yume-chan/scrcpy-decoder-tinyh264": "0.0.0-20240714132542",
|
|
35
|
+
"@yume-chan/stream-extra": "0.0.0-20240714132542"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@jest/globals": "^30.0.0-alpha.4",
|
|
39
|
+
"@yume-chan/eslint-config": "^1.0.0",
|
|
40
|
+
"@yume-chan/tsconfig": "^1.0.0",
|
|
41
|
+
"cross-env": "^7.0.3",
|
|
42
|
+
"jest": "^30.0.0-alpha.4",
|
|
43
|
+
"prettier": "^3.3.3",
|
|
44
|
+
"ts-jest": "^29.2.2",
|
|
45
|
+
"typescript": "^5.5.3"
|
|
46
|
+
},
|
|
47
|
+
"scripts": {
|
|
48
|
+
"build": "tsc -b tsconfig.build.json",
|
|
49
|
+
"build:watch": "tsc -b tsconfig.build.json",
|
|
50
|
+
"lint": "run-eslint && prettier src/**/*.ts --write --tab-width 4"
|
|
51
|
+
}
|
|
52
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./video/index.js";
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import type { ScrcpyMediaStreamPacket } from "@yume-chan/scrcpy";
|
|
2
|
+
import { Av1 } from "@yume-chan/scrcpy";
|
|
3
|
+
|
|
4
|
+
import type { CodecDecoder } from "./type.js";
|
|
5
|
+
import { decimalTwoDigits } from "./utils.js";
|
|
6
|
+
|
|
7
|
+
export class Av1Codec implements CodecDecoder {
|
|
8
|
+
#decoder: VideoDecoder;
|
|
9
|
+
#updateSize: (width: number, height: number) => void;
|
|
10
|
+
|
|
11
|
+
constructor(
|
|
12
|
+
decoder: VideoDecoder,
|
|
13
|
+
updateSize: (width: number, height: number) => void,
|
|
14
|
+
) {
|
|
15
|
+
this.#decoder = decoder;
|
|
16
|
+
this.#updateSize = updateSize;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
#configure(data: Uint8Array) {
|
|
20
|
+
const parser = new Av1(data);
|
|
21
|
+
const sequenceHeader = parser.searchSequenceHeaderObu();
|
|
22
|
+
|
|
23
|
+
if (!sequenceHeader) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const {
|
|
28
|
+
seq_profile: seqProfile,
|
|
29
|
+
seq_level_idx: [seqLevelIdx = 0],
|
|
30
|
+
max_frame_width_minus_1,
|
|
31
|
+
max_frame_height_minus_1,
|
|
32
|
+
color_config: {
|
|
33
|
+
BitDepth,
|
|
34
|
+
mono_chrome: monoChrome,
|
|
35
|
+
subsampling_x: subsamplingX,
|
|
36
|
+
subsampling_y: subsamplingY,
|
|
37
|
+
chroma_sample_position: chromaSamplePosition,
|
|
38
|
+
color_description_present_flag,
|
|
39
|
+
},
|
|
40
|
+
} = sequenceHeader;
|
|
41
|
+
|
|
42
|
+
let colorPrimaries: Av1.ColorPrimaries;
|
|
43
|
+
let transferCharacteristics: Av1.TransferCharacteristics;
|
|
44
|
+
let matrixCoefficients: Av1.MatrixCoefficients;
|
|
45
|
+
let colorRange: boolean;
|
|
46
|
+
if (color_description_present_flag) {
|
|
47
|
+
({
|
|
48
|
+
color_primaries: colorPrimaries,
|
|
49
|
+
transfer_characteristics: transferCharacteristics,
|
|
50
|
+
matrix_coefficients: matrixCoefficients,
|
|
51
|
+
color_range: colorRange,
|
|
52
|
+
} = sequenceHeader.color_config);
|
|
53
|
+
} else {
|
|
54
|
+
colorPrimaries = Av1.ColorPrimaries.Bt709;
|
|
55
|
+
transferCharacteristics = Av1.TransferCharacteristics.Bt709;
|
|
56
|
+
matrixCoefficients = Av1.MatrixCoefficients.Bt709;
|
|
57
|
+
colorRange = false;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const width = max_frame_width_minus_1 + 1;
|
|
61
|
+
const height = max_frame_height_minus_1 + 1;
|
|
62
|
+
|
|
63
|
+
this.#updateSize(width, height);
|
|
64
|
+
|
|
65
|
+
const codec = [
|
|
66
|
+
"av01",
|
|
67
|
+
seqProfile.toString(16),
|
|
68
|
+
decimalTwoDigits(seqLevelIdx) +
|
|
69
|
+
(sequenceHeader.seq_tier[0] ? "H" : "M"),
|
|
70
|
+
decimalTwoDigits(BitDepth),
|
|
71
|
+
monoChrome ? "1" : "0",
|
|
72
|
+
(subsamplingX ? "1" : "0") +
|
|
73
|
+
(subsamplingY ? "1" : "0") +
|
|
74
|
+
chromaSamplePosition.toString(),
|
|
75
|
+
decimalTwoDigits(colorPrimaries),
|
|
76
|
+
decimalTwoDigits(transferCharacteristics),
|
|
77
|
+
decimalTwoDigits(matrixCoefficients),
|
|
78
|
+
colorRange ? "1" : "0",
|
|
79
|
+
].join(".");
|
|
80
|
+
this.#decoder.configure({
|
|
81
|
+
codec,
|
|
82
|
+
optimizeForLatency: true,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
decode(packet: ScrcpyMediaStreamPacket): void {
|
|
87
|
+
if (packet.type === "configuration") {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
this.#configure(packet.data);
|
|
92
|
+
this.#decoder.decode(
|
|
93
|
+
new EncodedVideoChunk({
|
|
94
|
+
// Treat `undefined` as `key`, otherwise it won't decode.
|
|
95
|
+
type: packet.keyframe === false ? "delta" : "key",
|
|
96
|
+
timestamp: 0,
|
|
97
|
+
data: packet.data,
|
|
98
|
+
}),
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { h264ParseConfiguration } from "@yume-chan/scrcpy";
|
|
2
|
+
|
|
3
|
+
import { H26xDecoder } from "./h26x.js";
|
|
4
|
+
import { hexTwoDigits } from "./utils.js";
|
|
5
|
+
|
|
6
|
+
export class H264Decoder extends H26xDecoder {
|
|
7
|
+
#decoder: VideoDecoder;
|
|
8
|
+
#updateSize: (width: number, height: number) => void;
|
|
9
|
+
|
|
10
|
+
constructor(
|
|
11
|
+
decoder: VideoDecoder,
|
|
12
|
+
updateSize: (width: number, height: number) => void,
|
|
13
|
+
) {
|
|
14
|
+
super(decoder);
|
|
15
|
+
this.#decoder = decoder;
|
|
16
|
+
this.#updateSize = updateSize;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
override configure(data: Uint8Array): void {
|
|
20
|
+
const {
|
|
21
|
+
profileIndex,
|
|
22
|
+
constraintSet,
|
|
23
|
+
levelIndex,
|
|
24
|
+
croppedWidth,
|
|
25
|
+
croppedHeight,
|
|
26
|
+
} = h264ParseConfiguration(data);
|
|
27
|
+
|
|
28
|
+
this.#updateSize(croppedWidth, croppedHeight);
|
|
29
|
+
|
|
30
|
+
// https://www.rfc-editor.org/rfc/rfc6381#section-3.3
|
|
31
|
+
// ISO Base Media File Format Name Space
|
|
32
|
+
const codec =
|
|
33
|
+
"avc1." +
|
|
34
|
+
hexTwoDigits(profileIndex) +
|
|
35
|
+
hexTwoDigits(constraintSet) +
|
|
36
|
+
hexTwoDigits(levelIndex);
|
|
37
|
+
this.#decoder.configure({
|
|
38
|
+
codec: codec,
|
|
39
|
+
optimizeForLatency: true,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { getUint32LittleEndian } from "@yume-chan/no-data-view";
|
|
2
|
+
import { h265ParseConfiguration } from "@yume-chan/scrcpy";
|
|
3
|
+
|
|
4
|
+
import { H26xDecoder } from "./h26x.js";
|
|
5
|
+
import { hexDigits } from "./utils.js";
|
|
6
|
+
|
|
7
|
+
export class H265Decoder extends H26xDecoder {
|
|
8
|
+
#decoder: VideoDecoder;
|
|
9
|
+
#updateSize: (width: number, height: number) => void;
|
|
10
|
+
|
|
11
|
+
constructor(
|
|
12
|
+
decoder: VideoDecoder,
|
|
13
|
+
updateSize: (width: number, height: number) => void,
|
|
14
|
+
) {
|
|
15
|
+
super(decoder);
|
|
16
|
+
this.#decoder = decoder;
|
|
17
|
+
this.#updateSize = updateSize;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
override configure(data: Uint8Array): void {
|
|
21
|
+
const {
|
|
22
|
+
generalProfileSpace,
|
|
23
|
+
generalProfileIndex,
|
|
24
|
+
generalProfileCompatibilitySet,
|
|
25
|
+
generalTierFlag,
|
|
26
|
+
generalLevelIndex,
|
|
27
|
+
generalConstraintSet,
|
|
28
|
+
croppedWidth,
|
|
29
|
+
croppedHeight,
|
|
30
|
+
} = h265ParseConfiguration(data);
|
|
31
|
+
|
|
32
|
+
this.#updateSize(croppedWidth, croppedHeight);
|
|
33
|
+
|
|
34
|
+
const codec = [
|
|
35
|
+
"hev1",
|
|
36
|
+
["", "A", "B", "C"][generalProfileSpace]! +
|
|
37
|
+
generalProfileIndex.toString(),
|
|
38
|
+
hexDigits(getUint32LittleEndian(generalProfileCompatibilitySet, 0)),
|
|
39
|
+
(generalTierFlag ? "H" : "L") + generalLevelIndex.toString(),
|
|
40
|
+
...Array.from(generalConstraintSet, hexDigits),
|
|
41
|
+
].join(".");
|
|
42
|
+
this.#decoder.configure({
|
|
43
|
+
codec,
|
|
44
|
+
optimizeForLatency: true,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { ScrcpyMediaStreamPacket } from "@yume-chan/scrcpy";
|
|
2
|
+
|
|
3
|
+
import type { CodecDecoder } from "./type.js";
|
|
4
|
+
|
|
5
|
+
export abstract class H26xDecoder implements CodecDecoder {
|
|
6
|
+
#config: Uint8Array | undefined;
|
|
7
|
+
#decoder: VideoDecoder;
|
|
8
|
+
|
|
9
|
+
constructor(decoder: VideoDecoder) {
|
|
10
|
+
this.#decoder = decoder;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
abstract configure(data: Uint8Array): void;
|
|
14
|
+
|
|
15
|
+
decode(packet: ScrcpyMediaStreamPacket): void {
|
|
16
|
+
if (packet.type === "configuration") {
|
|
17
|
+
this.#config = packet.data;
|
|
18
|
+
this.configure(packet.data);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// For H.264 and H.265, when the stream is in Annex B format
|
|
23
|
+
// (which Scrcpy uses, as Android MediaCodec produces),
|
|
24
|
+
// configuration data needs to be combined with the first frame data.
|
|
25
|
+
// https://www.w3.org/TR/webcodecs-avc-codec-registration/#encodedvideochunk-type
|
|
26
|
+
let data: Uint8Array;
|
|
27
|
+
if (this.#config !== undefined) {
|
|
28
|
+
data = new Uint8Array(this.#config.length + packet.data.length);
|
|
29
|
+
data.set(this.#config, 0);
|
|
30
|
+
data.set(packet.data, this.#config.length);
|
|
31
|
+
this.#config = undefined;
|
|
32
|
+
} else {
|
|
33
|
+
data = packet.data;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
this.#decoder.decode(
|
|
37
|
+
new EncodedVideoChunk({
|
|
38
|
+
// Treat `undefined` as `key`, otherwise won't decode.
|
|
39
|
+
type: packet.keyframe === false ? "delta" : "key",
|
|
40
|
+
timestamp: 0,
|
|
41
|
+
data,
|
|
42
|
+
}),
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
}
|