nandi/frqpublic Fork 0
87e5be936b4e930029a374fca9949728c86c4469
Commits
Clone
git clone https://git.rickub.com/nandi/frq.git
git clone ssh://git@rickub.com/nandi/frq.git

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

frq_h264.c · 132 lines · 4.8 KBC Blame HistoryRaw
Bind the C libraries a call actually needs 87e5be9 nandi 9d ago1/* A flat C face for openh264's encoder.
2 *
3 * WHY THIS EXISTS. openh264's C API is not flat. `ISVCEncoder` is
4 * `const ISVCEncoderVtbl*` — a pointer to a table of function pointers — so
5 * calling Initialize or EncodeFrame means dereferencing the object, reading a
6 * slot, and calling through it. jolt.ffi cannot do that: Chez fixes a foreign
7 * procedure's types when it COMPILES it, and the target has to be a literal C
8 * symbol name rather than a function pointer (see jolt/ffi.clj, "the target
9 * must be a literal C symbol name"). So the vtable is walked here, in C, and
10 * what jolt binds is the five plain symbols below.
11 *
12 * It is deliberately thin. No policy, no buffering beyond what openh264's own
13 * output demands, and no decisions that belong in frq — the shim exists to
14 * change a calling convention, not to be a video pipeline.
15 *
16 * THE ONE THING IT DOES DO is flatten the output. openh264 hands back an
17 * SFrameBSInfo describing up to MAX_LAYER_NUM_OF_FRAME layers, each with its
18 * own NAL count and a shared bitstream buffer. A caller wanting one Annex B
19 * frame has to walk that; doing it in jolt would mean reading nested C structs
20 * whose layout is openh264's business. It is copied into one contiguous
21 * buffer owned by the encoder handle and handed over as a borrowed span,
22 * valid until the next encode — which is exactly the contract frq.av already
23 * has for a video frame.
24 */
25#include <stdlib.h>
26#include <string.h>
27#include <stdint.h>
28#include <wels/codec_api.h>
29#include <wels/codec_app_def.h>
30
31typedef struct {
32 ISVCEncoder *enc;
33 unsigned char *out; /* flattened Annex B, grown as needed */
34 size_t out_cap;
35 int width, height;
36} frq_h264;
37
38/* Answers 0 on success, or openh264's own non-zero return. */
39int frq_h264_open(int width, int height, int fps, int bitrate, void **handle) {
40 ISVCEncoder *enc = NULL;
41 frq_h264 *h;
42 SEncParamBase p;
43 int rc;
44
45 *handle = NULL;
46 rc = WelsCreateSVCEncoder(&enc);
47 if (rc != 0 || enc == NULL) return rc ? rc : -1;
48
49 memset(&p, 0, sizeof(p));
50 p.iUsageType = CAMERA_VIDEO_REAL_TIME;
51 p.iPicWidth = width;
52 p.iPicHeight = height;
53 p.iTargetBitrate = bitrate;
54 p.fMaxFrameRate = (float)fps;
55
56 rc = (*enc)->Initialize(enc, &p);
57 if (rc != 0) { WelsDestroySVCEncoder(enc); return rc; }
58
59 h = (frq_h264 *)calloc(1, sizeof(frq_h264));
60 if (h == NULL) { (*enc)->Uninitialize(enc); WelsDestroySVCEncoder(enc); return -1; }
61 h->enc = enc; h->width = width; h->height = height;
62 *handle = h;
63 return 0;
64}
65
66/* Encode one I420 frame.
67 *
68 * `i420` is width*height luma followed by two (width/2)*(height/2) planes.
69 * On success answers 0 and sets *out / *out_len to a BORROWED span, valid
70 * until the next call on this handle. *keyframe says whether it is an IDR.
71 * A frame openh264 chose to skip answers 0 with *out_len == 0. */
72int frq_h264_encode(void *handle, const unsigned char *i420, long long pts_us,
73 const unsigned char **out, int *out_len, int *keyframe) {
74 frq_h264 *h = (frq_h264 *)handle;
75 SSourcePicture pic;
76 SFrameBSInfo info;
77 int rc, i, j, total = 0, off = 0;
78
79 *out = NULL; *out_len = 0; *keyframe = 0;
80
81 memset(&pic, 0, sizeof(pic));
82 pic.iPicWidth = h->width;
83 pic.iPicHeight = h->height;
84 pic.iColorFormat = videoFormatI420;
85 pic.iStride[0] = h->width;
86 pic.iStride[1] = h->width / 2;
87 pic.iStride[2] = h->width / 2;
88 pic.pData[0] = (unsigned char *)i420;
89 pic.pData[1] = pic.pData[0] + h->width * h->height;
90 pic.pData[2] = pic.pData[1] + (h->width / 2) * (h->height / 2);
91 pic.uiTimeStamp = pts_us / 1000; /* openh264 counts milliseconds */
92
93 memset(&info, 0, sizeof(info));
94 rc = (*h->enc)->EncodeFrame(h->enc, &pic, &info);
95 if (rc != cmResultSuccess) return rc;
96 if (info.eFrameType == videoFrameTypeSkip) return 0;
97
98 for (i = 0; i < info.iLayerNum; i++)
99 for (j = 0; j < info.sLayerInfo[i].iNalCount; j++)
100 total += info.sLayerInfo[i].pNalLengthInByte[j];
101
102 if ((size_t)total > h->out_cap) {
103 unsigned char *grown = (unsigned char *)realloc(h->out, (size_t)total);
104 if (grown == NULL) return -1;
105 h->out = grown; h->out_cap = (size_t)total;
106 }
107 for (i = 0; i < info.iLayerNum; i++) {
108 int n = 0, k;
109 for (k = 0; k < info.sLayerInfo[i].iNalCount; k++)
110 n += info.sLayerInfo[i].pNalLengthInByte[k];
111 memcpy(h->out + off, info.sLayerInfo[i].pBsBuf, (size_t)n);
112 off += n;
113 }
114
115 *out = h->out;
116 *out_len = total;
117 *keyframe = (info.eFrameType == videoFrameTypeIDR);
118 return 0;
119}
120
121int frq_h264_force_keyframe(void *handle) {
122 frq_h264 *h = (frq_h264 *)handle;
123 return (*h->enc)->ForceIntraFrame(h->enc, true);
124}
125
126void frq_h264_close(void *handle) {
127 frq_h264 *h = (frq_h264 *)handle;
128 if (h == NULL) return;
129 if (h->enc) { (*h->enc)->Uninitialize(h->enc); WelsDestroySVCEncoder(h->enc); }
130 free(h->out);
131 free(h);
132}