@desklink/host 0.2.0 → 0.3.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.
Files changed (82) hide show
  1. package/NOTICE +26 -0
  2. package/README.md +134 -56
  3. package/bin/desklink-host.mjs +13 -5
  4. package/dist/bridge.d.ts +1 -0
  5. package/dist/bridge.d.ts.map +1 -1
  6. package/dist/bridge.js +12 -4
  7. package/dist/bridge.js.map +1 -1
  8. package/dist/engineProcess.d.ts +6 -1
  9. package/dist/engineProcess.d.ts.map +1 -1
  10. package/dist/engineProcess.js +35 -4
  11. package/dist/engineProcess.js.map +1 -1
  12. package/dist/protocol.d.ts +58 -0
  13. package/dist/protocol.d.ts.map +1 -1
  14. package/dist/protocol.js.map +1 -1
  15. package/dist/resolveEngine.d.ts +7 -1
  16. package/dist/resolveEngine.d.ts.map +1 -1
  17. package/dist/resolveEngine.js +66 -14
  18. package/dist/resolveEngine.js.map +1 -1
  19. package/docs/BOUNDARY.md +15 -7
  20. package/docs/PROTOCOL.md +267 -27
  21. package/engine/Cargo.lock +47 -2
  22. package/engine/Cargo.toml +18 -13
  23. package/engine/build.rs +86 -4
  24. package/engine/native/agent_overlay_mac.m +51 -4
  25. package/engine/native/agent_overlay_wayland.c +145 -12
  26. package/engine/native/agent_overlay_x11.c +72 -0
  27. package/engine/native/nvenc_shim.c +453 -0
  28. package/engine/native/vaapi_shim.c +727 -0
  29. package/engine/native/vpx_shim.c +54 -4
  30. package/engine/native/vt_shim.c +448 -0
  31. package/engine/src/capture.rs +167 -58
  32. package/engine/src/capture_mac.rs +24 -14
  33. package/engine/src/capture_win.rs +23 -0
  34. package/engine/src/clipboard_win.rs +8 -0
  35. package/engine/src/convert.rs +286 -61
  36. package/engine/src/cursor.rs +118 -0
  37. package/engine/src/encoder.rs +122 -13
  38. package/engine/src/h264.rs +188 -0
  39. package/engine/src/h264_nvenc.rs +308 -0
  40. package/engine/src/h264_openh264.rs +735 -0
  41. package/engine/src/h264_vaapi.rs +263 -0
  42. package/engine/src/h264_vt.rs +368 -0
  43. package/engine/src/indicator.rs +104 -3
  44. package/engine/src/input_win.rs +91 -0
  45. package/engine/src/keymap_win.rs +39 -0
  46. package/engine/src/mac.rs +34 -9
  47. package/engine/src/main.rs +186 -26
  48. package/engine/src/peer.rs +466 -28
  49. package/engine/src/portal.rs +42 -4
  50. package/engine/src/portal_win.rs +37 -0
  51. package/engine/src/protocol.rs +64 -2
  52. package/engine/src/session.rs +1252 -105
  53. package/engine/src/win.rs +110 -0
  54. package/engine/src/x11.rs +225 -9
  55. package/engine/src/x11_mac.rs +12 -0
  56. package/engine/vendor/libva/COPYING +19 -0
  57. package/engine/vendor/libva/va/va.h +5450 -0
  58. package/engine/vendor/libva/va/va_dec_av1.h +695 -0
  59. package/engine/vendor/libva/va/va_dec_hevc.h +616 -0
  60. package/engine/vendor/libva/va/va_dec_jpeg.h +157 -0
  61. package/engine/vendor/libva/va/va_dec_vp8.h +249 -0
  62. package/engine/vendor/libva/va/va_dec_vp9.h +312 -0
  63. package/engine/vendor/libva/va/va_dec_vvc.h +688 -0
  64. package/engine/vendor/libva/va/va_drm.h +61 -0
  65. package/engine/vendor/libva/va/va_enc_av1.h +1010 -0
  66. package/engine/vendor/libva/va/va_enc_h264.h +659 -0
  67. package/engine/vendor/libva/va/va_enc_hevc.h +958 -0
  68. package/engine/vendor/libva/va/va_enc_jpeg.h +162 -0
  69. package/engine/vendor/libva/va/va_enc_mpeg2.h +308 -0
  70. package/engine/vendor/libva/va/va_enc_vp8.h +350 -0
  71. package/engine/vendor/libva/va/va_enc_vp9.h +639 -0
  72. package/engine/vendor/libva/va/va_fei.h +170 -0
  73. package/engine/vendor/libva/va/va_fei_h264.h +505 -0
  74. package/engine/vendor/libva/va/va_fei_hevc.h +254 -0
  75. package/engine/vendor/libva/va/va_prot.h +525 -0
  76. package/engine/vendor/libva/va/va_version.h +87 -0
  77. package/engine/vendor/libva/va/va_vpp.h +1590 -0
  78. package/engine/vendor/nv-codec-headers/LICENSE +24 -0
  79. package/engine/vendor/nv-codec-headers/README +7 -0
  80. package/engine/vendor/nv-codec-headers/include/ffnvcodec/nvEncodeAPI.h +3825 -0
  81. package/examples/reference-client.html +1 -1
  82. package/package.json +4 -3
@@ -24,11 +24,17 @@
24
24
  /* The quantizer ceiling while the desktop moves. Refinement frames lower it. */
25
25
  #define DL_VPX_MOTION_MAX_Q 52
26
26
 
27
+ /* Below this width a half-size frame is too small to be worth a key frame. */
28
+ #define DL_VPX_HALF_MIN_WIDTH 640
29
+
27
30
  typedef struct {
28
31
  vpx_codec_ctx_t ctx;
29
32
  vpx_codec_enc_cfg_t cfg;
30
33
  int width;
31
34
  int height;
35
+ /* The half-size copy of a frame coded while the desktop moves faster than
36
+ * the encoder keeps up; NULL until the first such frame. */
37
+ uint8_t *half;
32
38
  int frame_index;
33
39
  /* The packet libvpx last produced; valid until the next encode call. */
34
40
  const void *packet;
@@ -105,18 +111,61 @@ dl_vpx_encoder *dl_vpx_create(int width, int height, int bitrate_kbps, int fps,
105
111
  return self;
106
112
  }
107
113
 
108
- /* Encode one tightly packed I420 frame. Returns:
114
+ /* One plane at half size, each output pixel the mean of a 2x2 block. */
115
+ static void halve_plane(const uint8_t *src, int src_w, uint8_t *dst, int dst_w, int dst_h) {
116
+ for (int y = 0; y < dst_h; y++) {
117
+ const uint8_t *a = src + (size_t)2 * y * src_w;
118
+ const uint8_t *b = a + src_w;
119
+ uint8_t *out = dst + (size_t)y * dst_w;
120
+ for (int x = 0; x < dst_w; x++) {
121
+ out[x] = (uint8_t)((a[2 * x] + a[2 * x + 1] + b[2 * x] + b[2 * x + 1] + 2) >> 2);
122
+ }
123
+ }
124
+ }
125
+
126
+ /* Encode one tightly packed I420 frame, at its own size or, with `half`, at
127
+ * half its size. The coded size changes only on a key frame: a decoder that
128
+ * scales references across a size change is a feature many hardware decoders
129
+ * get wrong, and the frame that changes size costs a key frame's bits anyway.
130
+ * Returns:
109
131
  * 1 a packet is available through dl_vpx_packet_*
110
132
  * 0 libvpx held the frame (should not happen with zero lookahead)
111
133
  * -1 libvpx rejected the frame
112
134
  */
113
- int dl_vpx_encode(dl_vpx_encoder *self, const uint8_t *i420, int force_keyframe) {
135
+ int dl_vpx_encode(dl_vpx_encoder *self, const uint8_t *i420, int force_keyframe, int half) {
114
136
  if (self == NULL || i420 == NULL) {
115
137
  return -1;
116
138
  }
139
+ int w = self->width;
140
+ int h = self->height;
141
+ if (half && w >= DL_VPX_HALF_MIN_WIDTH) {
142
+ w = (w / 2) & ~1;
143
+ h = (h / 2) & ~1;
144
+ if (self->half == NULL) {
145
+ self->half = malloc((size_t)w * h * 3 / 2);
146
+ if (self->half == NULL) {
147
+ return -1;
148
+ }
149
+ }
150
+ const uint8_t *u = i420 + (size_t)self->width * self->height;
151
+ const uint8_t *v = u + (size_t)(self->width / 2) * (self->height / 2);
152
+ uint8_t *hu = self->half + (size_t)w * h;
153
+ halve_plane(i420, self->width, self->half, w, h);
154
+ halve_plane(u, self->width / 2, hu, w / 2, h / 2);
155
+ halve_plane(v, self->width / 2, hu + (size_t)(w / 2) * (h / 2), w / 2, h / 2);
156
+ i420 = self->half;
157
+ }
158
+ if (self->cfg.g_w != (unsigned int)w || self->cfg.g_h != (unsigned int)h) {
159
+ self->cfg.g_w = (unsigned int)w;
160
+ self->cfg.g_h = (unsigned int)h;
161
+ if (vpx_codec_enc_config_set(&self->ctx, &self->cfg) != VPX_CODEC_OK) {
162
+ return -1;
163
+ }
164
+ force_keyframe = 1;
165
+ }
117
166
  vpx_image_t image;
118
- if (vpx_img_wrap(&image, VPX_IMG_FMT_I420, (unsigned int)self->width,
119
- (unsigned int)self->height, 1, (unsigned char *)i420) == NULL) {
167
+ if (vpx_img_wrap(&image, VPX_IMG_FMT_I420, (unsigned int)w, (unsigned int)h, 1,
168
+ (unsigned char *)i420) == NULL) {
120
169
  return -1;
121
170
  }
122
171
  if (vpx_codec_encode(&self->ctx, &image, self->frame_index, 1,
@@ -180,5 +229,6 @@ void dl_vpx_destroy(dl_vpx_encoder *self) {
180
229
  return;
181
230
  }
182
231
  vpx_codec_destroy(&self->ctx);
232
+ free(self->half);
183
233
  free(self);
184
234
  }
@@ -0,0 +1,448 @@
1
+ /*
2
+ * Thin C surface over VideoToolbox's H.264 encoder.
3
+ *
4
+ * VideoToolbox is configured through CoreFoundation dictionaries and delivers
5
+ * output on a callback; doing both here, next to the headers, keeps the Rust
6
+ * side to an opaque handle, a status code and one Annex-B access unit.
7
+ *
8
+ * Every call is synchronous: a frame goes in, VTCompressionSessionCompleteFrames
9
+ * flushes it, and the callback has stored its access unit before encode returns.
10
+ * VideoToolbox is a system framework; nothing here is derived from any encoder
11
+ * implementation.
12
+ */
13
+
14
+ #include <stdint.h>
15
+ #include <stdlib.h>
16
+ #include <string.h>
17
+
18
+ #include <CoreFoundation/CoreFoundation.h>
19
+ #include <CoreMedia/CoreMedia.h>
20
+ #include <CoreVideo/CoreVideo.h>
21
+ #include <VideoToolbox/VideoToolbox.h>
22
+
23
+ /* H.264's own ceiling: no cap. Refinement frames lower it for one frame. */
24
+ #define DL_VT_MOTION_MAX_QP 51
25
+
26
+ /* Not an OSStatus: the encoder chose to drop the frame rather than fail. */
27
+ #define DL_VT_DROPPED 1
28
+
29
+ /* How many times one picture is offered to low-latency rate control before
30
+ * the session falls back to the ordinary real-time mode. */
31
+ #define DL_VT_ATTEMPTS 4
32
+
33
+ /* Outside low-latency mode MaxAllowedFrameQP (and MinAllowedFrameQP) are
34
+ * accepted but ignored, and the per-frame BaseFrameQP would have to be set on
35
+ * every frame, so a refinement frame gets a rate target this many times the
36
+ * session's instead. Rate control moves QP about 5 steps per frame whatever
37
+ * the target, so the refinement lands about 5 below its neighbours: QP 20 when
38
+ * they sit at 25, QP 40 when they sit at 45. */
39
+ #define DL_VT_REFINE_RATE_SCALE 16
40
+
41
+ typedef struct {
42
+ VTCompressionSessionRef session;
43
+ int width;
44
+ int height;
45
+ int fps;
46
+ /* Low-latency rate control, when the machine's encoder offers it for
47
+ * Constrained Baseline; otherwise the ordinary real-time mode, which needs
48
+ * a hard data-rate cap to keep a key frame from flooding the link. */
49
+ int low_latency;
50
+ int bitrate_kbps;
51
+ int64_t frame_index;
52
+ /* The access unit the callback last produced, Annex-B. */
53
+ uint8_t *out;
54
+ size_t out_size;
55
+ size_t out_cap;
56
+ int out_is_key;
57
+ OSStatus out_status;
58
+ int out_ready;
59
+ } dl_vt_encoder;
60
+
61
+ static int append(dl_vt_encoder *self, const void *bytes, size_t size) {
62
+ if (self->out_size + size > self->out_cap) {
63
+ size_t cap = (self->out_size + size) * 2;
64
+ uint8_t *grown = realloc(self->out, cap);
65
+ if (grown == NULL) {
66
+ return -1;
67
+ }
68
+ self->out = grown;
69
+ self->out_cap = cap;
70
+ }
71
+ memcpy(self->out + self->out_size, bytes, size);
72
+ self->out_size += size;
73
+ return 0;
74
+ }
75
+
76
+ static const uint8_t START_CODE[4] = {0, 0, 0, 1};
77
+
78
+ /* The encoder writes length-prefixed NAL units and keeps SPS/PPS in the format
79
+ * description; receivers want start codes and the parameter sets in-band, in
80
+ * front of every IDR so a decoder can join at any of them. */
81
+ static OSStatus to_annex_b(dl_vt_encoder *self, CMSampleBufferRef sample) {
82
+ CMBlockBufferRef block = CMSampleBufferGetDataBuffer(sample);
83
+ CMFormatDescriptionRef format = CMSampleBufferGetFormatDescription(sample);
84
+ if (block == NULL || format == NULL) {
85
+ return kVTParameterErr;
86
+ }
87
+ size_t size = CMBlockBufferGetDataLength(block);
88
+ uint8_t *avcc = malloc(size);
89
+ if (avcc == NULL) {
90
+ return kVTAllocationFailedErr;
91
+ }
92
+ OSStatus status = CMBlockBufferCopyDataBytes(block, 0, size, avcc);
93
+ size_t sets = 0;
94
+ int length_size = 4;
95
+ if (status == noErr) {
96
+ status = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(format, 0, NULL, NULL, &sets,
97
+ &length_size);
98
+ }
99
+ if (status != noErr || length_size < 1 || length_size > 4) {
100
+ free(avcc);
101
+ return status != noErr ? status : kVTParameterErr;
102
+ }
103
+ int idr = 0;
104
+ for (size_t at = 0; at + length_size <= size;) {
105
+ size_t nal = 0;
106
+ for (int i = 0; i < length_size; i++) {
107
+ nal = (nal << 8) | avcc[at + i];
108
+ }
109
+ at += length_size;
110
+ if (nal == 0 || nal > size - at) {
111
+ break;
112
+ }
113
+ if ((avcc[at] & 0x1f) == 5) {
114
+ idr = 1;
115
+ }
116
+ at += nal;
117
+ }
118
+ if (idr) {
119
+ for (size_t i = 0; i < sets && status == noErr; i++) {
120
+ const uint8_t *set = NULL;
121
+ size_t set_size = 0;
122
+ status = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(format, i, &set, &set_size,
123
+ NULL, NULL);
124
+ if (status == noErr && (append(self, START_CODE, 4) || append(self, set, set_size))) {
125
+ status = kVTAllocationFailedErr;
126
+ }
127
+ }
128
+ }
129
+ for (size_t at = 0; status == noErr && at + length_size <= size;) {
130
+ size_t nal = 0;
131
+ for (int i = 0; i < length_size; i++) {
132
+ nal = (nal << 8) | avcc[at + i];
133
+ }
134
+ at += length_size;
135
+ if (nal == 0 || nal > size - at) {
136
+ break;
137
+ }
138
+ if (append(self, START_CODE, 4) || append(self, avcc + at, nal)) {
139
+ status = kVTAllocationFailedErr;
140
+ }
141
+ at += nal;
142
+ }
143
+ free(avcc);
144
+ self->out_is_key = idr;
145
+ return status;
146
+ }
147
+
148
+ static void on_output(void *refcon, void *frame_refcon, OSStatus status,
149
+ VTEncodeInfoFlags flags, CMSampleBufferRef sample) {
150
+ (void)frame_refcon;
151
+ dl_vt_encoder *self = refcon;
152
+ self->out_ready = 1;
153
+ if (status == noErr && ((flags & kVTEncodeInfo_FrameDropped) || sample == NULL)) {
154
+ status = DL_VT_DROPPED;
155
+ }
156
+ self->out_status = status == noErr ? to_annex_b(self, sample) : status;
157
+ }
158
+
159
+ static OSStatus set_int(VTSessionRef session, CFStringRef key, int64_t value) {
160
+ CFNumberRef number = CFNumberCreate(NULL, kCFNumberSInt64Type, &value);
161
+ OSStatus status = VTSessionSetProperty(session, key, number);
162
+ CFRelease(number);
163
+ return status;
164
+ }
165
+
166
+ /* A hard cap of 1.5x the target over one second: room for a key frame without
167
+ * letting it take the link. Only used outside low-latency mode, which paces
168
+ * itself. */
169
+ static OSStatus set_rate(dl_vt_encoder *self, int bitrate_kbps) {
170
+ self->bitrate_kbps = bitrate_kbps;
171
+ OSStatus status =
172
+ set_int(self->session, kVTCompressionPropertyKey_AverageBitRate, (int64_t)bitrate_kbps * 1000);
173
+ if (status != noErr || self->low_latency) {
174
+ return status;
175
+ }
176
+ int64_t bytes = (int64_t)bitrate_kbps * 1000 * 3 / 2 / 8;
177
+ double seconds = 1.0;
178
+ CFNumberRef limit[2] = {CFNumberCreate(NULL, kCFNumberSInt64Type, &bytes),
179
+ CFNumberCreate(NULL, kCFNumberDoubleType, &seconds)};
180
+ CFArrayRef limits = CFArrayCreate(NULL, (const void **)limit, 2, &kCFTypeArrayCallBacks);
181
+ status = VTSessionSetProperty(self->session, kVTCompressionPropertyKey_DataRateLimits, limits);
182
+ CFRelease(limits);
183
+ CFRelease(limit[0]);
184
+ CFRelease(limit[1]);
185
+ return status;
186
+ }
187
+
188
+ static void close_session(dl_vt_encoder *self) {
189
+ if (self->session != NULL) {
190
+ VTCompressionSessionInvalidate(self->session);
191
+ CFRelease(self->session);
192
+ self->session = NULL;
193
+ }
194
+ }
195
+
196
+ static OSStatus open_session(dl_vt_encoder *self, int low_latency, int bitrate_kbps) {
197
+ CFMutableDictionaryRef spec = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks,
198
+ &kCFTypeDictionaryValueCallBacks);
199
+ /* Required, not just enabled: the backend reports itself as hardware, and
200
+ * a software fallback would code 4K at a fraction of the frame rate. */
201
+ CFDictionarySetValue(spec, kVTVideoEncoderSpecification_RequireHardwareAcceleratedVideoEncoder,
202
+ kCFBooleanTrue);
203
+ if (low_latency) {
204
+ CFDictionarySetValue(spec, kVTVideoEncoderSpecification_EnableLowLatencyRateControl,
205
+ kCFBooleanTrue);
206
+ }
207
+ /* NV12 is what every hardware encoder takes without an internal copy. */
208
+ int32_t pixel_format = kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange;
209
+ CFNumberRef format_number = CFNumberCreate(NULL, kCFNumberSInt32Type, &pixel_format);
210
+ CFMutableDictionaryRef source = CFDictionaryCreateMutable(
211
+ NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
212
+ CFDictionarySetValue(source, kCVPixelBufferPixelFormatTypeKey, format_number);
213
+ CFRelease(format_number);
214
+
215
+ OSStatus status = VTCompressionSessionCreate(NULL, self->width, self->height,
216
+ kCMVideoCodecType_H264, spec, source, NULL,
217
+ on_output, self, &self->session);
218
+ CFRelease(spec);
219
+ CFRelease(source);
220
+ if (status != noErr) {
221
+ self->session = NULL;
222
+ return status;
223
+ }
224
+ self->low_latency = low_latency;
225
+ VTSessionRef s = self->session;
226
+ /* Constrained Baseline is what every receiver decodes, an iPhone's hardware
227
+ * decoder included; the profile itself rules out B-frames and CABAC. */
228
+ status = VTSessionSetProperty(s, kVTCompressionPropertyKey_ProfileLevel,
229
+ kVTProfileLevel_H264_ConstrainedBaseline_AutoLevel);
230
+ if (status == noErr) {
231
+ status = VTSessionSetProperty(s, kVTCompressionPropertyKey_RealTime, kCFBooleanTrue);
232
+ }
233
+ if (status == noErr) {
234
+ status = VTSessionSetProperty(s, kVTCompressionPropertyKey_AllowFrameReordering,
235
+ kCFBooleanFalse);
236
+ }
237
+ /* Key frames only when asked, as with VP9: on a desktop a periodic IDR is
238
+ * the most expensive frame and the one that softens a sharp picture. */
239
+ if (status == noErr) {
240
+ status = set_int(s, kVTCompressionPropertyKey_MaxKeyFrameInterval, INT32_MAX);
241
+ }
242
+ if (status == noErr) {
243
+ status = set_int(s, kVTCompressionPropertyKey_ExpectedFrameRate, self->fps);
244
+ }
245
+ /* The converter produces BT.601 limited range, as the VP9 path signals;
246
+ * without these in the VUI a receiver guesses BT.709 for HD and shifts
247
+ * every colour. */
248
+ if (status == noErr) {
249
+ status = VTSessionSetProperty(s, kVTCompressionPropertyKey_YCbCrMatrix,
250
+ kCVImageBufferYCbCrMatrix_ITU_R_601_4);
251
+ }
252
+ if (status == noErr) {
253
+ status = VTSessionSetProperty(s, kVTCompressionPropertyKey_ColorPrimaries,
254
+ kCVImageBufferColorPrimaries_SMPTE_C);
255
+ }
256
+ if (status == noErr) {
257
+ status = VTSessionSetProperty(s, kVTCompressionPropertyKey_TransferFunction,
258
+ kCVImageBufferTransferFunction_ITU_R_709_2);
259
+ }
260
+ if (status == noErr) {
261
+ status = set_rate(self, bitrate_kbps);
262
+ }
263
+ if (status == noErr) {
264
+ status = set_int(s, kVTCompressionPropertyKey_MaxAllowedFrameQP, DL_VT_MOTION_MAX_QP);
265
+ }
266
+ if (status == noErr) {
267
+ status = VTCompressionSessionPrepareToEncodeFrames(self->session);
268
+ }
269
+ if (status != noErr) {
270
+ close_session(self);
271
+ }
272
+ return status;
273
+ }
274
+
275
+ void dl_vt_destroy(dl_vt_encoder *self) {
276
+ if (self == NULL) {
277
+ return;
278
+ }
279
+ close_session(self);
280
+ free(self->out);
281
+ free(self);
282
+ }
283
+
284
+ /* Low-latency mode first; where the encoder will not do it for this profile or
285
+ * size, the ordinary real-time mode. Returns the last refusal's status, with
286
+ * *out left NULL. */
287
+ OSStatus dl_vt_create(int width, int height, int bitrate_kbps, int fps, dl_vt_encoder **out) {
288
+ *out = NULL;
289
+ if (width <= 0 || height <= 0 || bitrate_kbps <= 0 || fps <= 0) {
290
+ return kVTParameterErr;
291
+ }
292
+ dl_vt_encoder *self = calloc(1, sizeof(dl_vt_encoder));
293
+ if (self == NULL) {
294
+ return kVTAllocationFailedErr;
295
+ }
296
+ self->width = width;
297
+ self->height = height;
298
+ self->fps = fps;
299
+ OSStatus status = open_session(self, 1, bitrate_kbps);
300
+ if (status != noErr) {
301
+ status = open_session(self, 0, bitrate_kbps);
302
+ }
303
+ if (status != noErr) {
304
+ dl_vt_destroy(self);
305
+ return status;
306
+ }
307
+ *out = self;
308
+ return noErr;
309
+ }
310
+
311
+ int dl_vt_low_latency(const dl_vt_encoder *self) { return self->low_latency; }
312
+
313
+ static OSStatus copy_i420(dl_vt_encoder *self, const uint8_t *i420, CVPixelBufferRef *out) {
314
+ CVPixelBufferPoolRef pool = VTCompressionSessionGetPixelBufferPool(self->session);
315
+ if (pool == NULL) {
316
+ return kVTAllocationFailedErr;
317
+ }
318
+ CVPixelBufferRef buffer = NULL;
319
+ CVReturn made = CVPixelBufferPoolCreatePixelBuffer(NULL, pool, &buffer);
320
+ if (made != kCVReturnSuccess) {
321
+ return made;
322
+ }
323
+ CVReturn locked = CVPixelBufferLockBaseAddress(buffer, 0);
324
+ if (locked != kCVReturnSuccess) {
325
+ CVPixelBufferRelease(buffer);
326
+ return locked;
327
+ }
328
+ int w = self->width, h = self->height;
329
+ uint8_t *y = CVPixelBufferGetBaseAddressOfPlane(buffer, 0);
330
+ size_t y_stride = CVPixelBufferGetBytesPerRowOfPlane(buffer, 0);
331
+ for (int row = 0; row < h; row++) {
332
+ memcpy(y + row * y_stride, i420 + (size_t)row * w, w);
333
+ }
334
+ const uint8_t *u = i420 + (size_t)w * h;
335
+ const uint8_t *v = u + (size_t)(w / 2) * (h / 2);
336
+ uint8_t *uv = CVPixelBufferGetBaseAddressOfPlane(buffer, 1);
337
+ size_t uv_stride = CVPixelBufferGetBytesPerRowOfPlane(buffer, 1);
338
+ for (int row = 0; row < h / 2; row++) {
339
+ uint8_t *dst = uv + row * uv_stride;
340
+ const uint8_t *su = u + (size_t)row * (w / 2);
341
+ const uint8_t *sv = v + (size_t)row * (w / 2);
342
+ for (int x = 0; x < w / 2; x++) {
343
+ dst[2 * x] = su[x];
344
+ dst[2 * x + 1] = sv[x];
345
+ }
346
+ }
347
+ CVPixelBufferUnlockBaseAddress(buffer, 0);
348
+ *out = buffer;
349
+ return noErr;
350
+ }
351
+
352
+ /* Offer one picture to the current session. Low-latency rate control skips a
353
+ * frame to pay for a large one (an IDR or a refinement); the same picture then
354
+ * goes in again, stamped later each time, which is normally enough. */
355
+ static OSStatus submit(dl_vt_encoder *self, const uint8_t *i420, int force_keyframe, int max_qp) {
356
+ CVPixelBufferRef buffer = NULL;
357
+ OSStatus status = copy_i420(self, i420, &buffer);
358
+ if (status != noErr) {
359
+ return status;
360
+ }
361
+ int refine = max_qp < DL_VT_MOTION_MAX_QP;
362
+ int bitrate_kbps = self->bitrate_kbps;
363
+ if (refine) {
364
+ status = self->low_latency
365
+ ? set_int(self->session, kVTCompressionPropertyKey_MaxAllowedFrameQP, max_qp)
366
+ : set_rate(self, self->bitrate_kbps * DL_VT_REFINE_RATE_SCALE);
367
+ }
368
+ CFDictionaryRef options = NULL;
369
+ if (status == noErr && force_keyframe) {
370
+ const void *key = kVTEncodeFrameOptionKey_ForceKeyFrame;
371
+ const void *value = kCFBooleanTrue;
372
+ options = CFDictionaryCreate(NULL, &key, &value, 1, &kCFTypeDictionaryKeyCallBacks,
373
+ &kCFTypeDictionaryValueCallBacks);
374
+ }
375
+ int64_t gap = 1;
376
+ for (int attempt = 0; status == noErr && attempt < DL_VT_ATTEMPTS; attempt++, gap *= 2) {
377
+ self->out_size = 0;
378
+ self->out_is_key = 0;
379
+ self->out_ready = 0;
380
+ self->out_status = noErr;
381
+ CMTime pts = CMTimeMake(self->frame_index, self->fps);
382
+ self->frame_index += gap;
383
+ status = VTCompressionSessionEncodeFrame(self->session, buffer, pts, CMTimeMake(1, self->fps),
384
+ options, NULL, NULL);
385
+ if (status == noErr) {
386
+ status = VTCompressionSessionCompleteFrames(self->session, kCMTimeInvalid);
387
+ }
388
+ if (status == noErr) {
389
+ status = self->out_ready ? self->out_status : kVTVideoEncoderMalfunctionErr;
390
+ }
391
+ if (status == DL_VT_DROPPED && attempt + 1 < DL_VT_ATTEMPTS) {
392
+ status = noErr;
393
+ } else {
394
+ break;
395
+ }
396
+ }
397
+ if (refine) {
398
+ OSStatus restored =
399
+ self->low_latency
400
+ ? set_int(self->session, kVTCompressionPropertyKey_MaxAllowedFrameQP,
401
+ DL_VT_MOTION_MAX_QP)
402
+ : set_rate(self, bitrate_kbps);
403
+ if (status == noErr) {
404
+ status = restored;
405
+ }
406
+ }
407
+ if (options != NULL) {
408
+ CFRelease(options);
409
+ }
410
+ CVPixelBufferRelease(buffer);
411
+ return status;
412
+ }
413
+
414
+ /* Encode one tightly packed I420 frame. With `max_qp` below H.264's 51 the
415
+ * frame is a refinement pass, coded under that ceiling. The access unit is
416
+ * available through dl_vt_out_* until the next call.
417
+ *
418
+ * At a rate too low for the size, low-latency rate control skips frame after
419
+ * frame (1080p at a few hundred kbps); the session needs every frame coded, so
420
+ * it moves to the ordinary real-time mode, which has not been seen to skip
421
+ * at any rate, and this picture becomes the new session's IDR. */
422
+ OSStatus dl_vt_encode(dl_vt_encoder *self, const uint8_t *i420, int force_keyframe, int max_qp) {
423
+ if (self->session == NULL) {
424
+ return kVTInvalidSessionErr; /* a failed fallback reopen */
425
+ }
426
+ OSStatus status = submit(self, i420, force_keyframe, max_qp);
427
+ if (status == DL_VT_DROPPED && self->low_latency) {
428
+ close_session(self);
429
+ status = open_session(self, 0, self->bitrate_kbps);
430
+ if (status == noErr) {
431
+ status = submit(self, i420, 1, max_qp);
432
+ }
433
+ }
434
+ return status;
435
+ }
436
+
437
+ OSStatus dl_vt_set_bitrate(dl_vt_encoder *self, int bitrate_kbps) {
438
+ if (self->session == NULL) {
439
+ return kVTInvalidSessionErr;
440
+ }
441
+ return bitrate_kbps > 0 ? set_rate(self, bitrate_kbps) : kVTParameterErr;
442
+ }
443
+
444
+ const uint8_t *dl_vt_out_data(const dl_vt_encoder *self) { return self->out; }
445
+
446
+ size_t dl_vt_out_size(const dl_vt_encoder *self) { return self->out_size; }
447
+
448
+ int dl_vt_out_is_key(const dl_vt_encoder *self) { return self->out_is_key; }