@yume-chan/scrcpy-decoder-webcodecs 2.1.0 → 2.5.3

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.
@@ -1,22 +1,24 @@
1
1
  import { h264ParseConfiguration } from "@yume-chan/scrcpy";
2
2
 
3
3
  import { H26xDecoder } from "./h26x.js";
4
+ import type { CodecDecoderOptions } from "./type.js";
4
5
  import { hexTwoDigits } from "./utils.js";
5
6
 
6
7
  export class H264Decoder extends H26xDecoder {
7
- #decoder: VideoDecoder;
8
8
  #updateSize: (width: number, height: number) => void;
9
+ #options: CodecDecoderOptions | undefined;
9
10
 
10
11
  constructor(
11
12
  decoder: VideoDecoder,
12
13
  updateSize: (width: number, height: number) => void,
14
+ options?: CodecDecoderOptions,
13
15
  ) {
14
16
  super(decoder);
15
- this.#decoder = decoder;
16
17
  this.#updateSize = updateSize;
18
+ this.#options = options;
17
19
  }
18
20
 
19
- override configure(data: Uint8Array): void {
21
+ override configure(data: Uint8Array): VideoDecoderConfig {
20
22
  const {
21
23
  profileIndex,
22
24
  constraintSet,
@@ -34,9 +36,11 @@ export class H264Decoder extends H26xDecoder {
34
36
  hexTwoDigits(profileIndex) +
35
37
  hexTwoDigits(constraintSet) +
36
38
  hexTwoDigits(levelIndex);
37
- this.#decoder.configure({
39
+ return {
38
40
  codec: codec,
41
+ hardwareAcceleration:
42
+ this.#options?.hardwareAcceleration ?? "no-preference",
39
43
  optimizeForLatency: true,
40
- });
44
+ };
41
45
  }
42
46
  }
@@ -2,22 +2,24 @@ import { getUint32LittleEndian } from "@yume-chan/no-data-view";
2
2
  import { h265ParseConfiguration } from "@yume-chan/scrcpy";
3
3
 
4
4
  import { H26xDecoder } from "./h26x.js";
5
+ import type { CodecDecoderOptions } from "./type.js";
5
6
  import { hexDigits } from "./utils.js";
6
7
 
7
8
  export class H265Decoder extends H26xDecoder {
8
- #decoder: VideoDecoder;
9
9
  #updateSize: (width: number, height: number) => void;
10
+ #options: CodecDecoderOptions | undefined;
10
11
 
11
12
  constructor(
12
13
  decoder: VideoDecoder,
13
14
  updateSize: (width: number, height: number) => void,
15
+ options?: CodecDecoderOptions,
14
16
  ) {
15
17
  super(decoder);
16
- this.#decoder = decoder;
17
18
  this.#updateSize = updateSize;
19
+ this.#options = options;
18
20
  }
19
21
 
20
- override configure(data: Uint8Array): void {
22
+ override configure(data: Uint8Array): VideoDecoderConfig {
21
23
  const {
22
24
  generalProfileSpace,
23
25
  generalProfileIndex,
@@ -39,12 +41,14 @@ export class H265Decoder extends H26xDecoder {
39
41
  (generalTierFlag ? "H" : "L") + generalLevelIndex.toString(),
40
42
  ...Array.from(generalConstraintSet, hexDigits),
41
43
  ].join(".");
42
- this.#decoder.configure({
44
+ return {
43
45
  codec,
44
46
  // Microsoft Edge requires explicit size to work
45
47
  codedWidth: croppedWidth,
46
48
  codedHeight: croppedHeight,
49
+ hardwareAcceleration:
50
+ this.#options?.hardwareAcceleration ?? "no-preference",
47
51
  optimizeForLatency: true,
48
- });
52
+ };
49
53
  }
50
54
  }
@@ -1,36 +1,92 @@
1
- import type { ScrcpyMediaStreamPacket } from "@yume-chan/scrcpy";
1
+ import type {
2
+ ScrcpyMediaStreamDataPacket,
3
+ ScrcpyMediaStreamPacket,
4
+ } from "@yume-chan/scrcpy";
2
5
 
3
6
  import type { CodecDecoder } from "./type.js";
4
7
 
5
8
  export abstract class H26xDecoder implements CodecDecoder {
6
- #config: Uint8Array | undefined;
7
9
  #decoder: VideoDecoder;
8
10
 
11
+ #config: (VideoDecoderConfig & { raw: Uint8Array }) | undefined;
12
+ #configured = false;
13
+
9
14
  constructor(decoder: VideoDecoder) {
10
15
  this.#decoder = decoder;
11
16
  }
12
17
 
13
- abstract configure(data: Uint8Array): void;
18
+ abstract configure(data: Uint8Array): VideoDecoderConfig;
14
19
 
15
- decode(packet: ScrcpyMediaStreamPacket): void {
16
- if (packet.type === "configuration") {
17
- this.#config = packet.data;
18
- this.configure(packet.data);
19
- return;
20
- }
20
+ #configureAndDecodeFirstKeyframe(
21
+ config: VideoDecoderConfig & { raw: Uint8Array },
22
+ packet: ScrcpyMediaStreamDataPacket,
23
+ ) {
24
+ this.#decoder.configure(config);
25
+ this.#configured = true;
21
26
 
22
27
  // For H.264 and H.265, when the stream is in Annex B format
23
28
  // (which Scrcpy uses, as Android MediaCodec produces),
24
29
  // configuration data needs to be combined with the first frame data.
25
30
  // 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;
31
+ const { raw } = config;
32
+ const data = new Uint8Array(raw.length + packet.data.length);
33
+ data.set(raw, 0);
34
+ data.set(packet.data, raw.length);
35
+
36
+ this.#decoder.decode(
37
+ new EncodedVideoChunk({
38
+ type: "key",
39
+ timestamp: 0,
40
+ data,
41
+ }),
42
+ );
43
+ }
44
+
45
+ decode(packet: ScrcpyMediaStreamPacket): void {
46
+ if (packet.type === "configuration") {
47
+ this.#config = {
48
+ ...this.configure(packet.data),
49
+ raw: packet.data,
50
+ };
51
+ this.#configured = false;
52
+ return;
53
+ }
54
+
55
+ if (!this.#config) {
56
+ throw new Error("Decoder not configured");
57
+ }
58
+
59
+ if (packet.keyframe) {
60
+ if (this.#decoder.decodeQueueSize) {
61
+ // If the device is too slow to decode all frames,
62
+ // discard queued frames when next keyframe arrives.
63
+ // (can only do this for keyframes because decoding must start from a keyframe)
64
+ // This limits the maximum latency to 1 keyframe interval
65
+ // (60 frames by default).
66
+ this.#decoder.reset();
67
+
68
+ // `reset` also resets the decoder configuration
69
+ // so we need to re-configure it again.
70
+ this.#configureAndDecodeFirstKeyframe(this.#config, packet);
71
+ return;
72
+ }
73
+
74
+ if (!this.#configured) {
75
+ this.#configureAndDecodeFirstKeyframe(this.#config, packet);
76
+ return;
77
+ }
78
+ }
79
+
80
+ if (!this.#configured) {
81
+ if (packet.keyframe === undefined) {
82
+ // Scrcpy <1.23 doesn't send `keyframe` flag
83
+ // Infer the first frame after configuration as keyframe
84
+ // (`VideoDecoder` will throw error if it's not)
85
+ this.#configureAndDecodeFirstKeyframe(this.#config, packet);
86
+ return;
87
+ }
88
+
89
+ throw new Error("Expect a keyframe but got a delta frame");
34
90
  }
35
91
 
36
92
  this.#decoder.decode(
@@ -38,7 +94,7 @@ export abstract class H26xDecoder implements CodecDecoder {
38
94
  // Treat `undefined` as `key`, otherwise won't decode.
39
95
  type: packet.keyframe === false ? "delta" : "key",
40
96
  timestamp: 0,
41
- data,
97
+ data: packet.data,
42
98
  }),
