@mediaway/ffi 0.1.6 → 0.2.1
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/container-replay.d.ts +125 -0
- package/dist/container-replay.js +102 -0
- package/dist/index.d.ts +58 -9
- package/dist/index.js +32 -62
- package/dist/loader.d.ts +11 -0
- package/dist/loader.js +74 -0
- package/dist/pipeline-v7.d.ts +48 -0
- package/dist/pipeline-v7.js +54 -0
- package/dist/rational.d.ts +8 -0
- package/dist/rational.js +12 -0
- package/native/darwin-arm64/libmediaway_ffi.dylib +0 -0
- package/native/darwin-x64/libmediaway_ffi.dylib +0 -0
- package/native/linux-x64/libmediaway_ffi.so +0 -0
- package/native/{mediaway_ffi.dll → win32-x64/mediaway_ffi.dll} +0 -0
- package/package.json +1 -1
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Replay ring + MP4 payload placements (adr/container/0009-replay-ring-c-abi.md).
|
|
3
|
+
*
|
|
4
|
+
* Split out of index.ts, which sits under the workspace's 1000-line source cap. This module
|
|
5
|
+
* imports only leaf modules (koffi, loader), never index.ts, so there is no import cycle:
|
|
6
|
+
* index.ts calls {@link createReplayBindings} once, after it has defined `MwRational` and
|
|
7
|
+
* `MwPacketView`, whose koffi names the signatures below refer to.
|
|
8
|
+
*/
|
|
9
|
+
import { type TypeObject } from "koffi";
|
|
10
|
+
/** `mediaway_replay_payload_kind_t`. */
|
|
11
|
+
export declare const REPLAY_PAYLOAD_BYTES = 0;
|
|
12
|
+
export declare const REPLAY_PAYLOAD_STORED = 1;
|
|
13
|
+
/** `mediaway_replay_ring_config_t`. */
|
|
14
|
+
export interface RawReplayRingConfig {
|
|
15
|
+
anchor_stream_id: number;
|
|
16
|
+
anchor_time_base: {
|
|
17
|
+
num: bigint | number;
|
|
18
|
+
den: number;
|
|
19
|
+
};
|
|
20
|
+
window_ms: bigint | number;
|
|
21
|
+
max_bytes: bigint | number;
|
|
22
|
+
payload_kind: number;
|
|
23
|
+
}
|
|
24
|
+
/** `mediaway_packet_meta_t`. */
|
|
25
|
+
export interface RawPacketMeta {
|
|
26
|
+
stream_id: number;
|
|
27
|
+
pts: bigint | number;
|
|
28
|
+
dts: bigint | number;
|
|
29
|
+
duration: bigint | number;
|
|
30
|
+
is_keyframe: boolean;
|
|
31
|
+
is_discard: boolean;
|
|
32
|
+
}
|
|
33
|
+
/** `mediaway_stored_payload_t`. */
|
|
34
|
+
export interface RawStoredPayload {
|
|
35
|
+
file: number;
|
|
36
|
+
offset: bigint | number;
|
|
37
|
+
len: number;
|
|
38
|
+
}
|
|
39
|
+
/** `mediaway_replay_clip_entry_t`. `payload` is BORROWED from the clip. */
|
|
40
|
+
export interface RawReplayClipEntry {
|
|
41
|
+
stream_id: number;
|
|
42
|
+
pts: bigint;
|
|
43
|
+
dts: bigint;
|
|
44
|
+
duration: bigint;
|
|
45
|
+
is_keyframe: boolean;
|
|
46
|
+
is_discard: boolean;
|
|
47
|
+
payload_kind: number;
|
|
48
|
+
payload: unknown;
|
|
49
|
+
payload_len: number;
|
|
50
|
+
stored_file: number;
|
|
51
|
+
stored_offset: bigint;
|
|
52
|
+
stored_len: number;
|
|
53
|
+
}
|
|
54
|
+
/** `mediaway_placement_t`. */
|
|
55
|
+
export interface RawPlacement {
|
|
56
|
+
track_id: number;
|
|
57
|
+
dts: bigint;
|
|
58
|
+
offset: bigint;
|
|
59
|
+
len: number;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Declare the replay structs and bind the functions.
|
|
63
|
+
*
|
|
64
|
+
* `rational` is index.ts's `MwRational` (the struct is registered there once; koffi rejects a
|
|
65
|
+
* second definition under the same name).
|
|
66
|
+
*/
|
|
67
|
+
export declare function createReplayBindings(rational: TypeObject): {
|
|
68
|
+
structs: {
|
|
69
|
+
MwReplayRingConfig: TypeObject;
|
|
70
|
+
MwPacketMeta: TypeObject;
|
|
71
|
+
MwStoredPayload: TypeObject;
|
|
72
|
+
MwReplayClipEntry: TypeObject;
|
|
73
|
+
MwPlacement: TypeObject;
|
|
74
|
+
};
|
|
75
|
+
ringCreate: import("koffi").Callable;
|
|
76
|
+
ringAddStream: import("koffi").Callable;
|
|
77
|
+
ringPush: import("koffi").Callable;
|
|
78
|
+
ringPushStored: import("koffi").Callable;
|
|
79
|
+
ringSpanMs: import("koffi").Callable;
|
|
80
|
+
ringClipLast: import("koffi").Callable;
|
|
81
|
+
ringClose: import("koffi").Callable;
|
|
82
|
+
clipPacketCount: import("koffi").Callable;
|
|
83
|
+
clipDurationMs: import("koffi").Callable;
|
|
84
|
+
clipPacketAt: import("koffi").Callable;
|
|
85
|
+
clipFree: import("koffi").Callable;
|
|
86
|
+
muxerCreateWithPlacements: import("koffi").Callable;
|
|
87
|
+
muxerPollPlacements: import("koffi").Callable;
|
|
88
|
+
placementsFree: import("koffi").Callable;
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* `sizeof` and member offsets of the replay structs as koffi lays them out, for the test that
|
|
92
|
+
* pins them against a gcc probe of the real header.
|
|
93
|
+
*/
|
|
94
|
+
export declare function replayLayout(structs: ReturnType<typeof createReplayBindings>["structs"]): {
|
|
95
|
+
replayRingConfig: {
|
|
96
|
+
size: number;
|
|
97
|
+
offsets: {
|
|
98
|
+
[k: string]: number;
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
packetMeta: {
|
|
102
|
+
size: number;
|
|
103
|
+
offsets: {
|
|
104
|
+
[k: string]: number;
|
|
105
|
+
};
|
|
106
|
+
};
|
|
107
|
+
storedPayload: {
|
|
108
|
+
size: number;
|
|
109
|
+
offsets: {
|
|
110
|
+
[k: string]: number;
|
|
111
|
+
};
|
|
112
|
+
};
|
|
113
|
+
replayClipEntry: {
|
|
114
|
+
size: number;
|
|
115
|
+
offsets: {
|
|
116
|
+
[k: string]: number;
|
|
117
|
+
};
|
|
118
|
+
};
|
|
119
|
+
placement: {
|
|
120
|
+
size: number;
|
|
121
|
+
offsets: {
|
|
122
|
+
[k: string]: number;
|
|
123
|
+
};
|
|
124
|
+
};
|
|
125
|
+
};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Replay ring + MP4 payload placements (adr/container/0009-replay-ring-c-abi.md).
|
|
3
|
+
*
|
|
4
|
+
* Split out of index.ts, which sits under the workspace's 1000-line source cap. This module
|
|
5
|
+
* imports only leaf modules (koffi, loader), never index.ts, so there is no import cycle:
|
|
6
|
+
* index.ts calls {@link createReplayBindings} once, after it has defined `MwRational` and
|
|
7
|
+
* `MwPacketView`, whose koffi names the signatures below refer to.
|
|
8
|
+
*/
|
|
9
|
+
import koffi, {} from "koffi";
|
|
10
|
+
import { containerLib } from "./loader.js";
|
|
11
|
+
/** `mediaway_replay_payload_kind_t`. */
|
|
12
|
+
export const REPLAY_PAYLOAD_BYTES = 0;
|
|
13
|
+
export const REPLAY_PAYLOAD_STORED = 1;
|
|
14
|
+
/**
|
|
15
|
+
* Declare the replay structs and bind the functions.
|
|
16
|
+
*
|
|
17
|
+
* `rational` is index.ts's `MwRational` (the struct is registered there once; koffi rejects a
|
|
18
|
+
* second definition under the same name).
|
|
19
|
+
*/
|
|
20
|
+
export function createReplayBindings(rational) {
|
|
21
|
+
const MwReplayRingConfig = koffi.struct("MwReplayRingConfig", {
|
|
22
|
+
anchor_stream_id: "uint32",
|
|
23
|
+
anchor_time_base: rational,
|
|
24
|
+
window_ms: "uint64",
|
|
25
|
+
max_bytes: "uint64",
|
|
26
|
+
payload_kind: "int32",
|
|
27
|
+
});
|
|
28
|
+
const MwPacketMeta = koffi.struct("MwPacketMeta", {
|
|
29
|
+
stream_id: "uint32",
|
|
30
|
+
pts: "int64",
|
|
31
|
+
dts: "int64",
|
|
32
|
+
duration: "uint64",
|
|
33
|
+
is_keyframe: "bool",
|
|
34
|
+
is_discard: "bool",
|
|
35
|
+
});
|
|
36
|
+
const MwStoredPayload = koffi.struct("MwStoredPayload", {
|
|
37
|
+
file: "uint32",
|
|
38
|
+
offset: "uint64",
|
|
39
|
+
len: "uint32",
|
|
40
|
+
});
|
|
41
|
+
const MwReplayClipEntry = koffi.struct("MwReplayClipEntry", {
|
|
42
|
+
stream_id: "uint32",
|
|
43
|
+
pts: "int64",
|
|
44
|
+
dts: "int64",
|
|
45
|
+
duration: "uint64",
|
|
46
|
+
is_keyframe: "bool",
|
|
47
|
+
is_discard: "bool",
|
|
48
|
+
payload_kind: "int32",
|
|
49
|
+
payload: "uint8_t *",
|
|
50
|
+
payload_len: "size_t",
|
|
51
|
+
stored_file: "uint32",
|
|
52
|
+
stored_offset: "uint64",
|
|
53
|
+
stored_len: "uint32",
|
|
54
|
+
});
|
|
55
|
+
const MwPlacement = koffi.struct("MwPlacement", {
|
|
56
|
+
track_id: "uint32",
|
|
57
|
+
dts: "int64",
|
|
58
|
+
offset: "uint64",
|
|
59
|
+
len: "uint32",
|
|
60
|
+
});
|
|
61
|
+
return {
|
|
62
|
+
structs: { MwReplayRingConfig, MwPacketMeta, MwStoredPayload, MwReplayClipEntry, MwPlacement },
|
|
63
|
+
ringCreate: containerLib.func("int mediaway_replay_ring_create(MwReplayRingConfig *config, _Out_ void **out_ring)"),
|
|
64
|
+
ringAddStream: containerLib.func("int mediaway_replay_ring_add_stream(void *ring, uint32_t stream_id, MwRational time_base)"),
|
|
65
|
+
ringPush: containerLib.func("int mediaway_replay_ring_push(void *ring, MwPacketView *packet)"),
|
|
66
|
+
ringPushStored: containerLib.func("int mediaway_replay_ring_push_stored(void *ring, MwPacketMeta *meta, MwStoredPayload *stored)"),
|
|
67
|
+
ringSpanMs: containerLib.func("int mediaway_replay_ring_span_ms(void *ring, _Out_ uint64_t *out_span_ms)"),
|
|
68
|
+
ringClipLast: containerLib.func("int mediaway_replay_ring_clip_last(void *ring, uint64_t span_ms, _Out_ void **out_clip, _Out_ bool *out_has)"),
|
|
69
|
+
ringClose: containerLib.func("void mediaway_replay_ring_close(void *ring)"),
|
|
70
|
+
clipPacketCount: containerLib.func("size_t mediaway_replay_clip_packet_count(void *clip)"),
|
|
71
|
+
clipDurationMs: containerLib.func("int mediaway_replay_clip_duration_ms(void *clip, _Out_ uint64_t *out_ms)"),
|
|
72
|
+
clipPacketAt: containerLib.func("int mediaway_replay_clip_packet_at(void *clip, size_t index, _Out_ MwReplayClipEntry *out_entry)"),
|
|
73
|
+
clipFree: containerLib.func("void mediaway_replay_clip_free(void *clip)"),
|
|
74
|
+
muxerCreateWithPlacements: containerLib.func("void *mediaway_muxer_create_with_placements()"),
|
|
75
|
+
muxerPollPlacements: containerLib.func("int mediaway_muxer_poll_placements(void *muxer, _Out_ MwPlacement **out_rows, _Out_ size_t *out_count)"),
|
|
76
|
+
placementsFree: containerLib.func("void mediaway_placements_free(MwPlacement *rows, size_t count)"),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* `sizeof` and member offsets of the replay structs as koffi lays them out, for the test that
|
|
81
|
+
* pins them against a gcc probe of the real header.
|
|
82
|
+
*/
|
|
83
|
+
export function replayLayout(structs) {
|
|
84
|
+
const describe = (type, members) => ({
|
|
85
|
+
size: koffi.sizeof(type),
|
|
86
|
+
offsets: Object.fromEntries(members.map((m) => [m, koffi.offsetof(type, m)])),
|
|
87
|
+
});
|
|
88
|
+
return {
|
|
89
|
+
replayRingConfig: describe(structs.MwReplayRingConfig, [
|
|
90
|
+
"anchor_stream_id", "anchor_time_base", "window_ms", "max_bytes", "payload_kind",
|
|
91
|
+
]),
|
|
92
|
+
packetMeta: describe(structs.MwPacketMeta, [
|
|
93
|
+
"stream_id", "pts", "dts", "duration", "is_keyframe", "is_discard",
|
|
94
|
+
]),
|
|
95
|
+
storedPayload: describe(structs.MwStoredPayload, ["file", "offset", "len"]),
|
|
96
|
+
replayClipEntry: describe(structs.MwReplayClipEntry, [
|
|
97
|
+
"stream_id", "pts", "dts", "duration", "is_keyframe", "is_discard", "payload_kind",
|
|
98
|
+
"payload", "payload_len", "stored_file", "stored_offset", "stored_len",
|
|
99
|
+
]),
|
|
100
|
+
placement: describe(structs.MwPlacement, ["track_id", "dts", "offset", "len"]),
|
|
101
|
+
};
|
|
102
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -14,11 +14,12 @@
|
|
|
14
14
|
* `_free` — the public packages do this automatically.
|
|
15
15
|
*/
|
|
16
16
|
import { type TypeObject } from "koffi";
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
export
|
|
21
|
-
export
|
|
17
|
+
import { findLibrary, containerLib, pipelineLib, deviceLib } from "./loader.js";
|
|
18
|
+
import { MwRational } from "./rational.js";
|
|
19
|
+
import { replayLayout } from "./container-replay.js";
|
|
20
|
+
export * from "./container-replay.js";
|
|
21
|
+
export { findLibrary, containerLib, pipelineLib, deviceLib, MwRational };
|
|
22
|
+
export * from "./pipeline-v7.js";
|
|
22
23
|
export declare const MwVideoTrackInfo: TypeObject;
|
|
23
24
|
export declare const MwAudioTrackInfo: TypeObject;
|
|
24
25
|
export declare const MwPacketView: TypeObject;
|
|
@@ -47,7 +48,6 @@ export declare const MwAudioStreamInfo: TypeObject;
|
|
|
47
48
|
export declare const MwDecodePacketView: TypeObject;
|
|
48
49
|
export declare const MwAutoVideoDecodeConfig: TypeObject;
|
|
49
50
|
export declare const MwDecodedVideoFrame: TypeObject;
|
|
50
|
-
export declare const MwAudioDecodeConfig: TypeObject;
|
|
51
51
|
export declare const MwDecodedAudioFrame: TypeObject;
|
|
52
52
|
export declare const container: {
|
|
53
53
|
abiVersion: import("koffi").Callable;
|
|
@@ -136,6 +136,13 @@ export declare const container: {
|
|
|
136
136
|
wavParse: import("koffi").Callable;
|
|
137
137
|
};
|
|
138
138
|
export declare const pipeline: {
|
|
139
|
+
sessionWriteFrameFromCameraCapture: import("koffi").Callable;
|
|
140
|
+
sessionWriteFrameFromDesktopCapture: import("koffi").Callable;
|
|
141
|
+
audioDecodeConfigAac: import("koffi").Callable;
|
|
142
|
+
sessionPollBytes: import("koffi").Callable;
|
|
143
|
+
encoderSupportAt: import("koffi").Callable;
|
|
144
|
+
encoderSupportFree: import("koffi").Callable;
|
|
145
|
+
decoderSupport: import("koffi").Callable;
|
|
139
146
|
abiVersion: import("koffi").Callable;
|
|
140
147
|
encConfigNew: import("koffi").Callable;
|
|
141
148
|
encConfigH264: import("koffi").Callable;
|
|
@@ -169,8 +176,6 @@ export declare const pipeline: {
|
|
|
169
176
|
audioDecodeSessionFlush: import("koffi").Callable;
|
|
170
177
|
audioDecodeSessionClose: import("koffi").Callable;
|
|
171
178
|
decodedAudioFrameFree: import("koffi").Callable;
|
|
172
|
-
sessionWriteFrameFromCameraCapture: import("koffi").Callable;
|
|
173
|
-
sessionWriteFrameFromDesktopCapture: import("koffi").Callable;
|
|
174
179
|
};
|
|
175
180
|
export declare const device: {
|
|
176
181
|
abiVersion: import("koffi").Callable;
|
|
@@ -183,6 +188,8 @@ export declare const device: {
|
|
|
183
188
|
cameraClose: import("koffi").Callable;
|
|
184
189
|
cameraFrameFree: import("koffi").Callable;
|
|
185
190
|
desktopConfigScreen: import("koffi").Callable;
|
|
191
|
+
desktopConfigWindow: import("koffi").Callable;
|
|
192
|
+
desktopBorderHidden: import("koffi").Callable;
|
|
186
193
|
desktopOpen: import("koffi").Callable;
|
|
187
194
|
desktopGeometry: import("koffi").Callable;
|
|
188
195
|
desktopPollFrame: import("koffi").Callable;
|
|
@@ -201,6 +208,31 @@ export declare const device: {
|
|
|
201
208
|
gpuDeviceHandle: import("koffi").Callable;
|
|
202
209
|
gpuDeviceClose: import("koffi").Callable;
|
|
203
210
|
};
|
|
211
|
+
export declare const replay: {
|
|
212
|
+
structs: {
|
|
213
|
+
MwReplayRingConfig: TypeObject;
|
|
214
|
+
MwPacketMeta: TypeObject;
|
|
215
|
+
MwStoredPayload: TypeObject;
|
|
216
|
+
MwReplayClipEntry: TypeObject;
|
|
217
|
+
MwPlacement: TypeObject;
|
|
218
|
+
};
|
|
219
|
+
ringCreate: import("koffi").Callable;
|
|
220
|
+
ringAddStream: import("koffi").Callable;
|
|
221
|
+
ringPush: import("koffi").Callable;
|
|
222
|
+
ringPushStored: import("koffi").Callable;
|
|
223
|
+
ringSpanMs: import("koffi").Callable;
|
|
224
|
+
ringClipLast: import("koffi").Callable;
|
|
225
|
+
ringClose: import("koffi").Callable;
|
|
226
|
+
clipPacketCount: import("koffi").Callable;
|
|
227
|
+
clipDurationMs: import("koffi").Callable;
|
|
228
|
+
clipPacketAt: import("koffi").Callable;
|
|
229
|
+
clipFree: import("koffi").Callable;
|
|
230
|
+
muxerCreateWithPlacements: import("koffi").Callable;
|
|
231
|
+
muxerPollPlacements: import("koffi").Callable;
|
|
232
|
+
placementsFree: import("koffi").Callable;
|
|
233
|
+
};
|
|
234
|
+
/** koffi's sizeof/offsetof for the replay structs — pinned by test/replay-ring.test.ts. */
|
|
235
|
+
export declare const replayStructLayout: () => ReturnType<typeof replayLayout>;
|
|
204
236
|
/** Copy `len` bytes out of a koffi pointer/opaque handle. */
|
|
205
237
|
export declare function copyBytes(ptr: unknown, len: number): Buffer;
|
|
206
238
|
/**
|
|
@@ -337,6 +369,21 @@ export interface RawPipelineFrame {
|
|
|
337
369
|
}
|
|
338
370
|
/** `mediaway_gpu_device_handle_t` (Screen capture / GPU-input encode input,
|
|
339
371
|
* mediaway_gpu_device_handle()'s output). */
|
|
372
|
+
export interface RawDesktopConfig {
|
|
373
|
+
source_kind: number;
|
|
374
|
+
source_index: number;
|
|
375
|
+
time_base: RawRational;
|
|
376
|
+
gpu_device: RawGpuDeviceHandle;
|
|
377
|
+
window_handle: bigint | number;
|
|
378
|
+
cursor: number;
|
|
379
|
+
border: number;
|
|
380
|
+
dimensions: number;
|
|
381
|
+
region_x: number;
|
|
382
|
+
region_y: number;
|
|
383
|
+
region_width: number;
|
|
384
|
+
region_height: number;
|
|
385
|
+
region_enabled: boolean;
|
|
386
|
+
}
|
|
340
387
|
export interface RawGpuDeviceHandle {
|
|
341
388
|
kind: number;
|
|
342
389
|
native: number | bigint;
|
|
@@ -417,12 +464,14 @@ export interface RawDecodedVideoFrame {
|
|
|
417
464
|
data: unknown;
|
|
418
465
|
data_len: number;
|
|
419
466
|
}
|
|
420
|
-
/** `mediaway_audio_decode_config_t` (Opus
|
|
467
|
+
/** `mediaway_audio_decode_config_t` (Opus or AAC; AAC needs `extra_data`, the ASC). */
|
|
421
468
|
export interface RawAudioDecodeConfig {
|
|
422
469
|
codec: number;
|
|
423
470
|
sample_rate: number;
|
|
424
471
|
channels: number;
|
|
425
472
|
time_base: RawRational;
|
|
473
|
+
extra_data: unknown;
|
|
474
|
+
extra_data_len: number;
|
|
426
475
|
}
|
|
427
476
|
/** `mediaway_decoded_audio_frame_t` (audio decode session output; always F32). */
|
|
428
477
|
export interface RawDecodedAudioFrame {
|
package/dist/index.js
CHANGED
|
@@ -14,63 +14,17 @@
|
|
|
14
14
|
* `_free` — the public packages do this automatically.
|
|
15
15
|
*/
|
|
16
16
|
import koffi, {} from "koffi";
|
|
17
|
-
import
|
|
18
|
-
import
|
|
19
|
-
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
28
|
-
const repoRoot = path.resolve(here, "..", "..", "..", "..", "..");
|
|
29
|
-
const searchDirs = [
|
|
30
|
-
process.env.MEDIAWAY_FFI_DIR ?? "",
|
|
31
|
-
path.resolve(here, "..", "native"),
|
|
32
|
-
path.join(repoRoot, "target", "x86_64-pc-windows-gnu", "debug"),
|
|
33
|
-
path.join(repoRoot, "target", "debug"),
|
|
34
|
-
process.cwd(),
|
|
35
|
-
];
|
|
36
|
-
export function findLibrary(name) {
|
|
37
|
-
for (const dir of searchDirs) {
|
|
38
|
-
if (!dir)
|
|
39
|
-
continue;
|
|
40
|
-
const candidate = path.join(dir, name);
|
|
41
|
-
if (fs.existsSync(candidate))
|
|
42
|
-
return candidate;
|
|
43
|
-
}
|
|
44
|
-
throw new Error(`cannot find ${name}; set $MEDIAWAY_FFI_DIR or build the -ffi crates`);
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* cdylib filename Cargo produces for this platform. Windows and Linux only —
|
|
48
|
-
* the workspace's own hardware/CI coverage is limited to those two (see
|
|
49
|
-
* docs/ai/wiki/platform/order.md); macOS support is not claimed here since
|
|
50
|
-
* it has never been built or run.
|
|
51
|
-
*/
|
|
52
|
-
function libraryFilename() {
|
|
53
|
-
switch (process.platform) {
|
|
54
|
-
case "win32":
|
|
55
|
-
return "mediaway_ffi.dll";
|
|
56
|
-
case "linux":
|
|
57
|
-
return "libmediaway_ffi.so";
|
|
58
|
-
default:
|
|
59
|
-
throw new Error(`mediaway: unsupported platform ${process.platform} (Windows and Linux only)`);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
function load(name) {
|
|
63
|
-
return koffi.load(findLibrary(name));
|
|
64
|
-
}
|
|
65
|
-
const libraryName = libraryFilename();
|
|
66
|
-
export const containerLib = load(libraryName);
|
|
67
|
-
export const pipelineLib = load(libraryName);
|
|
68
|
-
export const deviceLib = load(libraryName);
|
|
17
|
+
import { findLibrary, containerLib, pipelineLib, deviceLib } from "./loader.js";
|
|
18
|
+
import { MwRational } from "./rational.js";
|
|
19
|
+
// Pipeline ABI v7 (streaming bytes, AAC decode, capability probes) lives in its own module for
|
|
20
|
+
// the 1000-line source cap. Importing it first also registers `MwAudioDecodeConfig`, which the
|
|
21
|
+
// `pipeline` object's prototype strings below name.
|
|
22
|
+
import { pipelineV7 } from "./pipeline-v7.js";
|
|
23
|
+
import { createReplayBindings, replayLayout } from "./container-replay.js";
|
|
24
|
+
export * from "./container-replay.js";
|
|
25
|
+
export { findLibrary, containerLib, pipelineLib, deviceLib, MwRational };
|
|
26
|
+
export * from "./pipeline-v7.js";
|
|
69
27
|
// ── Structs (layouts mirror the headers exactly) ───────────────────────────────
|
|
70
|
-
export const MwRational = koffi.struct("MwRational", {
|
|
71
|
-
num: "uint64",
|
|
72
|
-
den: "uint32",
|
|
73
|
-
});
|
|
74
28
|
export const MwVideoTrackInfo = koffi.struct("MwVideoTrackInfo", {
|
|
75
29
|
id: "uint32",
|
|
76
30
|
codec: "int32",
|
|
@@ -182,11 +136,22 @@ export const MwCameraFrame = koffi.struct("MwCameraFrame", {
|
|
|
182
136
|
data: "uint8_t *",
|
|
183
137
|
data_len: "size_t",
|
|
184
138
|
});
|
|
139
|
+
// Field order and widths mirror `mediaway_desktop_capture_config_t` in device.h
|
|
140
|
+
// exactly (ADR-0005). Every field after `gpu_device` is zero-means-previous-behaviour.
|
|
185
141
|
export const MwDesktopConfig = koffi.struct("MwDesktopConfig", {
|
|
186
142
|
source_kind: "int32",
|
|
187
143
|
source_index: "uint32",
|
|
188
144
|
time_base: MwRational,
|
|
189
145
|
gpu_device: MwGpuDeviceHandle,
|
|
146
|
+
window_handle: "uint64",
|
|
147
|
+
cursor: "int32", // 0 = excluded, 1 = included
|
|
148
|
+
border: "int32", // 0 = shown, 1 = hidden (Window only)
|
|
149
|
+
dimensions: "int32", // 0 = native, 1 = even-cropped (Window only)
|
|
150
|
+
region_x: "uint32",
|
|
151
|
+
region_y: "uint32",
|
|
152
|
+
region_width: "uint32",
|
|
153
|
+
region_height: "uint32",
|
|
154
|
+
region_enabled: "bool",
|
|
190
155
|
});
|
|
191
156
|
export const MwDesktopFrame = koffi.struct("MwDesktopFrame", {
|
|
192
157
|
pts: "int64",
|
|
@@ -295,12 +260,7 @@ export const MwDecodedVideoFrame = koffi.struct("MwDecodedVideoFrame", {
|
|
|
295
260
|
data: "uint8_t *", // owned
|
|
296
261
|
data_len: "size_t",
|
|
297
262
|
});
|
|
298
|
-
|
|
299
|
-
codec: "int32", // Opus only today
|
|
300
|
-
sample_rate: "uint32",
|
|
301
|
-
channels: "uint16",
|
|
302
|
-
time_base: MwRational,
|
|
303
|
-
});
|
|
263
|
+
// `MwAudioDecodeConfig` (Opus + AAC) is defined in pipeline-v7.ts.
|
|
304
264
|
export const MwDecodedAudioFrame = koffi.struct("MwDecodedAudioFrame", {
|
|
305
265
|
pts: "int64",
|
|
306
266
|
duration: "uint64",
|
|
@@ -439,6 +399,8 @@ export const pipeline = {
|
|
|
439
399
|
audioDecodeSessionFlush: pipelineLib.func("int mediaway_audio_decode_session_flush(void *session)"),
|
|
440
400
|
audioDecodeSessionClose: pipelineLib.func("void mediaway_audio_decode_session_close(void *session)"),
|
|
441
401
|
decodedAudioFrameFree: pipelineLib.func("void mediaway_decoded_audio_frame_free(MwDecodedAudioFrame *frame)"),
|
|
402
|
+
// ── ABI v7: streaming bytes, AAC decode config, capability probes (pipeline-v7.ts) ──
|
|
403
|
+
...pipelineV7,
|
|
442
404
|
// ── Capture-to-encode bridge (adr/pipeline/0005-capture-encode-bridge-c-abi.md) ──
|
|
443
405
|
// Pushes one polled frame from a device.h capture handle straight into a
|
|
444
406
|
// session — no intermediate MwCameraFrame/MwDesktopFrame, no extra copy.
|
|
@@ -457,6 +419,8 @@ export const device = {
|
|
|
457
419
|
cameraClose: deviceLib.func("int mediaway_camera_capture_close(void *capture)"),
|
|
458
420
|
cameraFrameFree: deviceLib.func("void mediaway_camera_frame_free(MwCameraFrame *frame)"),
|
|
459
421
|
desktopConfigScreen: deviceLib.func("MwDesktopConfig mediaway_desktop_capture_config_screen(uint32_t output_index, MwRational time_base, MwGpuDeviceHandle gpu_device)"),
|
|
422
|
+
desktopConfigWindow: deviceLib.func("MwDesktopConfig mediaway_desktop_capture_config_window(uint64_t hwnd, MwRational time_base, MwGpuDeviceHandle gpu_device)"),
|
|
423
|
+
desktopBorderHidden: deviceLib.func("int mediaway_desktop_capture_border_hidden(void *capture, _Out_ bool *out_hidden)"),
|
|
460
424
|
desktopOpen: deviceLib.func("int mediaway_desktop_capture_open(MwDesktopConfig *config, _Out_ void **out_capture)"),
|
|
461
425
|
desktopGeometry: deviceLib.func("int mediaway_desktop_capture_geometry(void *capture, _Out_ uint32_t *out_width, _Out_ uint32_t *out_height)"),
|
|
462
426
|
desktopPollFrame: deviceLib.func("int mediaway_desktop_capture_poll_frame(void *capture, _Out_ MwDesktopFrame *out_frame, _Out_ bool *out_has)"),
|
|
@@ -476,6 +440,12 @@ export const device = {
|
|
|
476
440
|
gpuDeviceHandle: deviceLib.func("int mediaway_gpu_device_handle(void *device, _Out_ MwGpuDeviceHandle *out_handle)"),
|
|
477
441
|
gpuDeviceClose: deviceLib.func("void mediaway_gpu_device_close(void *device)"),
|
|
478
442
|
};
|
|
443
|
+
// ── Replay ring + payload placements (adr/container/0009) ────────────────────
|
|
444
|
+
// Declared in container-replay.ts (this file is near the 1000-line cap). Called here, after
|
|
445
|
+
// `MwRational` and `MwPacketView` exist, because the signatures name them.
|
|
446
|
+
export const replay = createReplayBindings(MwRational);
|
|
447
|
+
/** koffi's sizeof/offsetof for the replay structs — pinned by test/replay-ring.test.ts. */
|
|
448
|
+
export const replayStructLayout = () => replayLayout(replay.structs);
|
|
479
449
|
// ── Copy helpers ───────────────────────────────────────────────────────────────
|
|
480
450
|
/** Copy `len` bytes out of a koffi pointer/opaque handle. */
|
|
481
451
|
export function copyBytes(ptr, len) {
|
package/dist/loader.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native library discovery + koffi load for @mediaway/ffi.
|
|
3
|
+
*
|
|
4
|
+
* Split out of index.ts (ADR-0024, multi-platform binding distribution)
|
|
5
|
+
* once the added per-platform search-path logic pushed index.ts's struct/
|
|
6
|
+
* function-binding definitions past the workspace's 1000-line source cap.
|
|
7
|
+
*/
|
|
8
|
+
export declare function findLibrary(name: string): string;
|
|
9
|
+
export declare const containerLib: import("koffi").LibraryHandle;
|
|
10
|
+
export declare const pipelineLib: import("koffi").LibraryHandle;
|
|
11
|
+
export declare const deviceLib: import("koffi").LibraryHandle;
|
package/dist/loader.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native library discovery + koffi load for @mediaway/ffi.
|
|
3
|
+
*
|
|
4
|
+
* Split out of index.ts (ADR-0024, multi-platform binding distribution)
|
|
5
|
+
* once the added per-platform search-path logic pushed index.ts's struct/
|
|
6
|
+
* function-binding definitions past the workspace's 1000-line source cap.
|
|
7
|
+
*/
|
|
8
|
+
import koffi from "koffi";
|
|
9
|
+
import fs from "node:fs";
|
|
10
|
+
import path from "node:path";
|
|
11
|
+
import { fileURLToPath } from "node:url";
|
|
12
|
+
// ── Library discovery ──────────────────────────────────────────────────────────
|
|
13
|
+
// The cdylib is a Rust build artifact, not an installed system library.
|
|
14
|
+
// Search:
|
|
15
|
+
// 1. $MEDIAWAY_FFI_DIR
|
|
16
|
+
// 2. <this package>/native/<platform>-<arch> (libs for every platform
|
|
17
|
+
// bundled at pack time — the npm distribution; see § platform tag)
|
|
18
|
+
// 3. <repo root>/target/x86_64-pc-windows-gnu/debug (GNU toolchain, dev runs)
|
|
19
|
+
// 4. <repo root>/target/debug (host/MSVC toolchain)
|
|
20
|
+
// 5. cwd
|
|
21
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
22
|
+
const repoRoot = path.resolve(here, "..", "..", "..", "..", "..");
|
|
23
|
+
/**
|
|
24
|
+
* `os-arch` tag matching one of `native/<tag>`'s subdirectories (this
|
|
25
|
+
* package bundles every platform's prebuilt lib directly — see
|
|
26
|
+
* tools/scripts/copy-native-dlls.ts). Returns `undefined` for an
|
|
27
|
+
* unsupported platform/arch pair rather than throwing, so callers can still
|
|
28
|
+
* fall through to the other search dirs (e.g. `$MEDIAWAY_FFI_DIR` during
|
|
29
|
+
* local development on an unlisted arch).
|
|
30
|
+
*/
|
|
31
|
+
function platformTag() {
|
|
32
|
+
const os = process.platform === "win32" ? "win32" : process.platform === "linux" ? "linux"
|
|
33
|
+
: process.platform === "darwin" ? "darwin" : undefined;
|
|
34
|
+
const arch = process.arch === "x64" ? "x64" : process.arch === "arm64" ? "arm64" : undefined;
|
|
35
|
+
return os && arch ? `${os}-${arch}` : undefined;
|
|
36
|
+
}
|
|
37
|
+
const tag = platformTag();
|
|
38
|
+
const searchDirs = [
|
|
39
|
+
process.env.MEDIAWAY_FFI_DIR ?? "",
|
|
40
|
+
tag ? path.resolve(here, "..", "native", tag) : "",
|
|
41
|
+
path.join(repoRoot, "target", "x86_64-pc-windows-gnu", "debug"),
|
|
42
|
+
path.join(repoRoot, "target", "debug"),
|
|
43
|
+
process.cwd(),
|
|
44
|
+
];
|
|
45
|
+
export function findLibrary(name) {
|
|
46
|
+
for (const dir of searchDirs) {
|
|
47
|
+
if (!dir)
|
|
48
|
+
continue;
|
|
49
|
+
const candidate = path.join(dir, name);
|
|
50
|
+
if (fs.existsSync(candidate))
|
|
51
|
+
return candidate;
|
|
52
|
+
}
|
|
53
|
+
throw new Error(`cannot find ${name}; set $MEDIAWAY_FFI_DIR or build the -ffi crates`);
|
|
54
|
+
}
|
|
55
|
+
/** cdylib filename Cargo produces for this platform. */
|
|
56
|
+
function libraryFilename() {
|
|
57
|
+
switch (process.platform) {
|
|
58
|
+
case "win32":
|
|
59
|
+
return "mediaway_ffi.dll";
|
|
60
|
+
case "linux":
|
|
61
|
+
return "libmediaway_ffi.so";
|
|
62
|
+
case "darwin":
|
|
63
|
+
return "libmediaway_ffi.dylib";
|
|
64
|
+
default:
|
|
65
|
+
throw new Error(`mediaway: unsupported platform ${process.platform}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function load(name) {
|
|
69
|
+
return koffi.load(findLibrary(name));
|
|
70
|
+
}
|
|
71
|
+
const libraryName = libraryFilename();
|
|
72
|
+
export const containerLib = load(libraryName);
|
|
73
|
+
export const pipelineLib = load(libraryName);
|
|
74
|
+
export const deviceLib = load(libraryName);
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pipeline ABI v7 additions — `crates/mediaway-ffi/adr/pipeline/0007-stream-bytes-aac-decode-
|
|
3
|
+
* support-probe.md`: streaming fMP4 bytes, AAC decode, and the capability probes.
|
|
4
|
+
*
|
|
5
|
+
* Split out of index.ts for the workspace's 1000-line source cap. It imports only leaf modules
|
|
6
|
+
* (`rational.ts`, `loader.ts`), never `index.ts`, so `index.ts` can re-export it without a cycle.
|
|
7
|
+
*
|
|
8
|
+
* Ownership (from `pipeline.h`):
|
|
9
|
+
* - `mediaway_encode_session_poll_bytes` returns an OWNED buffer (release it with
|
|
10
|
+
* `pipeline.bufferFree`); nothing ready is `NULL` / `0` and needs no free.
|
|
11
|
+
* - `mediaway_encoder_support_at` returns an OWNED array; release it with
|
|
12
|
+
* `mediaway_encoder_support_free`, passing back the same pointer and count.
|
|
13
|
+
* - `mediaway_audio_decode_config_t.extra_data` is BORROWED, valid for `open()` only.
|
|
14
|
+
*/
|
|
15
|
+
/** `mediaway_audio_decode_config_t` — Opus passes `extra_data = NULL`, AAC the raw ASC. */
|
|
16
|
+
export declare const MwAudioDecodeConfig: import("koffi").TypeObject;
|
|
17
|
+
/** `mediaway_encoder_capability_t` — one backend row of `mediaway_encoder_support_at`. */
|
|
18
|
+
export declare const MwEncoderCapability: import("koffi").TypeObject;
|
|
19
|
+
/** `mediaway_encoder_capability_t` as decoded by koffi. */
|
|
20
|
+
export interface RawEncoderCapability {
|
|
21
|
+
backend: number;
|
|
22
|
+
state: number;
|
|
23
|
+
path_class: number;
|
|
24
|
+
}
|
|
25
|
+
/** Codec numbers the probes and AAC decode accept (`mediaway_pipeline_codec_kind_t`). */
|
|
26
|
+
export declare const PIPELINE_CODEC: {
|
|
27
|
+
readonly h264: 0;
|
|
28
|
+
readonly hevc: 1;
|
|
29
|
+
readonly av1: 2;
|
|
30
|
+
readonly vp9: 3;
|
|
31
|
+
readonly aac: 4;
|
|
32
|
+
readonly opus: 5;
|
|
33
|
+
};
|
|
34
|
+
export declare const pipelineV7: {
|
|
35
|
+
audioDecodeConfigAac: import("koffi").Callable;
|
|
36
|
+
sessionPollBytes: import("koffi").Callable;
|
|
37
|
+
encoderSupportAt: import("koffi").Callable;
|
|
38
|
+
encoderSupportFree: import("koffi").Callable;
|
|
39
|
+
decoderSupport: import("koffi").Callable;
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Size and field offsets of a registered struct, for pinning a mirror against the C header's
|
|
43
|
+
* layout in a test. `fields` are read in the order given.
|
|
44
|
+
*/
|
|
45
|
+
export declare function structLayout(type: unknown, fields: readonly string[]): {
|
|
46
|
+
size: number;
|
|
47
|
+
offsets: Record<string, number>;
|
|
48
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pipeline ABI v7 additions — `crates/mediaway-ffi/adr/pipeline/0007-stream-bytes-aac-decode-
|
|
3
|
+
* support-probe.md`: streaming fMP4 bytes, AAC decode, and the capability probes.
|
|
4
|
+
*
|
|
5
|
+
* Split out of index.ts for the workspace's 1000-line source cap. It imports only leaf modules
|
|
6
|
+
* (`rational.ts`, `loader.ts`), never `index.ts`, so `index.ts` can re-export it without a cycle.
|
|
7
|
+
*
|
|
8
|
+
* Ownership (from `pipeline.h`):
|
|
9
|
+
* - `mediaway_encode_session_poll_bytes` returns an OWNED buffer (release it with
|
|
10
|
+
* `pipeline.bufferFree`); nothing ready is `NULL` / `0` and needs no free.
|
|
11
|
+
* - `mediaway_encoder_support_at` returns an OWNED array; release it with
|
|
12
|
+
* `mediaway_encoder_support_free`, passing back the same pointer and count.
|
|
13
|
+
* - `mediaway_audio_decode_config_t.extra_data` is BORROWED, valid for `open()` only.
|
|
14
|
+
*/
|
|
15
|
+
import koffi from "koffi";
|
|
16
|
+
import { pipelineLib } from "./loader.js";
|
|
17
|
+
import { MwRational } from "./rational.js";
|
|
18
|
+
/** `mediaway_audio_decode_config_t` — Opus passes `extra_data = NULL`, AAC the raw ASC. */
|
|
19
|
+
export const MwAudioDecodeConfig = koffi.struct("MwAudioDecodeConfig", {
|
|
20
|
+
codec: "int32", // Opus (5) or AAC (4)
|
|
21
|
+
sample_rate: "uint32",
|
|
22
|
+
channels: "uint16",
|
|
23
|
+
time_base: MwRational,
|
|
24
|
+
extra_data: "uint8_t *", // borrowed, open-call only; AAC's AudioSpecificConfig
|
|
25
|
+
extra_data_len: "size_t",
|
|
26
|
+
});
|
|
27
|
+
/** `mediaway_encoder_capability_t` — one backend row of `mediaway_encoder_support_at`. */
|
|
28
|
+
export const MwEncoderCapability = koffi.struct("MwEncoderCapability", {
|
|
29
|
+
backend: "int32",
|
|
30
|
+
state: "int32",
|
|
31
|
+
path_class: "int32", // meaningful only when state == SUPPORTED
|
|
32
|
+
});
|
|
33
|
+
/** Codec numbers the probes and AAC decode accept (`mediaway_pipeline_codec_kind_t`). */
|
|
34
|
+
export const PIPELINE_CODEC = { h264: 0, hevc: 1, av1: 2, vp9: 3, aac: 4, opus: 5 };
|
|
35
|
+
export const pipelineV7 = {
|
|
36
|
+
audioDecodeConfigAac: pipelineLib.func("MwAudioDecodeConfig mediaway_audio_decode_config_aac(uint32_t sample_rate, uint16_t channels, MwRational time_base, uint8_t *extra_data, size_t extra_data_len)"),
|
|
37
|
+
sessionPollBytes: pipelineLib.func("int mediaway_encode_session_poll_bytes(void *session, _Out_ uint8_t **out_data, _Out_ size_t *out_len)"),
|
|
38
|
+
encoderSupportAt: pipelineLib.func("int mediaway_encoder_support_at(int codec, uint32_t width, uint32_t height, _Out_ void **out_rows, _Out_ size_t *out_count)"),
|
|
39
|
+
encoderSupportFree: pipelineLib.func("void mediaway_encoder_support_free(void *rows, size_t count)"),
|
|
40
|
+
decoderSupport: pipelineLib.func("int mediaway_decoder_support(int codec, _Out_ int *out_state)"),
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Size and field offsets of a registered struct, for pinning a mirror against the C header's
|
|
44
|
+
* layout in a test. `fields` are read in the order given.
|
|
45
|
+
*/
|
|
46
|
+
export function structLayout(type, fields) {
|
|
47
|
+
// `type` stays `unknown` so callers can pass whatever `koffi.struct` returned; `koffi.d.ts`
|
|
48
|
+
// types the pair strictly (`TypeObject`), so narrow it once here.
|
|
49
|
+
const registered = type;
|
|
50
|
+
const offsets = {};
|
|
51
|
+
for (const field of fields)
|
|
52
|
+
offsets[field] = koffi.offsetof(registered, field);
|
|
53
|
+
return { size: koffi.sizeof(registered), offsets };
|
|
54
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `mediaway_rational_t`, in a leaf module.
|
|
3
|
+
*
|
|
4
|
+
* Split out of index.ts so `pipeline-v7.ts` (which needs it to define struct layouts that
|
|
5
|
+
* embed a timebase) can import it without a cycle: index.ts imports pipeline-v7.ts, so
|
|
6
|
+
* pipeline-v7.ts must not import index.ts.
|
|
7
|
+
*/
|
|
8
|
+
export declare const MwRational: import("koffi").TypeObject;
|
package/dist/rational.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `mediaway_rational_t`, in a leaf module.
|
|
3
|
+
*
|
|
4
|
+
* Split out of index.ts so `pipeline-v7.ts` (which needs it to define struct layouts that
|
|
5
|
+
* embed a timebase) can import it without a cycle: index.ts imports pipeline-v7.ts, so
|
|
6
|
+
* pipeline-v7.ts must not import index.ts.
|
|
7
|
+
*/
|
|
8
|
+
import koffi from "koffi";
|
|
9
|
+
export const MwRational = koffi.struct("MwRational", {
|
|
10
|
+
num: "uint64",
|
|
11
|
+
den: "uint32",
|
|
12
|
+
});
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED