30#include <freerdp/config.h>
32#include <emscripten.h>
33#include <emscripten/proxying.h>
34#include <emscripten/threading.h>
36#include <winpr/assert.h>
37#include <freerdp/codec/color.h>
39#include "av1_webcodecs.h"
57EM_JS(
int, av1_wc_js_available, (
void), {
58 return (typeof VideoDecoder ===
"function" && typeof EncodedVideoChunk ===
"function") ? 1 : 0;
61EM_JS(
int, av1_wc_js_open, (
void), {
62 const reg = (Module.freerdpAv1 = Module.freerdpAv1 || { next : 1, decoders :
new Map() });
63 const handle = reg.next++;
64 reg.decoders.set(handle, { decoder : null, profile : -1, pending : null });
68EM_JS(
void, av1_wc_js_close, (
int handle), {
69 const reg = Module.freerdpAv1;
70 const d = reg?.decoders.get(handle);
73 try { d.decoder?.close(); }
catch (e) {}
74 reg.decoders.delete(handle);
79EM_JS(
void, av1_wc_js_decode, (
void* ctx,
int handle,
const BYTE* src, UINT32 size, BYTE* dst,
80 const char* formatName, UINT32 stride, UINT32 width, UINT32 height,
81 int key,
int profile,
int* rc), {
82 src >>>= 0; size >>>= 0; dst >>>= 0; stride >>>= 0; width >>>= 0; height >>>= 0;
83 const format = UTF8ToString(formatName);
84 const finish = (r) => { HEAP32[rc >> 2] = r; _freerdp_av1_wc_finish(ctx); };
85 const d = Module.freerdpAv1?.decoders.get(handle);
90 if (!d.decoder || d.decoder.state ===
"closed" || (key && profile >= 0 && profile !== d.profile)) {
93 try { d.decoder?.close(); }
catch (e) {}
95 d.decoder =
new VideoDecoder({
99 if (!p) { frame.close();
return; }
100 clearTimeout(p.timer);
101 const w = Math.min(frame.displayWidth, p.width), h = Math.min(frame.displayHeight, p.height);
102 const view = HEAPU8.subarray(p.dst, p.dst + p.stride * h);
103 frame.copyTo(view, { rect : { x : 0, y : 0, width : w, height : h },
104 layout : [ { offset : 0, stride : p.stride } ], format : p.format })
105 .then(() => { frame.close(); p.finish(1); },
106 (e) => { frame.close(); console.error(
"[av1-webcodecs] copyTo", e); p.finish(-3); });
109 console.error(
"[av1-webcodecs] decoder error", e);
112 if (p) { clearTimeout(p.timer); p.finish(-2); }
115 d.decoder.configure({ codec : profile === 1 ?
"av01.1.13M.08" :
"av01.0.13M.08",
116 optimizeForLatency :
true, hardwareAcceleration :
"no-preference" });
119 d.pending = { dst, format, stride, width, height, finish,
121 timer : setTimeout(() => {
if (d.pending) { d.pending = null; finish(0); } }, 1000) };
123 const data = HEAPU8.slice(src, src + size);
124 d.decoder.decode(
new EncodedVideoChunk({ type : key ?
"key" :
"delta", timestamp : 0, data }));
126 console.error(
"[av1-webcodecs] decode", e);
127 clearTimeout(d.pending.timer);
134EMSCRIPTEN_KEEPALIVE
void freerdp_av1_wc_finish(em_proxying_ctx* ctx)
136 emscripten_proxy_finish(ctx);
139static void main_available(
void* arg)
141 *(
int*)arg = av1_wc_js_available();
144static void main_open(
void* arg)
146 *(
int*)arg = av1_wc_js_open();
149static void main_close(
void* arg)
151 av1_wc_js_close(*(
int*)arg);
154static void main_decode(em_proxying_ctx* ctx,
void* arg)
156 av1_wc_job* job = arg;
159 av1_wc_js_decode(ctx, job->handle, job->src, job->size, job->dst, job->format, job->stride,
160 job->width, job->height, job->key, job->profile, &job->rc);
163static BOOL on_main(
void (*fn)(
void*),
void* arg)
167 if (emscripten_is_main_browser_thread())
172 return emscripten_proxy_sync(emscripten_proxy_get_system_queue(),
173 emscripten_main_runtime_thread_id(), fn, arg) == 1;
179 if (!on_main(main_available, &available) || !available)
182 if (!on_main(main_open, &handle))
187void av1_wc_close(
int handle)
190 (void)on_main(main_close, &handle);
195static int sequence_header_profile(
const BYTE* p, UINT32 size)
200 const BYTE header = p[pos];
201 const int type = (header >> 3) & 0x0f;
202 const int has_extension = (header >> 2) & 1;
203 const int has_size = (header >> 1) & 1;
204 pos += 1 + (UINT32)has_extension;
206 return (type == 1 && pos < size) ? (p[pos] >> 5) : -1;
208 for (
int i = 0; i < 8 && pos < size; i++)
210 const BYTE b = p[pos++];
211 len |= (UINT64)(b & 0x7f) << (7 * i);
216 return (pos < size) ? (p[pos] >> 5) : -1;
217 if (len > size - pos)
224static const char* copy_format(DWORD DstFormat)
228 case PIXEL_FORMAT_BGRA32:
230 case PIXEL_FORMAT_BGRX32:
232 case PIXEL_FORMAT_RGBA32:
234 case PIXEL_FORMAT_RGBX32:
241INT32 av1_wc_decode(
int handle,
const BYTE* pSrcData, UINT32 SrcSize, BYTE* pDstData,
242 DWORD DstFormat, UINT32 nDstStep, UINT32 nDstWidth, UINT32 nDstHeight)
244 const char* format = copy_format(DstFormat);
246 return AV1_WC_UNSUPPORTED;
247 if (emscripten_is_main_browser_thread())
250 const int profile = sequence_header_profile(pSrcData, SrcSize);
251 av1_wc_job job = { .handle = handle,
258 .height = nDstHeight,
262 if (emscripten_proxy_sync_with_ctx(emscripten_proxy_get_system_queue(),
263 emscripten_main_runtime_thread_id(), main_decode, &job) != 1)