FreeRDP
Loading...
Searching...
No Matches
av1_webcodecs.c
1
18/*
19 * freerdp_av1_decompress() runs on the graphics channel thread (a Web Worker) and needs the
20 * picture back synchronously, while VideoDecoder delivers frames asynchronously on the event
21 * loop of the thread that owns it. The worker therefore proxies each job to the main browser
22 * thread with emscripten_proxy_sync_with_ctx() and sleeps until the main thread has decoded
23 * the frame, copied it into the destination surface and called emscripten_proxy_finish().
24 *
25 * VideoFrame.copyTo() converts to the surface's RGB layout directly, so the browser (GPU or
26 * its native decoder) does both the decoding and the colour conversion, honouring the colour
27 * space signalled in the bitstream.
28 */
29
30#include <freerdp/config.h>
31
32#include <emscripten.h>
33#include <emscripten/proxying.h>
34#include <emscripten/threading.h>
35
36#include <winpr/assert.h>
37#include <freerdp/codec/color.h>
38
39#include "av1_webcodecs.h"
40
41typedef struct
42{
43 int handle;
44 const BYTE* src;
45 UINT32 size;
46 BYTE* dst;
47 const char* format;
48 UINT32 stride;
49 UINT32 width;
50 UINT32 height;
51 int key;
52 int profile;
53 int rc; /* 1: picture written, 0: no picture, <0: error */
54} av1_wc_job;
55
56/* in av1_webcodecs.js */
57extern int av1_wc_js_available(void);
58extern int av1_wc_js_open(void);
59extern void av1_wc_js_close(int handle);
60extern void av1_wc_js_decode(void* ctx, int handle, const BYTE* src, UINT32 size, BYTE* dst,
61 const char* formatName, UINT32 stride, UINT32 width, UINT32 height,
62 int key, int profile, int* rc);
63
64EMSCRIPTEN_KEEPALIVE void freerdp_av1_wc_finish(em_proxying_ctx* ctx)
65{
66 emscripten_proxy_finish(ctx);
67}
68
69static void main_available(void* arg)
70{
71 *(int*)arg = av1_wc_js_available();
72}
73
74static void main_open(void* arg)
75{
76 *(int*)arg = av1_wc_js_open();
77}
78
79static void main_close(void* arg)
80{
81 av1_wc_js_close(*(int*)arg);
82}
83
84static void main_decode(em_proxying_ctx* ctx, void* arg)
85{
86 av1_wc_job* job = arg;
87 WINPR_ASSERT(job);
88
89 av1_wc_js_decode(ctx, job->handle, job->src, job->size, job->dst, job->format, job->stride,
90 job->width, job->height, job->key, job->profile, &job->rc);
91}
92
93static BOOL on_main(void (*fn)(void*), void* arg)
94{
95 WINPR_ASSERT(fn);
96
97 if (emscripten_is_main_browser_thread())
98 {
99 fn(arg);
100 return TRUE;
101 }
102 return emscripten_proxy_sync(emscripten_proxy_get_system_queue(),
103 emscripten_main_runtime_thread_id(), fn, arg) == 1;
104}
105
106int av1_wc_open(void)
107{
108 int available = 0;
109 if (!on_main(main_available, &available) || !available)
110 return 0;
111 int handle = 0;
112 if (!on_main(main_open, &handle))
113 return 0;
114 return handle;
115}
116
117void av1_wc_close(int handle)
118{
119 if (handle > 0)
120 (void)on_main(main_close, &handle);
121}
122
123/* AV1 low overhead bitstream: a key frame carries a sequence header OBU (type 1), whose
124 * first 3 bits are seq_profile. Returns the profile, or -1 if there is no sequence header. */
125static int sequence_header_profile(const BYTE* p, UINT32 size)
126{
127 UINT32 pos = 0;
128 while (pos < size)
129 {
130 const BYTE header = p[pos];
131 const int type = (header >> 3) & 0x0f;
132 const int has_extension = (header >> 2) & 1;
133 const int has_size = (header >> 1) & 1;
134 pos += 1 + (UINT32)has_extension;
135 if (!has_size)
136 return (type == 1 && pos < size) ? (p[pos] >> 5) : -1;
137 UINT64 len = 0;
138 for (int i = 0; i < 8 && pos < size; i++)
139 {
140 const BYTE b = p[pos++];
141 len |= (UINT64)(b & 0x7f) << (7 * i);
142 if (!(b & 0x80))
143 break;
144 }
145 if (type == 1)
146 return (pos < size) ? (p[pos] >> 5) : -1;
147 if (len > size - pos)
148 return -1;
149 pos += (UINT32)len;
150 }
151 return -1;
152}
153
154static const char* copy_format(DWORD DstFormat)
155{
156 switch (DstFormat)
157 {
158 case PIXEL_FORMAT_BGRA32:
159 return "BGRA";
160 case PIXEL_FORMAT_BGRX32:
161 return "BGRX";
162 case PIXEL_FORMAT_RGBA32:
163 return "RGBA";
164 case PIXEL_FORMAT_RGBX32:
165 return "RGBX";
166 default:
167 return nullptr;
168 }
169}
170
171INT32 av1_wc_decode(int handle, const BYTE* pSrcData, UINT32 SrcSize, BYTE* pDstData,
172 DWORD DstFormat, UINT32 nDstStep, UINT32 nDstWidth, UINT32 nDstHeight)
173{
174 const char* format = copy_format(DstFormat);
175 if (!format)
176 return AV1_WC_UNSUPPORTED; /* copyTo() only writes 32 bit RGB: let dav1d handle it */
177 if (emscripten_is_main_browser_thread())
178 return -1; /* the main thread cannot block for its own asynchronous decode */
179
180 const int profile = sequence_header_profile(pSrcData, SrcSize);
181 av1_wc_job job = { .handle = handle,
182 .src = pSrcData,
183 .size = SrcSize,
184 .dst = pDstData,
185 .format = format,
186 .stride = nDstStep,
187 .width = nDstWidth,
188 .height = nDstHeight,
189 .key = profile >= 0,
190 .profile = profile,
191 .rc = -1 };
192 if (emscripten_proxy_sync_with_ctx(emscripten_proxy_get_system_queue(),
193 emscripten_main_runtime_thread_id(), main_decode, &job) != 1)
194 return -1;
195 return job.rc;
196}