@mediaway/encoder 0.1.4 → 0.1.6
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/README.md +30 -0
- package/dist/index.d.ts +25 -1
- package/dist/index.js +38 -3
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -8,6 +8,34 @@ session you push frames into.
|
|
|
8
8
|
Windows is the verified platform. Software fallbacks and other platforms are
|
|
9
9
|
explicit follow-up work — see the [Mediaway roadmap](https://github.com/nyxways/mediaway).
|
|
10
10
|
|
|
11
|
+
Video/audio **decode** moved to its own peer package, [`@mediaway/decoder`](../decoder)
|
|
12
|
+
— it is not re-exported here anymore.
|
|
13
|
+
|
|
14
|
+
## Capture-to-encode bridge
|
|
15
|
+
|
|
16
|
+
`EncodeSession.writeFrameFromCameraCapture(capture)` /
|
|
17
|
+
`.writeFrameFromDesktopCapture(capture)` poll a `CameraSession`/`ScreenSession`
|
|
18
|
+
(`@mediaway/device`) and push straight into the session — no intermediate
|
|
19
|
+
`VideoFrame`, no CPU copy for Screen's GPU frames:
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import { AutoVideoEncodeConfig, openAutoEncoder } from "@mediaway/encoder";
|
|
23
|
+
import { GpuDevice, openScreenCapture } from "@mediaway/device";
|
|
24
|
+
|
|
25
|
+
const gpuDevice = await GpuDevice.create();
|
|
26
|
+
const screen = await openScreenCapture({ timeBase: { num: 1, den: 30 }, gpuDevice });
|
|
27
|
+
|
|
28
|
+
const config = AutoVideoEncodeConfig.defaults("h264", screen.width, screen.height, screen.timeBase);
|
|
29
|
+
config.gpuDevice = gpuDevice; // Zero-Copy GPU input, same device as capture
|
|
30
|
+
config.pixelFormat = "bgra8"; // DXGI Desktop Duplication's native format
|
|
31
|
+
const session = await openAutoEncoder(config);
|
|
32
|
+
|
|
33
|
+
while (recording) {
|
|
34
|
+
await session.writeFrameFromDesktopCapture(screen); // returns false if nothing was ready yet
|
|
35
|
+
}
|
|
36
|
+
const mp4 = await session.finish();
|
|
37
|
+
```
|
|
38
|
+
|
|
11
39
|
## Install
|
|
12
40
|
|
|
13
41
|
```bash
|
|
@@ -57,8 +85,10 @@ const mp4 = await session.finish(); // terminal: complete fMP4 bytes
|
|
|
57
85
|
| Member | Notes |
|
|
58
86
|
| --- | --- |
|
|
59
87
|
| `AutoVideoEncodeConfig.defaults(codec, width, height, timeBase)` | sensible defaults for the resolution; `codec` is `"h264" \| "hevc" \| "av1" \| "vp9"` |
|
|
88
|
+
| `AutoVideoEncodeConfig.gpuDevice` | a `GpuDevice` (`@mediaway/device`) for Zero-Copy GPU input; pair with `pixelFormat: "bgra8"` for Screen |
|
|
60
89
|
| `openAutoEncoder(config)` | resolves a hardware backend or throws `EncoderUnavailableError` |
|
|
61
90
|
| `EncodeSession.writeFrame(frame)` | async; the frame is copied — reuse the buffer |
|
|
91
|
+
| `EncodeSession.writeFrameFromCameraCapture(capture)` / `writeFrameFromDesktopCapture(capture)` | async; polls+pushes a capture session directly, `false` if nothing was ready |
|
|
62
92
|
| `EncodeSession.finish()` | terminal; returns the complete fMP4 `Buffer` |
|
|
63
93
|
|
|
64
94
|
## License
|
package/dist/index.d.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { type RawRational } from "@mediaway/ffi";
|
|
11
11
|
import { MediawayError, type Rational } from "@mediaway/container";
|
|
12
|
+
import { type CameraSession, type GpuDevice, type ScreenSession } from "@mediaway/device";
|
|
12
13
|
export type { Rational } from "@mediaway/container";
|
|
13
14
|
export { MediawayError } from "@mediaway/container";
|
|
14
15
|
export type PixelFormat = "nv12" | "bgra8" | "rgba8" | "i420" | "yuy2";
|
|
@@ -33,11 +34,18 @@ export declare class AutoVideoEncodeConfig {
|
|
|
33
34
|
timeBase: Rational;
|
|
34
35
|
bitrateBps?: number;
|
|
35
36
|
pixelFormat?: PixelFormat;
|
|
37
|
+
/** Zero-Copy GPU input: the same device a Screen capture session was opened
|
|
38
|
+
* with (or any `GpuDevice`) — set together with `pixelFormat: "bgra8"` for
|
|
39
|
+
* `writeFrameFromDesktopCapture()`'s GPU frames (DXGI delivers BGRA8, not
|
|
40
|
+
* the NV12 default). Leave unset for CPU input (Camera, or manual
|
|
41
|
+
* `writeFrame()` with CPU-storage frames). */
|
|
42
|
+
gpuDevice?: GpuDevice;
|
|
36
43
|
private constructor();
|
|
37
|
-
/** The ABI's defaults (backend-default bitrate, NV12 input). */
|
|
44
|
+
/** The ABI's defaults (backend-default bitrate, NV12 input, CPU-only). */
|
|
38
45
|
static defaults(codec: VideoCodec, width: number, height: number, timeBase: Rational): AutoVideoEncodeConfig;
|
|
39
46
|
toAbi(): RawRational;
|
|
40
47
|
}
|
|
48
|
+
export declare function checkPipeline(status: number, noBackendError?: new (status: number, message: string) => MediawayError): void;
|
|
41
49
|
/**
|
|
42
50
|
* A single-use encode session. `finish()` is terminal — it consumes the
|
|
43
51
|
* session; `close()` after it is a no-op (idempotent), but is required to free
|
|
@@ -52,6 +60,22 @@ export declare class EncodeSession {
|
|
|
52
60
|
private finished;
|
|
53
61
|
constructor(config: AutoVideoEncodeConfig, encoderHandle: unknown);
|
|
54
62
|
writeFrame(frame: VideoFrame): Promise<void>;
|
|
63
|
+
/**
|
|
64
|
+
* Poll one frame from `capture` (a `CameraSession`, `@mediaway/device`) and
|
|
65
|
+
* push it straight into this session — no intermediate `VideoFrame`, no
|
|
66
|
+
* extra copy (`adr/pipeline/0005-capture-encode-bridge-c-abi.md`). Returns
|
|
67
|
+
* `false` when nothing was ready yet (not an error) — mirrors
|
|
68
|
+
* `CameraSession.pollFrame()`'s own null-vs-error shape.
|
|
69
|
+
*/
|
|
70
|
+
writeFrameFromCameraCapture(capture: CameraSession): Promise<boolean>;
|
|
71
|
+
/**
|
|
72
|
+
* Same shape as `writeFrameFromCameraCapture`, for a `ScreenSession`
|
|
73
|
+
* (`@mediaway/device`) instead of Camera. GPU frames pass through
|
|
74
|
+
* Zero-Copy: the polled frame's GPU handle moves straight into the encoder
|
|
75
|
+
* with no CPU copy — this is the real way to consume Screen frames (see
|
|
76
|
+
* `ScreenSession.pollFrame()`'s own doc: it never copies pixels out).
|
|
77
|
+
*/
|
|
78
|
+
writeFrameFromDesktopCapture(capture: ScreenSession): Promise<boolean>;
|
|
55
79
|
/** Flush the encoder + muxer and return the complete fMP4 bytes. Terminal. */
|
|
56
80
|
finish(): Promise<Buffer>;
|
|
57
81
|
/** Idempotent; no-op after finish(). Frees the native handle on error paths. */
|
package/dist/index.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { MwAudioEncodeConfig, MwAudioFrameView, MwAudioPacket, MwAudioStreamInfo, MwPipelineFrame, MwRational, pipeline, copyBytes, } from "@mediaway/ffi";
|
|
11
11
|
import { MediawayError } from "@mediaway/container";
|
|
12
|
+
import { NATIVE_HANDLE } from "@mediaway/device";
|
|
12
13
|
export { MediawayError } from "@mediaway/container";
|
|
13
14
|
/** No encode backend is openable for this config — expected on machines
|
|
14
15
|
* without a usable encoder; catch it and exit gracefully. */
|
|
@@ -21,13 +22,19 @@ export class AutoVideoEncodeConfig {
|
|
|
21
22
|
timeBase;
|
|
22
23
|
bitrateBps; // 0 / undefined = backend default
|
|
23
24
|
pixelFormat;
|
|
25
|
+
/** Zero-Copy GPU input: the same device a Screen capture session was opened
|
|
26
|
+
* with (or any `GpuDevice`) — set together with `pixelFormat: "bgra8"` for
|
|
27
|
+
* `writeFrameFromDesktopCapture()`'s GPU frames (DXGI delivers BGRA8, not
|
|
28
|
+
* the NV12 default). Leave unset for CPU input (Camera, or manual
|
|
29
|
+
* `writeFrame()` with CPU-storage frames). */
|
|
30
|
+
gpuDevice;
|
|
24
31
|
constructor(codec, width, height, timeBase) {
|
|
25
32
|
this.codec = codec;
|
|
26
33
|
this.width = width;
|
|
27
34
|
this.height = height;
|
|
28
35
|
this.timeBase = timeBase;
|
|
29
36
|
}
|
|
30
|
-
/** The ABI's defaults (backend-default bitrate, NV12 input). */
|
|
37
|
+
/** The ABI's defaults (backend-default bitrate, NV12 input, CPU-only). */
|
|
31
38
|
static defaults(codec, width, height, timeBase) {
|
|
32
39
|
return new AutoVideoEncodeConfig(codec, width, height, timeBase);
|
|
33
40
|
}
|
|
@@ -39,14 +46,16 @@ export class AutoVideoEncodeConfig {
|
|
|
39
46
|
const fmtMap = { nv12: 0, i420: 1, bgra8: 2, rgba8: 3, yuy2: 4 };
|
|
40
47
|
if (this.pixelFormat !== undefined)
|
|
41
48
|
raw.pixel_format = fmtMap[this.pixelFormat] ?? 0;
|
|
49
|
+
if (this.gpuDevice !== undefined)
|
|
50
|
+
raw.gpu_device = this.gpuDevice[NATIVE_HANDLE]();
|
|
42
51
|
return raw;
|
|
43
52
|
}
|
|
44
53
|
}
|
|
45
|
-
function checkPipeline(status) {
|
|
54
|
+
export function checkPipeline(status, noBackendError = EncoderUnavailableError) {
|
|
46
55
|
if (status === 0)
|
|
47
56
|
return;
|
|
48
57
|
if (status === 3)
|
|
49
|
-
throw new
|
|
58
|
+
throw new noBackendError(status, "no backend compiled in or openable");
|
|
50
59
|
const names = {
|
|
51
60
|
1: "invalid argument",
|
|
52
61
|
2: "handle poisoned by an earlier panic",
|
|
@@ -59,6 +68,8 @@ function checkPipeline(status) {
|
|
|
59
68
|
10: "malformed container data",
|
|
60
69
|
11: "unknown error",
|
|
61
70
|
12: "internal panic (handle poisoned)",
|
|
71
|
+
13: "decoder backend OS/API failure",
|
|
72
|
+
14: "decode session already finished or not open",
|
|
62
73
|
};
|
|
63
74
|
throw new MediawayError(status, names[status] ?? "unknown pipeline error");
|
|
64
75
|
}
|
|
@@ -98,6 +109,30 @@ export class EncodeSession {
|
|
|
98
109
|
};
|
|
99
110
|
checkPipeline(pipeline.sessionWriteFrame(this.handle, raw));
|
|
100
111
|
}
|
|
112
|
+
/**
|
|
113
|
+
* Poll one frame from `capture` (a `CameraSession`, `@mediaway/device`) and
|
|
114
|
+
* push it straight into this session — no intermediate `VideoFrame`, no
|
|
115
|
+
* extra copy (`adr/pipeline/0005-capture-encode-bridge-c-abi.md`). Returns
|
|
116
|
+
* `false` when nothing was ready yet (not an error) — mirrors
|
|
117
|
+
* `CameraSession.pollFrame()`'s own null-vs-error shape.
|
|
118
|
+
*/
|
|
119
|
+
async writeFrameFromCameraCapture(capture) {
|
|
120
|
+
const wrote = [false];
|
|
121
|
+
checkPipeline(pipeline.sessionWriteFrameFromCameraCapture(this.handle, capture[NATIVE_HANDLE](), wrote));
|
|
122
|
+
return wrote[0];
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Same shape as `writeFrameFromCameraCapture`, for a `ScreenSession`
|
|
126
|
+
* (`@mediaway/device`) instead of Camera. GPU frames pass through
|
|
127
|
+
* Zero-Copy: the polled frame's GPU handle moves straight into the encoder
|
|
128
|
+
* with no CPU copy — this is the real way to consume Screen frames (see
|
|
129
|
+
* `ScreenSession.pollFrame()`'s own doc: it never copies pixels out).
|
|
130
|
+
*/
|
|
131
|
+
async writeFrameFromDesktopCapture(capture) {
|
|
132
|
+
const wrote = [false];
|
|
133
|
+
checkPipeline(pipeline.sessionWriteFrameFromDesktopCapture(this.handle, capture[NATIVE_HANDLE](), wrote));
|
|
134
|
+
return wrote[0];
|
|
135
|
+
}
|
|
101
136
|
/** Flush the encoder + muxer and return the complete fMP4 bytes. Terminal. */
|
|
102
137
|
async finish() {
|
|
103
138
|
const outData = [null];
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mediaway/encoder",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.1.6",
|
|
4
|
+
"description": "Rust-based, lightweight, fast — automatic hardware video encoding to fragmented MP4 for Node.js (NVENC / Vulkan Video / QuickSync)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
@@ -26,8 +26,9 @@
|
|
|
26
26
|
"build": "tsc -p tsconfig.json"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@mediaway/ffi": "0.1.0",
|
|
30
|
-
"@mediaway/container": "0.1.0"
|
|
29
|
+
"@mediaway/ffi": "^0.1.0",
|
|
30
|
+
"@mediaway/container": "^0.1.0",
|
|
31
|
+
"@mediaway/device": "^0.1.0"
|
|
31
32
|
},
|
|
32
33
|
"devDependencies": {
|
|
33
34
|
"typescript": "^5.6.0"
|