43
99
  );
44
100
  }
@@ -4,9 +4,14 @@ export interface CodecDecoder {
4
4
  decode(packet: ScrcpyMediaStreamPacket): void;
5
5
  }
6
6
 
7
+ export interface CodecDecoderOptions {
8
+ hardwareAcceleration?: HardwareAcceleration | undefined;
9
+ }
10
+
7
11
  export interface CodecDecoderConstructor {
8
12
  new (
9
13
  decoder: VideoDecoder,
10
14
  updateSize: (width: number, height: number) => void,
15
+ options?: CodecDecoderOptions,
11
16
  ): CodecDecoder;
12
17
  }
@@ -9,7 +9,7 @@ import type { WritableStreamDefaultController } from "@yume-chan/stream-extra";
9
9
  import { WritableStream } from "@yume-chan/stream-extra";
10
10
 
11
11
  import { Av1Codec, H264Decoder, H265Decoder } from "./codec/index.js";
12
- import type { CodecDecoder } from "./codec/type.js";
12
+ import type { CodecDecoder, CodecDecoderOptions } from "./codec/type.js";
13
13
  import { Pool } from "./pool.js";
14
14
  import type { VideoFrameRenderer } from "./render/index.js";
15
15
  import { VideoFrameCapturer } from "./snapshot.js";
