FreeRDP
Loading...
Searching...
No Matches
h264_webcodecs.c
1
18/*
19 * The H.264 decompress callback runs on the graphics channel thread (a Web Worker) and must
20 * return the picture synchronously, while VideoDecoder delivers frames asynchronously on the
21 * event loop of the thread that owns it. The worker therefore proxies each job to the main
22 * browser thread with emscripten_proxy_sync_with_ctx() and sleeps until the main thread has
23 * decoded the frame, copied it into the I420 planes and called emscripten_proxy_finish().
24 *
25 * The planes keep the decoded sample values (no colour conversion in the browser), so
26 * h264.c converts them and combines the AVC444 main and auxiliary pictures as it does for
27 * every other backend.
28 */
29
30#include <freerdp/config.h>
31
32#include <stdio.h>
33#include <string.h>
34
35#include <emscripten.h>
36#include <emscripten/proxying.h>
37#include <emscripten/threading.h>
38
39#include <winpr/assert.h>
40#include <winpr/string.h>
41#include <freerdp/log.h>
42
43#include "h264.h"
44
45#define H264_WC_NEEDS_BUFFER 2 /* picture decoded but larger than the planes */
46
47typedef struct
48{
49 int handle;
50 BYTE* params; /* last SPS/PPS NAL units, each with a 4 byte start code */
51 size_t paramsSize;
52 BYTE* yuv; /* Y, U and V planes in one allocation */
53 UINT32 width;
54 UINT32 height;
55 UINT32 stride[3];
56 BYTE* plane[3];
57} H264_CONTEXT_WEBCODECS;
58
59typedef struct
60{
61 int handle;
62 const BYTE* src;
63 UINT32 size;
64 int key;
65 const char* codec;
66 BYTE* const* plane;
67 const UINT32* stride;
68 UINT32 maxWidth;
69 UINT32 maxHeight;
70 UINT32 dim[2]; /* decoded width and height */
71 int rc; /* 1: picture copied, 0: no picture, 2: planes too small, <0: error */
72} h264_wc_job;
73
74/* in h264_webcodecs.js */
75extern void h264_wc_js_open(void* ctx, int* handle);
76extern void h264_wc_js_close(int handle);
77extern void h264_wc_js_decode(void* ctx, int handle, const BYTE* src, UINT32 size, int key,
78 const char* codec, BYTE* y, BYTE* u, BYTE* v, UINT32 ys, UINT32 cs,
79 UINT32 maxWidth, UINT32 maxHeight, UINT32* dim, int* rc);
80extern void h264_wc_js_copy(void* ctx, int handle, BYTE* y, BYTE* u, BYTE* v, UINT32 ys, UINT32 cs,
81 int* rc);
82
83EMSCRIPTEN_KEEPALIVE void freerdp_h264_wc_finish(em_proxying_ctx* ctx)
84{
85 emscripten_proxy_finish(ctx);
86}
87
88static void main_open(em_proxying_ctx* ctx, void* arg)
89{
90 h264_wc_js_open(ctx, arg);
91}
92
93static void main_close(void* arg)
94{
95 h264_wc_js_close(*(int*)arg);
96}
97
98static void main_decode(em_proxying_ctx* ctx, void* arg)
99{
100 h264_wc_job* job = arg;
101 h264_wc_js_decode(ctx, job->handle, job->src, job->size, job->key, job->codec, job->plane[0],
102 job->plane[1], job->plane[2], job->stride[0], job->stride[1], job->maxWidth,
103 job->maxHeight, job->dim, &job->rc);
104}
105
106static void main_copy(em_proxying_ctx* ctx, void* arg)
107{
108 h264_wc_job* job = arg;
109 h264_wc_js_copy(ctx, job->handle, job->plane[0], job->plane[1], job->plane[2], job->stride[0],
110 job->stride[1], &job->rc);
111}
112
113WINPR_ATTR_NODISCARD
114static BOOL on_main(void (*fn)(em_proxying_ctx*, void*), void* arg)
115{
116 return emscripten_proxy_sync_with_ctx(emscripten_proxy_get_system_queue(),
117 emscripten_main_runtime_thread_id(), fn, arg) == 1;
118}
119
120/* Finds the next Annex B NAL unit; trailing zero bytes belong to the next start code. */
121WINPR_ATTR_NODISCARD
122static BOOL next_nal(const BYTE* data, size_t size, size_t* offset, const BYTE** nal,
123 size_t* length)
124{
125 size_t pos = *offset;
126 if ((size < 3) || (pos == SIZE_MAX - size))
127 return FALSE;
128 while ((pos <= size - 3ull) &&
129 !((data[pos] == 0) && (data[pos + 1] == 0) && (data[pos + 2] == 1)))
130 pos++;
131 if (pos >= size - 3ull)
132 return FALSE;
133
134 const size_t start = pos + 3;
135 pos = start;
136 while ((pos <= size - 3ull) &&
137 !((data[pos] == 0) && (data[pos + 1] == 0) && (data[pos + 2] == 1)))
138 pos++;
139 size_t end = (pos <= size - 3ull) ? pos : size;
140 while ((end > start) && (data[end - 1] == 0))
141 end--;
142 *offset = end;
143 *nal = &data[start];
144 *length = end - start;
145 return TRUE;
146}
147
148/* Remembers the parameter sets: they may arrive separately from the picture (MS-RDPEVOR),
149 * and a decoder that is created again after an error needs them with the next IDR picture. */
150WINPR_ATTR_NODISCARD
151static BOOL save_parameters(H264_CONTEXT_WEBCODECS* sys, const BYTE* data, size_t size, BOOL hasSps)
152{
153 size_t total = hasSps ? 0 : sys->paramsSize;
154 size_t offset = 0;
155 const BYTE* nal = nullptr;
156 size_t length = 0;
157 while (next_nal(data, size, &offset, &nal, &length))
158 {
159 if (length == 0)
160 continue;
161 const BYTE type = nal[0] & 0x1F;
162 if ((type == 7) && (length < 4))
163 continue;
164 if ((type == 7) || (type == 8))
165 total += 4 + length;
166 }
167
168 BYTE* params = malloc(total);
169 if (!params)
170 return FALSE;
171 size_t pos = 0;
172 if (!hasSps && (sys->paramsSize > 0))
173 {
174 memcpy(params, sys->params, sys->paramsSize);
175 pos = sys->paramsSize;
176 }
177 offset = 0;
178 while (next_nal(data, size, &offset, &nal, &length))
179 {
180 if (length == 0)
181 continue;
182 const BYTE type = nal[0] & 0x1F;
183 if ((type == 7) && (length < 4))
184 continue;
185 if ((type != 7) && (type != 8))
186 continue;
187 const BYTE startCode[] = { 0, 0, 0, 1 };
188 memcpy(&params[pos], startCode, sizeof(startCode));
189 memcpy(&params[pos + 4], nal, length);
190 pos += 4 + length;
191 }
192 WINPR_ASSERT(pos == total);
193 free(sys->params);
194 sys->params = params;
195 sys->paramsSize = total;
196 return TRUE;
197}
198
199WINPR_ATTR_NODISCARD
200static BOOL ensure_planes(H264_CONTEXT_WEBCODECS* sys, UINT32 width, UINT32 height)
201{
202 if (sys->yuv && (width <= sys->width) && (height <= sys->height))
203 return TRUE;
204 width = MAX(width, sys->width);
205 height = MAX(height, sys->height);
206 if ((width == 0) || (height == 0) || (width > 16384) || (height > 16384))
207 return FALSE;
208
209 /* same padding as the encoder planes in h264.c; relaxes bounds checks in the converters */
210 const UINT32 ystride = width + 32 - width % 16;
211 const UINT32 cstride = (ystride + 1) / 2;
212 const size_t lines = height + 32 - height % 16;
213 const size_t ysize = (size_t)ystride * lines;
214 const size_t csize = (size_t)cstride * (lines / 2);
215 BYTE* yuv = winpr_aligned_calloc(ysize + 2 * csize, 1, 16);
216 if (!yuv)
217 return FALSE;
218 winpr_aligned_free(sys->yuv);
219 sys->yuv = yuv;
220 sys->width = width;
221 sys->height = height;
222 sys->stride[0] = ystride;
223 sys->stride[1] = cstride;
224 sys->stride[2] = cstride;
225 /* h264_wc_js_copy expects the planes in this order, in one buffer */
226 sys->plane[0] = yuv;
227 sys->plane[1] = &yuv[ysize];
228 sys->plane[2] = &yuv[ysize + csize];
229 return TRUE;
230}
231
232WINPR_ATTR_NODISCARD
233static int webcodecs_decompress(H264_CONTEXT* WINPR_RESTRICT h264,
234 const BYTE* WINPR_RESTRICT pSrcData, UINT32 SrcSize)
235{
236 WINPR_ASSERT(h264);
237 WINPR_ASSERT(pSrcData || (SrcSize == 0));
238
239 H264_CONTEXT_WEBCODECS* sys = h264->pSystemData;
240 WINPR_ASSERT(sys);
241
242 if (emscripten_is_main_browser_thread())
243 return -1; /* the main thread cannot block for its own asynchronous decode */
244
245 BOOL hasSlice = FALSE;
246 BOOL hasIdr = FALSE;
247 BOOL hasSps = FALSE;
248 BOOL hasPps = FALSE;
249 size_t offset = 0;
250 const BYTE* nal = nullptr;
251 size_t length = 0;
252 while (next_nal(pSrcData, SrcSize, &offset, &nal, &length))
253 {
254 if (length == 0)
255 continue;
256 const BYTE type = nal[0] & 0x1F;
257 hasSlice |= (type == 1) || (type == 5);
258 hasIdr |= (type == 5);
259 hasSps |= (type == 7);
260 hasPps |= (type == 8);
261 }
262 if ((hasSps || hasPps) && !save_parameters(sys, pSrcData, SrcSize, hasSps))
263 return -1;
264 if (!hasSlice)
265 return 0; /* parameter sets only, no picture */
266
267 /* avc1.PPCCLL from profile_idc, constraint flags and level_idc of the first SPS */
268 char codec[16] = "avc1.42e01f";
269 if ((sys->paramsSize > 7) && ((sys->params[4] & 0x1F) == 7))
270 (void)_snprintf(codec, sizeof(codec), "avc1.%02x%02x%02x", sys->params[5], sys->params[6],
271 sys->params[7]);
272
273 /* an IDR picture without in-band SPS/PPS gets the saved ones */
274 const BYTE* src = pSrcData;
275 UINT32 size = SrcSize;
276 BYTE* combined = nullptr;
277 if (hasIdr && !hasSps && (sys->paramsSize > 0))
278 {
279 if (sys->paramsSize > UINT32_MAX - SrcSize)
280 return -1;
281 size = (UINT32)sys->paramsSize + SrcSize;
282 combined = malloc(size);
283 if (!combined)
284 return -1;
285 memcpy(combined, sys->params, sys->paramsSize);
286 memcpy(&combined[sys->paramsSize], pSrcData, SrcSize);
287 src = combined;
288 }
289
290 int rc = -1;
291 h264_wc_job job = { .handle = sys->handle,
292 .src = src,
293 .size = size,
294 .key = hasIdr,
295 .codec = codec,
296 .plane = sys->plane,
297 .stride = sys->stride,
298 .rc = -1 };
299 if (!ensure_planes(sys, MAX(h264->width, 16), MAX(h264->height, 16)))
300 goto fail;
301 job.maxWidth = sys->width;
302 job.maxHeight = sys->height;
303 if (!on_main(main_decode, &job))
304 goto fail;
305 if (job.rc == H264_WC_NEEDS_BUFFER)
306 {
307 job.rc = -1;
308 if (!ensure_planes(sys, job.dim[0], job.dim[1]) || !on_main(main_copy, &job))
309 goto fail;
310 }
311 rc = job.rc;
312 if (rc < 0)
313 WLog_Print(h264->log, WLOG_WARN, "WebCodecs H.264 decode failed (%d)", rc);
314 if (rc != 1)
315 goto fail;
316
317 for (size_t x = 0; x < 3; x++)
318 {
319 h264->pYUVData[x] = sys->plane[x];
320 h264->iStride[x] = sys->stride[x];
321 }
322 h264->YUVWidth = job.dim[0];
323 h264->YUVHeight = job.dim[1];
324fail:
325 free(combined);
326 return rc;
327}
328
329static void webcodecs_uninit(H264_CONTEXT* h264)
330{
331 WINPR_ASSERT(h264);
332
333 H264_CONTEXT_WEBCODECS* sys = h264->pSystemData;
334 if (!sys)
335 return;
336 if ((sys->handle > 0) && !emscripten_is_main_browser_thread())
337 (void)emscripten_proxy_sync(emscripten_proxy_get_system_queue(),
338 emscripten_main_runtime_thread_id(), main_close, &sys->handle);
339 else if (sys->handle > 0)
340 main_close(&sys->handle);
341 for (size_t x = 0; x < 3; x++)
342 h264->pYUVData[x] = nullptr;
343 winpr_aligned_free(sys->yuv);
344 free(sys->params);
345 free(sys);
346 h264->pSystemData = nullptr;
347}
348
349WINPR_ATTR_NODISCARD
350static BOOL webcodecs_init(H264_CONTEXT* h264)
351{
352 WINPR_ASSERT(h264);
353
354 /* decoder only; the main thread cannot wait for the asynchronous VideoDecoder */
355 if (h264->Compressor || emscripten_is_main_browser_thread())
356 return FALSE;
357
358 H264_CONTEXT_WEBCODECS* sys = calloc(1, sizeof(*sys));
359 if (!sys)
360 return FALSE;
361 if (!on_main(main_open, &sys->handle) || (sys->handle <= 0))
362 {
363 WLog_Print(h264->log, WLOG_DEBUG, "the browser cannot decode H.264 with WebCodecs");
364 free(sys);
365 return FALSE;
366 }
367 h264->pSystemData = sys;
368 return TRUE;
369}
370
371const H264_CONTEXT_SUBSYSTEM g_Subsystem_WebCodecs = { "WebCodecs", webcodecs_init,
372 webcodecs_uninit, webcodecs_decompress,
373 nullptr };