nandi/jolt-nativepublic Fork 0
d5dfd53e28ca47b0a4fdb550d5225ffa10aff7cf
Commits
Clone
git clone https://git.rickub.com/nandi/jolt-native.git
git clone ssh://git@rickub.com/nandi/jolt-native.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

joltmoq.h · 186 lines · 7.8 KBC Blame HistoryRaw
Lift freeq's AV media plane out of sleek 90f8b89 nandi 19d ago1/* joltmoq — freeq's AV media plane, for callers that are not Rust.
2 *
3 * A freeq call has two halves. *Signaling* rides IRC TAGMSGs
4 * (`+freeq.at/av-start`, `av-join`, `av-leave`, and the server's `av-state`
5 * broadcasts back), and is not here: a client that speaks IRC already has
6 * everything it needs for that in whatever language it speaks IRC in. *Media*
7 * rides MoQ — Media over QUIC — through the freeq SFU, and is what this is.
8 *
9 * The boundary begins once the session id and the SFU token are known, and
10 * ends when the call does.
11 *
12 * Rules for every function below:
13 *
14 * - Only integers, doubles, borrowed UTF-8 strings and one borrowed pixel
15 * pointer cross. Nothing here retains caller memory past a call, and
16 * nothing hands out memory the caller must free.
17 * - A returned string is valid until the next call to the same function.
18 * Copy it if you need to keep it.
19 * - An empty string argument means "no preference" — the system default.
20 * - Booleans cross as 0 or 1.
21 * - Panics are caught at the boundary, so a bug in a decoder is a black tile
22 * rather than a dead process.
23 * - Nothing calls back. Everything asynchronous is polled.
24 * - One call at a time. There is one microphone, so there is one session.
25 */
26
27#ifndef JOLTMOQ_H
28#define JOLTMOQ_H
29
30#ifdef __cplusplus
31extern "C" {
32#endif
33
34/* Logging to stderr, honouring RUST_LOG. Safe to call twice. Worth calling
35 * first while a call refuses to connect: the media plane says a great deal
36 * about why, and says none of it otherwise. */
37void joltmoq_init_logging(void);
38
Let the media plane cross to the phone, camera and all fd0e21a nandi 18d ago39/* Android only. Hand over the process's JavaVM and its Activity, once, before
40 * any call starts.
41 *
42 * The camera on the phone is Camera2, which is Java, and this library cannot
43 * reach the handles a JNI call needs: android-activity's glue receives them and
44 * the glue lives in libvidya.so, a different shared object. `ndk_context` does
45 * not bridge that — its handles sit in a static, and a static is per object —
46 * so the APK's glue reads them out of libvidya (vidya_android_vm,
47 * vidya_android_activity) and passes them here.
48 *
49 * Everything else works without it. Only the camera calls fail, and they say
50 * which call was missed. Not declared on other platforms, where there is
51 * nothing to hand over. */
52#ifdef __ANDROID__
53void joltmoq_android_init(void *vm, void *activity);
54#endif
55
Lift freeq's AV media plane out of sleek 90f8b89 nandi 19d ago56/* Lifecycle
57 *
58 * `joltmoq_start` joins the call at `sfu_url` (see joltmoq_sfu_url) as `nick`.
59 * `session_id` is what the server broadcast in `+freeq.at/av-id`; `instance`
60 * is the per-device id from your own `av-join` — two devices signed in as the
61 * same person need different ones, or their broadcast paths collide and each
62 * unpublishes the other.
63 *
64 * Returns 1 when the media task started. That is not the same as connected:
65 * poll the status for that. Starting a second call while one is live is
66 * refused.
67 *
68 * `joltmoq_stop` waits briefly for MoQ to be torn down rather than aborting.
69 * An abandoned broadcast lingers on the SFU, and peers subscribe to it and
70 * hear silence from someone who looks present.
71 */
72int joltmoq_start(const char *sfu_url, const char *session_id, const char *nick,
73 const char *instance, int muted, int speaker_muted, int camera,
74 const char *camera_id, const char *mic_id, const char *speaker_id);
75void joltmoq_stop(void);
76int joltmoq_is_live(void);
77
78/* Controls
79 *
80 * Mic mute and speaker mute are deliberately separate: muting the speaker
81 * (deafening) leaves peers hearing you if the mic is open. The camera device
82 * is only held while publishing, so turning the camera off gives the hardware
83 * back to the rest of the machine.
84 *
85 * A control with no call under it is inert, not an error — a UI that sends a
86 * mute as the call is ending must not have to race it.
87 *
88 * `joltmoq_mic_level` is the live envelope, 0.0 to 1.0: what a level meter
89 * draws. 0 when no call is up.
90 */
91void joltmoq_set_muted(int muted);
92void joltmoq_set_speaker_muted(int muted);
93void joltmoq_set_camera(int enabled);
94void joltmoq_set_camera_device(const char *id);
95void joltmoq_set_mic_device(const char *id);
96void joltmoq_set_speaker_device(const char *id);
97double joltmoq_mic_level(void);
98
99/* Status
100 *
101 * The media task runs on its own threads; this is how what it learns reaches
102 * yours. Drain `joltmoq_poll_status` until it answers NONE wherever you poll —
103 * beside a repaint is the natural place. Missing an update means a call that is
104 * up still looks like it is connecting.
105 *
106 * The accessors describe the most recently dequeued status. `has_camera` is
107 * false when there is no capture device for this call at all, so a camera
108 * control has nothing to offer; `has_mic` is false for listen-only, where
109 * audio is still published, as silence.
110 */
111#define JOLTMOQ_STATUS_NONE 0
112#define JOLTMOQ_STATUS_LIVE 1
113#define JOLTMOQ_STATUS_ENDED 2
114#define JOLTMOQ_STATUS_FAILED 3
115
116int joltmoq_poll_status(void);
117const char *joltmoq_status_text(void); /* why it failed; "" otherwise */
118int joltmoq_status_has_camera(void);
119int joltmoq_status_has_mic(void);
120
121/* Video
122 *
123 * Loop `joltmoq_frame_poll` until it answers 0, reading the four accessors for
124 * each frame it hands over and passing them to whatever paints. Only what
125 * changed comes over: a participant sitting still costs nothing, and a decoder
126 * running ahead of the window is coalesced to its newest frame rather than
127 * queued behind stale ones.
128 *
129 * The key is the participant's nick, or "__local__" for the self-view.
130 *
131 * `joltmoq_frame_rgba` is width * height * 4 bytes, row-major, 8 bits a
132 * channel, un-premultiplied and opaque. It is BORROWED, and only until the
133 * next `joltmoq_frame_poll` — that is the point of polling rather than
134 * copying: a frame is a megabyte or two, and copying it out so you can hand it
135 * straight to a texture upload would be two copies a frame for nothing.
136 *
137 * `joltmoq_video_keys` is every feed the call currently carries, one a line.
138 * Use it to notice a tile you should stop painting: someone who left stops
139 * appearing, while their last frame would otherwise hang on the wall for the
140 * rest of the call.
141 */
142int joltmoq_frame_poll(void);
143const char *joltmoq_frame_key(void);
144int joltmoq_frame_width(void);
145int joltmoq_frame_height(void);
146const unsigned char *joltmoq_frame_rgba(void);
147const char *joltmoq_video_keys(void);
148
149/* Devices
150 *
151 * One a line, each `id<TAB>name<TAB>default`, where the last column is 1 for
152 * the system default. Tab and newline delimit because a device name may hold
153 * anything else — "EMEET SmartCam C960, Mono" has spaces and a comma in it.
154 *
155 * Enumerating opens nothing, so it is safe before a call and during one. Empty
156 * is a normal answer on a machine with no such device.
157 */
158const char *joltmoq_cameras(void);
159const char *joltmoq_microphones(void);
160const char *joltmoq_speakers(void);
161
162/* Dialling
163 *
164 * `joltmoq_sfu_url` builds what `joltmoq_start` wants from whatever your
165 * client knows the IRC server as — "irc.freeq.at:6697" and
166 * "wss://irc.freeq.at/irc" both become "https://irc.freeq.at/av/moq" — plus
167 * the JWT the server minted in `+freeq.at/av-token`. Empty string when the
168 * server is not something a URL can be made of.
169 *
170 * `joltmoq_can_dial` answers whether the attempt is worth making. A remote SFU
171 * with no token accepts the connection and closes it, and the MoQ client then
172 * retries in a tight loop that looks, from the outside, exactly like a hang.
173 *
174 * `joltmoq_new_instance` is eight hex characters: the per-device id that keeps
175 * two of your own devices from colliding on the SFU. Put it in your
176 * `+freeq.at/av-instance` tag and hand the same one to `joltmoq_start`.
177 */
178const char *joltmoq_sfu_url(const char *server, const char *jwt, const char *instance);
179int joltmoq_can_dial(const char *server, const char *jwt);
180const char *joltmoq_new_instance(void);
181
182#ifdef __cplusplus
183}
184#endif
185
186#endif /* JOLTMOQ_H */