@@ -35,6 +35,13 @@ export class WebCodecsVideoDecoder implements ScrcpyVideoDecoder {
35
35
  return this.#codec;
36
36
  }
37
37
 
38
+ #renderer: VideoFrameRenderer;
39
+ get renderer() {
40
+ return this.#renderer;
41
+ }
42
+
43
+ #options: CodecDecoderOptions;
44
+
38
45
  #codecDecoder: CodecDecoder;
39
46
 
40
47
  #writable: WritableStream<ScrcpyMediaStreamPacket>;
@@ -45,11 +52,6 @@ export class WebCodecsVideoDecoder implements ScrcpyVideoDecoder {
45
52
  #error: Error | undefined;
46
53
  #controller!: WritableStreamDefaultController;
47
54
 
48
- #renderer: VideoFrameRenderer;
49
- get renderer() {
50
- return this.#renderer;
51
- }
52
-
53
55
  #framesDraw = 0;
54
56
  #framesPresented = 0;
55
57
  get framesRendered() {
@@ -87,10 +89,14 @@ export class WebCodecsVideoDecoder implements ScrcpyVideoDecoder {
87
89
  /**
88
90
  * Create a new WebCodecs video decoder.
89
91
  */
90
- constructor({ codec, renderer }: WebCodecsVideoDecoder.Options) {
92
+ constructor({
93
+ codec,
94
+ renderer,
95
+ ...options
96
+ }: WebCodecsVideoDecoder.Options) {
91
97
  this.#codec = codec;
92
-
93
98
  this.#renderer = renderer;
99
+ this.#options = options;
94
100
 
95
101
  this.#decoder = new VideoDecoder({
96
102
  output: (frame) => {
@@ -119,18 +125,21 @@ export class WebCodecsVideoDecoder implements ScrcpyVideoDecoder {
119
125
  this.#codecDecoder = new H264Decoder(
120
126
  this.#decoder,
121
127
  this.#updateSize,
128
+ this.#options,
122
129
  );
123
130
  break;
124
131
  case ScrcpyVideoCodecId.H265:
125
132
  this.#codecDecoder = new H265Decoder(
126
133
  this.#decoder,
127
134
  this.#updateSize,
135
+ this.#options,
128
136
  );
129
137
  break;
130
138
  case ScrcpyVideoCodecId.AV1:
131
139
  this.#codecDecoder = new Av1Codec(
132
140
  this.#decoder,
133
141
  this.#updateSize,
142
+ this.#options,
134
143
  );
135
144
  break;
136
145
  default:
@@ -232,7 +241,7 @@ export class WebCodecsVideoDecoder implements ScrcpyVideoDecoder {
232
241
  }
233
242
 
234
243
  export namespace WebCodecsVideoDecoder {
235
- export interface Options {
244
+ export interface Options extends CodecDecoderOptions {
236
245
  /**
237
246
  * The video codec to decode
238
247
  */