@mediaway/encoder 0.1.3 → 0.1.5
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/dist/decode.d.ts +106 -0
- package/dist/decode.js +165 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +5 -2
- package/package.json +4 -4
package/dist/decode.d.ts
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @mediaway/encoder — pipeline capability: auto video decode + Opus audio decode.
|
|
3
|
+
*
|
|
4
|
+
* Wraps the mediaway-ffi C ABI's decode sessions (adr/0004-auto-decode-c-abi.md,
|
|
5
|
+
* adr/pipeline/0006-audio-decode-c-abi.md) — the same "C ABI real, no
|
|
6
|
+
* language binding wired" gap the container format series closed for
|
|
7
|
+
* mux/demux, closed here for decode. Both sessions mirror
|
|
8
|
+
* `openAutoEncoder()`/`AudioEncoder`'s single-step shape (the handle IS the
|
|
9
|
+
* decoder, no consumption trap); `NO_BACKEND` throws
|
|
10
|
+
* `DecoderUnavailableError`, an expected/graceful outcome.
|
|
11
|
+
*/
|
|
12
|
+
import { MediawayError, type Rational } from "@mediaway/container";
|
|
13
|
+
import { type PixelFormat, type VideoCodec } from "./index.js";
|
|
14
|
+
/** No decoder backend is openable for this config — expected on machines
|
|
15
|
+
* without a usable decoder; catch it and exit gracefully. */
|
|
16
|
+
export declare class DecoderUnavailableError extends MediawayError {
|
|
17
|
+
}
|
|
18
|
+
export interface VideoDecodeConfig {
|
|
19
|
+
codec: VideoCodec;
|
|
20
|
+
/** Expected; may be refined from the bitstream. */
|
|
21
|
+
width: number;
|
|
22
|
+
height: number;
|
|
23
|
+
timeBase: Rational;
|
|
24
|
+
/** Preferred output format when the backend converts. */
|
|
25
|
+
pixelFormat?: PixelFormat;
|
|
26
|
+
/** AVCC / SPS-PPS codec config, required at open time — not supplied via
|
|
27
|
+
* the first pushed packet (adr/0004 §1: the muxer-track analogy does not
|
|
28
|
+
* hold for the wrapped decoder). */
|
|
29
|
+
extraData?: Buffer;
|
|
30
|
+
}
|
|
31
|
+
/** Input to `DecodeSession.pushPacket`/`AudioDecodeSession.pushPacket` — a
|
|
32
|
+
* pipeline-scoped packet view, distinct from `@mediaway/container`'s
|
|
33
|
+
* `Packet` (adr/pipeline/0006 §4: shared by both video and audio decode). */
|
|
34
|
+
export interface DecodePacket {
|
|
35
|
+
pts: number;
|
|
36
|
+
dts?: number;
|
|
37
|
+
duration?: number;
|
|
38
|
+
keyframe?: boolean;
|
|
39
|
+
/** For audio: an empty payload is Opus's packet-loss-concealment hint for
|
|
40
|
+
* a lost frame, not an error — pass it whenever a frame is known lost. */
|
|
41
|
+
payload: Buffer;
|
|
42
|
+
}
|
|
43
|
+
export interface DecodedVideoFrame {
|
|
44
|
+
pts: number;
|
|
45
|
+
duration: number;
|
|
46
|
+
width: number;
|
|
47
|
+
height: number;
|
|
48
|
+
pixelFormat: PixelFormat;
|
|
49
|
+
data: Buffer;
|
|
50
|
+
}
|
|
51
|
+
export interface DecodedAudioFrame {
|
|
52
|
+
pts: number;
|
|
53
|
+
duration: number;
|
|
54
|
+
sampleRate: number;
|
|
55
|
+
channels: number;
|
|
56
|
+
data: Buffer;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The best available video decode session for a config — the handle IS the
|
|
60
|
+
* decoder (single-step open, no consumption trap, mirrors `openAutoEncoder`'s
|
|
61
|
+
* `NO_BACKEND` handling). CPU output only (GPU decode output is deferred,
|
|
62
|
+
* adr/0004 §1/§5).
|
|
63
|
+
*/
|
|
64
|
+
export declare class DecodeSession {
|
|
65
|
+
private handle;
|
|
66
|
+
private constructor();
|
|
67
|
+
/** Open the best available video decoder for `config`. Throws
|
|
68
|
+
* DecoderUnavailableError when no decode backend exists on this machine. */
|
|
69
|
+
static open(config: VideoDecodeConfig): Promise<DecodeSession>;
|
|
70
|
+
/** Push one compressed packet. May produce zero or more frames (drain via
|
|
71
|
+
* `pollFrame()`). */
|
|
72
|
+
pushPacket(packet: DecodePacket): Promise<void>;
|
|
73
|
+
/** Pull the next decoded frame, if one is ready. null is a valid "nothing
|
|
74
|
+
* ready" result, not an error. */
|
|
75
|
+
pollFrame(): Promise<DecodedVideoFrame | null>;
|
|
76
|
+
/** Signal end of input; drain the remaining frames with pollFrame(). */
|
|
77
|
+
flush(): Promise<void>;
|
|
78
|
+
/** Always safe — no handle-consumption trap on this surface. */
|
|
79
|
+
close(): void;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* An Opus audio decode session — the handle IS the decoder (adr/pipeline/0006,
|
|
83
|
+
* mirrors `DecodeSession`'s video shape; no muxer to wire, no consumption
|
|
84
|
+
* trap). Cross-platform (mediaway-sw, no OS dependency), unlike
|
|
85
|
+
* `DecodeSession`'s Windows-only WMF backend.
|
|
86
|
+
*/
|
|
87
|
+
export declare class AudioDecodeSession {
|
|
88
|
+
readonly sampleRate: number;
|
|
89
|
+
readonly channels: number;
|
|
90
|
+
private handle;
|
|
91
|
+
private constructor();
|
|
92
|
+
/** Open an Opus decode session. Throws DecoderUnavailableError when no
|
|
93
|
+
* decode backend exists on this machine. */
|
|
94
|
+
static open(sampleRate: number, channels: number, timeBase: Rational): Promise<AudioDecodeSession>;
|
|
95
|
+
/** Push one compressed Opus packet. An empty payload is Opus's
|
|
96
|
+
* packet-loss-concealment hint for a lost frame, not an error. May produce
|
|
97
|
+
* zero or more frames (drain via pollFrame()). */
|
|
98
|
+
pushPacket(packet: DecodePacket): Promise<void>;
|
|
99
|
+
/** Pull the next decoded PCM frame, if one is ready. null is a valid
|
|
100
|
+
* "nothing ready" result, not an error. */
|
|
101
|
+
pollFrame(): Promise<DecodedAudioFrame | null>;
|
|
102
|
+
/** Signal end of input; drain the remaining frames with pollFrame(). */
|
|
103
|
+
flush(): Promise<void>;
|
|
104
|
+
/** Always safe — no handle-consumption trap on this surface. */
|
|
105
|
+
close(): void;
|
|
106
|
+
}
|
package/dist/decode.js
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @mediaway/encoder — pipeline capability: auto video decode + Opus audio decode.
|
|
3
|
+
*
|
|
4
|
+
* Wraps the mediaway-ffi C ABI's decode sessions (adr/0004-auto-decode-c-abi.md,
|
|
5
|
+
* adr/pipeline/0006-audio-decode-c-abi.md) — the same "C ABI real, no
|
|
6
|
+
* language binding wired" gap the container format series closed for
|
|
7
|
+
* mux/demux, closed here for decode. Both sessions mirror
|
|
8
|
+
* `openAutoEncoder()`/`AudioEncoder`'s single-step shape (the handle IS the
|
|
9
|
+
* decoder, no consumption trap); `NO_BACKEND` throws
|
|
10
|
+
* `DecoderUnavailableError`, an expected/graceful outcome.
|
|
11
|
+
*/
|
|
12
|
+
import { pipeline, copyBytes, } from "@mediaway/ffi";
|
|
13
|
+
import { MediawayError } from "@mediaway/container";
|
|
14
|
+
import { checkPipeline } from "./index.js";
|
|
15
|
+
/** No decoder backend is openable for this config — expected on machines
|
|
16
|
+
* without a usable decoder; catch it and exit gracefully. */
|
|
17
|
+
export class DecoderUnavailableError extends MediawayError {
|
|
18
|
+
}
|
|
19
|
+
const codecMap = { h264: 0, hevc: 1, av1: 2, vp9: 3 };
|
|
20
|
+
const pixelFmtMap = { nv12: 0, i420: 1, bgra8: 2, rgba8: 3, yuy2: 4 };
|
|
21
|
+
const pixelFmtRev = ["nv12", "i420", "bgra8", "rgba8", "yuy2"];
|
|
22
|
+
/**
|
|
23
|
+
* The best available video decode session for a config — the handle IS the
|
|
24
|
+
* decoder (single-step open, no consumption trap, mirrors `openAutoEncoder`'s
|
|
25
|
+
* `NO_BACKEND` handling). CPU output only (GPU decode output is deferred,
|
|
26
|
+
* adr/0004 §1/§5).
|
|
27
|
+
*/
|
|
28
|
+
export class DecodeSession {
|
|
29
|
+
handle;
|
|
30
|
+
constructor(handle) {
|
|
31
|
+
this.handle = handle;
|
|
32
|
+
}
|
|
33
|
+
/** Open the best available video decoder for `config`. Throws
|
|
34
|
+
* DecoderUnavailableError when no decode backend exists on this machine. */
|
|
35
|
+
static async open(config) {
|
|
36
|
+
const extraData = config.extraData ?? Buffer.alloc(0);
|
|
37
|
+
const raw = pipeline.decodeConfigNew(codecMap[config.codec] ?? 0, config.width, config.height, { num: BigInt(config.timeBase.num), den: config.timeBase.den }, extraData.length > 0 ? extraData : null, extraData.length);
|
|
38
|
+
if (config.pixelFormat !== undefined)
|
|
39
|
+
raw.pixel_format = pixelFmtMap[config.pixelFormat] ?? 0;
|
|
40
|
+
const out = [null];
|
|
41
|
+
checkPipeline(pipeline.decodeSessionOpen(raw, out), DecoderUnavailableError);
|
|
42
|
+
if (!out[0])
|
|
43
|
+
throw new MediawayError(11, "decode session open returned no handle");
|
|
44
|
+
return new DecodeSession(out[0]);
|
|
45
|
+
}
|
|
46
|
+
/** Push one compressed packet. May produce zero or more frames (drain via
|
|
47
|
+
* `pollFrame()`). */
|
|
48
|
+
async pushPacket(packet) {
|
|
49
|
+
const raw = {
|
|
50
|
+
stream_id: 0,
|
|
51
|
+
pts: BigInt(packet.pts),
|
|
52
|
+
dts: BigInt(packet.dts ?? packet.pts),
|
|
53
|
+
duration: BigInt(packet.duration ?? 0),
|
|
54
|
+
is_keyframe: packet.keyframe ?? false,
|
|
55
|
+
is_discard: false,
|
|
56
|
+
payload: packet.payload,
|
|
57
|
+
payload_len: packet.payload.length,
|
|
58
|
+
};
|
|
59
|
+
checkPipeline(pipeline.decodeSessionPushPacket(this.handle, raw));
|
|
60
|
+
}
|
|
61
|
+
/** Pull the next decoded frame, if one is ready. null is a valid "nothing
|
|
62
|
+
* ready" result, not an error. */
|
|
63
|
+
async pollFrame() {
|
|
64
|
+
const raw = {};
|
|
65
|
+
const has = [false];
|
|
66
|
+
checkPipeline(pipeline.decodeSessionPollFrame(this.handle, raw, has));
|
|
67
|
+
if (!has[0])
|
|
68
|
+
return null;
|
|
69
|
+
const data = copyBytes(raw.data, raw.data_len);
|
|
70
|
+
pipeline.decodedVideoFrameFree(raw);
|
|
71
|
+
return {
|
|
72
|
+
pts: Number(raw.pts),
|
|
73
|
+
duration: Number(raw.duration),
|
|
74
|
+
width: raw.width,
|
|
75
|
+
height: raw.height,
|
|
76
|
+
pixelFormat: pixelFmtRev[raw.pixel_format] ?? "nv12",
|
|
77
|
+
data,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/** Signal end of input; drain the remaining frames with pollFrame(). */
|
|
81
|
+
async flush() {
|
|
82
|
+
checkPipeline(pipeline.decodeSessionFlush(this.handle));
|
|
83
|
+
}
|
|
84
|
+
/** Always safe — no handle-consumption trap on this surface. */
|
|
85
|
+
close() {
|
|
86
|
+
if (this.handle) {
|
|
87
|
+
pipeline.decodeSessionClose(this.handle);
|
|
88
|
+
this.handle = null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* An Opus audio decode session — the handle IS the decoder (adr/pipeline/0006,
|
|
94
|
+
* mirrors `DecodeSession`'s video shape; no muxer to wire, no consumption
|
|
95
|
+
* trap). Cross-platform (mediaway-sw, no OS dependency), unlike
|
|
96
|
+
* `DecodeSession`'s Windows-only WMF backend.
|
|
97
|
+
*/
|
|
98
|
+
export class AudioDecodeSession {
|
|
99
|
+
sampleRate;
|
|
100
|
+
channels;
|
|
101
|
+
handle;
|
|
102
|
+
constructor(handle, sampleRate, channels) {
|
|
103
|
+
this.handle = handle;
|
|
104
|
+
this.sampleRate = sampleRate;
|
|
105
|
+
this.channels = channels;
|
|
106
|
+
}
|
|
107
|
+
/** Open an Opus decode session. Throws DecoderUnavailableError when no
|
|
108
|
+
* decode backend exists on this machine. */
|
|
109
|
+
static async open(sampleRate, channels, timeBase) {
|
|
110
|
+
const raw = pipeline.audioDecodeConfigOpus(sampleRate, channels, {
|
|
111
|
+
num: BigInt(timeBase.num),
|
|
112
|
+
den: timeBase.den,
|
|
113
|
+
});
|
|
114
|
+
const out = [null];
|
|
115
|
+
checkPipeline(pipeline.audioDecodeSessionOpen(raw, out), DecoderUnavailableError);
|
|
116
|
+
if (!out[0])
|
|
117
|
+
throw new MediawayError(11, "audio decode session open returned no handle");
|
|
118
|
+
return new AudioDecodeSession(out[0], sampleRate, channels);
|
|
119
|
+
}
|
|
120
|
+
/** Push one compressed Opus packet. An empty payload is Opus's
|
|
121
|
+
* packet-loss-concealment hint for a lost frame, not an error. May produce
|
|
122
|
+
* zero or more frames (drain via pollFrame()). */
|
|
123
|
+
async pushPacket(packet) {
|
|
124
|
+
const raw = {
|
|
125
|
+
stream_id: 0,
|
|
126
|
+
pts: BigInt(packet.pts),
|
|
127
|
+
dts: BigInt(packet.dts ?? packet.pts),
|
|
128
|
+
duration: BigInt(packet.duration ?? 0),
|
|
129
|
+
is_keyframe: packet.keyframe ?? false,
|
|
130
|
+
is_discard: false,
|
|
131
|
+
payload: packet.payload,
|
|
132
|
+
payload_len: packet.payload.length,
|
|
133
|
+
};
|
|
134
|
+
checkPipeline(pipeline.audioDecodeSessionPushPacket(this.handle, raw));
|
|
135
|
+
}
|
|
136
|
+
/** Pull the next decoded PCM frame, if one is ready. null is a valid
|
|
137
|
+
* "nothing ready" result, not an error. */
|
|
138
|
+
async pollFrame() {
|
|
139
|
+
const raw = {};
|
|
140
|
+
const has = [false];
|
|
141
|
+
checkPipeline(pipeline.audioDecodeSessionPollFrame(this.handle, raw, has));
|
|
142
|
+
if (!has[0])
|
|
143
|
+
return null;
|
|
144
|
+
const data = copyBytes(raw.data, raw.data_len);
|
|
145
|
+
pipeline.decodedAudioFrameFree(raw);
|
|
146
|
+
return {
|
|
147
|
+
pts: Number(raw.pts),
|
|
148
|
+
duration: Number(raw.duration),
|
|
149
|
+
sampleRate: raw.sample_rate,
|
|
150
|
+
channels: raw.channels,
|
|
151
|
+
data,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
/** Signal end of input; drain the remaining frames with pollFrame(). */
|
|
155
|
+
async flush() {
|
|
156
|
+
checkPipeline(pipeline.audioDecodeSessionFlush(this.handle));
|
|
157
|
+
}
|
|
158
|
+
/** Always safe — no handle-consumption trap on this surface. */
|
|
159
|
+
close() {
|
|
160
|
+
if (this.handle) {
|
|
161
|
+
pipeline.audioDecodeSessionClose(this.handle);
|
|
162
|
+
this.handle = null;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -38,6 +38,7 @@ export declare class AutoVideoEncodeConfig {
|
|
|
38
38
|
static defaults(codec: VideoCodec, width: number, height: number, timeBase: Rational): AutoVideoEncodeConfig;
|
|
39
39
|
toAbi(): RawRational;
|
|
40
40
|
}
|
|
41
|
+
export declare function checkPipeline(status: number, noBackendError?: new (status: number, message: string) => MediawayError): void;
|
|
41
42
|
/**
|
|
42
43
|
* A single-use encode session. `finish()` is terminal — it consumes the
|
|
43
44
|
* session; `close()` after it is a no-op (idempotent), but is required to free
|
|
@@ -128,3 +129,4 @@ export declare class AudioEncoder {
|
|
|
128
129
|
/** Always safe — no handle-consumption trap on this surface (adr/0003). */
|
|
129
130
|
close(): void;
|
|
130
131
|
}
|
|
132
|
+
export * from "./decode.js";
|
package/dist/index.js
CHANGED
|
@@ -42,11 +42,11 @@ export class AutoVideoEncodeConfig {
|
|
|
42
42
|
return raw;
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
|
-
function checkPipeline(status) {
|
|
45
|
+
export function checkPipeline(status, noBackendError = EncoderUnavailableError) {
|
|
46
46
|
if (status === 0)
|
|
47
47
|
return;
|
|
48
48
|
if (status === 3)
|
|
49
|
-
throw new
|
|
49
|
+
throw new noBackendError(status, "no backend compiled in or openable");
|
|
50
50
|
const names = {
|
|
51
51
|
1: "invalid argument",
|
|
52
52
|
2: "handle poisoned by an earlier panic",
|
|
@@ -59,6 +59,8 @@ function checkPipeline(status) {
|
|
|
59
59
|
10: "malformed container data",
|
|
60
60
|
11: "unknown error",
|
|
61
61
|
12: "internal panic (handle poisoned)",
|
|
62
|
+
13: "decoder backend OS/API failure",
|
|
63
|
+
14: "decode session already finished or not open",
|
|
62
64
|
};
|
|
63
65
|
throw new MediawayError(status, names[status] ?? "unknown pipeline error");
|
|
64
66
|
}
|
|
@@ -224,3 +226,4 @@ export class AudioEncoder {
|
|
|
224
226
|
}
|
|
225
227
|
}
|
|
226
228
|
}
|
|
229
|
+
export * from "./decode.js";
|
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.5",
|
|
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,8 @@
|
|
|
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
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"typescript": "^5.6.0"
|