diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 5724f6c509e7813e0fa69de2e914ea3ddea04523..37d5e5345dc6039dcf028e3e536ef955be45c977 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,5 +1,5 @@ image: - name: registry.videolan.org/vlc-debian-wasm-emscripten:20211104155236 + name: registry.videolan.org/vlc-debian-wasm-emscripten:20220505193036 entrypoint: [] # See issue : https://gitlab.com/gitlab-org/gitlab-runner/-/issues/1170 stages: @@ -20,5 +20,12 @@ compile-job: - experimental* # worker.js, wasm file and js file, data... - assets/ # Assets for logos etc... - vlc.html # HTML page + - vlc_media_list_poc.html - lib/* # libvlc.js, module-loader.js, overlay.js, wasm-imports.js + - ./vlc/modules/gui/qt/pixmaps/toolbar/volume-muted.png + - ./vlc/modules/gui/qt/pixmaps/pause.png + - ./vlc/modules/gui/qt/pixmaps/toolbar/volume-medium.png + - ./vlc/modules/gui/qt/pixmaps/play.png + - ./vlc/modules/gui/qt/pixmaps/play_button.svg + - ./vlc/modules/audio_output/webaudio/audio-worklet-processor.js expire_in: 1 week # Expiration date diff --git a/compile.sh b/compile.sh index 4e149418260851b1528fbdd1ddeea95d0520966e..7a6bd2546e22062c916181b744c38f1675a42330 100755 --- a/compile.sh +++ b/compile.sh @@ -19,7 +19,7 @@ checkfail() SLOW_MODE=${SLOW_MODE:=1} WORK_DIR=$PWD -EMSDK_VERSION="3.0.0" +EMSDK_VERSION="3.1.18" # Download the portable SDK and uncompress it if [ ! -d emsdk ]; then diagnostic "emsdk not found. Fetching it" @@ -29,7 +29,7 @@ if [ ! -d emsdk ]; then fi cd $WORK_DIR -TESTED_HASH="271d3552" +TESTED_HASH="06e361b127e4609e429909756212ed5e30e7d032" # Go go go vlc if [ ! -d vlc ]; then diagnostic "VLC source not found, cloning" @@ -40,15 +40,17 @@ if [ ! -d vlc ]; then # patching vlc if [ -d ../vlc_patches ] && [ "$(ls -A ../vlc_patches)" ]; then # core patches - git am -3 ../vlc_patches/0001-configure-improve-testing-unsupported-GL-functions-f.patch - git am -3 ../vlc_patches/0001-modules-disable-libvlc_json-and-ytbdl-vlc.js-17.patch - git am -3 ../vlc_patches/nacl-wasm/00*.patch - git am -3 ../vlc_patches/audio_output/00*.patch - git am -3 ../vlc_patches/video_output/00*.patch - git am -3 ../vlc_patches/logger/00*.patch - git am -3 ../vlc_patches/0001-vlc.js-modules-remove-category.patch - git am -3 ../vlc_patches/audio_output/new_aout.patch - # git am -3 ../vlc_patches/filesystem/*.patch + #git am -3 ../vlc_patches/0001-configure-improve-testing-unsupported-GL-functions-f.patch + #git am -3 ../vlc_patches/0001-modules-disable-libvlc_json-and-ytbdl-vlc.js-17.patch + #git am -3 ../vlc_patches/nacl-wasm/00*.patch + #git am -3 ../vlc_patches/audio_output/00*.patch + #git am -3 ../vlc_patches/video_output/00*.patch + #git am -3 ../vlc_patches/logger/00*.patch + #git am -3 ../vlc_patches/0001-vlc.js-modules-remove-category.patch + #git am -3 ../vlc_patches/audio_output/new_aout.patch + #git am -3 ../vlc_patches/filesystem/*.patch + #git am -3 ../vlc_patches/demo_alpha/* + git am -3 ../vlc_patches/aug/* fi checkfail "vlc source: git clone failed" fi @@ -60,14 +62,11 @@ diagnostic "Setting the environment" diagnostic "build libvlc" cd ./vlc/extras/package/wasm-emscripten/ ./build.sh --mode=${SLOW_MODE} +cd $WORK_DIR +echo "_main" > libvlc_wasm.sym +sed -e 's/^/_/' ./vlc/lib/libvlc.sym >> libvlc_wasm.sym -url="http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4" -diagnostic "getting video" cd $WORK_DIR -mkdir -p samples/ -if [ ! -f "./samples/BigBuckBunny.mp4" ]; then - curl ${url} -o samples/BigBuckBunny.mp4 -fi diagnostic "Generating executable" ./create_main.sh diff --git a/create_main.sh b/create_main.sh index f997c6d1f62b9cae6b22a351a8cbda4c767f6041..3bbaf9afc696f74029162e0902678f5ad62050bf 100755 --- a/create_main.sh +++ b/create_main.sh @@ -29,12 +29,24 @@ SAMPLE_DIR=${SAMPLE_DIR:=./samples} # For release builds, remove '--profiling-funcs' and add '-Os' # Note that we use '-s MODULARIZE', but no '-s EXPORT_ES6', which would # conflict with pthreads on Firefox. -emcc --bind -s USE_PTHREADS=1 -s TOTAL_MEMORY=1GB -s PTHREAD_POOL_SIZE=15 \ - -s OFFSCREEN_FRAMEBUFFER=1 -s USE_WEBGL2=1 --profiling-funcs \ - -s MODULARIZE=1 -s EXPORT_NAME="VlcModule" \ - -s EXPORTED_RUNTIME_METHODS="allocateUTF8" \ + +# -s TRACE_WEBGL_CALLS=1 \ + +# -s ASYNCIFY_IMPORTS="['init_js_file', 'getVoutMessagePort', 'bindVideoFrame', 'CopyFrameToBuffer', 'probeConfig', 'initDecoderWorkerMessagePort', 'flushAsync', 'initDecoderJS']" + + +emcc --bind -s USE_PTHREADS=1 -s TOTAL_MEMORY=2GB -s PTHREAD_POOL_SIZE=25 \ + -s OFFSCREEN_FRAMEBUFFER=1 \ + -s USE_WEBGL2=1 \ + --profiling-funcs \ + -s OFFSCREENCANVAS_SUPPORT=1 \ + -s MODULARIZE=1 -s EXPORT_NAME="initModule" \ + -s EXTRA_EXPORTED_RUNTIME_METHODS="[allocateUTF8, writeAsciiToMemory]" \ + -s ASYNCIFY=1 -O3 \ + -s EXIT_RUNTIME=1 -s ASSERTIONS=1 \ -I $PATH_VLC/include/ \ main.c exports_media_player.c exports_media.c \ + -s EXPORTED_FUNCTIONS=@libvlc_wasm.sym \ $PATH_VLC/build-emscripten/lib/.libs/libvlc.a \ $PATH_VLC/build-emscripten/vlc-modules.bc \ $PATH_VLC/build-emscripten/modules/.libs/*.a \ @@ -42,5 +54,26 @@ emcc --bind -s USE_PTHREADS=1 -s TOTAL_MEMORY=1GB -s PTHREAD_POOL_SIZE=15 \ $PATH_VLC/build-emscripten/src/.libs/libvlccore.a \ $PATH_VLC/build-emscripten/compat/.libs/libcompat.a \ --js-library lib/wasm-imports.js \ - --js-library vlc/modules/audio_output/webaudio/webaudio.js \ - -o experimental.js --preload-file ${SAMPLE_DIR} + -o experimental.js + + +#em++ --bind -s USE_PTHREADS=1 -s TOTAL_MEMORY=2GB -s PTHREAD_POOL_SIZE=21 \ +# -s OFFSCREEN_FRAMEBUFFER=1\ +# -s USE_WEBGL2=1 \ +# --profiling-funcs \ +# -s OFFSCREENCANVAS_SUPPORT=1 \ +# -s MODULARIZE=1 -s EXPORT_NAME="initModule" \ +# -s EXTRA_EXPORTED_RUNTIME_METHODS="[allocateUTF8]" \ +# -s ASYNCIFY=1 -O3 \ +# -s GL_ASSERTIONS=1 \ +# -s GL_TRACK_ERRORS=1 \ +# -I $PATH_VLC/include/ \ +# main_opengl.cpp \ +# $PATH_VLC/build-emscripten/lib/.libs/libvlc.a \ +# $PATH_VLC/build-emscripten/vlc-modules.bc \ +# $PATH_VLC/build-emscripten/modules/.libs/*.a \ +# $PATH_VLC/contrib/wasm32-unknown-emscripten/lib/*.a \ +# $PATH_VLC/build-emscripten/src/.libs/libvlccore.a \ +# $PATH_VLC/build-emscripten/compat/.libs/libcompat.a \ +# --js-library lib/wasm-imports.js \ +# -o opengl.js --preload-file ${SAMPLE_DIR} diff --git a/exports_media.c b/exports_media.c index 55d35e13faaf4050bd3b82f9b187c9336b6bf010..f7bd2fdd430a5b97a9aab5887622b8afa7be0134 100644 --- a/exports_media.c +++ b/exports_media.c @@ -11,7 +11,11 @@ extern libvlc_instance_t *libvlc; libvlc_media_t* EMSCRIPTEN_KEEPALIVE wasm_media_new_path(const char *path) { - return libvlc_media_new_path(libvlc, path); + return libvlc_media_new_path(libvlc, path); +} + +libvlc_media_t* EMSCRIPTEN_KEEPALIVE wasm_media_new_location(const char *path) { + return libvlc_media_new_location(libvlc, path); } void EMSCRIPTEN_KEEPALIVE wasm_media_retain( libvlc_media_t *media) { diff --git a/exports_media_player.c b/exports_media_player.c index af61c3777543f27ea51f4259ba763529e5ac4396..a9bc603f34917eb54e704808bf5e88091959ec4c 100644 --- a/exports_media_player.c +++ b/exports_media_player.c @@ -20,8 +20,12 @@ libvlc_media_player_t* EMSCRIPTEN_KEEPALIVE wasm_media_player_new() { return libvlc_media_player_new(libvlc); } +libvlc_media_list_player_t* EMSCRIPTEN_KEEPALIVE wasm_media_list_player_new() { + return libvlc_media_list_player_new(libvlc); +} + libvlc_media_player_t* EMSCRIPTEN_KEEPALIVE wasm_media_player_new_from_media(libvlc_media_t* media) { - return libvlc_media_player_new_from_media(media); + return libvlc_media_player_new_from_media(media); } void EMSCRIPTEN_KEEPALIVE wasm_media_player_release(libvlc_media_player_t *media_player) { @@ -59,6 +63,12 @@ void EMSCRIPTEN_KEEPALIVE wasm_media_player_pause(libvlc_media_player_t *media_p libvlc_media_player_pause(media_player); } +void EMSCRIPTEN_KEEPALIVE wasm_media_player_stop(libvlc_media_player_t *media_play\ +er) { + libvlc_media_player_stop_async(media_player); +} + + // TODO // LIBVLC_API int libvlc_media_player_stop_async ( libvlc_media_player_t *p_mi ); @@ -234,3 +244,24 @@ int EMSCRIPTEN_KEEPALIVE wasm_media_player_get_role(libvlc_media_player_t *media int EMSCRIPTEN_KEEPALIVE wasm_media_player_set_role(libvlc_media_player_t *media_player, unsigned role) { return libvlc_media_player_set_role(media_player, role); } + +int EMSCRIPTEN_KEEPALIVE wasm_libvlc_init(size_t size, char const *argv[]) { + /* + char const *vlc_argv[] = { + "-vvv", + "--no-spu", + "--no-osd", + "--aout=emworklet_audio", + "--codec=avcodec", + "-Idummy", + "--ignore-config", + }; + */ + libvlc = libvlc_new( size, argv ); + if (libvlc == NULL) + { + fprintf( stderr, "unable to create libvlc instance" ); + return -1; + } + return 0; +} diff --git a/lib/libvlc.js b/lib/libvlc.js index 51a8e4d2bbd5a2d4813d400b3c765896a8505717..8aa8bff73f0d93e3411acc9b7e42e5214bef3159 100644 --- a/lib/libvlc.js +++ b/lib/libvlc.js @@ -44,6 +44,9 @@ export class MediaPlayer { return this.module._wasm_media_player_is_playing(this.media_player_ptr); } + stop() { + return this.module._wasm_media_player_stop(this.media_player_ptr); + } play() { return this.module._wasm_media_player_play(this.media_player_ptr); } @@ -228,7 +231,7 @@ export class Media { this.module = module; let path_ptr = module.allocateUTF8(path) - this.media_ptr = module._wasm_media_new_path(path_ptr); + this.media_ptr = module._wasm_media_new_location(path_ptr); module._free(path_ptr); if (this.media_ptr == 0) { diff --git a/lib/module-loader.js b/lib/module-loader.js index 9507f15866e42dbac2857b00babef2e68ccf58ea..20b73642c1ebfc36a5810cd21ad38a9a8eff1c67 100644 --- a/lib/module-loader.js +++ b/lib/module-loader.js @@ -1,6 +1,10 @@ var statusElement = document.getElementById('status'); var progressElement = document.getElementById('progress'); var spinnerElement = document.getElementById('spinner'); +var overlayElement = document.getElementById('canvas'); + +// This should be set to true once the user clicks on the canvas for the first time +var was_clicked = false; // This should be set to true once the user clicks on the canvas for the first time var was_clicked = false; @@ -9,9 +13,22 @@ var VlcModuleExt = { preRun: [ function() { window.display_overlay = true }], + preRun: [ function() { + window.display_overlay = true + }], + vlc_access_file: {}, onRuntimeInitialized: function() { // This should run after the wasm module is instantiated // before, the Pthread object won't be available + var img = new Image(50, 50); + img.src = "./assets/VLC_Icon.svg"; + var context = overlayElement.getContext('2d'); + context.drawImage(img, + overlay.width/2 - img.width/2, + overlay.height/2 - img.height/2, + img.width, + img.height, + ); }, print: (function() { diff --git a/lib/overlay.js b/lib/overlay.js index 7e0d627386bc27abef1d9df4c113ddcfa533d13b..8f51b8ce0ef56f9af819787142323bebf3879292 100644 --- a/lib/overlay.js +++ b/lib/overlay.js @@ -59,6 +59,12 @@ volume_button.src = "vlc/modules/gui/qt/pixmaps/toolbar/volume-medium.png"; const muted_button = new Image(BUTTON_SIZE, BUTTON_SIZE); muted_button.src = "vlc/modules/gui/qt/pixmaps/toolbar/volume-muted.png"; + +const filepicker_button = document.createElement("input"); +filepicker_button.id = "fpicker_btn"; +filepicker_button.type = "file"; +filepicker_button.style = "display:none"; +document.body.appendChild(filepicker_button); // Apply underlying changes (eg video is paused) to the displayed UI export function update_overlay(overlay) { const ctx = overlay.getContext("2d"); @@ -224,7 +230,12 @@ export function on_overlay_click(overlay, mouse_event) { } // User clicked outside the menu bar - else { - media_player.toggle_play(); + else { + if (filepicker_button.files.length === 0) { + filepicker_button.click(); + } + else { + media_player.toggle_play(); + } } } diff --git a/main.c b/main.c index d6431c5dd554c443a76a0d17dc160ed72ac77ae8..d2b57924370d48a92f10ec28e5509f2430864cf3 100644 --- a/main.c +++ b/main.c @@ -7,84 +7,85 @@ #include <emscripten.h> #include <emscripten/html5.h> -libvlc_media_player_t *mp; +libvlc_media_player_t *mp = NULL; libvlc_instance_t *libvlc; libvlc_time_t t = -1; static void iter() { - if (!mp) - return; - if (libvlc_media_player_get_time(mp) == t) { - // when enable, the js does not respond. - //libvlc_media_player_release( mp ); - //libvlc_release( libvlc ); - emscripten_cancel_main_loop(); - } - t = libvlc_media_player_get_time(mp); + if (!mp) + return; + if (libvlc_media_player_get_time(mp) == t) { + // when enable, the js does not respond. + //libvlc_media_player_release( mp ); + //libvlc_release( libvlc ); + emscripten_cancel_main_loop(); + } + t = libvlc_media_player_get_time(mp); } void EMSCRIPTEN_KEEPALIVE set_global_media_player(libvlc_media_player_t *media_player) { - mp = media_player; + mp = media_player; } extern void update_overlay(); - static void on_position_changed(const libvlc_event_t *p_event, void *p_data){ - VLC_UNUSED(p_event); - VLC_UNUSED(p_data); - - MAIN_THREAD_ASYNC_EM_ASM({ - const overlay = document.getElementById("overlay"); - update_overlay(overlay); - }); + VLC_UNUSED(p_event); + VLC_UNUSED(p_data); + + MAIN_THREAD_ASYNC_EM_ASM({ + const overlay = document.getElementById("overlay"); + update_overlay(overlay); + }); } int main() { - /* We don't want to the main thread stop even if the main function exit. - * If this thread stop, all proxyfied functions wont be called. - */ - EM_ASM(Module['noExitRuntime']=true); - char const *vlc_argv[] = { - "-vvv", - "--no-spu", - "--no-osd", - "--aout=emworklet_audio", - "-Idummy", - "--ignore-config", - }; + /* We don't want to the main thread stop even if the main function exit. + * If this thread stop, all proxyfied functions wont be called. + */ + EM_ASM(Module['noExitRuntime']=true); + /* + char const *vlc_argv[] = { + "-vvv", + "--no-spu", + "--no-osd", + "--aout=emworklet_audio", + "--codec=avcodec", + "-Idummy", + "--ignore-config", + }; - libvlc = libvlc_new( ARRAY_SIZE( vlc_argv ), vlc_argv ); - if (libvlc == NULL) + libvlc = libvlc_new( ARRAY_SIZE( vlc_argv ), vlc_argv ); + if (libvlc == NULL) { - fprintf( stderr, "unable to create libvlc instance" ); - return -1; + fprintf( stderr, "unable to create libvlc instance" ); + return -1; } - - emscripten_set_main_loop(iter, 1, 1); - - return 0; + */ + emscripten_set_main_loop(iter, 1, 0); + emscripten_exit_with_live_runtime(); + return 0; } // Used to make sure the UI (progress bar, play/pause button, etc) is // updated as the video is read. void EMSCRIPTEN_KEEPALIVE attach_update_events(libvlc_media_player_t *media_player) { - libvlc_event_manager_t* event_manager = libvlc_media_player_event_manager(media_player); - int res; - res = libvlc_event_attach( - event_manager, - libvlc_MediaPlayerPositionChanged, - on_position_changed, - NULL - ); - assert(res == 0); - res = libvlc_event_attach( - event_manager, - libvlc_MediaPlayerPaused, - on_position_changed, - NULL - ); - assert(res == 0); + libvlc_event_manager_t* event_manager = libvlc_media_player_event_manager(media_player); + int res; + res = libvlc_event_attach( + event_manager, + libvlc_MediaPlayerPositionChanged, + on_position_changed, + NULL + ); + assert(res == 0); + res = libvlc_event_attach( + event_manager, + libvlc_MediaPlayerPaused, + on_position_changed, + NULL + ); + assert(res == 0); } diff --git a/main_opengl.cpp b/main_opengl.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ca92ef174bfd382b0aa3b514dd6cc177d6d8ca8c --- /dev/null +++ b/main_opengl.cpp @@ -0,0 +1,413 @@ +#include <stdio.h> +#include <vlc/vlc.h> +#include <assert.h> +#include <errno.h> + +#define restrict + +#include <emscripten.h> +#include <emscripten/html5.h> +#include <GLES3/gl3.h> + +#include <vlc_common.h> +#include <vlc_vout_window.h> +#include <vlc_opengl.h> +#include <vlc_vout_display.h> +#include <vlc_opengl_filter.h> +#include <vlc_opengl_interop.h> +#include <vlc_modules.h> +#include <vlc_messages.h> + +#include "vlc/lib/libvlc_internal.h" + +struct test_scenario; +static void scenario1_clear(struct test_scenario *scenario); +static void scenario3_clearsmooth(struct test_scenario *scenario); +static void scenario4_mock(struct test_scenario *scenario); +static void scenario5_videoframe(struct test_scenario *scenario); +static void scenario7_display(struct test_scenario *scenario); + +struct webcodec_context +{ + pthread_t gl_worker; +}; + +libvlc_instance_t *libvlc; + +struct test_scenario { + const char *canvas; + void (*setup)(struct test_scenario *scenario); + float color[3]; + + vout_window_t *window; + vlc_gl_t *gl; + vlc_thread_t thread; +} tests[] = { + { + .canvas = "canvas_step1", + .setup = scenario1_clear, + .color = { 1.f, 0.f, 0.f }, + }, + { + .canvas = "canvas_step2", + .setup = scenario1_clear, + .color = { 0.f, 1.f, 0.f }, + }, + { + .canvas = "canvas_step3", + .setup = scenario3_clearsmooth, + }, + { + .canvas = "canvas_step4", + .setup = scenario4_mock, + }, + { + .canvas = "canvas_step5", + .setup = scenario5_videoframe, + }, + { + .canvas = "canvas_step7", + .setup = scenario7_display, + } + +}; + +#define DECLARE_MEMBER(type, name) type name; +#define LOAD_SYMBOL(type, name) \ + gl.name = (type)vlc_gl_GetProcAddress(scenario->gl, "gl" #name); \ + assert(gl.name); + +#define CREATE_VTABLE(VTABLE) \ + struct { \ + VTABLE(DECLARE_MEMBER) \ + } gl; \ + VTABLE(LOAD_SYMBOL) + +static void iter() +{ + return; +} + +static void *test_run(void *opaque) +{ + auto *test = static_cast<struct test_scenario *>(opaque); + libvlc_int_t *root = libvlc->p_libvlc_int; + + test->window = vlc_object_create<vout_window_t>(root); + test->window->handle.canvas = test->canvas; + test->window->type = VOUT_WINDOW_TYPE_EMSCRIPTEN_WEBGL; + struct vout_display_cfg cfg = { + .window = test->window, + }; + test->setup(test); + + return NULL; +} + +static void init_gl(struct test_scenario *scenario) +{ + struct vout_display_cfg cfg = { + .window = scenario->window, + }; + + scenario->gl = vlc_gl_Create(&cfg, VLC_OPENGL_ES2, "any"); + assert(scenario->gl); +} + +int main() { + /* We don't want to the main thread stop even if the main function exit. + * If this thread stop, all proxyfied functions wont be called. + */ + EM_ASM(Module['noExitRuntime']=true); + char const *vlc_argv[] = { + "-vvv", + "--no-spu", + "--no-osd", + "--aout=emworklet_audio", + "-Idummy", + "--ignore-config", + }; + + libvlc = libvlc_new( ARRAY_SIZE( vlc_argv ), vlc_argv ); + if (libvlc == NULL) + { + fprintf( stderr, "unable to create libvlc instance" ); + return -1; + } + + emscripten_log(EM_LOG_INFO | EM_LOG_CONSOLE, "Creating tests...\n"); + for (size_t i=0; i<ARRAY_SIZE(tests); ++i) + { + emscripten_log(EM_LOG_INFO | EM_LOG_CONSOLE, "- test %zu/%zu...\n", + i, ARRAY_SIZE(tests)); + vlc_clone(&tests[i].thread, test_run, &tests[i], 0); + } + + emscripten_set_main_loop(iter, 1, 1); + + return 0; +} + +static void scenario1_clear(struct test_scenario *scenario) +{ + init_gl(scenario); + + vlc_gl_MakeCurrent(scenario->gl); + +#define OPENGL_VTABLE_F(F) \ + F(PFNGLCLEARCOLORPROC, ClearColor) \ + F(PFNGLCLEARPROC, Clear) + CREATE_VTABLE(OPENGL_VTABLE_F); +#undef OPENGL_VTABLE_F + + gl.ClearColor(scenario->color[0], scenario->color[1], scenario->color[2], 1.f); + gl.Clear(GL_COLOR_BUFFER_BIT); + vlc_gl_Swap(scenario->gl); + vlc_gl_ReleaseCurrent(scenario->gl); +} + +static void ClearColorAtTime(void *opaque) +{ + auto *scenario = static_cast<struct test_scenario *>(opaque); + vlc_gl_MakeCurrent(scenario->gl); + +#define OPENGL_VTABLE_F(F) \ + F(PFNGLCLEARCOLORPROC, ClearColor) \ + F(PFNGLCLEARPROC, Clear) + CREATE_VTABLE(OPENGL_VTABLE_F); +#undef OPENGL_VTABLE_F + + vlc_tick_t now = vlc_tick_now(); + float secf = secf_from_vlc_tick(now); + secf = fmod(secf / 5.f, 1.f); + + gl.ClearColor(secf, 0.f, 0.f, 1.f); + gl.Clear(GL_COLOR_BUFFER_BIT); + vlc_gl_Swap(scenario->gl); + vlc_gl_ReleaseCurrent(scenario->gl); + +} + +static void scenario3_clearsmooth(struct test_scenario *scenario) +{ + init_gl(scenario); + + emscripten_set_main_loop_arg(ClearColorAtTime, scenario, 0, false); +} + +static void scenario4_mock(struct test_scenario *scenario) +{ + init_gl(scenario); + + vlc_gl_MakeCurrent(scenario->gl); + +#define OPENGL_VTABLE_F(F) \ + F(PFNGLCLEARCOLORPROC, ClearColor) \ + F(PFNGLCLEARPROC, Clear) + CREATE_VTABLE(OPENGL_VTABLE_F); +#undef OPENGL_VTABLE_F + + auto end = [&]{ + gl.ClearColor(0.f, 1.f, 0.f, 1.f); + gl.Clear(GL_COLOR_BUFFER_BIT); + vlc_gl_Swap(scenario->gl); + vlc_gl_ReleaseCurrent(scenario->gl); + }; + + size_t count_strict; + module_t **modules; + ssize_t count = vlc_module_match( + "opengl filter", "mock", false, + &modules, &count_strict); + + + if (count == 0) + { + msg_Err(scenario->gl, "No opengl filter module named mock found"); + end(); + return; + } + + gl.ClearColor(0.f, 0.f, 0.f, 1.f); + gl.Clear(GL_COLOR_BUFFER_BIT); + + + struct vlc_gl_input_meta meta = { + .pts = VLC_TICK_0, + }; + struct vlc_gl_tex_size texsize = { .width = 400, .height = 300 }; + auto filter = vlc_object_create<struct vlc_gl_filter>(scenario->gl); + filter->gl = scenario->gl; + + auto *activate = reinterpret_cast<vlc_gl_filter_open_fn*>( + vlc_module_map(vlc_object_logger(scenario->gl), modules[0])); + + if (activate(filter, NULL, NULL, &texsize) != VLC_SUCCESS) + { + end(); + return; + } + + filter->ops->draw(filter, NULL, &meta); + + vlc_gl_Swap(scenario->gl); + vlc_gl_ReleaseCurrent(scenario->gl); + return; +} + +static int InteropInit(struct vlc_gl_interop *interop, uint32_t tex_target, + vlc_fourcc_t chroma, video_color_space_t yuv_space) +{ + return VLC_SUCCESS; +} + +EM_JS(void, SendVideoFrame, (int worker_id), { + let buffer = new Uint8Array(4*32*32); + for (let i=0; i<32; ++i) + { + for (let j=0; j<32; ++j) + { + let r1 = i - 16; + let r2 = j - 16; + let value = 255; + /* Let's draw a disk to recognize shape and size*/ + if (r1*r1 + r2*r2 > 10 * 10) + value = 0; + buffer[4*(i*32 + j) + 0] = value; + buffer[4*(i*32 + j) + 1] = value; + buffer[4*(i*32 + j) + 2] = value; + buffer[4*(i*32 + j) + 3] = 255; + } + } + let frame = new VideoFrame(buffer, { + format: 'RGBA', + codedWidth: 32, + codedHeight: 32, + timestamp: 0, + }); + self.postMessage({ + cmd: 'custom', + customCmd: 'displayFrame', + frame: frame, + targetThread: worker_id, + pictureId: 0, + }, [frame]); + frame.close(); +}) + +static void scenario5_videoframe(struct test_scenario *scenario) +{ + init_gl(scenario); + + vlc_gl_MakeCurrent(scenario->gl); + +#define OPENGL_VTABLE_F(F) \ + F(PFNGLCLEARCOLORPROC, ClearColor) \ + F(PFNGLCLEARPROC, Clear) \ + F(PFNGLBINDTEXTUREPROC, BindTexture) \ + F(PFNGLGENTEXTURESPROC, GenTextures) \ + F(PFNGLGENFRAMEBUFFERSPROC, GenFramebuffers) \ + F(PFNGLBINDFRAMEBUFFERPROC, BindFramebuffer) \ + F(PFNGLFRAMEBUFFERTEXTURE2DPROC, FramebufferTexture2D) \ + F(PFNGLBLITFRAMEBUFFERPROC, BlitFramebuffer) + CREATE_VTABLE(OPENGL_VTABLE_F); +#undef OPENGL_VTABLE_F + + auto end = [&]{ + gl.ClearColor(0.f, 1.f, 0.f, 1.f); + gl.Clear(GL_COLOR_BUFFER_BIT); + vlc_gl_Swap(scenario->gl); + vlc_gl_ReleaseCurrent(scenario->gl); + }; + + struct vlc_video_context *vctx = + vlc_video_context_Create(NULL, VLC_VIDEO_CONTEXT_WEBCODEC, + sizeof(struct webcodec_context), NULL); + auto *vctxPrivate = static_cast<struct webcodec_context *>( + vlc_video_context_GetPrivate(vctx, VLC_VIDEO_CONTEXT_WEBCODEC)); + + auto interop = vlc_object_create<struct vlc_gl_interop>(scenario->gl); + interop->gl = scenario->gl; + interop->vctx = vctx; + interop->init = InteropInit; + video_format_Init(&interop->fmt_in, VLC_CODEC_WEBCODEC_OPAQUE); + interop->fmt_in.i_visible_width + = interop->fmt_in.i_width + = 32; + interop->fmt_in.i_visible_height + = interop->fmt_in.i_height + = 32; + interop->tex_target = GL_TEXTURE_2D; + + interop->module = module_need(interop, "glinterop", "any", false); + if (interop->module == NULL) + { + end(); + return; + } + + gl.ClearColor(0.f, 0.f, 0.f, 1.f); + gl.Clear(GL_COLOR_BUFFER_BIT); + + SendVideoFrame(vctxPrivate->gl_worker); + + picture_t *pic = picture_NewFromFormat(&interop->fmt_in); + assert(pic); + GLsizei tex_width, tex_height; + + GLuint texture; + gl.GenTextures(1, &texture); + interop->ops->update_textures(interop, &texture, &tex_width, &tex_height, + pic, NULL); + + //GLuint defaultFb; + //gl.GetIntergerv(GL_FRAMEBUFFER_BINDING, &defaultFb); + + GLuint fb; + gl.GenFramebuffers(1, &fb); + gl.BindFramebuffer(GL_READ_FRAMEBUFFER, fb); + gl.FramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0); + + gl.BlitFramebuffer(0, 0, 32, 32, + 0, 0, 200, 200, + GL_COLOR_BUFFER_BIT, + GL_NEAREST); + + vlc_gl_Swap(scenario->gl); + vlc_gl_ReleaseCurrent(scenario->gl); + return; +} + +void scenario7_display(struct test_scenario *scenario) +{ + struct vout_display_cfg cfg = { + .window = scenario->window, + .display = { .width=400, .height=300, .sar = {1,1} }, + .is_display_filled = true, + }; + + video_format_t format; + video_format_Init(&format, VLC_CODEC_WEBCODEC_OPAQUE); + format.i_visible_width + = format.i_width + = 32; + format.i_visible_height + = format.i_height + = 32; + format.i_sar_num = format.i_sar_den = 1; + + struct vlc_video_context *vctx = + vlc_video_context_Create(NULL, VLC_VIDEO_CONTEXT_WEBCODEC, + sizeof(struct webcodec_context), NULL); + auto *vctxPrivate = static_cast<struct webcodec_context *>( + vlc_video_context_GetPrivate(vctx, VLC_VIDEO_CONTEXT_WEBCODEC)); + + vout_display_t *vd = vout_display_New(scenario->window, &format, vctx, &cfg, "gles2", NULL); + + SendVideoFrame(vctxPrivate->gl_worker); + picture_t *pic = picture_NewFromFormat(&format); + assert(pic); + + vout_display_Prepare(vd, pic, NULL, VLC_TICK_0); + vout_display_Display(vd, pic); +} diff --git a/vlc.html b/vlc.html index 345f3f898b2ab474369d829610a681b59578094f..4b00798b297b41d78412a05b88ba32760f3e266d 100644 --- a/vlc.html +++ b/vlc.html @@ -70,20 +70,17 @@ <body> <div class="emscripten_border"> <div class="emscripten"> - <div class="banner"> - <img src="./assets/emscripten.svg" id ="em_logo"> - <div class="vlc_head"> - <img src="./assets/VLC_Icon.svg" id="logo"> - <span id="point">.</span> - <span id="js">JS</span> - </div> - </div> <progress id="progress" value="0" max="100"></progress> <div class="spinner" id='spinner'></div> <div class="emscripten" id="status">Downloading...</div> </div> </div> - + <br></br> + <label for="opts">Enter libvlc options here:</label> + <input type="text" size=60 id="opts" name="opts" value="--codec=webcodec --aout=emworklet_audio -vvv"></input> + <button id="vlc_init">Start libvlc</button> + <button id="vlc_stop">stop</button> + <br></br> <div id="stack"> <canvas class="emscripten" id="canvas" @@ -96,20 +93,21 @@ width=1280 height=720 ></canvas> </div> - <script src="./lib/module-loader.js"></script> <script src="./experimental.js"></script> - <script type="module"> import { update_overlay, on_overlay_click } from "./lib/overlay.js"; import { MediaPlayer } from "./lib/libvlc.js"; + var vlc_options = ""; + // VlcModule is a function generated by emscripten in experimental.js, // that loads the wasm file and generates a module object from it. // VlcModuleExt is an object defined in 'lib/module-loader.js' with a // bunch of options; also, all the fields of VlcModuleExt are added to // the returned Module. - const Module = await VlcModule(VlcModuleExt); + + globalThis.Module = await initModule(VlcModuleExt); window.onerror = function(event) { // TODO: do not warn on ok events like simulating an infinite loop or exitStatus @@ -119,18 +117,64 @@ if (text) Module.printErr('[post-exception status] ' + text); }; }; - + const overlay = document.getElementById("overlay"); - const media_player = new MediaPlayer(Module, "./samples/BigBuckBunny.mp4"); + let inputElement = document.getElementById("fpicker_btn"); + let init_elem = document.getElementById("vlc_init"); + let stop_elem = document.getElementById("vlc_stop"); + let options_input = document.getElementById("opts"); + function handleFiles() { + // iterator in case the user selected multiple files. + for (var i=0; i < this.files.length; i++) { + var name = this.files.item(i).name; + console.log("opened file: ", name); + Module['vlc_access_file'][1] = this.files.item(i); + } + } - media_player.set_volume(80); - // FIXME - Module._set_global_media_player(media_player.media_player_ptr); - window.media_player = media_player; - window.on_overlay_click = on_overlay_click; - window.update_overlay = update_overlay; + inputElement.addEventListener("change", handleFiles, false); + var media_player; + function handleStart() { + vlc_options = options_input.value; + console.log("vlc.js: starting vlc.js with : " + vlc_options); + let vlc_opts_array = vlc_options.split(' '); - update_overlay(overlay); + let vlc_opts_size = 0; + for (let i in vlc_opts_array) { + vlc_opts_size += vlc_opts_array[i].length + 1; + } + var buffer = Module._malloc(vlc_opts_size); + var wrote_size = 0; + for (let i in vlc_opts_array) { + Module.writeAsciiToMemory(vlc_opts_array[i], buffer + wrote_size, true); + wrote_size += vlc_opts_array[i].length + 1; + } + var vlc_argv = Module._malloc(vlc_opts_array.length * 4 + 4); + var view_vlc_argv = new Uint32Array( + Module.wasmMemory.buffer, + vlc_argv, + vlc_opts_array.length + ); + wrote_size = 0; + for (let i in vlc_opts_array) { + view_vlc_argv[i] = buffer + wrote_size; + wrote_size += vlc_opts_array[i].length + 1; + } + Module._wasm_libvlc_init(vlc_opts_array.length, vlc_argv); + media_player = new MediaPlayer(Module, "emjsfile://1"); + media_player.set_volume(80); + + Module._set_global_media_player(media_player.media_player_ptr); + window.media_player = media_player; + window.on_overlay_click = on_overlay_click; + window.update_overlay = update_overlay; + update_overlay(overlay); + } + function handleStop() { + media_player.stop(); + } + init_elem.addEventListener("click", handleStart, false); + stop_elem.addEventListener("click", handleStop, false); </script> diff --git a/vlc_media_list_poc.html b/vlc_media_list_poc.html new file mode 100644 index 0000000000000000000000000000000000000000..0149d162aa79f0236f128e48c370e08462fcf8bd --- /dev/null +++ b/vlc_media_list_poc.html @@ -0,0 +1,259 @@ +<!doctype html> +<html lang="en-us"> +<head> + <meta charset="utf-8"> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> + <title>VLC.js</title> + <style> + .emscripten { + text-align: center; + } + body { + background-color: #343a40; + } + #canvas { + border:0 !important; + display: flex; + margin: 0 auto; + background-color: #000; + } + #overlay { + border:0 !important; + display: flex; + margin: 0 auto; + } + #stack { + display: grid; + } + #stack > canvas { + grid-column: 1; + grid-row: 1; + } + #spinner { + display: none; + } + #status { + display: none; + } + #progress { + margin-left: auto; + margin-right: auto; + } + #logo { + width: 64px; + height: 64px; + } + #em_logo { + position: absolute; + width: 200px; + height: 80px; + } + #point { + color: #fff; + font-size: 60px; + } + #js { + color: #fff; + font-size: 60px; + font-weight: bold; + } + .banner { + display: flex; + align-items: center; + justify-content: space-between; + } + .vlc_head { + flex: 1 1 0px; + } + </style> +</head> +<body> + <div class="emscripten_border"> + <div class="emscripten"> + <progress id="progress" value="0" max="100"></progress> + <div class="spinner" id='spinner'></div> + <div class="emscripten" id="status">Downloading...</div> + </div> + </div> + <br></br> + <label for="opts">Enter libvlc options here:</label> + <input type="text" size=60 id="opts" name="opts" value="--codec=webcodec --aout=emworklet_audio -vvv"></input> + <button id="vlc_init">Start libvlc</button> + <button id="vlc_select">Select Files</button> + <button id="vlc_play">Play</button> + <button id="vlc_next">Next</button> + <button id="vlc_prev">Previous</button> + <button id="vlc_stop">Stop</button> + <br></br> + <div id="stack"> + <canvas + class="emscripten" id="canvas" + oncontextmenu="event.preventDefault()" tabindex=-1 + width=1280 height=720 + ></canvas> + <canvas + class="emscripten hidden" id="overlay" + oncontextmenu="event.preventDefault()" tabindex=-2 + width=1280 height=720 + ></canvas> + </div> + <script src="./lib/module-loader.js"></script> + <script src="./experimental.js"></script> + <script type="module"> + import { update_overlay, on_overlay_click } from "./lib/overlay.js"; + import { MediaPlayer } from "./lib/libvlc.js"; + + var vlc_options = ""; + + // VlcModule is a function generated by emscripten in experimental.js, + // that loads the wasm file and generates a module object from it. + // VlcModuleExt is an object defined in 'lib/module-loader.js' with a + // bunch of options; also, all the fields of VlcModuleExt are added to + // the returned Module. + + globalThis.Module = await initModule(VlcModuleExt); + + window.onerror = function(event) { + // TODO: do not warn on ok events like simulating an infinite loop or exitStatus + Module.setStatus('Exception thrown, see JavaScript console'); + spinnerElement.style.display = 'none'; + Module.setStatus = function(text) { + if (text) Module.printErr('[post-exception status] ' + text); + }; + }; + + const overlay = document.getElementById("overlay"); + let inputElement = document.getElementById("fpicker_btn"); + let init_elem = document.getElementById("vlc_init"); + let select_elem = document.getElementById("vlc_select"); + let play_elem = document.getElementById("vlc_play"); + let next_elem = document.getElementById("vlc_next"); + let prev_elem = document.getElementById("vlc_prev"); + let stop_elem = document.getElementById("vlc_stop"); + let options_input = document.getElementById("opts"); + + var ml_ptr = undefined; + var mlp_ptr = undefined; + var flag = 0; + var counter = 0; + + select_elem.addEventListener("click", function () { + inputElement.click(); + }); + + var playing = -1; + play_elem.addEventListener("click", function () { + if (mlp_ptr === undefined) { + mlp_ptr = Module._wasm_media_list_player_new(); + } + if (ml_ptr === undefined) { + console.error("bad !"); + return ; + } + if (Module._libvlc_media_list_count(ml_ptr) === 0) { + console.error("nope, media list is empty... come back later!"); + } + if (playing === -1) { + Module._libvlc_media_list_player_set_media_list(mlp_ptr, ml_ptr); + Module._libvlc_media_list_player_play(mlp_ptr); + playing = 1 + } + else if (playing === 1) { + Module._libvlc_media_list_player_pause(mlp_ptr); + } + else { + Module._libvlc_media_list_player_play(mlp_ptr); + } + }); + + stop_elem.addEventListener("click", function () { + Module._libvlc_media_list_player_stop_async(mlp_ptr); + }); + + next_elem.addEventListener("click", function () { + Module._libvlc_media_list_player_next(mlp_ptr); + }); + prev_elem.addEventListener("click", function () { + Module._libvlc_media_list_player_previous(mlp_ptr); + }); + // iterator in case the user selected multiple files. + let id = 1; + + function handleFiles() { + if (ml_ptr === undefined) { + console.abort("bad !"); + return; + } + for (var i=0; i < this.files.length; i++) { + var name = this.files.item(i).name; + console.log("opened file: ", name); + Module['vlc_access_file'][id] = this.files.item(i); + // acquire lock + Module._libvlc_media_list_lock(ml_ptr); + // add_media, check if return value is 0 + var path_ptr = Module.allocateUTF8("emjsfile://"+ id); + var media_ptr = Module._wasm_media_new_location(path_ptr); + var ret = Module._libvlc_media_list_add_media(ml_ptr, media_ptr); + console.assert(ret == 0); + // release lock + Module._libvlc_media_list_unlock(ml_ptr); + Module._free(path_ptr); + counter += 1; + id++; + } + } + + inputElement.addEventListener("change", handleFiles, false); + + function handleStart() { + if (flag === 0) { + vlc_options = options_input.value; + console.log("vlc.js: starting vlc.js with : " + vlc_options); + let vlc_opts_array = vlc_options.split(' '); + let vlc_opts_size = 0; + for (let i in vlc_opts_array) { + vlc_opts_size += vlc_opts_array[i].length + 1; + } + var buffer = Module._malloc(vlc_opts_size); + var wrote_size = 0; + for (let i in vlc_opts_array) { + Module.writeAsciiToMemory(vlc_opts_array[i], buffer + wrote_size, true); + wrote_size += vlc_opts_array[i].length + 1; + } + var vlc_argv = Module._malloc(vlc_opts_array.length * 4 + 4); + var view_vlc_argv = new Uint32Array( + Module.wasmMemory.buffer, + vlc_argv, + vlc_opts_array.length + ); + wrote_size = 0; + for (let i in vlc_opts_array) { + view_vlc_argv[i] = buffer + wrote_size; + wrote_size += vlc_opts_array[i].length + 1; + } + Module._wasm_libvlc_init(vlc_opts_array.length, vlc_argv); + ml_ptr = Module._libvlc_media_list_new(); + flag = 1; + } + // libvlc + media_list setup: OK + else { + console.error("multiple instances not supported yet!, please reload the page"); + } + /* + const media_player = new MediaPlayer(Module, "emjsfile:///mediafile"); + media_player.set_volume(80); + + Module._set_global_media_player(media_player.media_player_ptr); + window.media_player = media_player; + window.on_overlay_click = on_overlay_click; + window.update_overlay = update_overlay; + update_overlay(overlay); + */ + } + init_elem.addEventListener("click", handleStart, false); + + </script> + + +</body> +</html> diff --git a/vlc_patches/aug/0001-configure-improve-testing-unsupported-GL-functions-f.patch b/vlc_patches/aug/0001-configure-improve-testing-unsupported-GL-functions-f.patch new file mode 100644 index 0000000000000000000000000000000000000000..832087cd8ab1003b58d419486859eead4e79c139 --- /dev/null +++ b/vlc_patches/aug/0001-configure-improve-testing-unsupported-GL-functions-f.patch @@ -0,0 +1,43 @@ +From cdd64a1c6c680245cd89a66c957347318be707ec Mon Sep 17 00:00:00 2001 +From: Mehdi Sabwat <mehdi@videolabs.io> +Date: Tue, 27 Apr 2021 15:34:23 +0200 +Subject: [PATCH 01/77] configure: improve testing unsupported GL functions for + emscripten + +The build system assumes OpenGL functions are implemented if the headers are defined. +Which is wrong, so we need to disable the HAVE_GL marker, if linking fails. + +we use AM_LINK_IFELSE() because AC_COMPILE will add the "-c" option, and thus +in wasm-emscripten this function will appear supported even if it is not. +--- + configure.ac | 10 +++++++--- + 1 file changed, 7 insertions(+), 3 deletions(-) + +diff --git a/configure.ac b/configure.ac +index a5771e36f4..5bde060fb7 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -3140,13 +3140,17 @@ have_gl="no" + PKG_CHECK_MODULES([GL], [gl], [ + have_gl="yes" + ], [ +- AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ ++ AC_MSG_CHECKING([for OpenGL]) ++ AC_LINK_IFELSE([AC_LANG_PROGRAM([[ + #ifdef _WIN32 + # include <GL/glew.h> + #endif + #include <GL/gl.h> +-]], [ +- [int t0 = GL_TEXTURE0;]]) ++]], [[ ++ int t0 = GL_TEXTURE0; ++ // glColorMaterial is unavailable in webgl, and emscripten ++ glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE); ++ ]]) + ], [ + GL_CFLAGS="" + AS_IF([test "${SYS}" != "mingw32"], [ +-- +2.35.1 + diff --git a/vlc_patches/aug/0002-modules-disable-libvlc_json-and-ytbdl-vlc.js-17.patch b/vlc_patches/aug/0002-modules-disable-libvlc_json-and-ytbdl-vlc.js-17.patch new file mode 100644 index 0000000000000000000000000000000000000000..93b1def20b2644c51925b7c4d06153548eaf2ed7 --- /dev/null +++ b/vlc_patches/aug/0002-modules-disable-libvlc_json-and-ytbdl-vlc.js-17.patch @@ -0,0 +1,25 @@ +From bcc3fabcc3dadb03cd0768daa137d019fc45d4c5 Mon Sep 17 00:00:00 2001 +From: Mehdi Sabwat <mehdi@videolabs.io> +Date: Fri, 16 Apr 2021 11:32:33 +0200 +Subject: [PATCH 02/77] modules: disable libvlc_json and ytbdl vlc.js#17 + +The libjson library is not linkable with emsdk v2.0.17, +this commit should be reverted when the ticket is resolved. +--- + modules/demux/Makefile.am | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/modules/demux/Makefile.am b/modules/demux/Makefile.am +index 86dfd8abee..3e64246e4d 100644 +--- a/modules/demux/Makefile.am ++++ b/modules/demux/Makefile.am +@@ -543,4 +543,6 @@ libvlc_json_la_SOURCES = \ + libvlc_json_la_CPPFLAGS = $(AM_CPPFLAGS) -I$(srcdir)/demux/json + libvlc_json_la_LIBADD = $(LTLIBVLCCORE) ../compat/libcompat.la $(LIBM) + libvlc_json_la_LDFLAGS = -static ++if !HAVE_EMSCRIPTEN + noinst_LTLIBRARIES += libvlc_json.la ++endif +-- +2.35.1 + diff --git a/vlc_patches/aug/0003-contrib-ass-add-support-for-wasm-emscripten.patch b/vlc_patches/aug/0003-contrib-ass-add-support-for-wasm-emscripten.patch new file mode 100644 index 0000000000000000000000000000000000000000..f96d2c845a32321e700f303d4630afcca25db1c0 --- /dev/null +++ b/vlc_patches/aug/0003-contrib-ass-add-support-for-wasm-emscripten.patch @@ -0,0 +1,157 @@ +From 035b067f845042fc0c87d80565783b21bfa50e6c Mon Sep 17 00:00:00 2001 +From: Mehdi Sabwat <mehdi@videolabs.io> +Date: Sun, 30 May 2021 19:22:31 +0200 +Subject: [PATCH 03/77] contrib: ass: add support for wasm-emscripten + +--- + contrib/src/ass/rules.mak | 5 + + ...nitial-support-for-wasm32-emscripten.patch | 105 ++++++++++++++++++ + contrib/src/fontconfig/rules.mak | 4 + + 3 files changed, 114 insertions(+) + create mode 100644 contrib/src/fontconfig/add-initial-support-for-wasm32-emscripten.patch + +diff --git a/contrib/src/ass/rules.mak b/contrib/src/ass/rules.mak +index 05c7679896..be465bf409 100644 +--- a/contrib/src/ass/rules.mak ++++ b/contrib/src/ass/rules.mak +@@ -25,6 +25,11 @@ WITH_DWRITE = 1 + else + WITH_FONTCONFIG = 1 + WITH_HARFBUZZ = 1 ++ifdef HAVE_EMSCRIPTEN ++WITH_FONTCONFIG = 1 ++WITH_HARFBUZZ = 1 ++WITH_ASS_ASM = 0 ++endif + endif + endif + endif +diff --git a/contrib/src/fontconfig/add-initial-support-for-wasm32-emscripten.patch b/contrib/src/fontconfig/add-initial-support-for-wasm32-emscripten.patch +new file mode 100644 +index 0000000000..b1308fb19e +--- /dev/null ++++ b/contrib/src/fontconfig/add-initial-support-for-wasm32-emscripten.patch +@@ -0,0 +1,105 @@ ++From b7f21ca85efd78c8034223c63786a0c01b8378fe Mon Sep 17 00:00:00 2001 ++From: Mehdi Sabwat <mehdi@videolabs.io> ++Date: Wed, 9 Jun 2021 03:42:51 +0200 ++Subject: [PATCH 1/1] add initial support for wasm32-emscripten ++ ++This commit adds a check for uuid_generate_random which is not supported for now, and fixes a failing test. ++It also handles a case where F_FSTYPENAME field is not present in statfs struct. ++--- ++ configure.ac | 1 + ++ src/Makefile.am | 1 + ++ src/fcint.h | 5 +++++ ++ src/fcstat.c | 2 +- ++ src/uuid_generate_random.c | 9 +++++++++ ++ test/test-hash.c | 5 ++++- ++ 6 files changed, 21 insertions(+), 2 deletions(-) ++ create mode 100644 src/uuid_generate_random.c ++ ++diff --git a/configure.ac b/configure.ac ++index fb8af46..018cfc1 100644 ++--- a/configure.ac +++++ b/configure.ac ++@@ -171,2 +171,3 @@ AC_FUNC_VPRINTF ++ AC_FUNC_MMAP ++ AC_CHECK_FUNCS([link mkstemp mkostemp _mktemp_s mkdtemp getopt getopt_long getprogname getexecname rand random lrand48 random_r rand_r readlink fstatvfs fstatfs lstat strerror strerror_r]) +++AC_REPLACE_FUNCS([uuid_generate_random]) ++ ++ dnl AC_CHECK_FUNCS doesn't check for header files. ++ dnl posix_fadvise() may be not available in older libc. ++ AC_CHECK_SYMBOL([posix_fadvise], [fcntl.h], [fc_func_posix_fadvise=1], [fc_func_posix_fadvise=0]) ++diff --git a/src/Makefile.am b/src/Makefile.am ++index 7b414df..de1d785 100644 ++--- a/src/Makefile.am +++++ b/src/Makefile.am ++@@ -127,6 +127,7 @@ EXTRA_DIST += \ ++ fcobjshash.gperf.h ++ ++ libfontconfig_la_SOURCES = \ +++ uuid_generate_random.c \ ++ fcarch.h \ ++ fcatomic.c \ ++ fcatomic.h \ ++diff --git a/src/fcint.h b/src/fcint.h ++index a9d075a..d8fdbfd 100644 ++--- a/src/fcint.h +++++ b/src/fcint.h ++@@ -598,6 +598,11 @@ struct _FcValuePromotionBuffer { ++ FcPrivate FcCache * ++ FcDirCacheScan (const FcChar8 *dir, FcConfig *config); ++ +++#ifndef HAVE_UUID_GENERATE_RANDOM +++#include <uuid/uuid.h> +++void uuid_generate_random(uuid_t out); +++#endif +++ ++ FcPrivate FcCache * ++ FcDirCacheBuild (FcFontSet *set, const FcChar8 *dir, struct stat *dir_stat, FcStrSet *dirs); ++ ++diff --git a/src/fcstat.c b/src/fcstat.c ++index 5aa1643..d1240c5 100644 ++--- a/src/fcstat.c +++++ b/src/fcstat.c ++@@ -384,7 +384,7 @@ FcFStatFs (int fd, FcStatFS *statb) ++ # endif ++ # if defined(HAVE_STRUCT_STATFS_F_FSTYPENAME) ++ p = buf.f_fstypename; ++-# elif defined(__linux__) +++# elif defined(__linux__) || defined(__EMSCRIPTEN__) ++ switch (buf.f_type) ++ { ++ case 0x6969: /* nfs */ ++diff --git a/src/uuid_generate_random.c b/src/uuid_generate_random.c ++new file mode 100644 ++index 0000000..c17a58d ++--- /dev/null +++++ b/src/uuid_generate_random.c ++@@ -0,0 +1,9 @@ +++// compat function for uuid_generate_random +++#include "fcint.h" +++ +++#ifndef HAVE_UUID_GENERATE_RANDOM +++void uuid_generate_random(uuid_t out) +++{ +++ uuid_generate(out); +++} +++#endif ++diff --git a/test/test-hash.c b/test/test-hash.c ++index 7530e82..221029d 100644 ++--- a/test/test-hash.c +++++ b/test/test-hash.c ++@@ -51,8 +51,11 @@ test_add (Test *test, FcChar8 *key, FcBool replace) ++ void *u; ++ FcBool (*hash_add) (FcHashTable *, void *, void *); ++ FcBool ret = FcFalse; ++- +++#ifdef HAVE_UUID_GENERATE_RANDOM ++ uuid_generate_random (uuid); +++#else +++ uuid_generate(uuid); +++#endif ++ if (replace) ++ hash_add = FcHashTableReplace; ++ else ++-- ++2.32.0 ++ +diff --git a/contrib/src/fontconfig/rules.mak b/contrib/src/fontconfig/rules.mak +index ae4b218fd2..5b4b15ca34 100644 +--- a/contrib/src/fontconfig/rules.mak ++++ b/contrib/src/fontconfig/rules.mak +@@ -23,6 +23,10 @@ ifdef HAVE_WIN32 + endif + $(RM) $(UNPACK_DIR)/src/fcobjshash.gperf + $(call pkg_static, "fontconfig.pc.in") ++ifdef HAVE_EMSCRIPTEN ++ $(APPLY) $(SRC)/fontconfig/add-initial-support-for-wasm32-emscripten.patch ++ $(UPDATE_AUTOCONFIG) ++endif + $(MOVE) + + FONTCONFIG_CONF := $(HOSTCONF) \ +-- +2.35.1 + diff --git a/vlc_patches/aug/0004-contrib-gcrypt-add-support-for-wasm-emscripten.patch b/vlc_patches/aug/0004-contrib-gcrypt-add-support-for-wasm-emscripten.patch new file mode 100644 index 0000000000000000000000000000000000000000..f66cb6f7be287a1c3f7c14077f7e1591c4ba2fce --- /dev/null +++ b/vlc_patches/aug/0004-contrib-gcrypt-add-support-for-wasm-emscripten.patch @@ -0,0 +1,93 @@ +From 46e99994434a293d92b11bdf7b5089e9c38b5aa2 Mon Sep 17 00:00:00 2001 +From: Mehdi Sabwat <mehdi@videolabs.io> +Date: Sun, 30 May 2021 20:38:46 +0200 +Subject: [PATCH 04/77] contrib: gcrypt: add support for wasm-emscripten + +--- + contrib/src/gcrypt/rules.mak | 6 ++++ + contrib/src/gpg-error/emscripten.patch | 43 ++++++++++++++++++++++++++ + contrib/src/gpg-error/rules.mak | 1 + + 3 files changed, 50 insertions(+) + create mode 100644 contrib/src/gpg-error/emscripten.patch + +diff --git a/contrib/src/gcrypt/rules.mak b/contrib/src/gcrypt/rules.mak +index c9c9be3efa..0275e21fe8 100644 +--- a/contrib/src/gcrypt/rules.mak ++++ b/contrib/src/gcrypt/rules.mak +@@ -72,6 +72,12 @@ ifeq ($(ARCH),aarch64) + GCRYPT_CONF += --disable-arm-crypto-support + endif + endif ++ifdef HAVE_EMSCRIPTEN ++GCRYPT_CONF += --disable-asm --disable-aesni-support ac_cv_func_syslog=no --disable-sse41-support ++GCRYPT_CONF += --disable-avx-support --disable-avx2-support --disable-padlock-support ++GCRYPT_CONF += --disable-amd64-as-feature-detection --disable-drng-support ++GCRYPT_CONF += --disable-pclmul-support ++endif + + .gcrypt: gcrypt + # Reconfiguring this requires a git repo to be available, to +diff --git a/contrib/src/gpg-error/emscripten.patch b/contrib/src/gpg-error/emscripten.patch +new file mode 100644 +index 0000000000..f60695c513 +--- /dev/null ++++ b/contrib/src/gpg-error/emscripten.patch +@@ -0,0 +1,43 @@ ++From 63aa1523659914acd6c84229fb31ff9b712fbf8b Mon Sep 17 00:00:00 2001 ++From: Mehdi Sabwat <mehdi@videolabs.io> ++Date: Wed, 2 Jun 2021 11:42:46 +0200 ++Subject: [PATCH 1/1] emscripten ++ ++--- ++ .../lock-obj-pub.wasm32-unknown-emscripten.h | 24 +++++++++++++++++++ ++ 1 file changed, 24 insertions(+) ++ create mode 100644 src/syscfg/lock-obj-pub.wasm32-unknown-emscripten.h ++ ++diff --git a/src/syscfg/lock-obj-pub.wasm32-unknown-emscripten.h b/src/syscfg/lock-obj-pub.wasm32-unknown-emscripten.h ++new file mode 100644 ++index 0000000..1651518 ++--- /dev/null +++++ b/src/syscfg/lock-obj-pub.wasm32-unknown-emscripten.h ++@@ -0,0 +1,24 @@ +++## lock-obj-pub.wasm32-unknown-emscripten.h +++## File created by gen-posix-lock-obj - DO NOT EDIT +++## To be included by mkheader into gpg-error.h +++ +++typedef struct +++{ +++ long _vers; +++ union { +++ volatile char _priv[28]; +++ long _x_align; +++ long *_xp_align; +++ } u; +++} gpgrt_lock_t; +++ +++#define GPGRT_LOCK_INITIALIZER {1,{{0,0,0,0,0,0,0,0, \ +++ 0,0,0,0,0,0,0,0, \ +++ 0,0,0,0,0,0,0,0, \ +++ 0,0,0,0}}} +++## +++## Local Variables: +++## mode: c +++## buffer-read-only: t +++## End: +++## ++-- ++2.31.1 ++ +diff --git a/contrib/src/gpg-error/rules.mak b/contrib/src/gpg-error/rules.mak +index 0ceb75d7a3..5d4236b77f 100644 +--- a/contrib/src/gpg-error/rules.mak ++++ b/contrib/src/gpg-error/rules.mak +@@ -25,6 +25,7 @@ endif + $(APPLY) $(SRC)/gpg-error/version-bump-gawk-5.patch + $(APPLY) $(SRC)/gpg-error/win32-extern-struct.patch + $(APPLY) $(SRC)/gpg-error/darwin-triplet.patch ++ $(APPLY) $(SRC)/gpg-error/emscripten.patch + ifndef HAVE_WIN32 + cp -f -- "$(SRC)/gpg-error/lock-obj-pub.posix.h" \ + "$(UNPACK_DIR)/src/lock-obj-pub.native.h" +-- +2.35.1 + diff --git a/vlc_patches/aug/0005-contrib-gmp-add-support-for-wasm-emscripten.patch b/vlc_patches/aug/0005-contrib-gmp-add-support-for-wasm-emscripten.patch new file mode 100644 index 0000000000000000000000000000000000000000..d9c252d772408dab74064597c096a9ca8251fe30 --- /dev/null +++ b/vlc_patches/aug/0005-contrib-gmp-add-support-for-wasm-emscripten.patch @@ -0,0 +1,26 @@ +From 540a5ac3eb4d987805e74a8b3d2d585186c2a0c4 Mon Sep 17 00:00:00 2001 +From: Mehdi Sabwat <mehdi@videolabs.io> +Date: Sun, 30 May 2021 21:22:40 +0200 +Subject: [PATCH 05/77] contrib: gmp: add support for wasm-emscripten + +--- + contrib/src/gmp/rules.mak | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/contrib/src/gmp/rules.mak b/contrib/src/gmp/rules.mak +index 11fe8acdfa..d6cd702ce0 100644 +--- a/contrib/src/gmp/rules.mak ++++ b/contrib/src/gmp/rules.mak +@@ -13,6 +13,9 @@ ifeq ($(ARCH),mips64el) + GMP_CONF += --disable-assembly + endif + endif ++ifdef HAVE_EMSCRIPTEN ++GMP_CONF += --disable-assembly ++endif + + ifdef HAVE_WIN32 + ifeq ($(ARCH),arm) +-- +2.35.1 + diff --git a/vlc_patches/aug/0006-contrib-gnutls-add-support-for-wasm-emscripten.patch b/vlc_patches/aug/0006-contrib-gnutls-add-support-for-wasm-emscripten.patch new file mode 100644 index 0000000000000000000000000000000000000000..df5621136f4fedee87199f228b97234d6c08ef04 --- /dev/null +++ b/vlc_patches/aug/0006-contrib-gnutls-add-support-for-wasm-emscripten.patch @@ -0,0 +1,29 @@ +From 907f99dd996a0a34c1f50ba34171da80c64ee0ba Mon Sep 17 00:00:00 2001 +From: Mehdi Sabwat <mehdi@videolabs.io> +Date: Mon, 31 May 2021 00:18:18 +0200 +Subject: [PATCH 06/77] contrib: gnutls: add support for wasm-emscripten + +the contrib is temporarily disabled until this pr is merged: +https://github.com/emscripten-core/emscripten/pull/14352 +--- + contrib/src/gnutls/rules.mak | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/contrib/src/gnutls/rules.mak b/contrib/src/gnutls/rules.mak +index edec0ad9ee..fb372f9da1 100644 +--- a/contrib/src/gnutls/rules.mak ++++ b/contrib/src/gnutls/rules.mak +@@ -82,6 +82,10 @@ ifeq ($(ARCH),aarch64) + endif + endif + ++ifdef HAVE_EMSCRIPTEN ++ GNUTLS_CONF += --disable-hardware-acceleration ++endif ++ + .gnutls: gnutls + cd $< && $(GNUTLS_ENV) ./configure $(GNUTLS_CONF) + $(call pkg_static,"lib/gnutls.pc") +-- +2.35.1 + diff --git a/vlc_patches/aug/0007-aout-add-emscripten-audio-worklet-module.patch b/vlc_patches/aug/0007-aout-add-emscripten-audio-worklet-module.patch new file mode 100644 index 0000000000000000000000000000000000000000..fca81e83eab396a9b9d4755ccdd22ce8fe04dfb5 --- /dev/null +++ b/vlc_patches/aug/0007-aout-add-emscripten-audio-worklet-module.patch @@ -0,0 +1,438 @@ +From ea0d8db56cedf16328c1f88e9bd2406ef3bdcd29 Mon Sep 17 00:00:00 2001 +From: Mehdi Sabwat <mehdi@videolabs.io> +Date: Fri, 30 Apr 2021 01:15:17 +0200 +Subject: [PATCH 07/77] aout: add emscripten audio worklet module + +--- + modules/audio_output/Makefile.am | 5 + + modules/audio_output/emscripten.cpp | 405 ++++++++++++++++++++++++++++ + 2 files changed, 410 insertions(+) + create mode 100644 modules/audio_output/emscripten.cpp + +diff --git a/modules/audio_output/Makefile.am b/modules/audio_output/Makefile.am +index 24c05429d0..dc91f3cd7e 100644 +--- a/modules/audio_output/Makefile.am ++++ b/modules/audio_output/Makefile.am +@@ -117,3 +117,8 @@ endif + if HAVE_TVOS + aout_LTLIBRARIES += libaudiounit_ios_plugin.la + endif ++ ++libemworklet_audio_plugin_la_SOURCES = audio_output/emscripten.cpp ++if HAVE_EMSCRIPTEN ++aout_LTLIBRARIES += libemworklet_audio_plugin.la ++endif +diff --git a/modules/audio_output/emscripten.cpp b/modules/audio_output/emscripten.cpp +new file mode 100644 +index 0000000000..1c890b3f6d +--- /dev/null ++++ b/modules/audio_output/emscripten.cpp +@@ -0,0 +1,405 @@ ++/***************************************************************************** ++ * emscripten.c: audio output module using audio worklets ++ ***************************************************************************** ++ * Copyright © 2020 VLC authors and VideoLAN ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, write to the Free Software Foundation, Inc., ++ * 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. ++ *****************************************************************************/ ++ ++#ifdef HAVE_CONFIG_H ++# include "config.h" ++#endif ++ ++#include <assert.h> ++#include <vlc_common.h> ++#include <vlc_plugin.h> ++#include <vlc_aout.h> ++ ++#include <emscripten.h> ++#include <emscripten/val.h> ++#include <emscripten/bind.h> ++#include <emscripten/html5.h> ++ ++#include <cstdint> ++#include <stdlib.h> ++ ++#define STORAGE_SIZE 1024 * 1024 ++// Sample rate might change, and it would be good to be able to change it during playback. ++#define AUDIO_WORKLET_SAMPLE_RATE 44100 ++// Don't know any way to get the browser's supported number of channels. ++#define AUDIO_WORKLET_NB_CHANNELS 2 ++ ++using namespace emscripten; ++namespace { ++ EM_BOOL requestAnimationFrame_cb( double time, void *userData ); ++ ++ class AWNodeWrapper { ++ public: ++ val context = val::undefined(); ++ val getCtx() const { return context; }; ++ void setCtx(val v_context) { context = v_context; }; ++ ++ uintptr_t sab_ptr; ++ uintptr_t getSabPtr() const { return sab_ptr; }; ++ void setSabPtr(uintptr_t p_sab) { sab_ptr = p_sab; }; ++ ++ size_t sab_size; ++ size_t getSabSize() const { return sab_size; }; ++ void setSabSize(size_t s_size) { sab_size = s_size; }; ++ ++ int8_t channels; ++ int8_t getChannels() const { return channels; }; ++ void setChannels(int8_t chan) { channels = chan; }; ++ ++ AWNodeWrapper(int sample_rate) { ++ // Prepare audio context options ++ val audio_ctx_options = val::object(); ++ audio_ctx_options.set("sampleRate", sample_rate); ++ ++ context = val::global("AudioContext").new_(audio_ctx_options); ++ context.call<void>("suspend"); ++ } ++ ++ val operator()( val undefined_promise_argument ) { ++ (val)undefined_promise_argument; ++ ++ // Prepare AWN Options ++ val awn_options = val::object(); ++ val awn_opt_outputChannelCount = val::array(); ++ awn_opt_outputChannelCount.call<val>("push", channels); ++ awn_options.set("outputChannelCount", awn_opt_outputChannelCount); ++ awn_options.set("numberOfInputs", 0); ++ awn_options.set("numberOfOutputs", 1); ++ ++ val AudioNode = val::global("AudioWorkletNode").new_(context, std::string("worklet-processor"), awn_options); ++ AudioNode.set("channelCount", channels); ++ ++ //Prepare postMessage message ++ val msg = val::object(); ++ msg.set("type", std::string("recv-audio-queue")); ++ msg.set("data", val::module_property("wasmMemory")["buffer"]); ++ msg.set("sab_ptr", sab_ptr); ++ msg.set("sab_size", sab_size); ++ ++ AudioNode["port"].call<val>("postMessage", msg); ++ AudioNode.call<val>("connect", context["destination"]); ++ ++ emscripten_request_animation_frame_loop(requestAnimationFrame_cb, this); ++ ++ return val::undefined(); ++ } ++ }; ++ ++ EMSCRIPTEN_BINDINGS(AWWSCOPE) { ++ class_<AWNodeWrapper>("awn_cb_wrapper") ++ .constructor<int>() ++ .property("context", &AWNodeWrapper::getCtx, &AWNodeWrapper::setCtx) ++ .property("sab_ptr", &AWNodeWrapper::getSabPtr, &AWNodeWrapper::setSabPtr) ++ .property("sab_size", &AWNodeWrapper::getSabSize, &AWNodeWrapper::setSabSize) ++ .property("channels", &AWNodeWrapper::getChannels, &AWNodeWrapper::setChannels) ++ .function("awn_call", &AWNodeWrapper::operator()); ++ }; ++ ++ typedef struct aout_sys_t ++ { ++ int8_t *sab; ++ size_t sab_size; ++ AWNodeWrapper *awn_inst; ++ float volume; ++ ++ } aout_sys_t; ++ ++ EM_BOOL requestAnimationFrame_cb( double time, void *userData ) { ++ (double) time; ++ AWNodeWrapper *inst = reinterpret_cast<AWNodeWrapper *>(userData); ++ uint32_t *sab = reinterpret_cast<uint32_t *>(inst->getSabPtr()); ++ val view = val(typed_memory_view(inst->getSabSize(), sab)); ++ val context = inst->getCtx(); ++ if ( view[0].as<int>() == 1 ) { ++ context.call<val>("resume"); ++ sab[0] = 0; ++ return EM_FALSE; ++ } ++ return EM_TRUE; ++ } ++ ++ // For Atomics.store() and .load() only integer types are supported ++ unsigned int js_index_load(int8_t *sab_ptr, int8_t index, size_t sab_size){ ++ uint32_t *buffer_view = reinterpret_cast<uint32_t *>(sab_ptr); ++ val buffer = val(typed_memory_view(sab_size, buffer_view)); ++ ++ return val::global("Atomics").call<unsigned int>("load", buffer, index); ++ } ++ ++ void js_index_store(int8_t *sab_ptr, int8_t index, unsigned int value, size_t sab_size) { ++ uint32_t *buffer_view = reinterpret_cast<uint32_t *>(sab_ptr); ++ val buffer = val(typed_memory_view(sab_size, buffer_view)); ++ ++ return val::global("Atomics").call<void>("store", buffer, index, value); ++ } ++ ++ // careful when calling this, you cannot wait on any index ++ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics/wait ++ unsigned int js_index_wait(int8_t *sab_ptr, int8_t index, size_t sab_size) { ++ int32_t *buffer_view = reinterpret_cast<int32_t *>(sab_ptr); ++ val buffer = val(typed_memory_view(sab_size, buffer_view)); ++ ++ return val::global("Atomics").call<unsigned int>("wait", buffer, index); ++ } ++ ++ void Flush( audio_output_t *aout ) ++ { ++ aout_sys_t * sys = reinterpret_cast<aout_sys_t *>(aout->sys); ++ bzero(sys->sab, sys->sab_size); ++ } ++ ++ int Start( audio_output_t *aout, audio_sample_format_t *restrict fmt ) ++ { ++ aout_sys_t *sys = reinterpret_cast<aout_sys_t *>(aout->sys); ++ unsigned nbChannels = aout_FormatNbChannels(fmt); ++ ++ if (( nbChannels == 0 ) || !AOUT_FMT_LINEAR(fmt)) ++ return VLC_EGENERIC; ++ fmt->i_format = VLC_CODEC_FL32; ++ fmt->i_channels = AUDIO_WORKLET_NB_CHANNELS; ++ fmt->i_rate = AUDIO_WORKLET_SAMPLE_RATE; ++ ++ // resume audio context (first start, it is paused when initialized) ++ js_index_store(sys->sab, 4, (int)sys->volume * 100, sys->sab_size); ++ js_index_store(sys->sab, 0, 1, sys->sab_size); ++ ++ return VLC_SUCCESS; ++ } ++ ++ void Stop (audio_output_t *aout) ++ { ++ Flush(aout); ++ } ++ ++ int audio_worklet_push (audio_output_t *aout, const int8_t *data, unsigned data_size) { ++ aout_sys_t *sys = reinterpret_cast<aout_sys_t *>(aout->sys); ++ int8_t *sab_view = sys->sab + 5 * sizeof(int32_t); ++ unsigned head = js_index_load(sys->sab, 1, sys->sab_size); ++ ++ // TODO: check that we do not write on unconsumed data. ++ if (head + data_size > STORAGE_SIZE) { ++ // Copy the part of the data at the buffer end ++ unsigned data_size_copy_end = STORAGE_SIZE - head; ++ memcpy(sab_view + head, data, data_size_copy_end); ++ head = 0; ++ ++ // Copy the part of the data at the buffer start ++ unsigned data_size_copy_start = data_size - data_size_copy_end; ++ memcpy(sab_view + head, data, data_size_copy_start); ++ head = data_size_copy_start; ++ } ++ else { ++ memcpy(sab_view + head, data, data_size); ++ head += data_size; ++ } ++ js_index_store(sys->sab, 1, head, sys->sab_size); ++ return 0; // return success to indicate successful push. ++ } ++ ++ void Play( audio_output_t *aout, block_t *block, vlc_tick_t date) ++ { ++ VLC_UNUSED(date); ++ aout_sys_t *sys = reinterpret_cast<aout_sys_t *>(aout->sys); ++ const int8_t* data = (int8_t *)block->p_buffer; ++ size_t data_size = block->i_buffer; ++ ++ unsigned head = js_index_load(sys->sab, 1, sys->sab_size); ++ unsigned tail = js_index_load(sys->sab, 2, sys->sab_size); ++ unsigned new_head = (head + data_size) % STORAGE_SIZE; ++ if (new_head > tail) ++ { ++ // the worklet processor keeps rendering until tail matches head ++ // it will be notified by an Atomics.notify() from the process() callback ++ js_index_wait(sys->sab, 3, sys->sab_size); ++ } ++ audio_worklet_push(aout, data, data_size); ++ block_Release(block); ++ } ++ ++ void Pause( audio_output_t *aout, bool paused, vlc_tick_t date ) ++ { ++ aout_sys_t * sys = reinterpret_cast<aout_sys_t *>(aout->sys); ++ if (paused == false) { ++ js_index_store(sys->sab, 0, 0, sys->sab_size); ++ } ++ else { ++ js_index_store(sys->sab, 0, 1, sys->sab_size); ++ } ++ VLC_UNUSED(date); ++ Flush(aout); ++ } ++ ++ int Time_Get( audio_output_t *aout, vlc_tick_t *delay) ++ { ++ return aout_TimeGetDefault(aout, delay); ++ } ++ ++ void Close( vlc_object_t *obj ) ++ { ++ audio_output_t *aout = (audio_output_t *)obj; ++ struct aout_sys_t *sys = reinterpret_cast<struct aout_sys_t *>(aout->sys); ++ ++ delete sys->awn_inst; ++ free(sys->sab); ++ free(sys); ++ } ++ ++ int Volume_Set( audio_output_t *aout, float volume) ++ { ++ struct aout_sys_t *sys = reinterpret_cast<struct aout_sys_t *>(aout->sys); ++ ++ if (volume > 1.0f) ++ volume = 1.0f; ++ else if (volume < 0.0f) ++ volume = 0.0f; ++ // TODO: implement gain ++ sys->volume = volume; ++ js_index_store(sys->sab, 4, (int)volume * 100, sys->sab_size); ++ aout_VolumeReport(aout, volume); ++ ++ return 0; ++ } ++ ++ int Mute_Set( audio_output_t *aout, bool mute) ++ { ++ struct aout_sys_t *sys = reinterpret_cast<struct aout_sys_t *>(aout->sys); ++ ++ if (mute == 0) ++ js_index_store(sys->sab, 4, 0, sys->sab_size); ++ else ++ js_index_store(sys->sab, 4, (int)sys->volume * 100, sys->sab_size); ++ aout_MuteReport(aout, mute); ++ return 0; ++ } ++ ++ ++ int Open( vlc_object_t *obj ) ++ { ++ audio_output_t * aout = (audio_output_t *) obj; ++ ++ /* Allocate structures */ ++ aout_sys_t *sys = reinterpret_cast<aout_sys_t *>(malloc( sizeof( *sys ) )); ++ if( unlikely(sys == NULL) ) ++ return VLC_ENOMEM; ++ ++ aout->sys = sys; ++ aout->start = Start; ++ aout->stop = Stop; ++ aout->play = Play; ++ aout->pause = Pause; ++ aout->flush = Flush; ++ aout->time_get = Time_Get; ++ aout->volume_set = Volume_Set; ++ aout->mute_set = Mute_Set; ++ ++ sys->awn_inst = new AWNodeWrapper(AUDIO_WORKLET_SAMPLE_RATE); ++ sys->sab_size = 5 * sizeof(int32_t) + STORAGE_SIZE; ++ sys->sab = reinterpret_cast<int8_t *>(malloc( sys->sab_size )); ++ sys->volume = 1.0f; ++ ++ if ( unlikely(sys->sab == NULL) ) ++ return VLC_ENOMEM; ++ bzero(sys->sab, sys->sab_size); ++ ++ val webaudio_context = sys->awn_inst->getCtx(); ++ ++ // Prepare audioWorkletProcessor blob ++ val document = val::global("document"); ++ val script = document.call<val>("createElement", std::string("SCRIPT")); ++ script.set("type", std::string("worklet")); ++ std::string processorStr = "class Processor extends AudioWorkletProcessor { \ ++ constructor() { \ ++ super(); \ ++ this.port.onmessage = e => { \ ++ if (e.data.type === 'recv-audio-queue') { \ ++ this.buf = e.data.data; \ ++ this.capacity = e.data.sab_size / 4; \ ++ this.flag = new Uint32Array(this.buf, e.data.sab_ptr, 1); \ ++ this.head = new Uint32Array(this.buf, e.data.sab_ptr + 4, 1); \ ++ this.tail = new Uint32Array(this.buf, e.data.sab_ptr + 8, 1); \ ++ this.can_write = new Int32Array(this.buf, e.data.sab_ptr + 12, 1); \ ++ this.volume = new Int32Array(this.buf, e.data.sab_ptr + 16, 1); \ ++ this.storage = new Float32Array(this.buf, e.data.sab_ptr + 20, this.capacity); \ ++ } else { \ ++ throw 'unexpected.'; \ ++ } \ ++ }; \ ++ } \ ++ process(inputs, outputs, parameters) { \ ++ const output = outputs[0]; \ ++ const nbChannels = output.length; \ ++ const nbSamples = output[0].length; \ ++ if (this.head.buffer.byteLength == 0) { \ ++ throw new Error('wasmMemory grew'); \ ++ } \ ++ var head = Atomics.load(this.head, 0) / 4; \ ++ var tail = Atomics.load(this.tail, 0) / 4; \ ++ var i = 0; \ ++ var volume = (Atomics.load(this.volume, 0) / 4) / 100; \ ++ while (tail != head && i < nbSamples) \ ++ { \ ++ for (let c = 0; c < nbChannels; ++c) { \ ++ output[c][i] = this.storage[tail] * volume; \ ++ tail++; \ ++ if (tail == this.capacity) { \ ++ tail = 0; \ ++ } \ ++ } \ ++ i++; \ ++ } \ ++ Atomics.store(this.tail, 0, tail * 4); \ ++ Atomics.notify(this.can_write, 0); \ ++ return true; \ ++ } \ ++} \ ++registerProcessor('worklet-processor', Processor);"; ++ script.set("innerText", processorStr); ++ val ProcessorTextArray = val::array(); ++ ProcessorTextArray.call<val>("push", script["innerText"]); ++ val BlobObject = val::object(); ++ BlobObject.set("type", std::string("application/javascript")); ++ val WorkletModuleUrl = val::global("URL").call<val>("createObjectURL", val::global("Blob").new_(ProcessorTextArray, BlobObject)); ++ ++ // Prepare audioWorkletProcessor callback ++ val cb_caller = val::module_property("awn_cb_wrapper").new_(AUDIO_WORKLET_SAMPLE_RATE); ++ cb_caller.set("context", val(webaudio_context)); ++ cb_caller.set("sab_ptr", val(reinterpret_cast<uintptr_t>(sys->sab))); ++ cb_caller.set("sab_size", val(sys->sab_size)); ++ cb_caller.set("channels", val(AUDIO_WORKLET_NB_CHANNELS)); ++ val awn_caller = cb_caller["awn_call"]; ++ val awn_cb = awn_caller.call<val>("bind", cb_caller); ++ ++ // start audio worklet (since the context is suspended, sound won't start now ++ // Since the WebAudio Context cannot be created in a worker, we create ++ // it in the main_thread and use the SAB to signal it when we want it to start ++ webaudio_context["audioWorklet"].call<val>("addModule", WorkletModuleUrl).call<val>("then", awn_cb); ++ ++ return VLC_SUCCESS; ++ } ++} ++ ++vlc_module_begin () ++ set_description( N_("Emscripten Worklet audio output") ) ++ set_shortname( "emworklet" ) ++ set_capability( "audio output", 100 ) ++ set_category( CAT_AUDIO ) ++ set_subcategory( SUBCAT_AUDIO_AOUT ) ++ set_callbacks( Open, Close ) ++vlc_module_end () +-- +2.35.1 + diff --git a/vlc_patches/aug/0008-Normalize-formatting-in-audio_output-emscripten.cpp.patch b/vlc_patches/aug/0008-Normalize-formatting-in-audio_output-emscripten.cpp.patch new file mode 100644 index 0000000000000000000000000000000000000000..8dc8721de3434a27b6c80cf09e3ef59bb2a4b396 --- /dev/null +++ b/vlc_patches/aug/0008-Normalize-formatting-in-audio_output-emscripten.cpp.patch @@ -0,0 +1,261 @@ +From 31ca0b2e4b1d26cbe867d2effd60bd1fae5325e5 Mon Sep 17 00:00:00 2001 +From: Olivier FAURE <couteaubleu@gmail.com> +Date: Sun, 30 May 2021 12:01:49 +0200 +Subject: [PATCH 08/77] Normalize formatting in audio_output/emscripten.cpp + +--- + modules/audio_output/emscripten.cpp | 74 ++++++++++++++--------------- + 1 file changed, 37 insertions(+), 37 deletions(-) + +diff --git a/modules/audio_output/emscripten.cpp b/modules/audio_output/emscripten.cpp +index 1c890b3f6d..b704b90699 100644 +--- a/modules/audio_output/emscripten.cpp ++++ b/modules/audio_output/emscripten.cpp +@@ -42,7 +42,7 @@ + #define AUDIO_WORKLET_NB_CHANNELS 2 + + using namespace emscripten; +-namespace { ++namespace { + EM_BOOL requestAnimationFrame_cb( double time, void *userData ); + + class AWNodeWrapper { +@@ -50,31 +50,31 @@ namespace { + val context = val::undefined(); + val getCtx() const { return context; }; + void setCtx(val v_context) { context = v_context; }; +- ++ + uintptr_t sab_ptr; + uintptr_t getSabPtr() const { return sab_ptr; }; + void setSabPtr(uintptr_t p_sab) { sab_ptr = p_sab; }; +- ++ + size_t sab_size; + size_t getSabSize() const { return sab_size; }; + void setSabSize(size_t s_size) { sab_size = s_size; }; +- ++ + int8_t channels; + int8_t getChannels() const { return channels; }; + void setChannels(int8_t chan) { channels = chan; }; +- ++ + AWNodeWrapper(int sample_rate) { + // Prepare audio context options + val audio_ctx_options = val::object(); + audio_ctx_options.set("sampleRate", sample_rate); +- ++ + context = val::global("AudioContext").new_(audio_ctx_options); + context.call<void>("suspend"); + } +- ++ + val operator()( val undefined_promise_argument ) { + (val)undefined_promise_argument; +- ++ + // Prepare AWN Options + val awn_options = val::object(); + val awn_opt_outputChannelCount = val::array(); +@@ -82,26 +82,26 @@ namespace { + awn_options.set("outputChannelCount", awn_opt_outputChannelCount); + awn_options.set("numberOfInputs", 0); + awn_options.set("numberOfOutputs", 1); +- ++ + val AudioNode = val::global("AudioWorkletNode").new_(context, std::string("worklet-processor"), awn_options); + AudioNode.set("channelCount", channels); +- ++ + //Prepare postMessage message + val msg = val::object(); + msg.set("type", std::string("recv-audio-queue")); + msg.set("data", val::module_property("wasmMemory")["buffer"]); + msg.set("sab_ptr", sab_ptr); + msg.set("sab_size", sab_size); +- ++ + AudioNode["port"].call<val>("postMessage", msg); + AudioNode.call<val>("connect", context["destination"]); +- ++ + emscripten_request_animation_frame_loop(requestAnimationFrame_cb, this); +- ++ + return val::undefined(); + } + }; +- ++ + EMSCRIPTEN_BINDINGS(AWWSCOPE) { + class_<AWNodeWrapper>("awn_cb_wrapper") + .constructor<int>() +@@ -111,14 +111,14 @@ namespace { + .property("channels", &AWNodeWrapper::getChannels, &AWNodeWrapper::setChannels) + .function("awn_call", &AWNodeWrapper::operator()); + }; +- ++ + typedef struct aout_sys_t + { + int8_t *sab; + size_t sab_size; + AWNodeWrapper *awn_inst; + float volume; +- ++ + } aout_sys_t; + + EM_BOOL requestAnimationFrame_cb( double time, void *userData ) { +@@ -134,26 +134,26 @@ namespace { + } + return EM_TRUE; + } +- ++ + // For Atomics.store() and .load() only integer types are supported + unsigned int js_index_load(int8_t *sab_ptr, int8_t index, size_t sab_size){ +- uint32_t *buffer_view = reinterpret_cast<uint32_t *>(sab_ptr); ++ uint32_t *buffer_view = reinterpret_cast<uint32_t *>(sab_ptr); + val buffer = val(typed_memory_view(sab_size, buffer_view)); +- ++ + return val::global("Atomics").call<unsigned int>("load", buffer, index); + } +- ++ + void js_index_store(int8_t *sab_ptr, int8_t index, unsigned int value, size_t sab_size) { +- uint32_t *buffer_view = reinterpret_cast<uint32_t *>(sab_ptr); ++ uint32_t *buffer_view = reinterpret_cast<uint32_t *>(sab_ptr); + val buffer = val(typed_memory_view(sab_size, buffer_view)); +- ++ + return val::global("Atomics").call<void>("store", buffer, index, value); + } + + // careful when calling this, you cannot wait on any index + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics/wait + unsigned int js_index_wait(int8_t *sab_ptr, int8_t index, size_t sab_size) { +- int32_t *buffer_view = reinterpret_cast<int32_t *>(sab_ptr); ++ int32_t *buffer_view = reinterpret_cast<int32_t *>(sab_ptr); + val buffer = val(typed_memory_view(sab_size, buffer_view)); + + return val::global("Atomics").call<unsigned int>("wait", buffer, index); +@@ -164,7 +164,7 @@ namespace { + aout_sys_t * sys = reinterpret_cast<aout_sys_t *>(aout->sys); + bzero(sys->sab, sys->sab_size); + } +- ++ + int Start( audio_output_t *aout, audio_sample_format_t *restrict fmt ) + { + aout_sys_t *sys = reinterpret_cast<aout_sys_t *>(aout->sys); +@@ -187,7 +187,7 @@ namespace { + { + Flush(aout); + } +- ++ + int audio_worklet_push (audio_output_t *aout, const int8_t *data, unsigned data_size) { + aout_sys_t *sys = reinterpret_cast<aout_sys_t *>(aout->sys); + int8_t *sab_view = sys->sab + 5 * sizeof(int32_t); +@@ -199,7 +199,7 @@ namespace { + unsigned data_size_copy_end = STORAGE_SIZE - head; + memcpy(sab_view + head, data, data_size_copy_end); + head = 0; +- ++ + // Copy the part of the data at the buffer start + unsigned data_size_copy_start = data_size - data_size_copy_end; + memcpy(sab_view + head, data, data_size_copy_start); +@@ -219,7 +219,7 @@ namespace { + aout_sys_t *sys = reinterpret_cast<aout_sys_t *>(aout->sys); + const int8_t* data = (int8_t *)block->p_buffer; + size_t data_size = block->i_buffer; +- ++ + unsigned head = js_index_load(sys->sab, 1, sys->sab_size); + unsigned tail = js_index_load(sys->sab, 2, sys->sab_size); + unsigned new_head = (head + data_size) % STORAGE_SIZE; +@@ -232,7 +232,7 @@ namespace { + audio_worklet_push(aout, data, data_size); + block_Release(block); + } +- ++ + void Pause( audio_output_t *aout, bool paused, vlc_tick_t date ) + { + aout_sys_t * sys = reinterpret_cast<aout_sys_t *>(aout->sys); +@@ -245,7 +245,7 @@ namespace { + VLC_UNUSED(date); + Flush(aout); + } +- ++ + int Time_Get( audio_output_t *aout, vlc_tick_t *delay) + { + return aout_TimeGetDefault(aout, delay); +@@ -289,16 +289,16 @@ namespace { + return 0; + } + +- ++ + int Open( vlc_object_t *obj ) + { + audio_output_t * aout = (audio_output_t *) obj; +- ++ + /* Allocate structures */ + aout_sys_t *sys = reinterpret_cast<aout_sys_t *>(malloc( sizeof( *sys ) )); + if( unlikely(sys == NULL) ) + return VLC_ENOMEM; +- ++ + aout->sys = sys; + aout->start = Start; + aout->stop = Stop; +@@ -308,7 +308,7 @@ namespace { + aout->time_get = Time_Get; + aout->volume_set = Volume_Set; + aout->mute_set = Mute_Set; +- ++ + sys->awn_inst = new AWNodeWrapper(AUDIO_WORKLET_SAMPLE_RATE); + sys->sab_size = 5 * sizeof(int32_t) + STORAGE_SIZE; + sys->sab = reinterpret_cast<int8_t *>(malloc( sys->sab_size )); +@@ -317,9 +317,9 @@ namespace { + if ( unlikely(sys->sab == NULL) ) + return VLC_ENOMEM; + bzero(sys->sab, sys->sab_size); +- ++ + val webaudio_context = sys->awn_inst->getCtx(); +- ++ + // Prepare audioWorkletProcessor blob + val document = val::global("document"); + val script = document.call<val>("createElement", std::string("SCRIPT")); +@@ -376,7 +376,7 @@ registerProcessor('worklet-processor', Processor);"; + val BlobObject = val::object(); + BlobObject.set("type", std::string("application/javascript")); + val WorkletModuleUrl = val::global("URL").call<val>("createObjectURL", val::global("Blob").new_(ProcessorTextArray, BlobObject)); +- ++ + // Prepare audioWorkletProcessor callback + val cb_caller = val::module_property("awn_cb_wrapper").new_(AUDIO_WORKLET_SAMPLE_RATE); + cb_caller.set("context", val(webaudio_context)); +@@ -385,7 +385,7 @@ registerProcessor('worklet-processor', Processor);"; + cb_caller.set("channels", val(AUDIO_WORKLET_NB_CHANNELS)); + val awn_caller = cb_caller["awn_call"]; + val awn_cb = awn_caller.call<val>("bind", cb_caller); +- ++ + // start audio worklet (since the context is suspended, sound won't start now + // Since the WebAudio Context cannot be created in a worker, we create + // it in the main_thread and use the SAB to signal it when we want it to start +-- +2.35.1 + diff --git a/vlc_patches/aug/0009-Fix-emscripten-API-to-get-set-volume-levels-in-audio.patch b/vlc_patches/aug/0009-Fix-emscripten-API-to-get-set-volume-levels-in-audio.patch new file mode 100644 index 0000000000000000000000000000000000000000..7c9242e5a07a080349fbfa611982b887bdf5eef7 --- /dev/null +++ b/vlc_patches/aug/0009-Fix-emscripten-API-to-get-set-volume-levels-in-audio.patch @@ -0,0 +1,54 @@ +From 1741a092eae194f532b3e95400d90020be4517ab Mon Sep 17 00:00:00 2001 +From: Olivier FAURE <couteaubleu@gmail.com> +Date: Sun, 30 May 2021 12:14:26 +0200 +Subject: [PATCH 09/77] Fix emscripten API to get/set volume levels in + audio_output module + +--- + modules/audio_output/emscripten.cpp | 9 ++++++--- + 1 file changed, 6 insertions(+), 3 deletions(-) + +diff --git a/modules/audio_output/emscripten.cpp b/modules/audio_output/emscripten.cpp +index b704b90699..3fefa60065 100644 +--- a/modules/audio_output/emscripten.cpp ++++ b/modules/audio_output/emscripten.cpp +@@ -162,7 +162,7 @@ namespace { + void Flush( audio_output_t *aout ) + { + aout_sys_t * sys = reinterpret_cast<aout_sys_t *>(aout->sys); +- bzero(sys->sab, sys->sab_size); ++ bzero(sys->sab + (5 * sizeof(int32_t)), STORAGE_SIZE); + } + + int Start( audio_output_t *aout, audio_sample_format_t *restrict fmt ) +@@ -177,7 +177,7 @@ namespace { + fmt->i_rate = AUDIO_WORKLET_SAMPLE_RATE; + + // resume audio context (first start, it is paused when initialized) +- js_index_store(sys->sab, 4, (int)sys->volume * 100, sys->sab_size); ++ js_index_store(sys->sab, 4, (int)(sys->volume * 100), sys->sab_size); + js_index_store(sys->sab, 0, 1, sys->sab_size); + + return VLC_SUCCESS; +@@ -229,6 +229,10 @@ namespace { + // it will be notified by an Atomics.notify() from the process() callback + js_index_wait(sys->sab, 3, sys->sab_size); + } ++ ++ // set volume ++ js_index_store(sys->sab, 4, (int)(sys->volume * 100), sys->sab_size); ++ + audio_worklet_push(aout, data, data_size); + block_Release(block); + } +@@ -271,7 +275,6 @@ namespace { + volume = 0.0f; + // TODO: implement gain + sys->volume = volume; +- js_index_store(sys->sab, 4, (int)volume * 100, sys->sab_size); + aout_VolumeReport(aout, volume); + + return 0; +-- +2.35.1 + diff --git a/vlc_patches/aug/0010-Move-some-audio-worklet-logic-out-of-JS-code.patch b/vlc_patches/aug/0010-Move-some-audio-worklet-logic-out-of-JS-code.patch new file mode 100644 index 0000000000000000000000000000000000000000..0b970d19afc344a63d5f3efd10ccc74220a6fe38 --- /dev/null +++ b/vlc_patches/aug/0010-Move-some-audio-worklet-logic-out-of-JS-code.patch @@ -0,0 +1,84 @@ +From 44959e310fbf34b740b25010387fd08914fb5f3d Mon Sep 17 00:00:00 2001 +From: Olivier FAURE <couteaubleu@gmail.com> +Date: Sun, 30 May 2021 14:18:32 +0200 +Subject: [PATCH 10/77] Move some audio worklet logic out of JS code. + +This isn't terribly useful on its own, but serves to prepare the next +commit. +--- + modules/audio_output/emscripten.cpp | 35 ++++++++++++++++++----------- + 1 file changed, 22 insertions(+), 13 deletions(-) + +diff --git a/modules/audio_output/emscripten.cpp b/modules/audio_output/emscripten.cpp +index 3fefa60065..625dda30c0 100644 +--- a/modules/audio_output/emscripten.cpp ++++ b/modules/audio_output/emscripten.cpp +@@ -86,12 +86,23 @@ namespace { + val AudioNode = val::global("AudioWorkletNode").new_(context, std::string("worklet-processor"), awn_options); + AudioNode.set("channelCount", channels); + ++ val Uint32Array = val::global("Uint32Array"); ++ val Int32Array = val::global("Int32Array"); ++ val Float32Array = val::global("Float32Array"); ++ ++ auto wasm_memory_buffer = val::module_property("wasmMemory")["buffer"]; ++ uint32_t storage_capacity = sab_size / 4; ++ + //Prepare postMessage message + val msg = val::object(); + msg.set("type", std::string("recv-audio-queue")); +- msg.set("data", val::module_property("wasmMemory")["buffer"]); +- msg.set("sab_ptr", sab_ptr); +- msg.set("sab_size", sab_size); ++ ++ msg.set("flag", Uint32Array.new_(wasm_memory_buffer, sab_ptr + 0, 1)); ++ msg.set("head", Uint32Array.new_(wasm_memory_buffer, sab_ptr + 4, 1)); ++ msg.set("tail", Uint32Array.new_(wasm_memory_buffer, sab_ptr + 8, 1)); ++ msg.set("can_write", Int32Array.new_(wasm_memory_buffer, sab_ptr + 12, 1)); ++ msg.set("volume", Int32Array.new_(wasm_memory_buffer, sab_ptr + 16, 1)); ++ msg.set("storage", Float32Array.new_(wasm_memory_buffer, sab_ptr + 20, storage_capacity)); + + AudioNode["port"].call<val>("postMessage", msg); + AudioNode.call<val>("connect", context["destination"]); +@@ -122,7 +133,7 @@ namespace { + } aout_sys_t; + + EM_BOOL requestAnimationFrame_cb( double time, void *userData ) { +- (double) time; ++ (void) time; + AWNodeWrapper *inst = reinterpret_cast<AWNodeWrapper *>(userData); + uint32_t *sab = reinterpret_cast<uint32_t *>(inst->getSabPtr()); + val view = val(typed_memory_view(inst->getSabSize(), sab)); +@@ -332,14 +343,12 @@ namespace { + super(); \ + this.port.onmessage = e => { \ + if (e.data.type === 'recv-audio-queue') { \ +- this.buf = e.data.data; \ +- this.capacity = e.data.sab_size / 4; \ +- this.flag = new Uint32Array(this.buf, e.data.sab_ptr, 1); \ +- this.head = new Uint32Array(this.buf, e.data.sab_ptr + 4, 1); \ +- this.tail = new Uint32Array(this.buf, e.data.sab_ptr + 8, 1); \ +- this.can_write = new Int32Array(this.buf, e.data.sab_ptr + 12, 1); \ +- this.volume = new Int32Array(this.buf, e.data.sab_ptr + 16, 1); \ +- this.storage = new Float32Array(this.buf, e.data.sab_ptr + 20, this.capacity); \ ++ this.flag = e.data.flag; \ ++ this.head = e.data.head; \ ++ this.tail = e.data.tail; \ ++ this.can_write = e.data.can_write; \ ++ this.volume = e.data.volume; \ ++ this.storage = e.data.storage; \ + } else { \ + throw 'unexpected.'; \ + } \ +@@ -361,7 +370,7 @@ namespace { + for (let c = 0; c < nbChannels; ++c) { \ + output[c][i] = this.storage[tail] * volume; \ + tail++; \ +- if (tail == this.capacity) { \ ++ if (tail == this.storage.length) { \ + tail = 0; \ + } \ + } \ +-- +2.35.1 + diff --git a/vlc_patches/aug/0011-Replace-untyped-js_index_load-store-functions-with-C.patch b/vlc_patches/aug/0011-Replace-untyped-js_index_load-store-functions-with-C.patch new file mode 100644 index 0000000000000000000000000000000000000000..c91b608e0741a1a714c3cdb35b7388ae97b146e4 --- /dev/null +++ b/vlc_patches/aug/0011-Replace-untyped-js_index_load-store-functions-with-C.patch @@ -0,0 +1,291 @@ +From 79a00fe61cde93bd8416a88b41a4a5a68737befa Mon Sep 17 00:00:00 2001 +From: Olivier FAURE <couteaubleu@gmail.com> +Date: Sun, 30 May 2021 15:15:22 +0200 +Subject: [PATCH 11/77] Replace untyped js_index_load/store functions with C++ + atomics + +--- + modules/audio_output/emscripten.cpp | 116 ++++++++++++++-------------- + 1 file changed, 59 insertions(+), 57 deletions(-) + +diff --git a/modules/audio_output/emscripten.cpp b/modules/audio_output/emscripten.cpp +index 625dda30c0..fa0f5bd3e4 100644 +--- a/modules/audio_output/emscripten.cpp ++++ b/modules/audio_output/emscripten.cpp +@@ -22,6 +22,8 @@ + # include "config.h" + #endif + ++#include <atomic> ++ + #include <assert.h> + #include <vlc_common.h> + #include <vlc_plugin.h> +@@ -45,6 +47,18 @@ using namespace emscripten; + namespace { + EM_BOOL requestAnimationFrame_cb( double time, void *userData ); + ++ typedef struct sound_buffer_t ++ { ++ // TODO - should be bool? ++ std::atomic<uint32_t> is_paused; ++ std::atomic<uint32_t> head; ++ std::atomic<uint32_t> tail; ++ std::atomic<uint32_t> can_write; ++ std::atomic<uint32_t> volume; ++ int8_t storage[STORAGE_SIZE]; ++ ++ } sound_buffer_t; ++ + class AWNodeWrapper { + public: + val context = val::undefined(); +@@ -55,10 +69,6 @@ namespace { + uintptr_t getSabPtr() const { return sab_ptr; }; + void setSabPtr(uintptr_t p_sab) { sab_ptr = p_sab; }; + +- size_t sab_size; +- size_t getSabSize() const { return sab_size; }; +- void setSabSize(size_t s_size) { sab_size = s_size; }; +- + int8_t channels; + int8_t getChannels() const { return channels; }; + void setChannels(int8_t chan) { channels = chan; }; +@@ -90,19 +100,26 @@ namespace { + val Int32Array = val::global("Int32Array"); + val Float32Array = val::global("Float32Array"); + +- auto wasm_memory_buffer = val::module_property("wasmMemory")["buffer"]; +- uint32_t storage_capacity = sab_size / 4; ++ auto wasm_mem = val::module_property("wasmMemory")["buffer"]; + + //Prepare postMessage message + val msg = val::object(); + msg.set("type", std::string("recv-audio-queue")); + +- msg.set("flag", Uint32Array.new_(wasm_memory_buffer, sab_ptr + 0, 1)); +- msg.set("head", Uint32Array.new_(wasm_memory_buffer, sab_ptr + 4, 1)); +- msg.set("tail", Uint32Array.new_(wasm_memory_buffer, sab_ptr + 8, 1)); +- msg.set("can_write", Int32Array.new_(wasm_memory_buffer, sab_ptr + 12, 1)); +- msg.set("volume", Int32Array.new_(wasm_memory_buffer, sab_ptr + 16, 1)); +- msg.set("storage", Float32Array.new_(wasm_memory_buffer, sab_ptr + 20, storage_capacity)); ++ msg.set("is_paused", ++ Uint32Array.new_(wasm_mem, sab_ptr + offsetof(sound_buffer_t, is_paused), 1)); ++ msg.set("head", ++ Uint32Array.new_(wasm_mem, sab_ptr + offsetof(sound_buffer_t, head), 1)); ++ msg.set("tail", ++ Uint32Array.new_(wasm_mem, sab_ptr + offsetof(sound_buffer_t, tail), 1)); ++ msg.set("can_write", ++ Int32Array.new_(wasm_mem, sab_ptr + offsetof(sound_buffer_t, can_write), 1)); ++ msg.set("volume", ++ Int32Array.new_(wasm_mem, sab_ptr + offsetof(sound_buffer_t, volume), 1)); ++ ++ uint32_t storage_capacity = STORAGE_SIZE / 4; ++ msg.set("storage", ++ Float32Array.new_(wasm_mem, sab_ptr + offsetof(sound_buffer_t, storage), storage_capacity)); + + AudioNode["port"].call<val>("postMessage", msg); + AudioNode.call<val>("connect", context["destination"]); +@@ -118,25 +135,25 @@ namespace { + .constructor<int>() + .property("context", &AWNodeWrapper::getCtx, &AWNodeWrapper::setCtx) + .property("sab_ptr", &AWNodeWrapper::getSabPtr, &AWNodeWrapper::setSabPtr) +- .property("sab_size", &AWNodeWrapper::getSabSize, &AWNodeWrapper::setSabSize) + .property("channels", &AWNodeWrapper::getChannels, &AWNodeWrapper::setChannels) + .function("awn_call", &AWNodeWrapper::operator()); + }; + + typedef struct aout_sys_t + { +- int8_t *sab; +- size_t sab_size; ++ sound_buffer_t *sab; // TODO - rename to sound_buff + AWNodeWrapper *awn_inst; + float volume; + + } aout_sys_t; + + EM_BOOL requestAnimationFrame_cb( double time, void *userData ) { +- (void) time; ++ VLC_UNUSED(time); ++ // FIXME - this function seems to mix two different views on the ++ // same memory, not sure why + AWNodeWrapper *inst = reinterpret_cast<AWNodeWrapper *>(userData); + uint32_t *sab = reinterpret_cast<uint32_t *>(inst->getSabPtr()); +- val view = val(typed_memory_view(inst->getSabSize(), sab)); ++ val view = val(typed_memory_view(sizeof(sound_buffer_t), sab)); + val context = inst->getCtx(); + if ( view[0].as<int>() == 1 ) { + context.call<val>("resume"); +@@ -146,34 +163,19 @@ namespace { + return EM_TRUE; + } + +- // For Atomics.store() and .load() only integer types are supported +- unsigned int js_index_load(int8_t *sab_ptr, int8_t index, size_t sab_size){ +- uint32_t *buffer_view = reinterpret_cast<uint32_t *>(sab_ptr); +- val buffer = val(typed_memory_view(sab_size, buffer_view)); +- +- return val::global("Atomics").call<unsigned int>("load", buffer, index); +- } +- +- void js_index_store(int8_t *sab_ptr, int8_t index, unsigned int value, size_t sab_size) { +- uint32_t *buffer_view = reinterpret_cast<uint32_t *>(sab_ptr); +- val buffer = val(typed_memory_view(sab_size, buffer_view)); +- +- return val::global("Atomics").call<void>("store", buffer, index, value); +- } +- + // careful when calling this, you cannot wait on any index + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics/wait +- unsigned int js_index_wait(int8_t *sab_ptr, int8_t index, size_t sab_size) { ++ uint32_t js_index_wait(sound_buffer_t *sab_ptr, int8_t index) { + int32_t *buffer_view = reinterpret_cast<int32_t *>(sab_ptr); +- val buffer = val(typed_memory_view(sab_size, buffer_view)); ++ val buffer = val(typed_memory_view(STORAGE_SIZE, buffer_view)); + +- return val::global("Atomics").call<unsigned int>("wait", buffer, index); ++ return val::global("Atomics").call<uint32_t>("wait", buffer, index); + } + + void Flush( audio_output_t *aout ) + { + aout_sys_t * sys = reinterpret_cast<aout_sys_t *>(aout->sys); +- bzero(sys->sab + (5 * sizeof(int32_t)), STORAGE_SIZE); ++ bzero(&sys->sab->storage, sizeof(sys->sab->storage)); + } + + int Start( audio_output_t *aout, audio_sample_format_t *restrict fmt ) +@@ -188,8 +190,8 @@ namespace { + fmt->i_rate = AUDIO_WORKLET_SAMPLE_RATE; + + // resume audio context (first start, it is paused when initialized) +- js_index_store(sys->sab, 4, (int)(sys->volume * 100), sys->sab_size); +- js_index_store(sys->sab, 0, 1, sys->sab_size); ++ sys->sab->volume.store((int)(sys->volume * 100)); ++ sys->sab->is_paused.store(1); + + return VLC_SUCCESS; + } +@@ -199,10 +201,10 @@ namespace { + Flush(aout); + } + +- int audio_worklet_push (audio_output_t *aout, const int8_t *data, unsigned data_size) { ++ int audio_worklet_push (audio_output_t *aout, const int8_t *data, uint32_t data_size) { + aout_sys_t *sys = reinterpret_cast<aout_sys_t *>(aout->sys); +- int8_t *sab_view = sys->sab + 5 * sizeof(int32_t); +- unsigned head = js_index_load(sys->sab, 1, sys->sab_size); ++ int8_t *sab_view = sys->sab->storage; ++ uint32_t head = sys->sab->head.load(); + + // TODO: check that we do not write on unconsumed data. + if (head + data_size > STORAGE_SIZE) { +@@ -220,7 +222,7 @@ namespace { + memcpy(sab_view + head, data, data_size); + head += data_size; + } +- js_index_store(sys->sab, 1, head, sys->sab_size); ++ sys->sab->head.store(head); + return 0; // return success to indicate successful push. + } + +@@ -231,18 +233,18 @@ namespace { + const int8_t* data = (int8_t *)block->p_buffer; + size_t data_size = block->i_buffer; + +- unsigned head = js_index_load(sys->sab, 1, sys->sab_size); +- unsigned tail = js_index_load(sys->sab, 2, sys->sab_size); +- unsigned new_head = (head + data_size) % STORAGE_SIZE; ++ uint32_t head = sys->sab->head.load(); ++ uint32_t tail = sys->sab->tail.load(); ++ uint32_t new_head = (head + data_size) % STORAGE_SIZE; + if (new_head > tail) + { + // the worklet processor keeps rendering until tail matches head + // it will be notified by an Atomics.notify() from the process() callback +- js_index_wait(sys->sab, 3, sys->sab_size); ++ js_index_wait(sys->sab, 3); + } + + // set volume +- js_index_store(sys->sab, 4, (int)(sys->volume * 100), sys->sab_size); ++ sys->sab->volume.store((int)(sys->volume * 100)); + + audio_worklet_push(aout, data, data_size); + block_Release(block); +@@ -250,14 +252,14 @@ namespace { + + void Pause( audio_output_t *aout, bool paused, vlc_tick_t date ) + { ++ VLC_UNUSED(date); + aout_sys_t * sys = reinterpret_cast<aout_sys_t *>(aout->sys); + if (paused == false) { +- js_index_store(sys->sab, 0, 0, sys->sab_size); ++ sys->sab->is_paused.store(0); + } + else { +- js_index_store(sys->sab, 0, 1, sys->sab_size); ++ sys->sab->is_paused.store(1); + } +- VLC_UNUSED(date); + Flush(aout); + } + +@@ -271,6 +273,7 @@ namespace { + audio_output_t *aout = (audio_output_t *)obj; + struct aout_sys_t *sys = reinterpret_cast<struct aout_sys_t *>(aout->sys); + ++ // FIXME + delete sys->awn_inst; + free(sys->sab); + free(sys); +@@ -296,9 +299,10 @@ namespace { + struct aout_sys_t *sys = reinterpret_cast<struct aout_sys_t *>(aout->sys); + + if (mute == 0) +- js_index_store(sys->sab, 4, 0, sys->sab_size); ++ sys->sab->volume.store(0); + else +- js_index_store(sys->sab, 4, (int)sys->volume * 100, sys->sab_size); ++ sys->sab->volume.store((int)(sys->volume * 100)); ++ + aout_MuteReport(aout, mute); + return 0; + } +@@ -324,13 +328,12 @@ namespace { + aout->mute_set = Mute_Set; + + sys->awn_inst = new AWNodeWrapper(AUDIO_WORKLET_SAMPLE_RATE); +- sys->sab_size = 5 * sizeof(int32_t) + STORAGE_SIZE; +- sys->sab = reinterpret_cast<int8_t *>(malloc( sys->sab_size )); ++ sys->sab = (sound_buffer_t*)malloc(sizeof(sound_buffer_t)); + sys->volume = 1.0f; + + if ( unlikely(sys->sab == NULL) ) + return VLC_ENOMEM; +- bzero(sys->sab, sys->sab_size); ++ bzero(sys->sab, sizeof(sound_buffer_t)); + + val webaudio_context = sys->awn_inst->getCtx(); + +@@ -343,7 +346,7 @@ namespace { + super(); \ + this.port.onmessage = e => { \ + if (e.data.type === 'recv-audio-queue') { \ +- this.flag = e.data.flag; \ ++ this.is_paused = e.data.is_paused; \ + this.head = e.data.head; \ + this.tail = e.data.tail; \ + this.can_write = e.data.can_write; \ +@@ -393,7 +396,6 @@ registerProcessor('worklet-processor', Processor);"; + val cb_caller = val::module_property("awn_cb_wrapper").new_(AUDIO_WORKLET_SAMPLE_RATE); + cb_caller.set("context", val(webaudio_context)); + cb_caller.set("sab_ptr", val(reinterpret_cast<uintptr_t>(sys->sab))); +- cb_caller.set("sab_size", val(sys->sab_size)); + cb_caller.set("channels", val(AUDIO_WORKLET_NB_CHANNELS)); + val awn_caller = cb_caller["awn_call"]; + val awn_cb = awn_caller.call<val>("bind", cb_caller); +-- +2.35.1 + diff --git a/vlc_patches/aug/0012-Fix-volume-handling-in-emscripten-audio_output-modul.patch b/vlc_patches/aug/0012-Fix-volume-handling-in-emscripten-audio_output-modul.patch new file mode 100644 index 0000000000000000000000000000000000000000..4cfd159570e553002499a1df37d22de0e28208b5 --- /dev/null +++ b/vlc_patches/aug/0012-Fix-volume-handling-in-emscripten-audio_output-modul.patch @@ -0,0 +1,124 @@ +From 57c7d65d059510b0a39567a0d69351780d8b44c0 Mon Sep 17 00:00:00 2001 +From: Olivier FAURE <couteaubleu@gmail.com> +Date: Sun, 30 May 2021 17:18:23 +0200 +Subject: [PATCH 12/77] Fix volume handling in emscripten audio_output module + +Fix bug that had volume divided by 4 +Store mute state separately +--- + modules/audio_output/emscripten.cpp | 28 +++++++++++++++------------- + 1 file changed, 15 insertions(+), 13 deletions(-) + +diff --git a/modules/audio_output/emscripten.cpp b/modules/audio_output/emscripten.cpp +index fa0f5bd3e4..c39ca884b6 100644 +--- a/modules/audio_output/emscripten.cpp ++++ b/modules/audio_output/emscripten.cpp +@@ -55,6 +55,7 @@ namespace { + std::atomic<uint32_t> tail; + std::atomic<uint32_t> can_write; + std::atomic<uint32_t> volume; ++ std::atomic<uint32_t> is_muted; + int8_t storage[STORAGE_SIZE]; + + } sound_buffer_t; +@@ -116,6 +117,8 @@ namespace { + Int32Array.new_(wasm_mem, sab_ptr + offsetof(sound_buffer_t, can_write), 1)); + msg.set("volume", + Int32Array.new_(wasm_mem, sab_ptr + offsetof(sound_buffer_t, volume), 1)); ++ msg.set("is_muted", ++ Uint32Array.new_(wasm_mem, sab_ptr + offsetof(sound_buffer_t, is_muted), 1)); + + uint32_t storage_capacity = STORAGE_SIZE / 4; + msg.set("storage", +@@ -143,7 +146,6 @@ namespace { + { + sound_buffer_t *sab; // TODO - rename to sound_buff + AWNodeWrapper *awn_inst; +- float volume; + + } aout_sys_t; + +@@ -190,7 +192,6 @@ namespace { + fmt->i_rate = AUDIO_WORKLET_SAMPLE_RATE; + + // resume audio context (first start, it is paused when initialized) +- sys->sab->volume.store((int)(sys->volume * 100)); + sys->sab->is_paused.store(1); + + return VLC_SUCCESS; +@@ -240,12 +241,10 @@ namespace { + { + // the worklet processor keeps rendering until tail matches head + // it will be notified by an Atomics.notify() from the process() callback ++ // FIXME - This is layout-dependent, which isn't ideal + js_index_wait(sys->sab, 3); + } + +- // set volume +- sys->sab->volume.store((int)(sys->volume * 100)); +- + audio_worklet_push(aout, data, data_size); + block_Release(block); + } +@@ -288,7 +287,9 @@ namespace { + else if (volume < 0.0f) + volume = 0.0f; + // TODO: implement gain +- sys->volume = volume; ++ // Note: We store volume as an integer between 0..100 because ++ // for some reason Float32Array doesn't allow atomic operations ++ sys->sab->volume.store((int)(volume * 100)); + aout_VolumeReport(aout, volume); + + return 0; +@@ -298,12 +299,9 @@ namespace { + { + struct aout_sys_t *sys = reinterpret_cast<struct aout_sys_t *>(aout->sys); + +- if (mute == 0) +- sys->sab->volume.store(0); +- else +- sys->sab->volume.store((int)(sys->volume * 100)); +- ++ sys->sab->is_muted.store(mute); + aout_MuteReport(aout, mute); ++ + return 0; + } + +@@ -329,11 +327,11 @@ namespace { + + sys->awn_inst = new AWNodeWrapper(AUDIO_WORKLET_SAMPLE_RATE); + sys->sab = (sound_buffer_t*)malloc(sizeof(sound_buffer_t)); +- sys->volume = 1.0f; + + if ( unlikely(sys->sab == NULL) ) + return VLC_ENOMEM; + bzero(sys->sab, sizeof(sound_buffer_t)); ++ sys->sab->volume = 100; + + val webaudio_context = sys->awn_inst->getCtx(); + +@@ -351,6 +349,7 @@ namespace { + this.tail = e.data.tail; \ + this.can_write = e.data.can_write; \ + this.volume = e.data.volume; \ ++ this.is_muted = e.data.is_muted; \ + this.storage = e.data.storage; \ + } else { \ + throw 'unexpected.'; \ +@@ -367,7 +366,10 @@ namespace { + var head = Atomics.load(this.head, 0) / 4; \ + var tail = Atomics.load(this.tail, 0) / 4; \ + var i = 0; \ +- var volume = (Atomics.load(this.volume, 0) / 4) / 100; \ ++ var volume = Atomics.load(this.volume, 0) / 100; \ ++ if (Atomics.load(this.is_paused, 0) != 0 || Atomics.load(this.is_muted, 0) != 0) { \ ++ volume = 0; \ ++ } \ + while (tail != head && i < nbSamples) \ + { \ + for (let c = 0; c < nbChannels; ++c) { \ +-- +2.35.1 + diff --git a/vlc_patches/aug/0013-aout-fix-js_index_wait.patch b/vlc_patches/aug/0013-aout-fix-js_index_wait.patch new file mode 100644 index 0000000000000000000000000000000000000000..ea66d4fec0f661c80c9ad517a961269a03e6e669 --- /dev/null +++ b/vlc_patches/aug/0013-aout-fix-js_index_wait.patch @@ -0,0 +1,51 @@ +From 7a138898779f9bf1d4af79361e05b580f068461c Mon Sep 17 00:00:00 2001 +From: Mehdi Sabwat <mehdi@videolabs.io> +Date: Thu, 1 Jul 2021 11:28:53 +0200 +Subject: [PATCH 13/77] aout: fix js_index_wait + +- fix return type +- notifying without changing the value won't update the value +--- + modules/audio_output/emscripten.cpp | 9 +++++---- + 1 file changed, 5 insertions(+), 4 deletions(-) + +diff --git a/modules/audio_output/emscripten.cpp b/modules/audio_output/emscripten.cpp +index c39ca884b6..6acea387e3 100644 +--- a/modules/audio_output/emscripten.cpp ++++ b/modules/audio_output/emscripten.cpp +@@ -167,11 +167,11 @@ namespace { + + // careful when calling this, you cannot wait on any index + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics/wait +- uint32_t js_index_wait(sound_buffer_t *sab_ptr, int8_t index) { ++ void js_index_wait(sound_buffer_t *sab_ptr, int8_t index) { + int32_t *buffer_view = reinterpret_cast<int32_t *>(sab_ptr); + val buffer = val(typed_memory_view(STORAGE_SIZE, buffer_view)); + +- return val::global("Atomics").call<uint32_t>("wait", buffer, index); ++ val::global("Atomics").call<val>("wait", buffer, index, 0); + } + + void Flush( audio_output_t *aout ) +@@ -241,7 +241,7 @@ namespace { + { + // the worklet processor keeps rendering until tail matches head + // it will be notified by an Atomics.notify() from the process() callback +- // FIXME - This is layout-dependent, which isn't ideal ++ // FIXME - This is layout-dependent, which isn't ideal + js_index_wait(sys->sab, 3); + } + +@@ -382,7 +382,8 @@ namespace { + i++; \ + } \ + Atomics.store(this.tail, 0, tail * 4); \ +- Atomics.notify(this.can_write, 0); \ ++ Atomics.store(this.can_write, 0, 1); \ ++ Atomics.notify(this.can_write, 0); \ + return true; \ + } \ + } \ +-- +2.35.1 + diff --git a/vlc_patches/aug/0014-opengl-set-egl-display.patch b/vlc_patches/aug/0014-opengl-set-egl-display.patch new file mode 100644 index 0000000000000000000000000000000000000000..f45519a4f8830b03bf01661b08fe68ff6bfd996a --- /dev/null +++ b/vlc_patches/aug/0014-opengl-set-egl-display.patch @@ -0,0 +1,29 @@ +From 1f56116d4a13943522a5e59d09b941adf4535a35 Mon Sep 17 00:00:00 2001 +From: Mehdi Sabwat <mehdi@videolabs.io> +Date: Tue, 27 Apr 2021 16:27:19 +0200 +Subject: [PATCH 14/77] opengl: set egl display + +Emscripten does not implement EGL extensions. +https://emscripten.org/docs/porting/multimedia_and_graphics/EGL-Support-in-Emscripten.html#egl-extensions +--- + modules/video_output/opengl/egl_display_generic.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/modules/video_output/opengl/egl_display_generic.c b/modules/video_output/opengl/egl_display_generic.c +index dfb841feef..730fb381d2 100644 +--- a/modules/video_output/opengl/egl_display_generic.c ++++ b/modules/video_output/opengl/egl_display_generic.c +@@ -52,8 +52,8 @@ static vlc_egl_display_open_fn Open; + static int + Open(struct vlc_egl_display *display) + { +-#ifdef __ANDROID__ +- /* The default display is refcounted on Android */ ++#if defined(__ANDROID__) || defined(__EMSCRIPTEN__) ++ /* The default display is refcounted on Android and Emscripten */ + display->display = eglGetDisplay(EGL_DEFAULT_DISPLAY); + #elif defined(EGL_KHR_display_reference) + const char *extensions = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); +-- +2.35.1 + diff --git a/vlc_patches/aug/0015-vout-add-emscripten-webgl-module.patch b/vlc_patches/aug/0015-vout-add-emscripten-webgl-module.patch new file mode 100644 index 0000000000000000000000000000000000000000..9f73141f5b09e9f0ed6c1df904fd0449877709b5 --- /dev/null +++ b/vlc_patches/aug/0015-vout-add-emscripten-webgl-module.patch @@ -0,0 +1,221 @@ +From 4a8434bf69a9f4b6e684bc750b4cbe3aa3c49685 Mon Sep 17 00:00:00 2001 +From: Mehdi Sabwat <mehdi@videolabs.io> +Date: Tue, 27 Apr 2021 16:45:02 +0200 +Subject: [PATCH 15/77] vout: add emscripten webgl module + +this module uses the OFFSCREEN_FRAMEBUFFER option which +adds a backbuffer to the webgl context, that will be used +for rendering. + +it works as a polyfill for offscreen canvas. + +Co-Authored-By: Etienne Brateau <etienne.brateau@gmail.com> +--- + modules/video_output/Makefile.am | 7 ++ + modules/video_output/emscripten.c | 176 ++++++++++++++++++++++++++++++ + 2 files changed, 183 insertions(+) + create mode 100644 modules/video_output/emscripten.c + +diff --git a/modules/video_output/Makefile.am b/modules/video_output/Makefile.am +index d4ada0394b..3134528bc1 100644 +--- a/modules/video_output/Makefile.am ++++ b/modules/video_output/Makefile.am +@@ -345,6 +345,13 @@ libcaca_plugin_la_LDFLAGS = $(AM_LDFLAGS) -rpath '$(voutdir)' + EXTRA_LTLIBRARIES += libcaca_plugin.la + vout_LTLIBRARIES += $(LTLIBcaca) + ++### Emscripten ### ++libemscripten_window_plugin_la_SOURCES = video_output/emscripten.c ++ ++if HAVE_EMSCRIPTEN ++vout_LTLIBRARIES += libemscripten_window_plugin.la ++endif ++ + ### Common ### + + libflaschen_plugin_la_SOURCES = video_output/flaschen.c +diff --git a/modules/video_output/emscripten.c b/modules/video_output/emscripten.c +new file mode 100644 +index 0000000000..05c17d3179 +--- /dev/null ++++ b/modules/video_output/emscripten.c +@@ -0,0 +1,176 @@ ++/** ++ * @file emscripten.c ++ * @brief Emscripten webgl video output for VLC media player ++ */ ++/***************************************************************************** ++ * Copyright © 2020 VLC authors and VideoLAN ++ * ++ * ++ * This program is free software; you can redistribute it and/or modify it ++ * under the terms of the GNU Lesser General Public License as published by ++ * the Free Software Foundation; either version 2.1 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU Lesser General Public License for more details. ++ * ++ * You should have received a copy of the GNU Lesser General Public License ++ * along with this program; if not, write to the Free Software Foundation, ++ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. ++ *****************************************************************************/ ++ ++#ifdef HAVE_CONFIG_H ++# include <config.h> ++#endif ++ ++#include <stdarg.h> ++ ++#include <vlc_common.h> ++#include <vlc_plugin.h> ++#include <vlc_vout_window.h> ++#include <vlc_vout_display.h> ++#include <vlc_opengl.h> ++ ++#include "./opengl/vout_helper.h" ++ ++#include <emscripten.h> ++#include <emscripten/html5.h> ++#include <webgl/webgl2.h> ++// eglGetProcAddress ++#include <EGL/egl.h> ++ ++extern emscripten_GetProcAddress(char *name); ++ ++static const struct vout_window_operations ops = { ++ //TODO: Implement canvas operations ++ //vout_window_ReportSize() should be called from here ++}; ++ ++typedef struct gl_sys_t ++{ ++ unsigned width; ++ unsigned height; ++ ++ EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context; ++} gl_sys_t; ++ ++static int OpenWindow(vout_window_t *wnd) ++{ ++ wnd->type = VOUT_WINDOW_TYPE_EMSCRIPTEN_WEBGL; ++ wnd->ops = &ops; ++ ++ return VLC_SUCCESS; ++} ++ ++static void *GetProcAddress(vlc_gl_t *gl, const char *name) ++{ ++ VLC_UNUSED(gl); ++ ++ return eglGetProcAddress(name); ++} ++static int MakeCurrent(vlc_gl_t *gl) ++{ ++ gl_sys_t *sys = gl->sys; ++ ++ if (emscripten_webgl_make_context_current(sys->context) != EMSCRIPTEN_RESULT_SUCCESS) ++ return VLC_EGENERIC; ++ return VLC_SUCCESS; ++} ++ ++static void ReleaseCurrent(vlc_gl_t *gl) ++{ ++ VLC_UNUSED(gl); ++ emscripten_webgl_make_context_current(0); ++} ++ ++static void Swap(vlc_gl_t *gl) ++{ ++ VLC_UNUSED(gl); ++ emscripten_webgl_commit_frame(); ++} ++ ++static void Resize(vlc_gl_t *gl, unsigned w, unsigned h) ++{ ++ VLC_UNUSED(gl); ++ VLC_UNUSED(w); ++ VLC_UNUSED(h); ++} ++ ++static void Close (vlc_gl_t *gl) ++{ ++ free(gl->sys); ++} ++ ++static int Open (vlc_gl_t *gl, unsigned width, unsigned height) ++{ ++ VLC_UNUSED(width), VLC_UNUSED(height); ++ ++ EmscriptenWebGLContextAttributes attr; ++ ++ emscripten_webgl_init_context_attributes(&attr); ++ attr.majorVersion=2; ++ attr.minorVersion=0; ++ attr.explicitSwapControl = 1; ++ ++ vout_window_t *wnd = gl->surface; ++ ++ if (wnd->type != VOUT_WINDOW_TYPE_EMSCRIPTEN_WEBGL) ++ goto error; ++ ++ gl_sys_t *sys; ++ ++ gl->sys = sys = calloc(1, sizeof(*sys)); ++ if (!sys) ++ return VLC_ENOMEM; ++ ++ sys->context = emscripten_webgl_create_context("#canvas", &attr); ++ if (!sys->context) { ++ msg_Err(gl, "Failed to make context current"); ++ goto error; ++ } ++ ++ // Check that the WebGL context is valid ++ if (emscripten_webgl_make_context_current(sys->context) != EMSCRIPTEN_RESULT_SUCCESS) { ++ emscripten_log(EM_LOG_CONSOLE, "failed to make context current"); ++ goto error; ++ } ++ ++ // Release the context ++ emscripten_webgl_make_context_current(0); ++ wnd->handle.em_context = sys->context; ++ ++ // Implement egl routines: ++ gl->make_current = MakeCurrent; ++ gl->release_current = ReleaseCurrent; ++ gl->resize = Resize; ++ gl->swap = Swap; ++ gl->get_proc_address = GetProcAddress; ++ gl->destroy = Close; ++ ++ return VLC_SUCCESS; ++error: ++ Close(gl); ++ return VLC_EGENERIC; ++} ++ ++/* ++ * Module descriptor ++ */ ++vlc_module_begin() ++ set_shortname(N_("Emscripten Window")) ++ set_description(N_("Emscripten drawing area")) ++ set_subcategory(SUBCAT_VIDEO_VOUT) ++ set_capability("vout window", 10) ++ set_callbacks(OpenWindow, NULL) ++ ++ add_submodule () ++ set_shortname("Emscripten GL") ++ set_description(N_("Emscripten extension for OpenGL")) ++ set_subcategory(SUBCAT_VIDEO_VOUT) ++ set_capability("opengl es2", 50) ++ set_callback(Open) ++ add_shortcut("em_webgl") ++vlc_module_end() ++ +-- +2.35.1 + diff --git a/vlc_patches/aug/0016-vout-add-emscripten-window-and-webgl-context.patch b/vlc_patches/aug/0016-vout-add-emscripten-window-and-webgl-context.patch new file mode 100644 index 0000000000000000000000000000000000000000..9b9045a8828604e3b97c3e107497949a3d13d44a --- /dev/null +++ b/vlc_patches/aug/0016-vout-add-emscripten-window-and-webgl-context.patch @@ -0,0 +1,32 @@ +From b28ea0ff21f0764f285f9094cd6f2359c91d9596 Mon Sep 17 00:00:00 2001 +From: Etienne Brateau <etienne.brateau@gmail.com> +Date: Tue, 27 Apr 2021 16:19:40 +0200 +Subject: [PATCH 16/77] vout: add emscripten window and webgl context + +--- + include/vlc_vout_window.h | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/include/vlc_vout_window.h b/include/vlc_vout_window.h +index a3dfd1bfca..b5a01b33e4 100644 +--- a/include/vlc_vout_window.h ++++ b/include/vlc_vout_window.h +@@ -65,6 +65,7 @@ enum vout_window_type { + VOUT_WINDOW_TYPE_WAYLAND /**< Wayland surface */, + VOUT_WINDOW_TYPE_DCOMP /**< Win32 DirectComposition */, + VOUT_WINDOW_TYPE_KMS /**< DRM KMS CRTC */, ++ VOUT_WINDOW_TYPE_EMSCRIPTEN_WEBGL /**< Emscripten surface */, + }; + + /** +@@ -385,6 +386,7 @@ typedef struct vout_window_t { + struct wl_surface *wl; /**< Wayland surface (client pointer) */ + void *dcomp_visual; /**< Win32 direct composition visual */ + uint32_t crtc; /**< KMS CRTC identifier */ ++ uint32_t em_context; /* Emscripten webgl context */ + } handle; + + /** Display server (mandatory) +-- +2.35.1 + diff --git a/vlc_patches/aug/0017-module-vout-Allow-C-OpenGL-modules.patch b/vlc_patches/aug/0017-module-vout-Allow-C-OpenGL-modules.patch new file mode 100644 index 0000000000000000000000000000000000000000..a767aabdc78b654dcf0f8bddd4dfc58f500c834e --- /dev/null +++ b/vlc_patches/aug/0017-module-vout-Allow-C-OpenGL-modules.patch @@ -0,0 +1,37 @@ +From 839d192745f4d60f7d68e7b2eee30162c08f7e05 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr> +Date: Mon, 17 Jan 2022 15:56:42 +0100 +Subject: [PATCH 17/77] module: vout: Allow C++ OpenGL modules + +--- + modules/video_output/opengl/sampler.h | 9 +++++++++ + 1 file changed, 9 insertions(+) + +diff --git a/modules/video_output/opengl/sampler.h b/modules/video_output/opengl/sampler.h +index c779f2a9ba..2a5f0ef063 100644 +--- a/modules/video_output/opengl/sampler.h ++++ b/modules/video_output/opengl/sampler.h +@@ -38,6 +38,11 @@ extern "C" + { + #endif + ++#ifdef __cplusplus ++extern "C" ++{ ++#endif ++ + /** + * The purpose of a sampler is to provide pixel values of a VLC input picture, + * stored in any format. +@@ -199,4 +204,8 @@ vlc_gl_sampler_SelectPlane(struct vlc_gl_sampler *sampler, unsigned plane); + } + #endif + ++#ifdef __cplusplus ++} ++#endif ++ + #endif +-- +2.35.1 + diff --git a/vlc_patches/aug/0018-TMP-Start-the-vout-from-the-vout-thread.patch b/vlc_patches/aug/0018-TMP-Start-the-vout-from-the-vout-thread.patch new file mode 100644 index 0000000000000000000000000000000000000000..996969d22b476f8d35ecba1ab6006e7661d9dccc --- /dev/null +++ b/vlc_patches/aug/0018-TMP-Start-the-vout-from-the-vout-thread.patch @@ -0,0 +1,82 @@ +From 5f9b0db3e274cbc2cd83414d3245bed255aa4b81 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr> +Date: Fri, 21 Jan 2022 09:59:35 +0100 +Subject: [PATCH 18/77] TMP: Start the vout from the vout thread + +--- + src/video_output/video_output.c | 31 +++++++++++++++++++++++++------ + 1 file changed, 25 insertions(+), 6 deletions(-) + +diff --git a/src/video_output/video_output.c b/src/video_output/video_output.c +index 7b03fca641..8b8ce5f323 100644 +--- a/src/video_output/video_output.c ++++ b/src/video_output/video_output.c +@@ -100,6 +100,13 @@ typedef struct vout_thread_sys_t + vout_control_t control; + atomic_bool control_is_terminated; // shutdown the vout thread + vlc_thread_t thread; ++ vlc_sem_t thread_ready_sem; ++ bool thread_success; ++ ++ // Begin lazy ass chouquette test: ++ vlc_video_context* vctx_vout_start; ++ const vout_configuration_t *cfg_vout_start; ++ //end of lazy ass chouquette test. don't use this for anything else than vout_start + + struct { + vlc_tick_t date; +@@ -1705,6 +1712,8 @@ error: + return VLC_EGENERIC; + } + ++ ++static void vout_DisableWindow(vout_thread_sys_t *sys); + /***************************************************************************** + * Thread: video output thread + ***************************************************************************** +@@ -1717,6 +1726,17 @@ static void *Thread(void *object) + vout_thread_sys_t *vout = object; + vout_thread_sys_t *sys = vout; + ++ if (vout_Start(vout, sys->vctx_vout_start, sys->cfg_vout_start)) ++ { ++ sys->thread_success = false; ++ msg_Err(&vout->obj, "video output display creation failed"); ++ vout_DisableWindow(vout); ++ vlc_sem_post(&sys->thread_ready_sem); ++ return NULL; ++ } ++ sys->thread_success = true; ++ vlc_sem_post(&sys->thread_ready_sem); ++ + vlc_tick_t deadline = VLC_TICK_INVALID; + + for (;;) { +@@ -2108,19 +2128,18 @@ int vout_Request(const vout_configuration_t *cfg, vlc_video_context *vctx, input + sys->rate = 1.f; + sys->clock = cfg->clock; + sys->delay = 0; ++ vlc_sem_init(&sys->thread_ready_sem, 0); ++ ++ sys->cfg_vout_start = cfg; ++ sys->vctx_vout_start = vctx; + +- if (vout_Start(vout, vctx, cfg)) +- { +- msg_Err(cfg->vout, "video output display creation failed"); +- vout_DisableWindow(vout); +- return -1; +- } + atomic_store(&sys->control_is_terminated, false); + if (vlc_clone(&sys->thread, Thread, vout, VLC_THREAD_PRIORITY_OUTPUT)) { + vout_ReleaseDisplay(vout); + vout_DisableWindow(vout); + return -1; + } ++ vlc_sem_wait(&sys->thread_ready_sem); + + if (input != NULL && sys->spu) + spu_Attach(sys->spu, input); +-- +2.35.1 + diff --git a/vlc_patches/aug/0019-build.sh-Forcefully-disable-accept4.patch b/vlc_patches/aug/0019-build.sh-Forcefully-disable-accept4.patch new file mode 100644 index 0000000000000000000000000000000000000000..30309ba6d55d94493bc140b15874ad150986ff17 --- /dev/null +++ b/vlc_patches/aug/0019-build.sh-Forcefully-disable-accept4.patch @@ -0,0 +1,26 @@ +From dc3975b019fb46dfa37c6216703b4eb97ff4e608 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr> +Date: Thu, 3 Feb 2022 09:30:49 +0100 +Subject: [PATCH 19/77] build.sh: Forcefully disable accept4 + +In order to declare vlc_cloexec +--- + extras/package/wasm-emscripten/build.sh | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/extras/package/wasm-emscripten/build.sh b/extras/package/wasm-emscripten/build.sh +index 69d6b98c47..c1ea999806 100755 +--- a/extras/package/wasm-emscripten/build.sh ++++ b/extras/package/wasm-emscripten/build.sh +@@ -164,6 +164,8 @@ if [ $BUILD_MODE -eq 1 ]; then + --disable-sout --disable-vlm --disable-a52 --disable-xcb --disable-lua \ + --disable-addonmanagermodules --disable-ssp --disable-nls \ + --enable-gles2 \ ++ ac_cv_func_if_nameindex=yes ac_cv_header_sys_shm_h=no \ ++ ac_cv_func_accept4=no \ + --with-contrib="$VLC_SRCPATH"/contrib/wasm32-unknown-emscripten + fi + +-- +2.35.1 + diff --git a/vlc_patches/aug/0020-modules-Convert-emscripten-vout-to-C.patch b/vlc_patches/aug/0020-modules-Convert-emscripten-vout-to-C.patch new file mode 100644 index 0000000000000000000000000000000000000000..9ea808ab7d1d4a4f32282c6d60a097dc55f6f8c9 --- /dev/null +++ b/vlc_patches/aug/0020-modules-Convert-emscripten-vout-to-C.patch @@ -0,0 +1,393 @@ +From de9a47b9c4bf6fb2d6ac20b7aecba3064943f4aa Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr> +Date: Thu, 3 Feb 2022 09:35:05 +0100 +Subject: [PATCH 20/77] modules: Convert emscripten vout to C++ + +We'll need to be able to use various emscripten features that are only +available through C++ +--- + modules/video_output/Makefile.am | 2 +- + modules/video_output/emscripten.c | 176 ---------------------------- + modules/video_output/emscripten.cpp | 174 +++++++++++++++++++++++++++ + 3 files changed, 175 insertions(+), 177 deletions(-) + delete mode 100644 modules/video_output/emscripten.c + create mode 100644 modules/video_output/emscripten.cpp + +diff --git a/modules/video_output/Makefile.am b/modules/video_output/Makefile.am +index 3134528bc1..191992d887 100644 +--- a/modules/video_output/Makefile.am ++++ b/modules/video_output/Makefile.am +@@ -346,7 +346,7 @@ EXTRA_LTLIBRARIES += libcaca_plugin.la + vout_LTLIBRARIES += $(LTLIBcaca) + + ### Emscripten ### +-libemscripten_window_plugin_la_SOURCES = video_output/emscripten.c ++libemscripten_window_plugin_la_SOURCES = video_output/emscripten.cpp + + if HAVE_EMSCRIPTEN + vout_LTLIBRARIES += libemscripten_window_plugin.la +diff --git a/modules/video_output/emscripten.c b/modules/video_output/emscripten.c +deleted file mode 100644 +index 05c17d3179..0000000000 +--- a/modules/video_output/emscripten.c ++++ /dev/null +@@ -1,176 +0,0 @@ +-/** +- * @file emscripten.c +- * @brief Emscripten webgl video output for VLC media player +- */ +-/***************************************************************************** +- * Copyright © 2020 VLC authors and VideoLAN +- * +- * +- * This program is free software; you can redistribute it and/or modify it +- * under the terms of the GNU Lesser General Public License as published by +- * the Free Software Foundation; either version 2.1 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +- * GNU Lesser General Public License for more details. +- * +- * You should have received a copy of the GNU Lesser General Public License +- * along with this program; if not, write to the Free Software Foundation, +- * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. +- *****************************************************************************/ +- +-#ifdef HAVE_CONFIG_H +-# include <config.h> +-#endif +- +-#include <stdarg.h> +- +-#include <vlc_common.h> +-#include <vlc_plugin.h> +-#include <vlc_vout_window.h> +-#include <vlc_vout_display.h> +-#include <vlc_opengl.h> +- +-#include "./opengl/vout_helper.h" +- +-#include <emscripten.h> +-#include <emscripten/html5.h> +-#include <webgl/webgl2.h> +-// eglGetProcAddress +-#include <EGL/egl.h> +- +-extern emscripten_GetProcAddress(char *name); +- +-static const struct vout_window_operations ops = { +- //TODO: Implement canvas operations +- //vout_window_ReportSize() should be called from here +-}; +- +-typedef struct gl_sys_t +-{ +- unsigned width; +- unsigned height; +- +- EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context; +-} gl_sys_t; +- +-static int OpenWindow(vout_window_t *wnd) +-{ +- wnd->type = VOUT_WINDOW_TYPE_EMSCRIPTEN_WEBGL; +- wnd->ops = &ops; +- +- return VLC_SUCCESS; +-} +- +-static void *GetProcAddress(vlc_gl_t *gl, const char *name) +-{ +- VLC_UNUSED(gl); +- +- return eglGetProcAddress(name); +-} +-static int MakeCurrent(vlc_gl_t *gl) +-{ +- gl_sys_t *sys = gl->sys; +- +- if (emscripten_webgl_make_context_current(sys->context) != EMSCRIPTEN_RESULT_SUCCESS) +- return VLC_EGENERIC; +- return VLC_SUCCESS; +-} +- +-static void ReleaseCurrent(vlc_gl_t *gl) +-{ +- VLC_UNUSED(gl); +- emscripten_webgl_make_context_current(0); +-} +- +-static void Swap(vlc_gl_t *gl) +-{ +- VLC_UNUSED(gl); +- emscripten_webgl_commit_frame(); +-} +- +-static void Resize(vlc_gl_t *gl, unsigned w, unsigned h) +-{ +- VLC_UNUSED(gl); +- VLC_UNUSED(w); +- VLC_UNUSED(h); +-} +- +-static void Close (vlc_gl_t *gl) +-{ +- free(gl->sys); +-} +- +-static int Open (vlc_gl_t *gl, unsigned width, unsigned height) +-{ +- VLC_UNUSED(width), VLC_UNUSED(height); +- +- EmscriptenWebGLContextAttributes attr; +- +- emscripten_webgl_init_context_attributes(&attr); +- attr.majorVersion=2; +- attr.minorVersion=0; +- attr.explicitSwapControl = 1; +- +- vout_window_t *wnd = gl->surface; +- +- if (wnd->type != VOUT_WINDOW_TYPE_EMSCRIPTEN_WEBGL) +- goto error; +- +- gl_sys_t *sys; +- +- gl->sys = sys = calloc(1, sizeof(*sys)); +- if (!sys) +- return VLC_ENOMEM; +- +- sys->context = emscripten_webgl_create_context("#canvas", &attr); +- if (!sys->context) { +- msg_Err(gl, "Failed to make context current"); +- goto error; +- } +- +- // Check that the WebGL context is valid +- if (emscripten_webgl_make_context_current(sys->context) != EMSCRIPTEN_RESULT_SUCCESS) { +- emscripten_log(EM_LOG_CONSOLE, "failed to make context current"); +- goto error; +- } +- +- // Release the context +- emscripten_webgl_make_context_current(0); +- wnd->handle.em_context = sys->context; +- +- // Implement egl routines: +- gl->make_current = MakeCurrent; +- gl->release_current = ReleaseCurrent; +- gl->resize = Resize; +- gl->swap = Swap; +- gl->get_proc_address = GetProcAddress; +- gl->destroy = Close; +- +- return VLC_SUCCESS; +-error: +- Close(gl); +- return VLC_EGENERIC; +-} +- +-/* +- * Module descriptor +- */ +-vlc_module_begin() +- set_shortname(N_("Emscripten Window")) +- set_description(N_("Emscripten drawing area")) +- set_subcategory(SUBCAT_VIDEO_VOUT) +- set_capability("vout window", 10) +- set_callbacks(OpenWindow, NULL) +- +- add_submodule () +- set_shortname("Emscripten GL") +- set_description(N_("Emscripten extension for OpenGL")) +- set_subcategory(SUBCAT_VIDEO_VOUT) +- set_capability("opengl es2", 50) +- set_callback(Open) +- add_shortcut("em_webgl") +-vlc_module_end() +- +diff --git a/modules/video_output/emscripten.cpp b/modules/video_output/emscripten.cpp +new file mode 100644 +index 0000000000..857d921c27 +--- /dev/null ++++ b/modules/video_output/emscripten.cpp +@@ -0,0 +1,174 @@ ++/** ++ * @file emscripten.c ++ * @brief Emscripten webgl video output for VLC media player ++ */ ++/***************************************************************************** ++ * Copyright © 2020 VLC authors and VideoLAN ++ * ++ * ++ * This program is free software; you can redistribute it and/or modify it ++ * under the terms of the GNU Lesser General Public License as published by ++ * the Free Software Foundation; either version 2.1 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU Lesser General Public License for more details. ++ * ++ * You should have received a copy of the GNU Lesser General Public License ++ * along with this program; if not, write to the Free Software Foundation, ++ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. ++ *****************************************************************************/ ++ ++#ifdef HAVE_CONFIG_H ++# include <config.h> ++#endif ++ ++#include <stdarg.h> ++ ++#include <vlc_common.h> ++#include <vlc_plugin.h> ++#include <vlc_vout_window.h> ++#include <vlc_vout_display.h> ++#include <vlc_opengl.h> ++ ++#include "./opengl/vout_helper.h" ++ ++#include <emscripten.h> ++#include <emscripten/html5.h> ++#include <webgl/webgl2.h> ++// eglGetProcAddress ++#include <EGL/egl.h> ++ ++static const struct vout_window_operations ops = { ++ //TODO: Implement canvas operations ++ //vout_window_ReportSize() should be called from here ++}; ++ ++typedef struct gl_sys_t ++{ ++ unsigned width; ++ unsigned height; ++ ++ EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context; ++} gl_sys_t; ++ ++static int OpenWindow(vout_window_t *wnd) ++{ ++ wnd->type = VOUT_WINDOW_TYPE_EMSCRIPTEN_WEBGL; ++ wnd->ops = &ops; ++ ++ return VLC_SUCCESS; ++} ++ ++static void *GetProcAddress(vlc_gl_t *gl, const char *name) ++{ ++ VLC_UNUSED(gl); ++ ++ return reinterpret_cast<void*>(eglGetProcAddress(name)); ++} ++static int MakeCurrent(vlc_gl_t *gl) ++{ ++ auto *sys = static_cast<gl_sys_t*>(gl->sys); ++ ++ if (emscripten_webgl_make_context_current(sys->context) != EMSCRIPTEN_RESULT_SUCCESS) ++ return VLC_EGENERIC; ++ return VLC_SUCCESS; ++} ++ ++static void ReleaseCurrent(vlc_gl_t *gl) ++{ ++ VLC_UNUSED(gl); ++ emscripten_webgl_make_context_current(0); ++} ++ ++static void Swap(vlc_gl_t *gl) ++{ ++ VLC_UNUSED(gl); ++ emscripten_webgl_commit_frame(); ++} ++ ++static void Resize(vlc_gl_t *gl, unsigned w, unsigned h) ++{ ++ VLC_UNUSED(gl); ++ VLC_UNUSED(w); ++ VLC_UNUSED(h); ++} ++ ++static void Close (vlc_gl_t *gl) ++{ ++ free(gl->sys); ++} ++ ++static int Open (vlc_gl_t *gl, unsigned width, unsigned height) ++{ ++ VLC_UNUSED(width), VLC_UNUSED(height); ++ ++ EmscriptenWebGLContextAttributes attr; ++ ++ emscripten_webgl_init_context_attributes(&attr); ++ attr.majorVersion=2; ++ attr.minorVersion=0; ++ attr.explicitSwapControl = 1; ++ ++ vout_window_t *wnd = gl->surface; ++ ++ if (wnd->type != VOUT_WINDOW_TYPE_EMSCRIPTEN_WEBGL) ++ goto error; ++ ++ gl_sys_t *sys; ++ ++ gl->sys = sys = static_cast<gl_sys_t*>(calloc(1, sizeof(*sys))); ++ if (!sys) ++ return VLC_ENOMEM; ++ ++ sys->context = emscripten_webgl_create_context("#canvas", &attr); ++ if (!sys->context) { ++ msg_Err(gl, "Failed to make context current"); ++ goto error; ++ } ++ ++ // Check that the WebGL context is valid ++ if (emscripten_webgl_make_context_current(sys->context) != EMSCRIPTEN_RESULT_SUCCESS) { ++ emscripten_log(EM_LOG_CONSOLE, "failed to make context current"); ++ goto error; ++ } ++ ++ // Release the context ++ emscripten_webgl_make_context_current(0); ++ wnd->handle.em_context = sys->context; ++ ++ // Implement egl routines: ++ gl->make_current = MakeCurrent; ++ gl->release_current = ReleaseCurrent; ++ gl->resize = Resize; ++ gl->swap = Swap; ++ gl->get_proc_address = GetProcAddress; ++ gl->destroy = Close; ++ ++ return VLC_SUCCESS; ++error: ++ Close(gl); ++ return VLC_EGENERIC; ++} ++ ++/* ++ * Module descriptor ++ */ ++vlc_module_begin() ++ set_shortname(N_("Emscripten Window")) ++ set_description(N_("Emscripten drawing area")) ++ set_subcategory(SUBCAT_VIDEO_VOUT) ++ set_capability("vout window", 10) ++ set_callbacks(OpenWindow, NULL) ++ ++ add_submodule () ++ set_shortname("Emscripten GL") ++ set_description(N_("Emscripten extension for OpenGL")) ++ set_subcategory(SUBCAT_VIDEO_VOUT) ++ set_capability("opengl es2", 50) ++ set_callback(Open) ++ add_shortcut("em_webgl") ++vlc_module_end() ++ +-- +2.35.1 + diff --git a/vlc_patches/aug/0021-codec-Add-a-webcodec-decoder-device-type.patch b/vlc_patches/aug/0021-codec-Add-a-webcodec-decoder-device-type.patch new file mode 100644 index 0000000000000000000000000000000000000000..f37268aa1b066b619e944dac6b43c5bc22dc7c5a --- /dev/null +++ b/vlc_patches/aug/0021-codec-Add-a-webcodec-decoder-device-type.patch @@ -0,0 +1,24 @@ +From d36a5341abd7cbb4ad39af1dc9434ba1608a6678 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr> +Date: Thu, 3 Feb 2022 09:47:13 +0100 +Subject: [PATCH 21/77] codec: Add a webcodec decoder device type + +--- + include/vlc_codec.h | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/include/vlc_codec.h b/include/vlc_codec.h +index ff707e73a3..7460d03ed6 100644 +--- a/include/vlc_codec.h ++++ b/include/vlc_codec.h +@@ -583,6 +583,7 @@ enum vlc_decoder_device_type + VLC_DECODER_DEVICE_AWINDOW, + VLC_DECODER_DEVICE_NVDEC, + VLC_DECODER_DEVICE_MMAL, ++ VLC_DECODER_DEVICE_WEBCODEC, + }; + + struct vlc_decoder_device_operations +-- +2.35.1 + diff --git a/vlc_patches/aug/0022-vlc_picture.h-Add-a-webcodec-video-context-type.patch b/vlc_patches/aug/0022-vlc_picture.h-Add-a-webcodec-video-context-type.patch new file mode 100644 index 0000000000000000000000000000000000000000..4ed5ada22b46d163b53d1c5f33ac5528e5bdc21c --- /dev/null +++ b/vlc_patches/aug/0022-vlc_picture.h-Add-a-webcodec-video-context-type.patch @@ -0,0 +1,24 @@ +From 1cc0491674a6287ae12dc6fa6c18a86d6e700f3a Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr> +Date: Thu, 3 Feb 2022 09:50:27 +0100 +Subject: [PATCH 22/77] vlc_picture.h: Add a webcodec video context type + +--- + include/vlc_picture.h | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/include/vlc_picture.h b/include/vlc_picture.h +index 5aece95120..fa62e307bd 100644 +--- a/include/vlc_picture.h ++++ b/include/vlc_picture.h +@@ -98,6 +98,7 @@ enum vlc_video_context_type + VLC_VIDEO_CONTEXT_NVDEC, //!< empty + VLC_VIDEO_CONTEXT_CVPX, //!< private: cvpx_video_context* + VLC_VIDEO_CONTEXT_MMAL, //!< empty ++ VLC_VIDEO_CONTEXT_WEBCODEC, + }; + + VLC_API vlc_video_context * vlc_video_context_Create(vlc_decoder_device *, +-- +2.35.1 + diff --git a/vlc_patches/aug/0023-fourcc-Add-an-opaque-webcodec-fourcc.patch b/vlc_patches/aug/0023-fourcc-Add-an-opaque-webcodec-fourcc.patch new file mode 100644 index 0000000000000000000000000000000000000000..7376b8c25e290ef15affe5e349cd33e62fbf9d30 --- /dev/null +++ b/vlc_patches/aug/0023-fourcc-Add-an-opaque-webcodec-fourcc.patch @@ -0,0 +1,40 @@ +From 2e7a43f54ae86fcf9fd913d5ec4478a022285c4a Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr> +Date: Thu, 3 Feb 2022 09:51:05 +0100 +Subject: [PATCH 23/77] fourcc: Add an opaque webcodec fourcc + +--- + include/vlc_fourcc.h | 3 +++ + src/misc/fourcc.c | 2 ++ + 2 files changed, 5 insertions(+) + +diff --git a/include/vlc_fourcc.h b/include/vlc_fourcc.h +index 176028e351..4c7f35d4ff 100644 +--- a/include/vlc_fourcc.h ++++ b/include/vlc_fourcc.h +@@ -440,6 +440,9 @@ + #define VLC_CODEC_CVPX_BGRA VLC_FOURCC('C','V','P','B') + #define VLC_CODEC_CVPX_P010 VLC_FOURCC('C','V','P','P') + ++/* Webcodec opaque VideoFrame types */ ++#define VLC_CODEC_WEBCODEC_OPAQUE VLC_FOURCC('W','C','O','P') ++ + /* Image codec (video) */ + #define VLC_CODEC_PNG VLC_FOURCC('p','n','g',' ') + #define VLC_CODEC_PPM VLC_FOURCC('p','p','m',' ') +diff --git a/src/misc/fourcc.c b/src/misc/fourcc.c +index 101bb0d980..479a1bd13e 100644 +--- a/src/misc/fourcc.c ++++ b/src/misc/fourcc.c +@@ -840,6 +840,8 @@ static const struct + { { VLC_CODEC_VAAPI_420, VLC_CODEC_VAAPI_420_10BPP }, + FAKE_FMT() }, + ++ { { VLC_CODEC_WEBCODEC_OPAQUE }, FAKE_FMT() }, ++ + { { 0 }, FAKE_FMT() } + }; + +-- +2.35.1 + diff --git a/vlc_patches/aug/0024-emscripten-build.sh-Enable-gles2.patch b/vlc_patches/aug/0024-emscripten-build.sh-Enable-gles2.patch new file mode 100644 index 0000000000000000000000000000000000000000..418ef520bcf9281fcea5ccce36fc4ea14d51eb8f --- /dev/null +++ b/vlc_patches/aug/0024-emscripten-build.sh-Enable-gles2.patch @@ -0,0 +1,25 @@ +From 16bd17c0e12b943504416dd67a317abbd9142685 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr> +Date: Thu, 3 Feb 2022 09:52:21 +0100 +Subject: [PATCH 24/77] emscripten: build.sh: Enable gles2 + +Already applied upstream +--- + extras/package/wasm-emscripten/build.sh | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/extras/package/wasm-emscripten/build.sh b/extras/package/wasm-emscripten/build.sh +index c1ea999806..5119fe6a6e 100755 +--- a/extras/package/wasm-emscripten/build.sh ++++ b/extras/package/wasm-emscripten/build.sh +@@ -166,6 +166,7 @@ if [ $BUILD_MODE -eq 1 ]; then + --enable-gles2 \ + ac_cv_func_if_nameindex=yes ac_cv_header_sys_shm_h=no \ + ac_cv_func_accept4=no \ ++ --enable-gles2 \ + --with-contrib="$VLC_SRCPATH"/contrib/wasm32-unknown-emscripten + fi + +-- +2.35.1 + diff --git a/vlc_patches/aug/0025-emscripten-build-Forcefully-disable-pipe2.patch b/vlc_patches/aug/0025-emscripten-build-Forcefully-disable-pipe2.patch new file mode 100644 index 0000000000000000000000000000000000000000..c256c2b48aae0a64a0ddd157dbb6ac4370e52b27 --- /dev/null +++ b/vlc_patches/aug/0025-emscripten-build-Forcefully-disable-pipe2.patch @@ -0,0 +1,24 @@ +From 5ec33317bdd1ef893d7e937c2fcc5435d3929652 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr> +Date: Thu, 3 Feb 2022 09:52:49 +0100 +Subject: [PATCH 25/77] emscripten: build: Forcefully disable pipe2 + +--- + extras/package/wasm-emscripten/build.sh | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/extras/package/wasm-emscripten/build.sh b/extras/package/wasm-emscripten/build.sh +index 5119fe6a6e..5a1dbd230d 100755 +--- a/extras/package/wasm-emscripten/build.sh ++++ b/extras/package/wasm-emscripten/build.sh +@@ -166,6 +166,7 @@ if [ $BUILD_MODE -eq 1 ]; then + --enable-gles2 \ + ac_cv_func_if_nameindex=yes ac_cv_header_sys_shm_h=no \ + ac_cv_func_accept4=no \ ++ ac_cv_func_pipe2=no \ + --enable-gles2 \ + --with-contrib="$VLC_SRCPATH"/contrib/wasm32-unknown-emscripten + fi +-- +2.35.1 + diff --git a/vlc_patches/aug/0026-emscripten-build-Disable-nvdec.patch b/vlc_patches/aug/0026-emscripten-build-Disable-nvdec.patch new file mode 100644 index 0000000000000000000000000000000000000000..aaf092f1da4e764ac6ddc04029881b9f88ccd9b8 --- /dev/null +++ b/vlc_patches/aug/0026-emscripten-build-Disable-nvdec.patch @@ -0,0 +1,24 @@ +From d80f3c5acc6455a4d3f8afd51b80d3f456203b85 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr> +Date: Thu, 3 Feb 2022 09:53:10 +0100 +Subject: [PATCH 26/77] emscripten: build: Disable nvdec + +--- + extras/package/wasm-emscripten/build.sh | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/extras/package/wasm-emscripten/build.sh b/extras/package/wasm-emscripten/build.sh +index 5a1dbd230d..acd74591fc 100755 +--- a/extras/package/wasm-emscripten/build.sh ++++ b/extras/package/wasm-emscripten/build.sh +@@ -168,6 +168,7 @@ if [ $BUILD_MODE -eq 1 ]; then + ac_cv_func_accept4=no \ + ac_cv_func_pipe2=no \ + --enable-gles2 \ ++ --disable-nvdec \ + --with-contrib="$VLC_SRCPATH"/contrib/wasm32-unknown-emscripten + fi + +-- +2.35.1 + diff --git a/vlc_patches/aug/0027-codec-Add-a-webcodec-decoder.patch b/vlc_patches/aug/0027-codec-Add-a-webcodec-decoder.patch new file mode 100644 index 0000000000000000000000000000000000000000..add60eba00050130262161cb66456a543d9d3a5d --- /dev/null +++ b/vlc_patches/aug/0027-codec-Add-a-webcodec-decoder.patch @@ -0,0 +1,480 @@ +From fff9ead944d55025adcb89a4424e2b40baf6d7ca Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr> +Date: Thu, 3 Feb 2022 09:54:14 +0100 +Subject: [PATCH 27/77] codec: Add a webcodec decoder + +--- + modules/codec/Makefile.am | 6 + + modules/codec/webcodec.cpp | 392 +++++++++++++++++++++++ + modules/video_output/emscripten/common.h | 46 +++ + 3 files changed, 444 insertions(+) + create mode 100644 modules/codec/webcodec.cpp + create mode 100644 modules/video_output/emscripten/common.h + +diff --git a/modules/codec/Makefile.am b/modules/codec/Makefile.am +index 6bdc40b491..31ec138bd0 100644 +--- a/modules/codec/Makefile.am ++++ b/modules/codec/Makefile.am +@@ -644,3 +644,9 @@ noinst_LTLIBRARIES += libhxxxhelper_testdec_plugin.la + libvlc_vtutils_la_SOURCES = codec/vt_utils.c codec/vt_utils.h + libvlc_vtutils_la_LDFLAGS = -static -no-undefined + EXTRA_LTLIBRARIES += libvlc_vtutils.la ++ ++libwebcodec_plugin_la_SOURCES = codec/webcodec.cpp ++libwebcodec_plugin_la_LDFLAGS = $(AM_LDFLAGS) ++if HAVE_EMSCRIPTEN ++codec_LTLIBRARIES += libwebcodec_plugin.la ++endif +diff --git a/modules/codec/webcodec.cpp b/modules/codec/webcodec.cpp +new file mode 100644 +index 0000000000..a75d3f7897 +--- /dev/null ++++ b/modules/codec/webcodec.cpp +@@ -0,0 +1,392 @@ ++/***************************************************************************** ++ * webcodec.cpp: Decoder module using browser provided codec implementations ++ ***************************************************************************** ++ * Copyright © 2021 VLC authors and VideoLAN ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, write to the Free Software Foundation, Inc., ++ * 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. ++ *****************************************************************************/ ++ ++#ifdef HAVE_CONFIG_H ++# include "config.h" ++#endif ++ ++#include <vlc_common.h> ++#include <vlc_plugin.h> ++#include <vlc_codec.h> ++#include <vlc_threads.h> ++#include <vlc_cxx_helpers.hpp> ++#include <vlc_block.h> ++#include <vlc_tick.h> ++#include <vlc_picture.h> ++ ++#include "../video_output/emscripten/common.h" ++ ++#include <emscripten/emscripten.h> ++#include <emscripten/val.h> ++#include <emscripten/bind.h> ++#include <emscripten/em_js.h> ++#include <emscripten/wire.h> ++#include <memory> ++#include <functional> ++#include <cstdint> ++#include <queue> ++ ++using emval = emscripten::val; ++ ++struct decoder_sys_t ++{ ++ emval decoder = emval::undefined(); ++ std::queue<block_t*> blocks; ++ vlc::threads::mutex mutex; ++ vlc::threads::condition_variable cond; ++ vlc_thread_t th; ++ ++ vlc_video_context* vctx; ++}; ++ ++extern "C" ++{ ++ ++EMSCRIPTEN_KEEPALIVE picture_t* createAndQueuePicture(decoder_t* dec, int pictureId, ++ int64_t timestamp) ++{ ++ auto sys = static_cast<decoder_sys_t*>( dec->p_sys ); ++ if ( decoder_UpdateVideoOutput( dec, sys->vctx ) ) ++ { ++ msg_Err( dec, "Failure during UpdateVideoOutput! FIXME" ); ++ return NULL; ++ } ++ ++ auto pic = decoder_NewPicture(dec); ++ if (pic == nullptr) ++ return nullptr; ++ pic->date = VLC_TICK_FROM_US(timestamp); ++ pic->b_progressive = true; ++ pic->p_sys = reinterpret_cast<void*>( static_cast<uintptr_t>( pictureId ) ); ++ decoder_QueueVideo(dec, pic); ++ return pic; ++} ++ ++} ++ ++EM_JS(void, initModuleContext, (void* ctx), { ++ globalThis.Module.webCodecCtx = ctx; ++}); ++ ++EM_ASYNC_JS(void, declareCallbacks, (), { ++ async function getVoutMessagePort() { ++ let p = new Promise((resolve, reject) => { ++ self.addEventListener('message', function(e) { ++ let msg = e['data']; ++ if (msg.customCmd == 'transferMessagePort') { ++ let port = msg['transferList'][0]; ++ if (!port) ++ reject(); ++ Module.voutPort = port; ++ resolve(); ++ } ++ }); ++ }); ++ await p; ++ } ++ ++ Module.pictureId = 0; ++ globalThis.Module.boundOutputCb = async function(frame) { ++ Module.pictureId = (Module.pictureId + 1); ++ _createAndQueuePicture(globalThis.Module.webCodecCtx, Module.pictureId, ++ frame.timestamp); ++ if (Module.voutPort === undefined) ++ await getVoutMessagePort(); ++ console.log('Posting frame', Module.pictureId); ++ Module.voutPort.postMessage({ ++ customCmd: 'displayFrame', ++ frame: frame, ++ pictureId: Module.pictureId, ++ }, [frame]); ++ }; ++ globalThis.Module.boundErrorCb = function(err) { ++ console.log('Error while decoding: '); ++ console.log(err); ++ }; ++}); ++ ++EM_ASYNC_JS(bool, probeConfig, (emscripten::EM_VAL cfg), { ++ var decoderCfg = Emval.toValue(cfg); ++ var res = await VideoDecoder.isConfigSupported(decoderCfg).catch((err) => { ++ console.log(err); ++ return {'supported': false}; ++ }); ++ return res['supported']; ++}); ++ ++emval blockToEncodedVideoChunk( decoder_t* dec, block_t* block ) ++{ ++ auto chunkType = emval::global("EncodedVideoChunk"); ++ auto chunkCfg = emval::object(); ++ ++ static bool first = true; ++ ++ if ( first == true ) ++ { ++ chunkCfg.set( "type", "key" ); ++ first = false; ++ } ++ else ++ chunkCfg.set( "type", (block->i_flags & BLOCK_FLAG_TYPE_I) ? "key" : "delta" ); ++ auto timestamp = block->i_pts ? block->i_pts : block->i_dts; ++ chunkCfg.set( "timestamp", (long int)US_FROM_VLC_TICK( timestamp ) ); ++ //if ( block->i_length > 0 ) ++ // chunkCfg.set( "duration", (long int)US_FROM_VLC_TICK( block->i_length ) ); ++ chunkCfg.set( "data", emscripten::typed_memory_view( block->i_buffer, block->p_buffer ) ); ++ return chunkType.new_( std::move( chunkCfg ) ); ++} ++ ++static emval getDecoderConfig( decoder_t* dec, bool includeExtraData ) ++{ ++ auto decoderConfig = emval::object(); ++ switch( dec->fmt_in.i_codec ) ++ { ++ case VLC_CODEC_VP8: ++ decoderConfig.set( "codec", "vp8" ); ++ break; ++ case VLC_CODEC_VP9: ++ decoderConfig.set( "codec", "vp09.*" ); ++ break; ++ case VLC_CODEC_H264: ++ { ++ char codec[12]; ++ snprintf(codec, sizeof(codec), "avc1.%.2X%.2X%.2X", dec->fmt_in.i_profile, ++ 0, dec->fmt_in.i_level); ++ decoderConfig.set( "codec", codec ); ++ ++ //decoderConfig.set("codec", "avc1.64000b" ); ++ break; ++ } ++ case VLC_CODEC_AV1: ++ decoderConfig.set( "codec", "av01" ); ++ break; ++ default: ++ return emval::undefined(); ++ } ++ decoderConfig.set( "codedWidth", dec->fmt_in.video.i_width ); ++ decoderConfig.set( "codedHeight", dec->fmt_in.video.i_height ); ++ decoderConfig.set( "displayAspectWidth", dec->fmt_in.video.i_visible_width ); ++ decoderConfig.set( "displayAspectHeight", dec->fmt_in.video.i_visible_height ); ++ decoderConfig.set( "optimizeForLatency", true ); ++ if ( includeExtraData ) ++ { ++ msg_Err( dec, "i_extra: %u", dec->fmt_in.i_extra ); ++ if ( dec->fmt_in.i_extra > 0 ) ++ { ++ decoderConfig.set( "description", ++ emscripten::typed_memory_view( ++ dec->fmt_in.i_extra, ++ static_cast<uint8_t*>( dec->fmt_in.p_extra ) ) ++ ); ++ } ++ } ++ return decoderConfig; ++} ++ ++static bool initDecoder( decoder_t* dec ) ++{ ++ auto sys = static_cast<decoder_sys_t*>( dec->p_sys ); ++ initModuleContext(dec); ++ declareCallbacks(); ++ ++ auto initCfg = emval::object(); ++ ++ auto outputCb = emval::module_property("boundOutputCb"); ++ if ( outputCb.isUndefined() ) ++ { ++ msg_Err( dec, "Failed to find output callback" ); ++ return false; ++ } ++ initCfg.set("output", outputCb); ++ ++ auto errorCb = emval::module_property("boundErrorCb"); ++ if ( errorCb.isUndefined() ) ++ { ++ msg_Err( dec, "Failed to find error callback" ); ++ return false; ++ } ++ initCfg.set("error", errorCb); ++ ++ auto decoderType = emval::global("VideoDecoder"); ++ sys->decoder = decoderType.new_(initCfg); ++ if ( sys->decoder.isUndefined() ) ++ { ++ msg_Err( dec, "Failed to instantiate VideoDecoder" ); ++ return false; ++ } ++ ++ sys->decoder.call<void>( "configure", getDecoderConfig( dec, true ) ); ++ ++ return true; ++} ++ ++static void WebcodecDecodeWorkerTick( void* arg ) ++{ ++ auto dec = static_cast<decoder_t*>( arg ); ++ auto sys = static_cast<decoder_sys_t*>( dec->p_sys ); ++ block_t* block; ++ ++ vlc::threads::mutex_locker lock{ sys->mutex }; ++ while ( sys->blocks.empty() == false ) ++ { ++ block = sys->blocks.front(); ++ sys->blocks.pop(); ++ ++ auto chunk = blockToEncodedVideoChunk( dec, block ); ++ block_Release(block); ++ msg_Err(dec, "Decoding a sample..."); ++ sys->decoder.call<void>( "decode", chunk ); ++ ++ auto queueSize = sys->decoder["decodeQueueSize"]; ++ auto state = sys->decoder["state"]; ++ msg_Err( dec, "Decoder state: %s ; queue size: %ld", state.as<std::string>().c_str(), queueSize.as<long int>()); ++ } ++} ++ ++static void* WebcodecDecodeWorker( void* arg ) ++{ ++ /* ++ * We need to be able to yield back to the browser main loop after we ++ * queue some blocks for decoding, otherwise the callback will never be ++ * invoked. ++ * In order to do so, we spawn a dedidated thread that will run a "main loop" ++ * which consist of dequeuing a single block and passing it to webcodec. ++ * After doing so, it yields back to the message loop and so on. If no ++ * blocks are queued for decoding, we can block as there nothing else being ++ * done from this worker. ++ */ ++ auto dec = static_cast<decoder_t*>( arg ); ++ if ( !initDecoder( dec ) ) ++ { ++ msg_Err( dec, "Failed to initialize decoder: FIXME" ); ++ return NULL; ++ } ++ auto sys = static_cast<decoder_sys_t*>( dec->p_sys ); ++ auto vctxPrivate = static_cast<webcodec_context*>( ++ vlc_video_context_GetPrivate(sys->vctx, VLC_VIDEO_CONTEXT_WEBCODEC)); ++ vctxPrivate->decoder_worker = pthread_self(); ++ emscripten_set_main_loop_arg( &WebcodecDecodeWorkerTick, dec, 0, false ); ++ /* ++ * We want our tick function to be invoked ASAP even if we'd only be blocking ++ * waiting for a new block to be queued ++ */ ++ emscripten_set_main_loop_timing( EM_TIMING_SETTIMEOUT, 1 ); ++ return NULL; ++} ++ ++static int Decode( decoder_t* dec, block_t* block ) ++{ ++ auto sys = static_cast<decoder_sys_t*>( dec->p_sys ); ++ vlc::threads::mutex_locker lock{ sys->mutex }; ++ sys->blocks.push( block ); ++ sys->cond.signal(); ++ return VLCDEC_SUCCESS; ++} ++ ++static void Flush( decoder_t* dec ) ++{ ++ //FIXME: Needs to be called from the decoder thread ++ auto sys = static_cast<decoder_sys_t*>( dec->p_sys ); ++ sys->decoder.call<emval>("flush").await(); ++} ++ ++static int Open( vlc_object_t* obj ) ++{ ++ auto dec = reinterpret_cast<decoder_t*>(obj); ++ ++ if ( dec->fmt_in.i_cat != VIDEO_ES ) ++ return VLC_EGENERIC; ++ ++ auto decoderType = emval::global("VideoDecoder"); ++ if ( !decoderType.as<bool>() ) ++ { ++ msg_Err( obj, "Can't get VideoDecoder type, webcodec is probably not " ++ "supported on this browser" ); ++ return false; ++ } ++ auto sys = std::make_unique<decoder_sys_t>(); ++ dec->p_sys = sys.get(); ++ ++ auto decoderConfig = getDecoderConfig( dec, false ); ++ if ( decoderConfig.isUndefined() ) ++ return VLC_EGENERIC; ++ auto isSupported = probeConfig(decoderConfig.as_handle()); ++ ++ if ( isSupported == false ) ++ { ++ msg_Err( dec, "VideoDecoder doesn't support this configuration" ); ++ return VLC_EGENERIC; ++ } ++ ++ if ( es_format_Copy( &dec->fmt_out, &dec->fmt_in ) != VLC_SUCCESS ) ++ return VLC_ENOMEM; ++ dec->fmt_out.i_codec = dec->fmt_out.video.i_chroma = VLC_CODEC_WEBCODEC_OPAQUE; ++ ++ auto dec_dev = decoder_GetDecoderDevice(dec); ++ sys->vctx = vlc_video_context_Create(dec_dev, VLC_VIDEO_CONTEXT_WEBCODEC, ++ sizeof(webcodec_context), nullptr); ++ auto vctxPrivate = static_cast<webcodec_context*>( ++ vlc_video_context_GetPrivate(sys->vctx, VLC_VIDEO_CONTEXT_WEBCODEC)); ++ new (vctxPrivate) webcodec_context(); ++ ++ if ( vlc_clone( &sys->th, &WebcodecDecodeWorker, dec, 0 ) != VLC_SUCCESS ) ++ { ++ msg_Err( obj, "Failed to create webcodec thread" ); ++ return VLC_EGENERIC; ++ } ++ ++ dec->pf_decode = &Decode; ++ dec->pf_flush = &Flush; ++ ++ sys.release(); ++ return VLCDEC_SUCCESS; ++} ++ ++static void Close( decoder_t* dec ) ++{ ++ auto sys = static_cast<decoder_sys_t*>( dec->p_sys ); ++ sys->decoder.call<void>("close"); ++ delete sys; ++} ++ ++static int ++OpenDecDevice(vlc_decoder_device *device, vout_window_t *) ++{ ++ static const struct vlc_decoder_device_operations ops = ++ { ++ nullptr, ++ }; ++ device->ops = &ops; ++ device->type = VLC_DECODER_DEVICE_WEBCODEC; ++ ++ return VLC_SUCCESS; ++} ++ ++vlc_module_begin () ++ set_description("Video decoder using browser provided implementation") ++ set_subcategory(SUBCAT_INPUT_VCODEC) ++ set_section(N_("Decoding"), NULL) ++ set_capability("video decoder", 100) ++ set_callbacks(Open, Close) ++ add_shortcut("webcodec") ++ ++ add_submodule() ++ set_callback_dec_device(OpenDecDevice, 1) ++vlc_module_end () +diff --git a/modules/video_output/emscripten/common.h b/modules/video_output/emscripten/common.h +new file mode 100644 +index 0000000000..3a2d3c9099 +--- /dev/null ++++ b/modules/video_output/emscripten/common.h +@@ -0,0 +1,46 @@ ++/***************************************************************************** ++ * common.h: Emscripten decoder/vout common code ++ ***************************************************************************** ++ * Copyright (C) 2021 VLC authors and VideoLAN ++ * ++ * This program is free software; you can redistribute it and/or modify it ++ * under the terms of the GNU Lesser General Public License as published by ++ * the Free Software Foundation; either version 2.1 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU Lesser General Public License for more details. ++ * ++ * You should have received a copy of the GNU Lesser General Public License ++ * along with this program; if not, write to the Free Software Foundation, ++ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. ++ *****************************************************************************/ ++ ++#ifndef EMSCRIPTEN_COMMON_H ++#define EMSCRIPTEN_COMMON_H ++ ++#ifndef __cplusplus ++# error This only supports C++ ++#endif ++ ++#include <vlc_threads.h> ++#include <vlc_picture.h> ++ ++#include <emscripten/html5_webgl.h> ++//#include <emscripten/val.h> ++ ++#define WEBCODEC_MAX_PICTURES 32 ++ ++struct webcodec_context ++{ ++ pthread_t decoder_worker; ++}; ++ ++struct webcodec_picture_sys_t ++{ ++ int pictureId; ++}; ++ ++#endif // EMSCRIPTEN_COMMON_H +-- +2.35.1 + diff --git a/vlc_patches/aug/0028-vout-Add-an-emscripten-webcodec-glinterop-module.patch b/vlc_patches/aug/0028-vout-Add-an-emscripten-webcodec-glinterop-module.patch new file mode 100644 index 0000000000000000000000000000000000000000..8a0739a8a055025f0719608a960cdde93c49156a --- /dev/null +++ b/vlc_patches/aug/0028-vout-Add-an-emscripten-webcodec-glinterop-module.patch @@ -0,0 +1,266 @@ +From 3b3a3979fb7d87035bd5607aeb3f8a3b66aacde4 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr> +Date: Thu, 3 Feb 2022 10:00:49 +0100 +Subject: [PATCH 28/77] vout: Add an emscripten/webcodec glinterop module + +--- + modules/video_output/Makefile.am | 7 + + .../opengl/interop_emscripten.cpp | 228 ++++++++++++++++++ + 2 files changed, 235 insertions(+) + create mode 100644 modules/video_output/opengl/interop_emscripten.cpp + +diff --git a/modules/video_output/Makefile.am b/modules/video_output/Makefile.am +index 191992d887..ea3a0c6da9 100644 +--- a/modules/video_output/Makefile.am ++++ b/modules/video_output/Makefile.am +@@ -305,6 +305,13 @@ vout_LTLIBRARIES += libegl_android_plugin.la libglinterop_android_plugin.la + endif + endif + ++### Emscripten ++libglinterop_emscripten_plugin_la_SOURCES = video_output/opengl/interop_emscripten.cpp \ ++ video_output/opengl/interop.h ++libglinterop_emscripten_plugin_la_CXXFLAGS = $(AM_CXXFLAGS) -DUSE_OPENGL_ES2 ++if HAVE_EMSCRIPTEN ++vout_LTLIBRARIES += libglinterop_emscripten_plugin.la ++endif + + ### FrameBuffer ### + +diff --git a/modules/video_output/opengl/interop_emscripten.cpp b/modules/video_output/opengl/interop_emscripten.cpp +new file mode 100644 +index 0000000000..123c1fdb25 +--- /dev/null ++++ b/modules/video_output/opengl/interop_emscripten.cpp +@@ -0,0 +1,228 @@ ++/***************************************************************************** ++ * interop_emscripten.cpp: OpenGL Emscripten/Webcodec opaque converter ++ ***************************************************************************** ++ * Copyright (C) 2021 VLC authors and VideoLAN ++ * ++ * This program is free software; you can redistribute it and/or modify it ++ * under the terms of the GNU Lesser General Public License as published by ++ * the Free Software Foundation; either version 2.1 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU Lesser General Public License for more details. ++ * ++ * You should have received a copy of the GNU Lesser General Public License ++ * along with this program; if not, write to the Free Software Foundation, ++ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. ++ *****************************************************************************/ ++ ++#ifdef HAVE_CONFIG_H ++# include "config.h" ++#endif ++ ++//#ifndef __EMSCRIPTEN__ ++//# error this file must be built with emscripten ++//#endif ++ ++#include <vlc_common.h> ++#include <vlc_plugin.h> ++#include "interop.h" ++#include "gl_common.h" ++#include <cassert> ++#include "../emscripten/common.h" ++ ++#include <emscripten.h> ++#include <emscripten/em_js.h> ++#include <emscripten/val.h> ++#include <emscripten/html5_webgl.h> ++ ++struct EmscriptenInterop ++{ ++ struct ++ { ++ PFNGLBINDTEXTUREPROC BindTexture; ++ } gl; ++}; ++ ++static int ++tc_emscripten_op_allocate_textures(const struct vlc_gl_interop *interop, GLuint *textures, ++ const GLsizei *tex_width, const GLsizei *tex_height) ++{ ++ (void) tex_width; (void) tex_height; ++ assert(textures[0] != 0); ++ ++ return VLC_SUCCESS; ++} ++ ++EM_ASYNC_JS(void, bindVideoFrame, (int pictureId), { ++ let frame = await Module.awaitFrame(pictureId); ++ ++ let glCtx = Module.glCtx; ++ glCtx.texImage2D(glCtx.TEXTURE_2D, 0, glCtx.RGBA, frame.codedWidth, frame.codedHeight, 0, ++ glCtx.RGBA, glCtx.UNSIGNED_BYTE, frame); ++}) ++ ++static int ++tc_emscripten_op_update(const struct vlc_gl_interop *interop, GLuint *textures, ++ const GLsizei *tex_width, const GLsizei *tex_height, ++ picture_t *pic, const size_t *plane_offset) ++{ ++ auto sys = static_cast<EmscriptenInterop *>( interop->priv ); ++ ++ sys->gl.BindTexture(interop->tex_target, textures[0]); ++ ++ auto pictureId = reinterpret_cast<uintptr_t>( pic->p_sys ); ++ bindVideoFrame(pictureId); ++ ++ return VLC_SUCCESS; ++} ++ ++extern "C" ++{ ++EMSCRIPTEN_KEEPALIVE int getDecoderWorker(vlc_video_context* vctx) ++{ ++ auto wcCtx = static_cast<webcodec_context*>( ++ vlc_video_context_GetPrivate(vctx, VLC_VIDEO_CONTEXT_WEBCODEC)); ++ if (!vctx) ++ return 0; ++ return wcCtx->decoder_worker; ++} ++} ++ ++EM_JS(void, transferMessagePort, (vlc_video_context* vctx), { ++ function onDecoderMessage(msg) { ++ let data = msg['data']; ++ if (data.customCmd == 'displayFrame') { ++ let pictureId = data.pictureId; ++ let pictureIdx = pictureId % 32; ++ let frame = data['frame']; ++ console.log('SINK Received frame ' + pictureId ); ++ if ( Module.glConv.promiseResolvers[pictureIdx] ) { ++ /* The interop is already waiting for this frame, resolve the promise */ ++ console.log('SINK Renderer is already waiting for frame ' + pictureId ); ++ Module.glConv.promiseResolvers[pictureIdx]( frame ); ++ } else { ++ /* The frame hasn't arrived to the interop yet, queue it */ ++ console.log('SINK No resolver, queuing frame ' + pictureId ); ++ Module.glConv.frameQueue[pictureIdx] = frame; ++ } ++ } ++ }; ++ Module.msgChannel = new MessageChannel(); ++ Module.msgChannel.port1.onmessage = onDecoderMessage; ++ let workerId = _getDecoderWorker(vctx); ++ if (workerId == 0) ++ return; ++ self.postMessage({ ++ customCmd: 'transferMessagePort', ++ targetThread: workerId, ++ transferList: [Module.msgChannel.port2], ++ }, [Module.msgChannel.port2]); ++}); ++ ++EM_JS(void, initGlConvWorker, (int maxPictures), { ++ Module.glConv = {}; ++ Module.glConv.promiseResolvers = []; ++ Module.glConv.frameQueue = []; ++ Module.glConv.lastFrame = { ++ pictureId: -1, ++ frame: undefined ++ }; ++ ++ Module.awaitFrame = async function(pictureId) { ++ console.log('CONSUME Fetching frame ' + pictureId); ++ if (Module.glConv.lastFrame.pictureId == pictureId) { ++ console.log('Reusing last frame'); ++ return Module.glConv.lastFrame.frame; ++ } ++ let pictureIndex = pictureId % 32; ++ let p = new Promise((resolve, reject) => { ++ if ( Module.glConv.frameQueue[pictureIndex] ) { ++ console.log('CONSUME Frame ', pictureId, '(index ', pictureIndex, ++ ') was already queued'); ++ let frame = Module.glConv.frameQueue[pictureIndex]; ++ resolve(frame); ++ Module.glConv.frameQueue[pictureIndex] = undefined; ++ } else { ++ console.log('CONSUME Frame ', pictureId, '(index ', pictureIndex, ++ ') not available yet; exposing resolver' ); ++ Module.glConv.promiseResolvers[pictureIndex] = resolve; ++ } ++ }); ++ let frame = await p; ++ Module.glConv.promiseResolvers[pictureId] = undefined; ++ if (Module.glConv.lastFrame.frame) ++ Module.glConv.lastFrame.frame.close(); ++ Module.glConv.lastFrame.frame = frame; ++ Module.glConv.lastFrame.pictureId = pictureId; ++ console.log('CONSUME got frame'); ++ return frame; ++ } ++}) ++ ++EM_JS(void, closeMessagePort, (void), { ++ if (Module.msgChannel) ++ delete Module.msgChannel; ++}); ++ ++static void ++Close(struct vlc_gl_interop *) ++{ ++ closeMessagePort(); ++} ++ ++static int ++Open(vlc_object_t *obj) ++{ ++ auto interop = reinterpret_cast<vlc_gl_interop*>( obj ); ++ ++ if (interop->fmt_in.i_chroma != VLC_CODEC_WEBCODEC_OPAQUE) ++ return VLC_EGENERIC; ++ ++ EmscriptenInterop *sys = (decltype(sys)) ++ vlc_obj_malloc(VLC_OBJECT(interop), sizeof *sys); ++ if (sys == NULL) ++ return VLC_EGENERIC; ++ sys->gl.BindTexture = (decltype(sys->gl.BindTexture)) ++ vlc_gl_GetProcAddress(interop->gl, "glBindTexture"); ++ if (sys->gl.BindTexture == NULL) ++ return VLC_EGENERIC; ++ ++ static const struct vlc_gl_interop_ops ops = { ++ .allocate_textures = tc_emscripten_op_allocate_textures, ++ .update_textures = tc_emscripten_op_update, ++ .close = Close, ++ }; ++ interop->ops = &ops; ++ initGlConvWorker(WEBCODEC_MAX_PICTURES); ++ transferMessagePort(interop->vctx); ++ ++ interop->tex_target = GL_TEXTURE_2D; ++ interop->fmt_out.i_chroma = VLC_CODEC_RGBA; ++ interop->fmt_out.space = COLOR_SPACE_UNDEF; ++ interop->tex_count = 1; ++ interop->texs[0] = vlc_gl_interop::vlc_gl_tex_cfg{ ++ /*.w =*/ { 1, 1 }, ++ /*.h =*/ { 1, 1 }, ++ /*.internal =*/ GL_RGBA, ++ /*.format = */GL_RGBA, ++ /*.type = */GL_UNSIGNED_BYTE, ++ }; ++ interop->fmt_out.i_rmask = 0xFF0000; ++ interop->fmt_out.i_gmask = 0x00FF00; ++ interop->fmt_out.i_bmask = 0x0000FF; ++ interop->priv = sys; ++ ++ interop->fmt_out.orientation = ORIENT_VFLIPPED; ++ ++ return VLC_SUCCESS; ++} ++ ++vlc_module_begin () ++ set_description("Emscripten OpenGL SurfaceTexture converter") ++ set_capability("glinterop", 1) ++ set_callback(Open) ++ set_subcategory(SUBCAT_VIDEO_VOUT) ++vlc_module_end () +-- +2.35.1 + diff --git a/vlc_patches/aug/0029-vout-emscripten-Adapt-to-webcodec-changes.patch b/vlc_patches/aug/0029-vout-emscripten-Adapt-to-webcodec-changes.patch new file mode 100644 index 0000000000000000000000000000000000000000..a6b70ef6f172357629913eefe7818f75c80132d8 --- /dev/null +++ b/vlc_patches/aug/0029-vout-emscripten-Adapt-to-webcodec-changes.patch @@ -0,0 +1,230 @@ +From 448ea00d254b1b970000eb60ba06f55c1186b903 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr> +Date: Thu, 3 Feb 2022 10:11:05 +0100 +Subject: [PATCH 29/77] vout: emscripten: Adapt to webcodec changes + +--- + modules/video_output/emscripten.cpp | 123 ++++++++++++++++++++++------ + 1 file changed, 100 insertions(+), 23 deletions(-) + +diff --git a/modules/video_output/emscripten.cpp b/modules/video_output/emscripten.cpp +index 857d921c27..0c863132f6 100644 +--- a/modules/video_output/emscripten.cpp ++++ b/modules/video_output/emscripten.cpp +@@ -25,7 +25,9 @@ + # include <config.h> + #endif + +-#include <stdarg.h> ++#include <cstdarg> ++#include <new> ++#include <string> + + #include <vlc_common.h> + #include <vlc_plugin.h> +@@ -34,9 +36,13 @@ + #include <vlc_opengl.h> + + #include "./opengl/vout_helper.h" ++#include "opengl/renderer.h" ++ ++#include "emscripten/common.h" + + #include <emscripten.h> + #include <emscripten/html5.h> ++#include <emscripten/val.h> + #include <webgl/webgl2.h> + // eglGetProcAddress + #include <EGL/egl.h> +@@ -46,19 +52,32 @@ static const struct vout_window_operations ops = { + //vout_window_ReportSize() should be called from here + }; + +-typedef struct gl_sys_t +-{ +- unsigned width; +- unsigned height; ++//MAIN_THREAD_ ++//EM_JS(emscripten::EM_VAL, GetCanvasSize, (), { ++// let canvas = document.getElementById('canvas'); ++// if (!canvas) ++// return Emval.bindValue([]); ++// return Emval.bindValue([canvas.width, canvas.height]); ++//}); + ++typedef struct emscripten_gl_sys_t ++{ + EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context; +-} gl_sys_t; ++} emscripten_gl_sys_t; + + static int OpenWindow(vout_window_t *wnd) + { + wnd->type = VOUT_WINDOW_TYPE_EMSCRIPTEN_WEBGL; + wnd->ops = &ops; ++ wnd->handle.canvas = "canvas"; + ++ //emscripten::val size = GetCanvasSize(); ++ //std::vector<int> vec_size = emscripten::vecFromJSArray(size); ++ //if (vec_size.size() != 2) ++ // return VLC_EGENERIC; ++ ++ //vout_window_ReportSize(wnd, vec_size[0], vec_size[1]); ++ vout_window_ReportSize(wnd, 1280, 720); + return VLC_SUCCESS; + } + +@@ -68,9 +87,10 @@ static void *GetProcAddress(vlc_gl_t *gl, const char *name) + + return reinterpret_cast<void*>(eglGetProcAddress(name)); + } ++ + static int MakeCurrent(vlc_gl_t *gl) + { +- auto *sys = static_cast<gl_sys_t*>(gl->sys); ++ auto sys = static_cast<emscripten_gl_sys_t*>( gl->sys ); + + if (emscripten_webgl_make_context_current(sys->context) != EMSCRIPTEN_RESULT_SUCCESS) + return VLC_EGENERIC; +@@ -85,8 +105,19 @@ static void ReleaseCurrent(vlc_gl_t *gl) + + static void Swap(vlc_gl_t *gl) + { ++ /** ++ * There is no way to commit/swapbuffer from an offscreen canvas, so instead ++ * we render the offscreen canvas onto a bitmap and let the actual canvas ++ * display that bitmap from the main thread. ++ */ + VLC_UNUSED(gl); +- emscripten_webgl_commit_frame(); ++ EM_ASM({ ++ let bitmap = Module.offscreenCanvas.transferToImageBitmap(); ++ Module.voutMsgPort.postMessage({ ++ customCmd: 'commitFrame', ++ bitmap: bitmap, ++ }, [bitmap]); ++ }); + } + + static void Resize(vlc_gl_t *gl, unsigned w, unsigned h) +@@ -96,39 +127,85 @@ static void Resize(vlc_gl_t *gl, unsigned w, unsigned h) + VLC_UNUSED(h); + } + ++EM_ASYNC_JS(void, getVoutMessagePort, (), { ++ let p = new Promise((resolve, reject) => { ++ let listener = function(e) { ++ let msg = e.data; ++ if (msg.customCmd == 'getVoutMessagePortResult') { ++ Module.voutMsgPort = msg['msgPort']; ++ resolve(); ++ self.removeEventListener('message', listener); ++ }; ++ }; ++ self.addEventListener('message', listener); ++ self.postMessage({ ++ customCmd: 'getVoutMessagePort' ++ }); ++ }); ++ await p; ++}); ++ ++EM_JS(int, createGlContext, (int width, int height), { ++ Module.offscreenCanvas = new OffscreenCanvas(width, height); ++ Module.glCtx = Module.offscreenCanvas.getContext('webgl2'); ++ ++ return GL.registerContext(Module.glCtx, { ++ antialias: false ++ }); ++}); ++ + static void Close (vlc_gl_t *gl) + { +- free(gl->sys); ++ auto sys = static_cast<emscripten_gl_sys_t*>( gl->sys ); ++ delete sys; + } + + static int Open (vlc_gl_t *gl, unsigned width, unsigned height) + { + VLC_UNUSED(width), VLC_UNUSED(height); + +- EmscriptenWebGLContextAttributes attr; +- +- emscripten_webgl_init_context_attributes(&attr); +- attr.majorVersion=2; +- attr.minorVersion=0; +- attr.explicitSwapControl = 1; +- + vout_window_t *wnd = gl->surface; + + if (wnd->type != VOUT_WINDOW_TYPE_EMSCRIPTEN_WEBGL) + goto error; + +- gl_sys_t *sys; ++ emscripten_gl_sys_t *sys; + +- gl->sys = sys = static_cast<gl_sys_t*>(calloc(1, sizeof(*sys))); ++ gl->sys = sys = new (std::nothrow)emscripten_gl_sys_t; + if (!sys) + return VLC_ENOMEM; + +- sys->context = emscripten_webgl_create_context("#canvas", &attr); ++ MAIN_THREAD_EM_ASM({ ++ if (Module.canvasCtx === undefined) { ++ let canvasName = UTF8ToString($1); ++ Module.canvasCtx = document.getElementById(canvasName).getContext('2d'); ++ } ++ function onVoutMessage(msg) { ++ let data = msg['data']; ++ if (data.customCmd == 'commitFrame') { ++ Module.canvasCtx.drawImage(data['bitmap'], 0, 0); ++ } ++ } ++ let w = Module.PThread.pthreads[$0].worker; ++ w.addEventListener('message', function (e) { ++ let msg = e['data']; ++ if (msg.customCmd == 'getVoutMessagePort') { ++ let msgChannel = new MessageChannel(); ++ msgChannel.port1.onmessage = onVoutMessage; ++ w.postMessage({ ++ customCmd: 'getVoutMessagePortResult', ++ msgPort: msgChannel.port2, ++ }, [msgChannel.port2]); ++ } ++ }); ++ }, pthread_self(), wnd->handle.canvas ? wnd->handle.canvas : "canvas"); ++ ++ getVoutMessagePort(); ++ sys->context = createGlContext(width, height); ++ + if (!sys->context) { +- msg_Err(gl, "Failed to make context current"); + goto error; + } +- + // Check that the WebGL context is valid + if (emscripten_webgl_make_context_current(sys->context) != EMSCRIPTEN_RESULT_SUCCESS) { + emscripten_log(EM_LOG_CONSOLE, "failed to make context current"); +@@ -146,8 +223,8 @@ static int Open (vlc_gl_t *gl, unsigned width, unsigned height) + gl->swap = Swap; + gl->get_proc_address = GetProcAddress; + gl->destroy = Close; +- + return VLC_SUCCESS; ++ + error: + Close(gl); + return VLC_EGENERIC; +@@ -161,7 +238,7 @@ vlc_module_begin() + set_description(N_("Emscripten drawing area")) + set_subcategory(SUBCAT_VIDEO_VOUT) + set_capability("vout window", 10) +- set_callbacks(OpenWindow, NULL) ++ set_callbacks(OpenWindow, nullptr) + + add_submodule () + set_shortname("Emscripten GL") +-- +2.35.1 + diff --git a/vlc_patches/aug/0030-opengl-sampler-avoid-crashing-on-unbind-location.patch b/vlc_patches/aug/0030-opengl-sampler-avoid-crashing-on-unbind-location.patch new file mode 100644 index 0000000000000000000000000000000000000000..e4aff5b8a085f8188e4f7ede2adcdd76bf94bacf --- /dev/null +++ b/vlc_patches/aug/0030-opengl-sampler-avoid-crashing-on-unbind-location.patch @@ -0,0 +1,82 @@ +From 5874bfc37d1ebd81e8b20382ec7b9a09207ca4ba Mon Sep 17 00:00:00 2001 +From: Alexandre Janniaux <ajanni@videolabs.io> +Date: Wed, 23 Feb 2022 11:13:41 +0100 +Subject: [PATCH 30/77] opengl: sampler: avoid crashing on unbind location + +--- + modules/video_output/opengl/sampler.c | 26 +++++++++++++++++++------- + 1 file changed, 19 insertions(+), 7 deletions(-) + +diff --git a/modules/video_output/opengl/sampler.c b/modules/video_output/opengl/sampler.c +index 846cbc75ab..9df0446284 100644 +--- a/modules/video_output/opengl/sampler.c ++++ b/modules/video_output/opengl/sampler.c +@@ -292,25 +292,30 @@ sampler_base_load(struct vlc_gl_sampler *sampler) + struct vlc_gl_format *glfmt = &sampler->glfmt; + struct vlc_gl_picture *pic = &priv->pic; + +- if (priv->yuv_color) ++ if (priv->yuv_color && priv->uloc.ConvMatrix != -1) + vt->UniformMatrix4fv(priv->uloc.ConvMatrix, 1, GL_FALSE, + priv->conv_matrix); + + for (unsigned i = 0; i < glfmt->tex_count; ++i) + { ++ if (priv->uloc.Textures[i] == -1) ++ continue; + vt->Uniform1i(priv->uloc.Textures[i], i); + + assert(pic->textures[i] != 0); + vt->ActiveTexture(GL_TEXTURE0 + i); + vt->BindTexture(glfmt->tex_target, pic->textures[i]); +- + } + + if (glfmt->tex_target == GL_TEXTURE_RECTANGLE) + { + for (unsigned i = 0; i < glfmt->tex_count; ++i) ++ { ++ if (priv->uloc.TexSizes[i] == -1) ++ continue; + vt->Uniform2f(priv->uloc.TexSizes[i], glfmt->tex_widths[i], + glfmt->tex_heights[i]); ++ } + } + + #ifdef HAVE_LIBPLACEBO +@@ -365,6 +370,9 @@ sampler_xyz12_load(struct vlc_gl_sampler *sampler) + struct vlc_gl_format *glfmt = &sampler->glfmt; + struct vlc_gl_picture *pic = &priv->pic; + ++ if (priv->uloc.Textures[0] == -1) ++ return; ++ + vt->Uniform1i(priv->uloc.Textures[0], 0); + + assert(pic->textures[0] != 0); +@@ -537,13 +545,17 @@ sampler_planes_load(struct vlc_gl_sampler *sampler) + struct vlc_gl_format *glfmt = &sampler->glfmt; + struct vlc_gl_picture *pic = &priv->pic; + +- vt->Uniform1i(priv->uloc.Textures[0], 0); ++ if (priv->uloc.Textures[0] != -1) ++ { ++ vt->Uniform1i(priv->uloc.Textures[0], 0); + +- assert(pic->textures[plane] != 0); +- vt->ActiveTexture(GL_TEXTURE0); +- vt->BindTexture(glfmt->tex_target, pic->textures[plane]); ++ assert(pic->textures[plane] != 0); ++ vt->ActiveTexture(GL_TEXTURE0); ++ vt->BindTexture(glfmt->tex_target, pic->textures[plane]); ++ } + +- if (glfmt->tex_target == GL_TEXTURE_RECTANGLE) ++ if (glfmt->tex_target == GL_TEXTURE_RECTANGLE && ++ priv->uloc.TexSizes[0] != -1) + { + vt->Uniform2f(priv->uloc.TexSizes[0], glfmt->tex_widths[plane], + glfmt->tex_heights[plane]); +-- +2.35.1 + diff --git a/vlc_patches/aug/0031-audio_output-emscripten-remove-set_category.patch b/vlc_patches/aug/0031-audio_output-emscripten-remove-set_category.patch new file mode 100644 index 0000000000000000000000000000000000000000..77f978d212efe379d9d2d120d90ce064aab31edb --- /dev/null +++ b/vlc_patches/aug/0031-audio_output-emscripten-remove-set_category.patch @@ -0,0 +1,25 @@ +From c4f0a0fe0e6977f99bcc16eb13d72df2adbbf0e9 Mon Sep 17 00:00:00 2001 +From: Alexandre Janniaux <ajanni@videolabs.io> +Date: Wed, 23 Feb 2022 11:15:45 +0100 +Subject: [PATCH 31/77] audio_output: emscripten: remove set_category + +please fixup. +--- + modules/audio_output/emscripten.cpp | 1 - + 1 file changed, 1 deletion(-) + +diff --git a/modules/audio_output/emscripten.cpp b/modules/audio_output/emscripten.cpp +index 6acea387e3..a7e9fb0d4d 100644 +--- a/modules/audio_output/emscripten.cpp ++++ b/modules/audio_output/emscripten.cpp +@@ -416,7 +416,6 @@ vlc_module_begin () + set_description( N_("Emscripten Worklet audio output") ) + set_shortname( "emworklet" ) + set_capability( "audio output", 100 ) +- set_category( CAT_AUDIO ) + set_subcategory( SUBCAT_AUDIO_AOUT ) + set_callbacks( Open, Close ) + vlc_module_end () +-- +2.35.1 + diff --git a/vlc_patches/aug/0032-vlc_vout_window-add-canvas-handle-type.patch b/vlc_patches/aug/0032-vlc_vout_window-add-canvas-handle-type.patch new file mode 100644 index 0000000000000000000000000000000000000000..a72e7df3c6988b49e8629a2260ebeaf354bb99ca --- /dev/null +++ b/vlc_patches/aug/0032-vlc_vout_window-add-canvas-handle-type.patch @@ -0,0 +1,24 @@ +From 62f2dea07966e934bec0185ecaa62cffcfa804f5 Mon Sep 17 00:00:00 2001 +From: Alexandre Janniaux <ajanni@videolabs.io> +Date: Tue, 22 Feb 2022 16:47:25 +0100 +Subject: [PATCH 32/77] vlc_vout_window: add canvas handle type + +--- + include/vlc_vout_window.h | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/include/vlc_vout_window.h b/include/vlc_vout_window.h +index b5a01b33e4..0803e65b78 100644 +--- a/include/vlc_vout_window.h ++++ b/include/vlc_vout_window.h +@@ -387,6 +387,7 @@ typedef struct vout_window_t { + void *dcomp_visual; /**< Win32 direct composition visual */ + uint32_t crtc; /**< KMS CRTC identifier */ + uint32_t em_context; /* Emscripten webgl context */ ++ const char *canvas; + } handle; + + /** Display server (mandatory) +-- +2.35.1 + diff --git a/vlc_patches/aug/0033-opengl-filter-forward-declaration-for-samplers.patch b/vlc_patches/aug/0033-opengl-filter-forward-declaration-for-samplers.patch new file mode 100644 index 0000000000000000000000000000000000000000..b7ff21d4e74aacdfce6f81a9b3d6a3dc2ab8bad6 --- /dev/null +++ b/vlc_patches/aug/0033-opengl-filter-forward-declaration-for-samplers.patch @@ -0,0 +1,93 @@ +From 4d52418f13ee742ae96f2a1d5fa72f411122eb40 Mon Sep 17 00:00:00 2001 +From: Alexandre Janniaux <ajanni@videolabs.io> +Date: Tue, 22 Feb 2022 17:06:21 +0100 +Subject: [PATCH 33/77] opengl: filter: forward-declaration for samplers + +--- + modules/video_output/opengl/filter.c | 1 + + modules/video_output/opengl/filter.h | 4 ++-- + modules/video_output/opengl/filter_mock.c | 1 + + modules/video_output/opengl/filter_priv.h | 1 + + modules/video_output/opengl/renderer.c | 1 + + modules/video_output/opengl/sampler.c | 1 + + 6 files changed, 7 insertions(+), 2 deletions(-) + +diff --git a/modules/video_output/opengl/filter.c b/modules/video_output/opengl/filter.c +index 7f0aa73e7c..491ce88dd5 100644 +--- a/modules/video_output/opengl/filter.c ++++ b/modules/video_output/opengl/filter.c +@@ -31,6 +31,7 @@ + #include <vlc_modules.h> + + #include "gl_api.h" ++#include "picture.h" + + struct vlc_gl_filter * + vlc_gl_filter_New(struct vlc_gl_t *gl, const struct vlc_gl_api *api) +diff --git a/modules/video_output/opengl/filter.h b/modules/video_output/opengl/filter.h +index dc81b69d8a..d8b672cc35 100644 +--- a/modules/video_output/opengl/filter.h ++++ b/modules/video_output/opengl/filter.h +@@ -24,9 +24,9 @@ + + #include <vlc_tick.h> + +-#include "picture.h" +- + struct vlc_gl_filter; ++struct vlc_gl_picture; ++struct vlc_gl_format; + + #ifdef __cplusplus + extern "C" +diff --git a/modules/video_output/opengl/filter_mock.c b/modules/video_output/opengl/filter_mock.c +index 64a73b303e..d455de4937 100644 +--- a/modules/video_output/opengl/filter_mock.c ++++ b/modules/video_output/opengl/filter_mock.c +@@ -73,6 +73,7 @@ + #include "gl_common.h" + #include "gl_util.h" + #include "sampler.h" ++#include "picture.h" + + #define MOCK_CFG_PREFIX "mock-" + +diff --git a/modules/video_output/opengl/filter_priv.h b/modules/video_output/opengl/filter_priv.h +index 7b702ddb17..cd0e3084fa 100644 +--- a/modules/video_output/opengl/filter_priv.h ++++ b/modules/video_output/opengl/filter_priv.h +@@ -27,6 +27,7 @@ + #include <vlc_picture.h> + + #include "filter.h" ++#include "picture.h" + + struct vlc_gl_filter_priv { + struct vlc_gl_filter filter; +diff --git a/modules/video_output/opengl/renderer.c b/modules/video_output/opengl/renderer.c +index aebbb84e67..092fc7b7e4 100644 +--- a/modules/video_output/opengl/renderer.c ++++ b/modules/video_output/opengl/renderer.c +@@ -41,6 +41,7 @@ + #include "gl_util.h" + #include "vout_helper.h" + #include "sampler.h" ++#include "picture.h" + + #define SPHERE_RADIUS 1.f + +diff --git a/modules/video_output/opengl/sampler.c b/modules/video_output/opengl/sampler.c +index 9df0446284..80f9d43e10 100644 +--- a/modules/video_output/opengl/sampler.c ++++ b/modules/video_output/opengl/sampler.c +@@ -37,6 +37,7 @@ + #include "gl_api.h" + #include "gl_common.h" + #include "gl_util.h" ++#include "picture.h" + + struct vlc_gl_sampler_priv { + struct vlc_gl_sampler sampler; +-- +2.35.1 + diff --git a/vlc_patches/aug/0034-include-move-vlc_opengl_filter.h-to-core.patch b/vlc_patches/aug/0034-include-move-vlc_opengl_filter.h-to-core.patch new file mode 100644 index 0000000000000000000000000000000000000000..37ef7b076f363151694779380ce0230ff9d8319e --- /dev/null +++ b/vlc_patches/aug/0034-include-move-vlc_opengl_filter.h-to-core.patch @@ -0,0 +1,160 @@ +From 356c5e7c876be70e73d84fa08b422794d674a540 Mon Sep 17 00:00:00 2001 +From: Alexandre Janniaux <ajanni@videolabs.io> +Date: Tue, 22 Feb 2022 17:07:53 +0100 +Subject: [PATCH 34/77] include: move vlc_opengl_filter.h to core + +--- + include/vlc_opengl_filter.h | 141 ++++++++++++++++++++++++++++++++++++ + 1 file changed, 141 insertions(+) + create mode 100644 include/vlc_opengl_filter.h + +diff --git a/include/vlc_opengl_filter.h b/include/vlc_opengl_filter.h +new file mode 100644 +index 0000000000..8cffea7708 +--- /dev/null ++++ b/include/vlc_opengl_filter.h +@@ -0,0 +1,141 @@ ++/***************************************************************************** ++ * filter.h ++ ***************************************************************************** ++ * Copyright (C) 2020 VLC authors and VideoLAN ++ * Copyright (C) 2020 Videolabs ++ * ++ * This program is free software; you can redistribute it and/or modify it ++ * under the terms of the GNU Lesser General Public License as published by ++ * the Free Software Foundation; either version 2.1 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU Lesser General Public License for more details. ++ * ++ * You should have received a copy of the GNU Lesser General Public License ++ * along with this program; if not, write to the Free Software Foundation, ++ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. ++ *****************************************************************************/ ++ ++#ifndef VLC_GL_FILTER_H ++#define VLC_GL_FILTER_H ++ ++#include <vlc_tick.h> ++ ++struct vlc_gl_filter; ++struct vlc_gl_sampler; ++ ++#ifdef __cplusplus ++extern "C" ++{ ++#endif ++ ++struct vlc_gl_tex_size { ++ unsigned width; ++ unsigned height; ++}; ++ ++struct vlc_gl_input_meta { ++ vlc_tick_t pts; ++ unsigned plane; ++}; ++ ++typedef int ++vlc_gl_filter_open_fn(struct vlc_gl_filter *filter, ++ const config_chain_t *config, ++ struct vlc_gl_tex_size *size_out); ++ ++struct vlc_gl_filter_ops { ++ /** ++ * Draw the result of the filter to the current framebuffer ++ */ ++ int (*draw)(struct vlc_gl_filter *filter, ++ const struct vlc_gl_input_meta *meta); ++ ++ /** ++ * Free filter resources ++ */ ++ void (*close)(struct vlc_gl_filter *filter); ++}; ++ ++struct vlc_gl_filter_owner_ops { ++ /** ++ * Get the sampler associated to this filter. ++ * ++ * The instance is lazy-loaded (to avoid creating one for blend filters). ++ * Successive calls to this function for the same filter is guaranteed to ++ * always return the same sampler. ++ * ++ * Important: filter->config must be initialized *before* getting the ++ * sampler, since the sampler behavior may depend on it. ++ * ++ * \param filter the filter ++ * \return sampler the sampler, NULL on error ++ */ ++ struct vlc_gl_sampler * ++ (*get_sampler)(struct vlc_gl_filter *filter); ++}; ++ ++/** ++ * OpenGL filter, in charge of a rendering pass. ++ */ ++struct vlc_gl_filter { ++ vlc_object_t obj; ++ module_t *module; ++ ++ const struct vlc_gl_api *api; ++ ++ struct { ++ /** ++ * An OpenGL filter may either operate on the input RGBA picture, or on ++ * individual input planes (without chroma conversion) separately. ++ * ++ * In practice, this is useful for deinterlace filters. ++ * ++ * This flag must be set by the filter module (default is false). ++ */ ++ bool filter_planes; ++ ++ /** ++ * A blend filter draws over the input picture (without reading it). ++ * ++ * Meaningless if filter_planes is true. ++ * ++ * This flag must be set by the filter module (default is false). ++ */ ++ bool blend; ++ ++ /** ++ * Request MSAA level. ++ * ++ * This value must be set by the filter module (default is 0, which ++ * means disabled). ++ * ++ * Meaningless if filter_planes is true. ++ * ++ * The actual MSAA level may be overwritten to 0 if multisampling is ++ * not supported, or to a higher value if another filter rendering on ++ * the same framebuffer requested a higher MSAA level. ++ */ ++ unsigned msaa_level; ++ } config; ++ ++ const struct vlc_gl_filter_ops *ops; ++ void *sys; ++ ++ const struct vlc_gl_filter_owner_ops *owner_ops; ++}; ++ ++static inline struct vlc_gl_sampler * ++vlc_gl_filter_GetSampler(struct vlc_gl_filter *filter) ++{ ++ return filter->owner_ops->get_sampler(filter); ++} ++ ++#ifdef __cplusplus ++} ++#endif ++ ++#endif +-- +2.35.1 + diff --git a/vlc_patches/aug/0035-opengl-mock-expose-module-on-emscripten-platform.patch b/vlc_patches/aug/0035-opengl-mock-expose-module-on-emscripten-platform.patch new file mode 100644 index 0000000000000000000000000000000000000000..a0ac19dc5e34b2c72a391d7fcab73c2416b18704 --- /dev/null +++ b/vlc_patches/aug/0035-opengl-mock-expose-module-on-emscripten-platform.patch @@ -0,0 +1,29 @@ +From e8dffa4b13264baa22bdb5c59b3a6c638753a589 Mon Sep 17 00:00:00 2001 +From: Alexandre Janniaux <ajanni@videolabs.io> +Date: Tue, 22 Feb 2022 17:45:12 +0100 +Subject: [PATCH 35/77] opengl: mock: expose module on emscripten platform + +--- + modules/video_output/opengl/Makefile.am | 6 ++++++ + 1 file changed, 6 insertions(+) + +diff --git a/modules/video_output/opengl/Makefile.am b/modules/video_output/opengl/Makefile.am +index bc4079fefc..bdb5a1b3eb 100644 +--- a/modules/video_output/opengl/Makefile.am ++++ b/modules/video_output/opengl/Makefile.am +@@ -108,6 +108,12 @@ libglfilter_mock_plugin_la_LIBADD += libvlc_opengl.la $(GL_LIBS) + noinst_LTLIBRARIES += libglfilter_mock_plugin.la + endif + ++if HAVE_EMSCRIPTEN ++libglfilter_mock_plugin_la_LIBADD += libvlc_opengles.la $(GL_LIBS) ++libglfilter_mock_plugin_la_CFLAGS = -DUSE_OPENGL_ES2=1 ++noinst_LTLIBRARIES += libglfilter_mock_plugin.la ++endif ++ + if HAVE_IOS + libglfilter_mock_plugin_la_LIBADD += libvlc_opengles.la $(GLES2_LIBS) + libglfilter_mock_plugin_la_CFLAGS = -DUSE_OPENGL_ES2=1 +-- +2.35.1 + diff --git a/vlc_patches/aug/0036-vlc_opengl_filter-add-gl-pointer.patch b/vlc_patches/aug/0036-vlc_opengl_filter-add-gl-pointer.patch new file mode 100644 index 0000000000000000000000000000000000000000..3c5fbe84df41d0d5359c6d3e3f5180b61603a502 --- /dev/null +++ b/vlc_patches/aug/0036-vlc_opengl_filter-add-gl-pointer.patch @@ -0,0 +1,37 @@ +From 78f9501c96671e0793b4aeca5d712b5ff21709f2 Mon Sep 17 00:00:00 2001 +From: Alexandre Janniaux <ajanni@videolabs.io> +Date: Tue, 22 Feb 2022 17:55:01 +0100 +Subject: [PATCH 36/77] vlc_opengl_filter: add gl pointer + +--- + include/vlc_opengl_filter.h | 1 + + modules/video_output/opengl/filters.c | 1 + + 2 files changed, 2 insertions(+) + +diff --git a/include/vlc_opengl_filter.h b/include/vlc_opengl_filter.h +index 8cffea7708..02228ec15c 100644 +--- a/include/vlc_opengl_filter.h ++++ b/include/vlc_opengl_filter.h +@@ -85,6 +85,7 @@ struct vlc_gl_filter { + vlc_object_t obj; + module_t *module; + ++ vlc_gl_t *gl; + const struct vlc_gl_api *api; + + struct { +diff --git a/modules/video_output/opengl/filters.c b/modules/video_output/opengl/filters.c +index b93c1ee2fe..abd00b755b 100644 +--- a/modules/video_output/opengl/filters.c ++++ b/modules/video_output/opengl/filters.c +@@ -183,6 +183,7 @@ vlc_gl_filters_Append(struct vlc_gl_filters *filters, const char *name, + struct vlc_gl_filter *filter = vlc_gl_filter_New(filters->gl, filters->api); + if (!filter) + return NULL; ++ filter->gl = filters->gl; + + struct vlc_gl_filter_priv *priv = vlc_gl_filter_PRIV(filter); + +-- +2.35.1 + diff --git a/vlc_patches/aug/0037-opengl-filter-move-to-vlc_opengl_filter.patch b/vlc_patches/aug/0037-opengl-filter-move-to-vlc_opengl_filter.patch new file mode 100644 index 0000000000000000000000000000000000000000..15fe2ac8a27e93bb1cce7b6000d5aa0a851dff5a --- /dev/null +++ b/vlc_patches/aug/0037-opengl-filter-move-to-vlc_opengl_filter.patch @@ -0,0 +1,287 @@ +From c1b6ccbce73b445fff4c7df1ffb84bd83eb3da48 Mon Sep 17 00:00:00 2001 +From: Alexandre Janniaux <ajanni@videolabs.io> +Date: Wed, 23 Feb 2022 13:40:41 +0100 +Subject: [PATCH 37/77] opengl: filter: move to vlc_opengl_filter + +--- + include/vlc_opengl_filter.h | 67 ++++++++----- + modules/video_output/opengl/filter.h | 137 +-------------------------- + 2 files changed, 42 insertions(+), 162 deletions(-) + +diff --git a/include/vlc_opengl_filter.h b/include/vlc_opengl_filter.h +index 02228ec15c..3a1f18197d 100644 +--- a/include/vlc_opengl_filter.h ++++ b/include/vlc_opengl_filter.h +@@ -1,5 +1,5 @@ + /***************************************************************************** +- * filter.h ++ * vlc_opengl_filter.h + ***************************************************************************** + * Copyright (C) 2020 VLC authors and VideoLAN + * Copyright (C) 2020 Videolabs +@@ -19,13 +19,14 @@ + * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ + +-#ifndef VLC_GL_FILTER_H +-#define VLC_GL_FILTER_H ++#ifndef VLC_OPENGL_FILTER_H ++#define VLC_OPENGL_FILTER_H + + #include <vlc_tick.h> + + struct vlc_gl_filter; +-struct vlc_gl_sampler; ++struct vlc_gl_picture; ++struct vlc_gl_format; + + #ifdef __cplusplus + extern "C" +@@ -45,37 +46,58 @@ struct vlc_gl_input_meta { + typedef int + vlc_gl_filter_open_fn(struct vlc_gl_filter *filter, + const config_chain_t *config, ++ const struct vlc_gl_format *glfmt, + struct vlc_gl_tex_size *size_out); + ++#define set_callback_opengl_filter(open) \ ++ { \ ++ vlc_gl_filter_open_fn *fn = open; \ ++ (void) fn; \ ++ set_callback(fn); \ ++ } ++ + struct vlc_gl_filter_ops { + /** + * Draw the result of the filter to the current framebuffer + */ +- int (*draw)(struct vlc_gl_filter *filter, ++ int (*draw)(struct vlc_gl_filter *filter, const struct vlc_gl_picture *pic, + const struct vlc_gl_input_meta *meta); + + /** + * Free filter resources + */ + void (*close)(struct vlc_gl_filter *filter); +-}; + +-struct vlc_gl_filter_owner_ops { + /** +- * Get the sampler associated to this filter. ++ * Request a (responsive) filter to adapt its output size (optional) ++ * ++ * A responsive filter is a filter for which the size of the produced ++ * pictures depends on the output (e.g. display) size rather than the ++ * input. This is for example the case for a renderer. + * +- * The instance is lazy-loaded (to avoid creating one for blend filters). +- * Successive calls to this function for the same filter is guaranteed to +- * always return the same sampler. ++ * A new output size is requested (size_out). The filter is authorized to ++ * change the size_out to enforce its own constraints. + * +- * Important: filter->config must be initialized *before* getting the +- * sampler, since the sampler behavior may depend on it. ++ * In addition, it may request to the previous filter (if any) an optimal ++ * size it wants to receive. If set to non-zero value, this previous filter ++ * will receive this size as its requested size (and so on). + * +- * \param filter the filter +- * \return sampler the sampler, NULL on error ++ * \retval true if the resize is accepted (possibly with a modified ++ * size_out) ++ * \retval false if the resize is rejected (included on error) + */ +- struct vlc_gl_sampler * +- (*get_sampler)(struct vlc_gl_filter *filter); ++ int (*request_output_size)(struct vlc_gl_filter *filter, ++ struct vlc_gl_tex_size *size_out, ++ struct vlc_gl_tex_size *optimal_in); ++ ++ /** ++ * Callback to notify input size changes ++ * ++ * When a filter changes its output size as a result of ++ * request_output_size(), the next filter is notified by this callback. ++ */ ++ void (*on_input_size_change)(struct vlc_gl_filter *filter, ++ const struct vlc_gl_tex_size *size); + }; + + /** +@@ -85,8 +107,9 @@ struct vlc_gl_filter { + vlc_object_t obj; + module_t *module; + +- vlc_gl_t *gl; ++ struct vlc_gl_t *gl; + const struct vlc_gl_api *api; ++ const struct vlc_gl_format *glfmt_in; + + struct { + /** +@@ -125,16 +148,8 @@ struct vlc_gl_filter { + + const struct vlc_gl_filter_ops *ops; + void *sys; +- +- const struct vlc_gl_filter_owner_ops *owner_ops; + }; + +-static inline struct vlc_gl_sampler * +-vlc_gl_filter_GetSampler(struct vlc_gl_filter *filter) +-{ +- return filter->owner_ops->get_sampler(filter); +-} +- + #ifdef __cplusplus + } + #endif +diff --git a/modules/video_output/opengl/filter.h b/modules/video_output/opengl/filter.h +index d8b672cc35..b8272a5115 100644 +--- a/modules/video_output/opengl/filter.h ++++ b/modules/video_output/opengl/filter.h +@@ -19,139 +19,4 @@ + * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ + +-#ifndef VLC_GL_FILTER_H +-#define VLC_GL_FILTER_H +- +-#include <vlc_tick.h> +- +-struct vlc_gl_filter; +-struct vlc_gl_picture; +-struct vlc_gl_format; +- +-#ifdef __cplusplus +-extern "C" +-{ +-#endif +- +-struct vlc_gl_tex_size { +- unsigned width; +- unsigned height; +-}; +- +-struct vlc_gl_input_meta { +- vlc_tick_t pts; +- unsigned plane; +-}; +- +-typedef int +-vlc_gl_filter_open_fn(struct vlc_gl_filter *filter, +- const config_chain_t *config, +- const struct vlc_gl_format *glfmt, +- struct vlc_gl_tex_size *size_out); +- +-#define set_callback_opengl_filter(open) \ +- { \ +- vlc_gl_filter_open_fn *fn = open; \ +- (void) fn; \ +- set_callback(fn); \ +- } +- +-struct vlc_gl_filter_ops { +- /** +- * Draw the result of the filter to the current framebuffer +- */ +- int (*draw)(struct vlc_gl_filter *filter, const struct vlc_gl_picture *pic, +- const struct vlc_gl_input_meta *meta); +- +- /** +- * Free filter resources +- */ +- void (*close)(struct vlc_gl_filter *filter); +- +- /** +- * Request a (responsive) filter to adapt its output size (optional) +- * +- * A responsive filter is a filter for which the size of the produced +- * pictures depends on the output (e.g. display) size rather than the +- * input. This is for example the case for a renderer. +- * +- * A new output size is requested (size_out). The filter is authorized to +- * change the size_out to enforce its own constraints. +- * +- * In addition, it may request to the previous filter (if any) an optimal +- * size it wants to receive. If set to non-zero value, this previous filter +- * will receive this size as its requested size (and so on). +- * +- * \retval true if the resize is accepted (possibly with a modified +- * size_out) +- * \retval false if the resize is rejected (included on error) +- */ +- int (*request_output_size)(struct vlc_gl_filter *filter, +- struct vlc_gl_tex_size *size_out, +- struct vlc_gl_tex_size *optimal_in); +- +- /** +- * Callback to notify input size changes +- * +- * When a filter changes its output size as a result of +- * request_output_size(), the next filter is notified by this callback. +- */ +- void (*on_input_size_change)(struct vlc_gl_filter *filter, +- const struct vlc_gl_tex_size *size); +-}; +- +-/** +- * OpenGL filter, in charge of a rendering pass. +- */ +-struct vlc_gl_filter { +- vlc_object_t obj; +- module_t *module; +- +- struct vlc_gl_t *gl; +- const struct vlc_gl_api *api; +- const struct vlc_gl_format *glfmt_in; +- +- struct { +- /** +- * An OpenGL filter may either operate on the input RGBA picture, or on +- * individual input planes (without chroma conversion) separately. +- * +- * In practice, this is useful for deinterlace filters. +- * +- * This flag must be set by the filter module (default is false). +- */ +- bool filter_planes; +- +- /** +- * A blend filter draws over the input picture (without reading it). +- * +- * Meaningless if filter_planes is true. +- * +- * This flag must be set by the filter module (default is false). +- */ +- bool blend; +- +- /** +- * Request MSAA level. +- * +- * This value must be set by the filter module (default is 0, which +- * means disabled). +- * +- * Meaningless if filter_planes is true. +- * +- * The actual MSAA level may be overwritten to 0 if multisampling is +- * not supported, or to a higher value if another filter rendering on +- * the same framebuffer requested a higher MSAA level. +- */ +- unsigned msaa_level; +- } config; +- +- const struct vlc_gl_filter_ops *ops; +- void *sys; +-}; +- +-#ifdef __cplusplus +-} +-#endif +- +-#endif ++#include <vlc_opengl_filter.h> +-- +2.35.1 + diff --git a/vlc_patches/aug/0038-opengl-mock-load-API-in-mock.patch b/vlc_patches/aug/0038-opengl-mock-load-API-in-mock.patch new file mode 100644 index 0000000000000000000000000000000000000000..dc5b7153eb16b3e8a5990c8555523d25240e7e3f --- /dev/null +++ b/vlc_patches/aug/0038-opengl-mock-load-API-in-mock.patch @@ -0,0 +1,34 @@ +From 098f9060b967025700bce2fe9e009e473f2eb0eb Mon Sep 17 00:00:00 2001 +From: Alexandre Janniaux <ajanni@videolabs.io> +Date: Tue, 22 Feb 2022 17:55:36 +0100 +Subject: [PATCH 38/77] opengl: mock: load API in mock + +--- + modules/video_output/opengl/filter_mock.c | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/modules/video_output/opengl/filter_mock.c b/modules/video_output/opengl/filter_mock.c +index d455de4937..3915456d7e 100644 +--- a/modules/video_output/opengl/filter_mock.c ++++ b/modules/video_output/opengl/filter_mock.c +@@ -102,6 +102,7 @@ struct sys { + + float rotation_matrix[16]; + float ar; ++ struct vlc_gl_api api; + }; + + static void +@@ -587,6 +588,9 @@ Open(struct vlc_gl_filter *filter, const config_chain_t *config, + + sys->sampler = NULL; + ++ vlc_gl_api_Init(&sys->api, filter->gl); ++ filter->api = &sys->api; ++ + int ret; + if (plane) + ret = InitPlane(filter, glfmt); +-- +2.35.1 + diff --git a/vlc_patches/aug/0039-vlc_vout_display-VLC_OBJECT-wrap-vout_display_New.patch b/vlc_patches/aug/0039-vlc_vout_display-VLC_OBJECT-wrap-vout_display_New.patch new file mode 100644 index 0000000000000000000000000000000000000000..804990e57dd9b703dbe6ef950d92956ea6182a43 --- /dev/null +++ b/vlc_patches/aug/0039-vlc_vout_display-VLC_OBJECT-wrap-vout_display_New.patch @@ -0,0 +1,39 @@ +From a2a7bee28811d1056880cb48f63f1d63a420a180 Mon Sep 17 00:00:00 2001 +From: Alexandre Janniaux <ajanni@videolabs.io> +Date: Fri, 25 Feb 2022 15:08:04 +0100 +Subject: [PATCH 39/77] vlc_vout_display: VLC_OBJECT-wrap vout_display_New() + +--- + include/vlc_vout_display.h | 2 ++ + src/video_output/display.c | 2 +- + 2 files changed, 3 insertions(+), 1 deletion(-) + +diff --git a/include/vlc_vout_display.h b/include/vlc_vout_display.h +index 6b11c4066c..64dd64581b 100644 +--- a/include/vlc_vout_display.h ++++ b/include/vlc_vout_display.h +@@ -375,6 +375,8 @@ vout_display_t *vout_display_New(vlc_object_t *, + const video_format_t *, vlc_video_context *, + const vout_display_cfg_t *, const char *module, + const vout_display_owner_t *); ++#define vout_display_New(O, ...) \ ++ (vout_display_New)(VLC_OBJECT(O), __VA_ARGS__) + + /** + * Destroys a video output display. +diff --git a/src/video_output/display.c b/src/video_output/display.c +index 9f07f04e3a..b00f0d1877 100644 +--- a/src/video_output/display.c ++++ b/src/video_output/display.c +@@ -615,7 +615,7 @@ void vout_SetDisplayViewpoint(vout_display_t *vd, + } + } + +-vout_display_t *vout_display_New(vlc_object_t *parent, ++vout_display_t *(vout_display_New)(vlc_object_t *parent, + const video_format_t *source, + vlc_video_context *vctx, + const vout_display_cfg_t *cfg, +-- +2.35.1 + diff --git a/vlc_patches/aug/0040-hw-Add-emscripten-video-converter.patch b/vlc_patches/aug/0040-hw-Add-emscripten-video-converter.patch new file mode 100644 index 0000000000000000000000000000000000000000..d007017c6add4cd0d1320c0f41a41cfa69906904 --- /dev/null +++ b/vlc_patches/aug/0040-hw-Add-emscripten-video-converter.patch @@ -0,0 +1,181 @@ +From f72d284a8d5aecef9c2d148048a004745d12a07a Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr> +Date: Wed, 16 Mar 2022 11:26:13 +0100 +Subject: [PATCH 40/77] hw: Add emscripten video converter + +--- + modules/Makefile.am | 1 + + modules/hw/emscripten/Makefile.am | 10 +++ + modules/hw/emscripten/converter.cpp | 131 ++++++++++++++++++++++++++++ + 3 files changed, 142 insertions(+) + create mode 100644 modules/hw/emscripten/Makefile.am + create mode 100644 modules/hw/emscripten/converter.cpp + +diff --git a/modules/Makefile.am b/modules/Makefile.am +index 333acb0586..0c931d51ca 100644 +--- a/modules/Makefile.am ++++ b/modules/Makefile.am +@@ -31,6 +31,7 @@ include hw/d3d11/Makefile.am + include hw/vaapi/Makefile.am + include hw/vdpau/Makefile.am + include hw/mmal/Makefile.am ++include hw/emscripten/Makefile.am + include isa/aarch64/Makefile.am + include isa/arm/Makefile.am + include isa/riscv/Makefile.am +diff --git a/modules/hw/emscripten/Makefile.am b/modules/hw/emscripten/Makefile.am +new file mode 100644 +index 0000000000..0402b1438b +--- /dev/null ++++ b/modules/hw/emscripten/Makefile.am +@@ -0,0 +1,10 @@ ++emscriptendir = $(pluginsdir)/emscripten ++ ++libglconverter_emscripten_plugin_la_SOURCES = hw/emscripten/converter.cpp ++libglconverter_emscripten_plugin_la_CPPFLAGS = $(AM_CPPFLAGS) ++libglconverter_emscripten_plugin_la_CXXFLAGS = $(AM_CXXFLAGS) ++libglconverter_emscripten_plugin_la_LIBADD = $(AM_LIBADD) ++ ++if HAVE_EMSCRIPTEN ++emscripten_LTLIBRARIES = libglconverter_emscripten_plugin.la ++endif +diff --git a/modules/hw/emscripten/converter.cpp b/modules/hw/emscripten/converter.cpp +new file mode 100644 +index 0000000000..623fc69919 +--- /dev/null ++++ b/modules/hw/emscripten/converter.cpp +@@ -0,0 +1,131 @@ ++/***************************************************************************** ++ * webcodec.cpp: VLC picture to WebCodec VideoFrame ++ ***************************************************************************** ++ * Copyright (C) 2022 VLC authors, VideoLAN and VideoLabs ++ * ++ * This program is free software; you can redistribute it and/or modify it ++ * under the terms of the GNU Lesser General Public License as published by ++ * the Free Software Foundation; either version 2.1 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU Lesser General Public License for more details. ++ * ++ * You should have received a copy of the GNU Lesser General Public License ++ * along with this program; if not, write to the Free Software Foundation, ++ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. ++ *****************************************************************************/ ++ ++#ifdef HAVE_CONFIG_H ++# include "config.h" ++#endif ++ ++#include <vlc_common.h> ++#include <vlc_filter.h> ++#include <vlc_plugin.h> ++ ++#include <emscripten/emscripten.h> ++#include <emscripten/em_js.h> ++#include <emscripten/val.h> ++ ++struct chroma_sys_t ++{ ++}; ++ ++static picture_t* UploadSurface(filter_t* filter, picture_t* src) ++{ ++ auto size = 0u; ++ for ( auto i = 0; i < src->i_planes; ++i ) ++ size += src->p[i].i_lines * src->p[i].i_pitch; ++ auto buffer = emscripten::val::global("Uint8Array").new_( size ); ++ auto planesLayout = emscripten::val::array(); ++ auto offset = 0u; ++ for ( auto i = 0; i < src->i_planes; ++i ) ++ { ++ auto planeSize = src->p[i].i_lines * src->p[i].i_pitch; ++ auto p = emscripten::typed_memory_view( planeSize, src->p[i].p_pixels ); ++ buffer.call<void>("set", p, offset); ++ auto layout = emscripten::val::object(); ++ layout.set("offset", offset); ++ layout.set("stride", src->p[i].i_pitch); ++ planesLayout.call<void>( "push", layout ); ++ offset += planeSize; ++ } ++ ++ auto init = emscripten::val::object(); ++ init.set( "format", "I420" ); ++ init.set( "codedWidth", src->format.i_width ); ++ init.set( "codedHeight", src->format.i_height ); ++ init.set( "timestamp", (long)US_FROM_VLC_TICK( src->date ) ); ++ init.set( "layout", planesLayout ); ++ init.set( "displayWidth", src->format.i_visible_width); ++ init.set( "displayHeight", src->format.i_visible_height ); ++ ++ auto visibleRect = emscripten::val::object(); ++ visibleRect.set( "x", src->format.i_x_offset ); ++ visibleRect.set( "y", src->format.i_y_offset ); ++ visibleRect.set( "width", src->format.i_visible_width); ++ visibleRect.set( "height", src->format.i_visible_height ); ++ init.set( "visibleRect", visibleRect ); ++ ++ auto frame = emscripten::val::global("VideoFrame").new_( buffer, init ); ++ uintptr_t pictureId = EM_ASM_INT({return Module['pictureId']}); ++ EM_ASM({ ++ let frame = Emval.toValue($0); ++ Module.msgChannel.port2.postMessage({ ++ customCmd: 'displayFrame', ++ pictureId: Module.pictureId, ++ frame: frame ++ }); ++ Module.pictureId = Module.pictureId + 1; ++ }, frame.as_handle()); ++ auto dst = filter_NewPicture( filter ); ++ if (dst == nullptr) ++ { ++ picture_Release(src); ++ return nullptr; ++ } ++ dst->p_sys = reinterpret_cast<void*>( pictureId ); ++ picture_CopyProperties( dst, src ); ++ picture_Release(src); ++ return dst; ++} ++ ++static void vlc_webcodec_CloseChroma(filter_t* filter) ++{ ++ ++} ++ ++static const struct vlc_filter_operations filter_ops = { ++ .filter_video = UploadSurface, .close = vlc_webcodec_CloseChroma, ++}; ++ ++static int vlc_webcodec_OpenChroma(filter_t* filter) ++{ ++ if (filter->fmt_in.video.i_height != filter->fmt_out.video.i_height ++ || filter->fmt_in.video.i_width != filter->fmt_out.video.i_width ++ || filter->fmt_in.video.orientation != filter->fmt_out.video.orientation) ++ { ++ return VLC_EGENERIC; ++ } ++ ++ if (filter->fmt_out.video.i_chroma != VLC_CODEC_WEBCODEC_OPAQUE) ++ return VLC_EGENERIC; ++ if (filter->fmt_in.video.i_chroma != VLC_CODEC_I420) ++ return VLC_EGENERIC; ++ ++ filter->ops = &filter_ops; ++ EM_ASM({Module.pictureId = 0;}); ++ return VLC_SUCCESS; ++} ++ ++vlc_module_begin() ++ set_shortname(N_("WebCodec filters")) ++ set_description(N_("WebCodec filters")) ++ set_subcategory(SUBCAT_VIDEO_VFILTER) ++ ++ add_submodule() ++ set_callback_video_converter(vlc_webcodec_OpenChroma, 10) ++vlc_module_end() +-- +2.35.1 + diff --git a/vlc_patches/aug/0041-emscripten-Add-a-WebCodec-frame-to-software-converte.patch b/vlc_patches/aug/0041-emscripten-Add-a-WebCodec-frame-to-software-converte.patch new file mode 100644 index 0000000000000000000000000000000000000000..16555c192bfb3794b715f2849478b99540c2b213 --- /dev/null +++ b/vlc_patches/aug/0041-emscripten-Add-a-WebCodec-frame-to-software-converte.patch @@ -0,0 +1,110 @@ +From fff1f64c6ce6245915c56c7696cc04362ab054e3 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr> +Date: Tue, 12 Apr 2022 13:55:51 +0200 +Subject: [PATCH 41/77] emscripten: Add a WebCodec frame to software converter + +--- + .../opengl/interop_emscripten.cpp | 77 +++++++++++++++++++ + 1 file changed, 77 insertions(+) + +diff --git a/modules/video_output/opengl/interop_emscripten.cpp b/modules/video_output/opengl/interop_emscripten.cpp +index 123c1fdb25..53b52fabd6 100644 +--- a/modules/video_output/opengl/interop_emscripten.cpp ++++ b/modules/video_output/opengl/interop_emscripten.cpp +@@ -28,6 +28,8 @@ + + #include <vlc_common.h> + #include <vlc_plugin.h> ++#include <vlc_filter.h> ++ + #include "interop.h" + #include "gl_common.h" + #include <cassert> +@@ -220,9 +222,84 @@ Open(vlc_object_t *obj) + return VLC_SUCCESS; + } + ++EM_ASYNC_JS(void, CopyFrameToBuffer, (int pictureId, emscripten::EM_VAL infoHandle), { ++ let info = Emval.toValue(infoHandle); ++ let frame = await Module.awaitFrame(pictureId); ++ let copyOpts = { ++ rect: frame.codedRect, ++ layout: info.layout ++ }; ++ await frame.copyTo(info.buffer, copyOpts); ++}); ++ ++static picture_t* vlc_webcodec_UploadVideoFrame(filter_t* filter, picture_t* src) ++{ ++ auto pic = filter_NewPicture(filter); ++ if (unlikely(pic == nullptr)) ++ { ++ picture_Release(src); ++ return nullptr; ++ } ++ auto pictureId = reinterpret_cast<uintptr_t>(src->p_sys); ++ ++ size_t picSize = 0; ++ auto info = emscripten::val::object(); ++ auto planesLayout = emscripten::val::array(); ++ for ( auto i = 0; i < pic->i_planes; ++i ) ++ { ++ auto p = pic->p[i]; ++ auto layout = emscripten::val::object(); ++ layout.set( "offset", picSize ); ++ layout.set( "stride", p.i_pitch ); ++ planesLayout.call<void>( "push", std::move( layout ) ); ++ picSize += p.i_lines * p.i_pitch; ++ } ++ ++ info.set( "buffer", emscripten::typed_memory_view( picSize, pic->p[0].p_pixels ) ); ++ info.set( "layout", std::move( planesLayout ) ); ++ ++ CopyFrameToBuffer( pictureId, info.as_handle() ); ++ ++ pic->date = src->date; ++ picture_Release(src); ++ return pic; ++} ++ ++static void vlc_webcodec_CloseConverter(filter_t*) ++{ ++ closeMessagePort(); ++} ++ ++static const struct vlc_filter_operations video_frame_to_sw_ops = { ++ .filter_video = vlc_webcodec_UploadVideoFrame, ++ .close = vlc_webcodec_CloseConverter, ++}; ++ ++static int OpenConverter(filter_t* filter) ++{ ++ if (filter->fmt_in.video.i_height != filter->fmt_out.video.i_height ++ || filter->fmt_in.video.i_width != filter->fmt_out.video.i_width ++ || filter->fmt_in.video.orientation != filter->fmt_out.video.orientation ++ || filter->fmt_in.video.i_chroma != VLC_CODEC_WEBCODEC_OPAQUE ++ || filter->fmt_out.video.i_chroma != VLC_CODEC_I420 ) ++ { ++ return VLC_EGENERIC; ++ } ++ initGlConvWorker(WEBCODEC_MAX_PICTURES); ++ transferMessagePort(filter->vctx_in); ++ ++ filter->ops = &video_frame_to_sw_ops; ++ ++ return VLC_SUCCESS; ++} ++ + vlc_module_begin () + set_description("Emscripten OpenGL SurfaceTexture converter") + set_capability("glinterop", 1) + set_callback(Open) + set_subcategory(SUBCAT_VIDEO_VOUT) ++ ++ add_submodule() ++ set_subcategory(SUBCAT_VIDEO_VFILTER) ++ set_callback_video_converter(OpenConverter, 10) + vlc_module_end () +-- +2.35.1 + diff --git a/vlc_patches/aug/0042-contrib-disable-ubsan-in-gme.patch b/vlc_patches/aug/0042-contrib-disable-ubsan-in-gme.patch new file mode 100644 index 0000000000000000000000000000000000000000..6920afe22fd16236da3f234a76d36fa11851a57e --- /dev/null +++ b/vlc_patches/aug/0042-contrib-disable-ubsan-in-gme.patch @@ -0,0 +1,28 @@ +From 624edf3c2ec7deb967e1071620762bca438aa3aa Mon Sep 17 00:00:00 2001 +From: Mehdi Sabwat <mehdisabwat@gmail.com> +Date: Wed, 13 Apr 2022 10:56:02 +0200 +Subject: [PATCH 42/77] contrib: disable ubsan in gme + +ubsan is enabled in the contrib recipe : +https://bitbucket.org/mpyne/game-music-emu/src/b3d158a30492181fd7c38ef795c8d4dcfd77eaa9/CMakeLists.txt#lines-67 + +This will require vlc to be always built with ubsan and we don't want this in production. +--- + contrib/src/gme/rules.mak | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/contrib/src/gme/rules.mak b/contrib/src/gme/rules.mak +index b0acdb8ca4..411b83fb24 100644 +--- a/contrib/src/gme/rules.mak ++++ b/contrib/src/gme/rules.mak +@@ -16,6 +16,6 @@ game-music-emu: game-music-emu-$(GME_VERSION).tar.xz .sum-gme + $(MOVE) + + .gme: game-music-emu toolchain.cmake +- cd $< && $(HOSTVARS_PIC) $(CMAKE) . ++ cd $< && $(HOSTVARS_PIC) $(CMAKE) . -DENABLE_UBSAN=OFF + +$(CMAKEBUILD) $< --target install + touch $@ +-- +2.35.1 + diff --git a/vlc_patches/aug/0043-emscripten-Add-a-picture_pool-to-the-common-code.patch b/vlc_patches/aug/0043-emscripten-Add-a-picture_pool-to-the-common-code.patch new file mode 100644 index 0000000000000000000000000000000000000000..8cf5aafdf1287395d32e372a50d2876d8041980b --- /dev/null +++ b/vlc_patches/aug/0043-emscripten-Add-a-picture_pool-to-the-common-code.patch @@ -0,0 +1,138 @@ +From 1a01a94d5d2eeb1fcc7e206c2e420f155bd25faa Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr> +Date: Tue, 3 May 2022 11:10:28 +0200 +Subject: [PATCH 43/77] emscripten: Add a picture_pool to the common code + +--- + modules/video_output/emscripten/common.cpp | 82 ++++++++++++++++++++++ + modules/video_output/emscripten/common.h | 15 +++- + 2 files changed, 95 insertions(+), 2 deletions(-) + create mode 100644 modules/video_output/emscripten/common.cpp + +diff --git a/modules/video_output/emscripten/common.cpp b/modules/video_output/emscripten/common.cpp +new file mode 100644 +index 0000000000..b2065be3b4 +--- /dev/null ++++ b/modules/video_output/emscripten/common.cpp +@@ -0,0 +1,82 @@ ++/***************************************************************************** ++ * common.h: Emscripten decoder/vout common code ++ ***************************************************************************** ++ * Copyright (C) 2021 VLC authors and VideoLAN ++ * ++ * This program is free software; you can redistribute it and/or modify it ++ * under the terms of the GNU Lesser General Public License as published by ++ * the Free Software Foundation; either version 2.1 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU Lesser General Public License for more details. ++ * ++ * You should have received a copy of the GNU Lesser General Public License ++ * along with this program; if not, write to the Free Software Foundation, ++ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. ++ *****************************************************************************/ ++ ++#ifdef HAVE_CONFIG_H ++# include "config.h" ++#endif ++ ++#include <vlc_common.h> ++#include <vlc_decoder.h> ++#include <vlc_picture_pool.h> ++ ++#include "common.h" ++ ++static picture_context_t* PictureContextCopy(picture_context_t* picCtx) ++{ ++ vlc_video_context_Hold( picCtx->vctx ); ++ return picCtx; ++} ++ ++static void PictureContextDestroy( picture_context_t* ) ++{ ++ /* Context is already released by picture_Release() */ ++} ++ ++static picture_context_t* webcodec_CreatePictureContext( vlc_video_context* vctx, uint32_t picIdx ) ++{ ++ auto picCtx = static_cast<webcodec_picture_ctx*>( ++ malloc( sizeof( webcodec_picture_ctx ) ) ); ++ if ( picCtx == nullptr ) ++ return nullptr; ++ picCtx->context.copy = PictureContextCopy; ++ picCtx->context.destroy = PictureContextDestroy; ++ picCtx->context.vctx = vctx; ++ picCtx->pictureIdx = picIdx; ++ return &picCtx->context; ++} ++ ++ ++int webcodec_CreatePool( vlc_video_context* vctx, const video_format_t* fmt ) ++{ ++ auto ctx = static_cast<webcodec_context*>( ++ vlc_video_context_GetPrivate( vctx, VLC_VIDEO_CONTEXT_WEBCODEC ) ); ++ ++ for ( auto i = 0u; i < ARRAY_SIZE(ctx->pictures); ++i ) ++ { ++ auto pic = picture_NewFromFormat(fmt); ++ if ( pic != nullptr ) ++ pic->context = webcodec_CreatePictureContext( vctx, i ); ++ if ( pic == nullptr || pic->context == nullptr ) ++ { ++ while ( i-- ) ++ picture_Release( ctx->pictures[i] ); ++ return VLC_EGENERIC; ++ } ++ ctx->pictures[i] = pic; ++ } ++ ctx->pool = picture_pool_New(ARRAY_SIZE(ctx->pictures), ctx->pictures); ++ if ( ctx->pool == nullptr ) ++ { ++ for ( auto i = 0u; i < ARRAY_SIZE(ctx->pictures); ++i ) ++ picture_Release( ctx->pictures[i] ); ++ return VLC_EGENERIC; ++ } ++ return VLC_SUCCESS; ++} +diff --git a/modules/video_output/emscripten/common.h b/modules/video_output/emscripten/common.h +index 3a2d3c9099..08967d0703 100644 +--- a/modules/video_output/emscripten/common.h ++++ b/modules/video_output/emscripten/common.h +@@ -27,6 +27,7 @@ + + #include <vlc_threads.h> + #include <vlc_picture.h> ++#include <vlc_picture_pool.h> + + #include <emscripten/html5_webgl.h> + //#include <emscripten/val.h> +@@ -36,11 +37,21 @@ + struct webcodec_context + { + pthread_t decoder_worker; ++ picture_pool_t* pool; ++ picture_t* pictures[WEBCODEC_MAX_PICTURES]; + }; + +-struct webcodec_picture_sys_t ++struct webcodec_picture_ctx + { +- int pictureId; ++ picture_context_t context; ++ uint32_t pictureIdx; + }; + ++int webcodec_CreatePool( vlc_video_context* vctx, const video_format_t* fmt ); ++ ++static inline webcodec_picture_ctx* PictureContextPrivate( picture_context_t* picCtx ) ++{ ++ return container_of( picCtx, webcodec_picture_ctx, context ); ++} ++ + #endif // EMSCRIPTEN_COMMON_H +-- +2.35.1 + diff --git a/vlc_patches/aug/0044-emscripten-webcodec-Use-picture-pool.patch b/vlc_patches/aug/0044-emscripten-webcodec-Use-picture-pool.patch new file mode 100644 index 0000000000000000000000000000000000000000..bd7a54eae1c61cde52950d20b6f960815b17a1a7 --- /dev/null +++ b/vlc_patches/aug/0044-emscripten-webcodec-Use-picture-pool.patch @@ -0,0 +1,104 @@ +From ec7c16fc7ae005b33f95742e0a33bba3ab6e19c9 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr> +Date: Tue, 3 May 2022 11:12:13 +0200 +Subject: [PATCH 44/77] emscripten: webcodec: Use picture pool + +--- + modules/codec/Makefile.am | 2 +- + modules/codec/webcodec.cpp | 30 ++++++++++++++++-------------- + 2 files changed, 17 insertions(+), 15 deletions(-) + +diff --git a/modules/codec/Makefile.am b/modules/codec/Makefile.am +index 31ec138bd0..59079ebce6 100644 +--- a/modules/codec/Makefile.am ++++ b/modules/codec/Makefile.am +@@ -645,7 +645,7 @@ libvlc_vtutils_la_SOURCES = codec/vt_utils.c codec/vt_utils.h + libvlc_vtutils_la_LDFLAGS = -static -no-undefined + EXTRA_LTLIBRARIES += libvlc_vtutils.la + +-libwebcodec_plugin_la_SOURCES = codec/webcodec.cpp ++libwebcodec_plugin_la_SOURCES = codec/webcodec.cpp video_output/emscripten/common.cpp + libwebcodec_plugin_la_LDFLAGS = $(AM_LDFLAGS) + if HAVE_EMSCRIPTEN + codec_LTLIBRARIES += libwebcodec_plugin.la +diff --git a/modules/codec/webcodec.cpp b/modules/codec/webcodec.cpp +index a75d3f7897..cd902851b0 100644 +--- a/modules/codec/webcodec.cpp ++++ b/modules/codec/webcodec.cpp +@@ -59,24 +59,23 @@ struct decoder_sys_t + extern "C" + { + +-EMSCRIPTEN_KEEPALIVE picture_t* createAndQueuePicture(decoder_t* dec, int pictureId, +- int64_t timestamp) ++EMSCRIPTEN_KEEPALIVE int32_t createAndQueuePicture(decoder_t* dec, int64_t timestamp) + { + auto sys = static_cast<decoder_sys_t*>( dec->p_sys ); + if ( decoder_UpdateVideoOutput( dec, sys->vctx ) ) + { + msg_Err( dec, "Failure during UpdateVideoOutput! FIXME" ); +- return NULL; ++ return -1; + } +- +- auto pic = decoder_NewPicture(dec); ++ auto vctx = static_cast<webcodec_context*>( ++ vlc_video_context_GetPrivate(sys->vctx, VLC_VIDEO_CONTEXT_WEBCODEC) ); ++ auto pic = picture_pool_Wait(vctx->pool); + if (pic == nullptr) +- return nullptr; ++ return -1; + pic->date = VLC_TICK_FROM_US(timestamp); + pic->b_progressive = true; +- pic->p_sys = reinterpret_cast<void*>( static_cast<uintptr_t>( pictureId ) ); + decoder_QueueVideo(dec, pic); +- return pic; ++ return PictureContextPrivate(pic->context)->pictureIdx; + } + + } +@@ -102,18 +101,15 @@ EM_ASYNC_JS(void, declareCallbacks, (), { + await p; + } + +- Module.pictureId = 0; + globalThis.Module.boundOutputCb = async function(frame) { +- Module.pictureId = (Module.pictureId + 1); +- _createAndQueuePicture(globalThis.Module.webCodecCtx, Module.pictureId, +- frame.timestamp); ++ let picIdx = _createAndQueuePicture(globalThis.Module.webCodecCtx, frame.timestamp); + if (Module.voutPort === undefined) + await getVoutMessagePort(); +- console.log('Posting frame', Module.pictureId); ++ console.log('Posting frame', picIdx); + Module.voutPort.postMessage({ + customCmd: 'displayFrame', + frame: frame, +- pictureId: Module.pictureId, ++ pictureId: picIdx, + }, [frame]); + }; + globalThis.Module.boundErrorCb = function(err) { +@@ -346,6 +342,9 @@ static int Open( vlc_object_t* obj ) + vlc_video_context_GetPrivate(sys->vctx, VLC_VIDEO_CONTEXT_WEBCODEC)); + new (vctxPrivate) webcodec_context(); + ++ if ( webcodec_CreatePool(sys->vctx, &dec->fmt_out.video) != VLC_SUCCESS ) ++ return VLC_EGENERIC; ++ + if ( vlc_clone( &sys->th, &WebcodecDecodeWorker, dec, 0 ) != VLC_SUCCESS ) + { + msg_Err( obj, "Failed to create webcodec thread" ); +@@ -363,6 +362,9 @@ static void Close( decoder_t* dec ) + { + auto sys = static_cast<decoder_sys_t*>( dec->p_sys ); + sys->decoder.call<void>("close"); ++ auto vctx = static_cast<webcodec_context*>( ++ vlc_video_context_GetPrivate(sys->vctx, VLC_VIDEO_CONTEXT_WEBCODEC ) ); ++ picture_pool_Release(vctx->pool); + delete sys; + } + +-- +2.35.1 + diff --git a/vlc_patches/aug/0045-emscripten-converter-Use-picture-pool-untested.patch b/vlc_patches/aug/0045-emscripten-converter-Use-picture-pool-untested.patch new file mode 100644 index 0000000000000000000000000000000000000000..7f591edf3f33d4d9d48de17fe0246818e348721d --- /dev/null +++ b/vlc_patches/aug/0045-emscripten-converter-Use-picture-pool-untested.patch @@ -0,0 +1,109 @@ +From e3696d52278c6fa8bdf780a2ec927374982b8300 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr> +Date: Tue, 3 May 2022 11:12:29 +0200 +Subject: [PATCH 45/77] emscripten: converter: Use picture pool (untested) + +--- + modules/hw/emscripten/Makefile.am | 2 +- + modules/hw/emscripten/converter.cpp | 44 ++++++++++++++++------------- + 2 files changed, 26 insertions(+), 20 deletions(-) + +diff --git a/modules/hw/emscripten/Makefile.am b/modules/hw/emscripten/Makefile.am +index 0402b1438b..3f2a118bc1 100644 +--- a/modules/hw/emscripten/Makefile.am ++++ b/modules/hw/emscripten/Makefile.am +@@ -1,6 +1,6 @@ + emscriptendir = $(pluginsdir)/emscripten + +-libglconverter_emscripten_plugin_la_SOURCES = hw/emscripten/converter.cpp ++libglconverter_emscripten_plugin_la_SOURCES = hw/emscripten/converter.cpp video_output/emscripten/common.cpp + libglconverter_emscripten_plugin_la_CPPFLAGS = $(AM_CPPFLAGS) + libglconverter_emscripten_plugin_la_CXXFLAGS = $(AM_CXXFLAGS) + libglconverter_emscripten_plugin_la_LIBADD = $(AM_LIBADD) +diff --git a/modules/hw/emscripten/converter.cpp b/modules/hw/emscripten/converter.cpp +index 623fc69919..edc8d93bc0 100644 +--- a/modules/hw/emscripten/converter.cpp ++++ b/modules/hw/emscripten/converter.cpp +@@ -26,6 +26,8 @@ + #include <vlc_filter.h> + #include <vlc_plugin.h> + ++#include "../../video_output/emscripten/common.h" ++ + #include <emscripten/emscripten.h> + #include <emscripten/em_js.h> + #include <emscripten/val.h> +@@ -36,6 +38,15 @@ struct chroma_sys_t + + static picture_t* UploadSurface(filter_t* filter, picture_t* src) + { ++ auto vctx = static_cast<webcodec_context*>( ++ vlc_video_context_GetPrivate( filter->vctx_out, VLC_VIDEO_CONTEXT_WEBCODEC) ); ++ auto dst = picture_pool_Wait( vctx->pool ); ++ if (dst == nullptr) ++ { ++ picture_Release(src); ++ return nullptr; ++ } ++ + auto size = 0u; + for ( auto i = 0; i < src->i_planes; ++i ) + size += src->p[i].i_lines * src->p[i].i_pitch; +@@ -71,23 +82,17 @@ static picture_t* UploadSurface(filter_t* filter, picture_t* src) + init.set( "visibleRect", visibleRect ); + + auto frame = emscripten::val::global("VideoFrame").new_( buffer, init ); +- uintptr_t pictureId = EM_ASM_INT({return Module['pictureId']}); ++ auto pictureIdx = PictureContextPrivate(dst->context)->pictureIdx; + EM_ASM({ +- let frame = Emval.toValue($0); +- Module.msgChannel.port2.postMessage({ +- customCmd: 'displayFrame', +- pictureId: Module.pictureId, +- frame: frame +- }); +- Module.pictureId = Module.pictureId + 1; +- }, frame.as_handle()); +- auto dst = filter_NewPicture( filter ); +- if (dst == nullptr) +- { +- picture_Release(src); +- return nullptr; +- } +- dst->p_sys = reinterpret_cast<void*>( pictureId ); ++ let frame = Emval.toValue($0); ++ Module.msgChannel.port2.postMessage({ ++ customCmd: 'displayFrame', ++ pictureId: $1, ++ frame: frame ++ }); ++ }, frame.as_handle(), pictureIdx); ++ ++ + picture_CopyProperties( dst, src ); + picture_Release(src); + return dst; +@@ -95,7 +100,9 @@ static picture_t* UploadSurface(filter_t* filter, picture_t* src) + + static void vlc_webcodec_CloseChroma(filter_t* filter) + { +- ++ auto vctx = static_cast<webcodec_context*>( ++ vlc_video_context_GetPrivate(filter->vctx_out, VLC_VIDEO_CONTEXT_WEBCODEC ) ); ++ picture_pool_Release(vctx->pool); + } + + static const struct vlc_filter_operations filter_ops = { +@@ -117,8 +124,7 @@ static int vlc_webcodec_OpenChroma(filter_t* filter) + return VLC_EGENERIC; + + filter->ops = &filter_ops; +- EM_ASM({Module.pictureId = 0;}); +- return VLC_SUCCESS; ++ return webcodec_CreatePool(filter->vctx_out, &filter->fmt_out.video); + } + + vlc_module_begin() +-- +2.35.1 + diff --git a/vlc_patches/aug/0046-emscripten-interop-Use-picture-context.patch b/vlc_patches/aug/0046-emscripten-interop-Use-picture-context.patch new file mode 100644 index 0000000000000000000000000000000000000000..1bc7323a404146e3946d915e7bb031b066f1d37f --- /dev/null +++ b/vlc_patches/aug/0046-emscripten-interop-Use-picture-context.patch @@ -0,0 +1,109 @@ +From a56c006fa9ff4108421ecce21684a63a2d723088 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr> +Date: Tue, 3 May 2022 11:12:45 +0200 +Subject: [PATCH 46/77] emscripten: interop: Use picture context + +Instead of a the Module.pictureId global +--- + .../opengl/interop_emscripten.cpp | 37 +++++++++---------- + 1 file changed, 18 insertions(+), 19 deletions(-) + +diff --git a/modules/video_output/opengl/interop_emscripten.cpp b/modules/video_output/opengl/interop_emscripten.cpp +index 53b52fabd6..b9a9a5087d 100644 +--- a/modules/video_output/opengl/interop_emscripten.cpp ++++ b/modules/video_output/opengl/interop_emscripten.cpp +@@ -58,9 +58,10 @@ tc_emscripten_op_allocate_textures(const struct vlc_gl_interop *interop, GLuint + return VLC_SUCCESS; + } + +-EM_ASYNC_JS(void, bindVideoFrame, (int pictureId), { +- let frame = await Module.awaitFrame(pictureId); ++EM_ASYNC_JS(void, bindVideoFrame, (int pictureIdx), { ++ let frame = await Module.awaitFrame(pictureIdx); + ++ console.log('Binding texture for picture at index ' + pictureIdx); + let glCtx = Module.glCtx; + glCtx.texImage2D(glCtx.TEXTURE_2D, 0, glCtx.RGBA, frame.codedWidth, frame.codedHeight, 0, + glCtx.RGBA, glCtx.UNSIGNED_BYTE, frame); +@@ -75,8 +76,8 @@ tc_emscripten_op_update(const struct vlc_gl_interop *interop, GLuint *textures, + + sys->gl.BindTexture(interop->tex_target, textures[0]); + +- auto pictureId = reinterpret_cast<uintptr_t>( pic->p_sys ); +- bindVideoFrame(pictureId); ++ auto picCtx = PictureContextPrivate(pic->context); ++ bindVideoFrame(picCtx->pictureIdx); + + return VLC_SUCCESS; + } +@@ -97,17 +98,16 @@ EM_JS(void, transferMessagePort, (vlc_video_context* vctx), { + function onDecoderMessage(msg) { + let data = msg['data']; + if (data.customCmd == 'displayFrame') { +- let pictureId = data.pictureId; +- let pictureIdx = pictureId % 32; ++ let pictureIdx = data.pictureId; + let frame = data['frame']; +- console.log('SINK Received frame ' + pictureId ); ++ console.log('SINK Received frame with index ' + pictureIdx ); + if ( Module.glConv.promiseResolvers[pictureIdx] ) { + /* The interop is already waiting for this frame, resolve the promise */ +- console.log('SINK Renderer is already waiting for frame ' + pictureId ); ++ console.log('SINK Renderer is already waiting for frame ' + pictureIdx ); + Module.glConv.promiseResolvers[pictureIdx]( frame ); + } else { + /* The frame hasn't arrived to the interop yet, queue it */ +- console.log('SINK No resolver, queuing frame ' + pictureId ); ++ console.log('SINK No resolver, queuing frame ' + pictureIdx ); + Module.glConv.frameQueue[pictureIdx] = frame; + } + } +@@ -129,36 +129,35 @@ EM_JS(void, initGlConvWorker, (int maxPictures), { + Module.glConv.promiseResolvers = []; + Module.glConv.frameQueue = []; + Module.glConv.lastFrame = { +- pictureId: -1, ++ pictureIdx: -1, + frame: undefined + }; + +- Module.awaitFrame = async function(pictureId) { +- console.log('CONSUME Fetching frame ' + pictureId); +- if (Module.glConv.lastFrame.pictureId == pictureId) { ++ Module.awaitFrame = async function(pictureIndex) { ++ console.log('CONSUME Fetching frame ' + pictureIndex); ++ if (Module.glConv.lastFrame.pictureIdx == pictureIndex) { + console.log('Reusing last frame'); + return Module.glConv.lastFrame.frame; + } +- let pictureIndex = pictureId % 32; + let p = new Promise((resolve, reject) => { + if ( Module.glConv.frameQueue[pictureIndex] ) { +- console.log('CONSUME Frame ', pictureId, '(index ', pictureIndex, +- ') was already queued'); ++ console.log('CONSUME Frame at index ', pictureIndex, ++ ' was already queued'); + let frame = Module.glConv.frameQueue[pictureIndex]; + resolve(frame); + Module.glConv.frameQueue[pictureIndex] = undefined; + } else { +- console.log('CONSUME Frame ', pictureId, '(index ', pictureIndex, ++ console.log('CONSUME Frame at index ', pictureIndex, + ') not available yet; exposing resolver' ); + Module.glConv.promiseResolvers[pictureIndex] = resolve; + } + }); + let frame = await p; +- Module.glConv.promiseResolvers[pictureId] = undefined; ++ Module.glConv.promiseResolvers[pictureIndex] = undefined; + if (Module.glConv.lastFrame.frame) + Module.glConv.lastFrame.frame.close(); + Module.glConv.lastFrame.frame = frame; +- Module.glConv.lastFrame.pictureId = pictureId; ++ Module.glConv.lastFrame.pictureIdx = pictureIndex; + console.log('CONSUME got frame'); + return frame; + } +-- +2.35.1 + diff --git a/vlc_patches/aug/0047-emscripten-vout-Fix-memory-leak.patch b/vlc_patches/aug/0047-emscripten-vout-Fix-memory-leak.patch new file mode 100644 index 0000000000000000000000000000000000000000..3c12904e94bf22f6e136d74f513405f3f464dde4 --- /dev/null +++ b/vlc_patches/aug/0047-emscripten-vout-Fix-memory-leak.patch @@ -0,0 +1,27 @@ +From ef16d725f44538a175ae1fff23b268375f98dc72 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr> +Date: Fri, 6 May 2022 08:22:17 +0200 +Subject: [PATCH 47/77] emscripten: vout: Fix memory leak + +--- + modules/video_output/emscripten.cpp | 4 +++- + 1 file changed, 3 insertions(+), 1 deletion(-) + +diff --git a/modules/video_output/emscripten.cpp b/modules/video_output/emscripten.cpp +index 0c863132f6..9fd34350b5 100644 +--- a/modules/video_output/emscripten.cpp ++++ b/modules/video_output/emscripten.cpp +@@ -183,7 +183,9 @@ static int Open (vlc_gl_t *gl, unsigned width, unsigned height) + function onVoutMessage(msg) { + let data = msg['data']; + if (data.customCmd == 'commitFrame') { +- Module.canvasCtx.drawImage(data['bitmap'], 0, 0); ++ let bmp = data['bitmap']; ++ Module.canvasCtx.drawImage(bmp, 0, 0); ++ bmp.close(); + } + } + let w = Module.PThread.pthreads[$0].worker; +-- +2.35.1 + diff --git a/vlc_patches/aug/0048-emscripten-interop-Query-the-frame-to-the-decoder-fr.patch b/vlc_patches/aug/0048-emscripten-interop-Query-the-frame-to-the-decoder-fr.patch new file mode 100644 index 0000000000000000000000000000000000000000..fbd95ceb03b4fb7c83dbb0013c87c9e587d3130a --- /dev/null +++ b/vlc_patches/aug/0048-emscripten-interop-Query-the-frame-to-the-decoder-fr.patch @@ -0,0 +1,157 @@ +From 7e0e3b2c1a0acbd4cebae963a465cd22053f9cde Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr> +Date: Mon, 9 May 2022 10:15:45 +0200 +Subject: [PATCH 48/77] emscripten: interop: Query the frame to the decoder + from the interop + +Instead of maintaining a 2nd queue in parallel with the one from the +vout +--- + .../opengl/interop_emscripten.cpp | 63 ++++++++----------- + 1 file changed, 25 insertions(+), 38 deletions(-) + +diff --git a/modules/video_output/opengl/interop_emscripten.cpp b/modules/video_output/opengl/interop_emscripten.cpp +index b9a9a5087d..65d4c84f94 100644 +--- a/modules/video_output/opengl/interop_emscripten.cpp ++++ b/modules/video_output/opengl/interop_emscripten.cpp +@@ -61,7 +61,6 @@ tc_emscripten_op_allocate_textures(const struct vlc_gl_interop *interop, GLuint + EM_ASYNC_JS(void, bindVideoFrame, (int pictureIdx), { + let frame = await Module.awaitFrame(pictureIdx); + +- console.log('Binding texture for picture at index ' + pictureIdx); + let glCtx = Module.glCtx; + glCtx.texImage2D(glCtx.TEXTURE_2D, 0, glCtx.RGBA, frame.codedWidth, frame.codedHeight, 0, + glCtx.RGBA, glCtx.UNSIGNED_BYTE, frame); +@@ -94,22 +93,13 @@ EMSCRIPTEN_KEEPALIVE int getDecoderWorker(vlc_video_context* vctx) + } + } + +-EM_JS(void, transferMessagePort, (vlc_video_context* vctx), { ++EM_JS(void, setupMessagePort, (vlc_video_context* vctx), { + function onDecoderMessage(msg) { + let data = msg['data']; + if (data.customCmd == 'displayFrame') { + let pictureIdx = data.pictureId; + let frame = data['frame']; +- console.log('SINK Received frame with index ' + pictureIdx ); +- if ( Module.glConv.promiseResolvers[pictureIdx] ) { +- /* The interop is already waiting for this frame, resolve the promise */ +- console.log('SINK Renderer is already waiting for frame ' + pictureIdx ); +- Module.glConv.promiseResolvers[pictureIdx]( frame ); +- } else { +- /* The frame hasn't arrived to the interop yet, queue it */ +- console.log('SINK No resolver, queuing frame ' + pictureIdx ); +- Module.glConv.frameQueue[pictureIdx] = frame; +- } ++ Module.glConv.frameResolver( frame ); + } + }; + Module.msgChannel = new MessageChannel(); +@@ -126,39 +116,35 @@ EM_JS(void, transferMessagePort, (vlc_video_context* vctx), { + + EM_JS(void, initGlConvWorker, (int maxPictures), { + Module.glConv = {}; +- Module.glConv.promiseResolvers = []; +- Module.glConv.frameQueue = []; + Module.glConv.lastFrame = { + pictureIdx: -1, + frame: undefined + }; + +- Module.awaitFrame = async function(pictureIndex) { +- console.log('CONSUME Fetching frame ' + pictureIndex); +- if (Module.glConv.lastFrame.pictureIdx == pictureIndex) { +- console.log('Reusing last frame'); ++ Module.awaitFrame = async function(pictureIdx) { ++ if (Module.glConv.lastFrame.pictureIdx == pictureIdx) { + return Module.glConv.lastFrame.frame; + } + let p = new Promise((resolve, reject) => { +- if ( Module.glConv.frameQueue[pictureIndex] ) { +- console.log('CONSUME Frame at index ', pictureIndex, +- ' was already queued'); +- let frame = Module.glConv.frameQueue[pictureIndex]; +- resolve(frame); +- Module.glConv.frameQueue[pictureIndex] = undefined; +- } else { +- console.log('CONSUME Frame at index ', pictureIndex, +- ') not available yet; exposing resolver' ); +- Module.glConv.promiseResolvers[pictureIndex] = resolve; +- } ++ if (Module.glConv.frameResolver) ++ { ++ alert('Duplicated interop promise'); ++ } ++ Module.glConv.frameResolver = resolve; ++ Module.msgChannel.port1.postMessage({ ++ 'customCmd': 'sendFrame', ++ 'pictureIdx': pictureIdx, ++ }); ++ + }); ++ console.log('Waiting for frame ', pictureIdx, ' in interop'); + let frame = await p; +- Module.glConv.promiseResolvers[pictureIndex] = undefined; ++ Module.glConv.frameResolver = undefined; ++ console.log('Received frame ', pictureIdx, ' in interop'); + if (Module.glConv.lastFrame.frame) + Module.glConv.lastFrame.frame.close(); + Module.glConv.lastFrame.frame = frame; +- Module.glConv.lastFrame.pictureIdx = pictureIndex; +- console.log('CONSUME got frame'); ++ Module.glConv.lastFrame.pictureIdx = pictureIdx; + return frame; + } + }) +@@ -198,7 +184,7 @@ Open(vlc_object_t *obj) + }; + interop->ops = &ops; + initGlConvWorker(WEBCODEC_MAX_PICTURES); +- transferMessagePort(interop->vctx); ++ setupMessagePort(interop->vctx); + + interop->tex_target = GL_TEXTURE_2D; + interop->fmt_out.i_chroma = VLC_CODEC_RGBA; +@@ -221,9 +207,9 @@ Open(vlc_object_t *obj) + return VLC_SUCCESS; + } + +-EM_ASYNC_JS(void, CopyFrameToBuffer, (int pictureId, emscripten::EM_VAL infoHandle), { ++EM_ASYNC_JS(void, CopyFrameToBuffer, (int pictureIdx, emscripten::EM_VAL infoHandle), { + let info = Emval.toValue(infoHandle); +- let frame = await Module.awaitFrame(pictureId); ++ let frame = await Module.awaitFrame(pictureIdx); + let copyOpts = { + rect: frame.codedRect, + layout: info.layout +@@ -239,7 +225,8 @@ static picture_t* vlc_webcodec_UploadVideoFrame(filter_t* filter, picture_t* src + picture_Release(src); + return nullptr; + } +- auto pictureId = reinterpret_cast<uintptr_t>(src->p_sys); ++ ++ auto pictureIdx = PictureContextPrivate(src->context)->pictureIdx; + + size_t picSize = 0; + auto info = emscripten::val::object(); +@@ -257,7 +244,7 @@ static picture_t* vlc_webcodec_UploadVideoFrame(filter_t* filter, picture_t* src + info.set( "buffer", emscripten::typed_memory_view( picSize, pic->p[0].p_pixels ) ); + info.set( "layout", std::move( planesLayout ) ); + +- CopyFrameToBuffer( pictureId, info.as_handle() ); ++ CopyFrameToBuffer( pictureIdx, info.as_handle() ); + + pic->date = src->date; + picture_Release(src); +@@ -285,7 +272,7 @@ static int OpenConverter(filter_t* filter) + return VLC_EGENERIC; + } + initGlConvWorker(WEBCODEC_MAX_PICTURES); +- transferMessagePort(filter->vctx_in); ++ setupMessagePort(filter->vctx_in); + + filter->ops = &video_frame_to_sw_ops; + +-- +2.35.1 + diff --git a/vlc_patches/aug/0049-emscripten-webcodec-Simplify-initialization-and-adap.patch b/vlc_patches/aug/0049-emscripten-webcodec-Simplify-initialization-and-adap.patch new file mode 100644 index 0000000000000000000000000000000000000000..cab7bcf0c8f8dc2b3f4e73c9658ef170e82da1ab --- /dev/null +++ b/vlc_patches/aug/0049-emscripten-webcodec-Simplify-initialization-and-adap.patch @@ -0,0 +1,253 @@ +From 996deba93b85bc9a9bf7cdb12a35d4628d08d897 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr> +Date: Mon, 9 May 2022 10:16:29 +0200 +Subject: [PATCH 49/77] emscripten: webcodec: Simplify initialization and adapt + to new queue + +--- + modules/codec/webcodec.cpp | 183 +++++++++++++++++++++++-------------- + 1 file changed, 112 insertions(+), 71 deletions(-) + +diff --git a/modules/codec/webcodec.cpp b/modules/codec/webcodec.cpp +index cd902851b0..92b23d2b94 100644 +--- a/modules/codec/webcodec.cpp ++++ b/modules/codec/webcodec.cpp +@@ -58,20 +58,28 @@ struct decoder_sys_t + + extern "C" + { ++EMSCRIPTEN_KEEPALIVE picture_t* tryGetPictureFromPool(decoder_t* dec) ++{ ++ auto sys = static_cast<decoder_sys_t*>( dec->p_sys ); ++ auto vctx = static_cast<webcodec_context*>( ++ vlc_video_context_GetPrivate(sys->vctx, VLC_VIDEO_CONTEXT_WEBCODEC) ); ++ return picture_pool_Get(vctx->pool); ++} + +-EMSCRIPTEN_KEEPALIVE int32_t createAndQueuePicture(decoder_t* dec, int64_t timestamp) ++EMSCRIPTEN_KEEPALIVE bool updateVideoOutput(decoder_t* dec) + { + auto sys = static_cast<decoder_sys_t*>( dec->p_sys ); + if ( decoder_UpdateVideoOutput( dec, sys->vctx ) ) + { + msg_Err( dec, "Failure during UpdateVideoOutput! FIXME" ); +- return -1; ++ return false; + } +- auto vctx = static_cast<webcodec_context*>( +- vlc_video_context_GetPrivate(sys->vctx, VLC_VIDEO_CONTEXT_WEBCODEC) ); +- auto pic = picture_pool_Wait(vctx->pool); +- if (pic == nullptr) +- return -1; ++ return true; ++} ++ ++EMSCRIPTEN_KEEPALIVE int32_t queuePicture(void* ctx, picture_t* pic, int64_t timestamp) ++{ ++ auto dec = static_cast<decoder_t*>(ctx); + pic->date = VLC_TICK_FROM_US(timestamp); + pic->b_progressive = true; + decoder_QueueVideo(dec, pic); +@@ -80,44 +88,6 @@ EMSCRIPTEN_KEEPALIVE int32_t createAndQueuePicture(decoder_t* dec, int64_t times + + } + +-EM_JS(void, initModuleContext, (void* ctx), { +- globalThis.Module.webCodecCtx = ctx; +-}); +- +-EM_ASYNC_JS(void, declareCallbacks, (), { +- async function getVoutMessagePort() { +- let p = new Promise((resolve, reject) => { +- self.addEventListener('message', function(e) { +- let msg = e['data']; +- if (msg.customCmd == 'transferMessagePort') { +- let port = msg['transferList'][0]; +- if (!port) +- reject(); +- Module.voutPort = port; +- resolve(); +- } +- }); +- }); +- await p; +- } +- +- globalThis.Module.boundOutputCb = async function(frame) { +- let picIdx = _createAndQueuePicture(globalThis.Module.webCodecCtx, frame.timestamp); +- if (Module.voutPort === undefined) +- await getVoutMessagePort(); +- console.log('Posting frame', picIdx); +- Module.voutPort.postMessage({ +- customCmd: 'displayFrame', +- frame: frame, +- pictureId: picIdx, +- }, [frame]); +- }; +- globalThis.Module.boundErrorCb = function(err) { +- console.log('Error while decoding: '); +- console.log(err); +- }; +-}); +- + EM_ASYNC_JS(bool, probeConfig, (emscripten::EM_VAL cfg), { + var decoderCfg = Emval.toValue(cfg); + var res = await VideoDecoder.isConfigSupported(decoderCfg).catch((err) => { +@@ -183,7 +153,6 @@ static emval getDecoderConfig( decoder_t* dec, bool includeExtraData ) + decoderConfig.set( "optimizeForLatency", true ); + if ( includeExtraData ) + { +- msg_Err( dec, "i_extra: %u", dec->fmt_in.i_extra ); + if ( dec->fmt_in.i_extra > 0 ) + { + decoderConfig.set( "description", +@@ -196,32 +165,100 @@ static emval getDecoderConfig( decoder_t* dec, bool includeExtraData ) + return decoderConfig; + } + +-static bool initDecoder( decoder_t* dec ) +-{ +- auto sys = static_cast<decoder_sys_t*>( dec->p_sys ); +- initModuleContext(dec); +- declareCallbacks(); +- +- auto initCfg = emval::object(); ++EM_JS(emscripten::EM_VAL, initDecoderJS, (void* decoder), { ++ function onInteropMessage(msg) { ++ let data = msg['data']; ++ if (data['customCmd'] == 'sendFrame') { ++ let picIdx = data['pictureIdx']; ++ let frame = Module.framesReady[picIdx]; ++ Module.framesReady[picIdx] = undefined; ++ console.log('Posting frame', picIdx, 'from webcodec', frame); ++ Module.voutPort.postMessage({ ++ customCmd: 'displayFrame', ++ frame: frame, ++ pictureId: picIdx, ++ }, [frame]); ++ } ++ } + +- auto outputCb = emval::module_property("boundOutputCb"); +- if ( outputCb.isUndefined() ) +- { +- msg_Err( dec, "Failed to find output callback" ); +- return false; ++ async function getVoutMessagePort() { ++ let p = new Promise((resolve, reject) => { ++ self.addEventListener('message', function(e) { ++ let msg = e['data']; ++ if (msg.customCmd == 'transferMessagePort') { ++ let port = msg['transferList'][0]; ++ if (!port) ++ reject(); ++ Module.voutPort = port; ++ Module.voutPort.onmessage = onInteropMessage; ++ resolve(); ++ } ++ }); ++ }); ++ await p; + } +- initCfg.set("output", outputCb); + +- auto errorCb = emval::module_property("boundErrorCb"); +- if ( errorCb.isUndefined() ) +- { +- msg_Err( dec, "Failed to find error callback" ); +- return false; ++ async function getPictureAsync(dec) { ++ function getPicture(dec, resolve) { ++ let pic = _tryGetPictureFromPool(dec); ++ if (!pic) { ++ setTimeout( function(d, r) { ++ getPicture(d, r); ++ }, 1, dec, resolve ); ++ return; ++ } ++ resolve(pic); ++ } ++ let p = new Promise((resolve) => { ++ getPicture(dec, resolve); ++ }); ++ return await p; + } +- initCfg.set("error", errorCb); + +- auto decoderType = emval::global("VideoDecoder"); +- sys->decoder = decoderType.new_(initCfg); ++ /* Store a handle to our decoder_t for later invocations */ ++ Module.webCodecCtx = decoder; ++ ++ /* Prepare an array containing the decoded frames, so the interop can query ++ * then later */ ++ Module.framesReady = []; ++ ++ let initCfg = { ++ 'output': async function (frame) { ++ /* Always update the video output format so we can ensure the vout ++ * is created when we try to acquire its message port */ ++ if ( !_updateVideoOutput(Module.webCodecCtx) ) ++ { ++ frame.close(); ++ return; ++ } ++ let p = await getPictureAsync(Module.webCodecCtx); ++ let picIdx = _queuePicture(Module.webCodecCtx, p, frame.timestamp); ++ if (Module.voutPort === undefined) ++ await getVoutMessagePort(); ++ if ( Module.framesReady[picIdx] ) { ++ /* ++ * If we end up overriding a frame, it means it was dropped ++ * by the vout before the interop asked for it, so we can close ++ * it safely. ++ */ ++ console.log('Discarding dropped frame ', picIdx); ++ Module.framesReady[picIdx].close(); ++ } ++ Module.framesReady[picIdx] = frame; ++ }, ++ 'error': function(err) { ++ console.log('Error while decoding: '); ++ console.log(err); ++ } ++ }; ++ return Emval.toHandle( new VideoDecoder( initCfg ) ); ++}); ++ ++static bool initDecoder( decoder_t* dec ) ++{ ++ auto sys = static_cast<decoder_sys_t*>( dec->p_sys ); ++ ++ sys->decoder = emval::take_ownership( initDecoderJS( dec ) ); + if ( sys->decoder.isUndefined() ) + { + msg_Err( dec, "Failed to instantiate VideoDecoder" ); +@@ -247,12 +284,12 @@ static void WebcodecDecodeWorkerTick( void* arg ) + + auto chunk = blockToEncodedVideoChunk( dec, block ); + block_Release(block); +- msg_Err(dec, "Decoding a sample..."); + sys->decoder.call<void>( "decode", chunk ); + +- auto queueSize = sys->decoder["decodeQueueSize"]; +- auto state = sys->decoder["state"]; +- msg_Err( dec, "Decoder state: %s ; queue size: %ld", state.as<std::string>().c_str(), queueSize.as<long int>()); ++// auto queueSize = sys->decoder["decodeQueueSize"]; ++// auto state = sys->decoder["state"]; ++// msg_Err( dec, "Decoder state: %s ; queue size: %ld", ++// state.as<std::string>().c_str(), queueSize.as<long int>()); + } + } + +@@ -392,3 +429,7 @@ vlc_module_begin () + add_submodule() + set_callback_dec_device(OpenDecDevice, 1) + vlc_module_end () ++ ++EMSCRIPTEN_BINDINGS(vlc_picture) { ++ emscripten::class_<picture_t>("picture_t"); ++} +-- +2.35.1 + diff --git a/vlc_patches/aug/0050-webcodec-Remove-unused-binding.patch b/vlc_patches/aug/0050-webcodec-Remove-unused-binding.patch new file mode 100644 index 0000000000000000000000000000000000000000..704543700d76b400d1ca3c16c92fa810fde1254a --- /dev/null +++ b/vlc_patches/aug/0050-webcodec-Remove-unused-binding.patch @@ -0,0 +1,24 @@ +From 0cc2af768d3d7f01460b092bed2bdb37f72b8a46 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr> +Date: Mon, 9 May 2022 11:58:08 +0200 +Subject: [PATCH 50/77] webcodec: Remove unused binding + +--- + modules/codec/webcodec.cpp | 4 ---- + 1 file changed, 4 deletions(-) + +diff --git a/modules/codec/webcodec.cpp b/modules/codec/webcodec.cpp +index 92b23d2b94..d44e9ee5ae 100644 +--- a/modules/codec/webcodec.cpp ++++ b/modules/codec/webcodec.cpp +@@ -429,7 +429,3 @@ vlc_module_begin () + add_submodule() + set_callback_dec_device(OpenDecDevice, 1) + vlc_module_end () +- +-EMSCRIPTEN_BINDINGS(vlc_picture) { +- emscripten::class_<picture_t>("picture_t"); +-} +-- +2.35.1 + diff --git a/vlc_patches/aug/0051-interop-remove-debug.patch b/vlc_patches/aug/0051-interop-remove-debug.patch new file mode 100644 index 0000000000000000000000000000000000000000..0994e3b616c0ff1f7b863ff3d61e15907e0dfe72 --- /dev/null +++ b/vlc_patches/aug/0051-interop-remove-debug.patch @@ -0,0 +1,38 @@ +From 0d2dfae2f2894a30d673c608813f7c7e84da3dc3 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr> +Date: Mon, 9 May 2022 11:58:15 +0200 +Subject: [PATCH 51/77] interop: remove debug + +--- + modules/video_output/opengl/interop_emscripten.cpp | 6 ------ + 1 file changed, 6 deletions(-) + +diff --git a/modules/video_output/opengl/interop_emscripten.cpp b/modules/video_output/opengl/interop_emscripten.cpp +index 65d4c84f94..e9905ac1a3 100644 +--- a/modules/video_output/opengl/interop_emscripten.cpp ++++ b/modules/video_output/opengl/interop_emscripten.cpp +@@ -126,10 +126,6 @@ EM_JS(void, initGlConvWorker, (int maxPictures), { + return Module.glConv.lastFrame.frame; + } + let p = new Promise((resolve, reject) => { +- if (Module.glConv.frameResolver) +- { +- alert('Duplicated interop promise'); +- } + Module.glConv.frameResolver = resolve; + Module.msgChannel.port1.postMessage({ + 'customCmd': 'sendFrame', +@@ -137,10 +133,8 @@ EM_JS(void, initGlConvWorker, (int maxPictures), { + }); + + }); +- console.log('Waiting for frame ', pictureIdx, ' in interop'); + let frame = await p; + Module.glConv.frameResolver = undefined; +- console.log('Received frame ', pictureIdx, ' in interop'); + if (Module.glConv.lastFrame.frame) + Module.glConv.lastFrame.frame.close(); + Module.glConv.lastFrame.frame = frame; +-- +2.35.1 + diff --git a/vlc_patches/aug/0052-webcodec-remove-debug.patch b/vlc_patches/aug/0052-webcodec-remove-debug.patch new file mode 100644 index 0000000000000000000000000000000000000000..d384ce680eb9134cfb757bd1741f02d21d9b020f --- /dev/null +++ b/vlc_patches/aug/0052-webcodec-remove-debug.patch @@ -0,0 +1,24 @@ +From c12fc5c5bc34fa684baf7a21ed16886462eb8756 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr> +Date: Mon, 9 May 2022 11:58:31 +0200 +Subject: [PATCH 52/77] webcodec: remove debug + +--- + modules/codec/webcodec.cpp | 1 - + 1 file changed, 1 deletion(-) + +diff --git a/modules/codec/webcodec.cpp b/modules/codec/webcodec.cpp +index d44e9ee5ae..bd2a1a083d 100644 +--- a/modules/codec/webcodec.cpp ++++ b/modules/codec/webcodec.cpp +@@ -172,7 +172,6 @@ EM_JS(emscripten::EM_VAL, initDecoderJS, (void* decoder), { + let picIdx = data['pictureIdx']; + let frame = Module.framesReady[picIdx]; + Module.framesReady[picIdx] = undefined; +- console.log('Posting frame', picIdx, 'from webcodec', frame); + Module.voutPort.postMessage({ + customCmd: 'displayFrame', + frame: frame, +-- +2.35.1 + diff --git a/vlc_patches/aug/0053-WIP-webcodec-remove-manual-block-queue.patch b/vlc_patches/aug/0053-WIP-webcodec-remove-manual-block-queue.patch new file mode 100644 index 0000000000000000000000000000000000000000..7caf1e444cb36151c4b329db254928b957bc2004 --- /dev/null +++ b/vlc_patches/aug/0053-WIP-webcodec-remove-manual-block-queue.patch @@ -0,0 +1,246 @@ +From 43c1f6c435426134decbd7dda0b86feccc2e6695 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr> +Date: Tue, 10 May 2022 08:16:38 +0200 +Subject: [PATCH 53/77] WIP webcodec remove manual block queue + +--- + modules/codec/webcodec.cpp | 147 ++++++++++++++++++++++++------------- + 1 file changed, 97 insertions(+), 50 deletions(-) + +diff --git a/modules/codec/webcodec.cpp b/modules/codec/webcodec.cpp +index bd2a1a083d..48383575f0 100644 +--- a/modules/codec/webcodec.cpp ++++ b/modules/codec/webcodec.cpp +@@ -47,10 +47,6 @@ using emval = emscripten::val; + + struct decoder_sys_t + { +- emval decoder = emval::undefined(); +- std::queue<block_t*> blocks; +- vlc::threads::mutex mutex; +- vlc::threads::condition_variable cond; + vlc_thread_t th; + + vlc_video_context* vctx; +@@ -81,11 +77,23 @@ EMSCRIPTEN_KEEPALIVE int32_t queuePicture(void* ctx, picture_t* pic, int64_t tim + { + auto dec = static_cast<decoder_t*>(ctx); + pic->date = VLC_TICK_FROM_US(timestamp); ++ msg_Dbg(dec, "Picture timestamp: %" PRId64 "\n", timestamp); + pic->b_progressive = true; + decoder_QueueVideo(dec, pic); + return PictureContextPrivate(pic->context)->pictureIdx; + } + ++EMSCRIPTEN_KEEPALIVE pthread_t getVlcDecoderWorkerThread(decoder_t* dec) ++{ ++ auto sys = static_cast<decoder_sys_t*>( dec->p_sys ); ++ return sys->th.handle; ++} ++ ++EMSCRIPTEN_KEEPALIVE void releaseBlock( block_t* block ) ++{ ++ block_Release( block ); ++} ++ + } + + EM_ASYNC_JS(bool, probeConfig, (emscripten::EM_VAL cfg), { +@@ -165,7 +173,7 @@ static emval getDecoderConfig( decoder_t* dec, bool includeExtraData ) + return decoderConfig; + } + +-EM_JS(emscripten::EM_VAL, initDecoderJS, (void* decoder), { ++EM_JS(emscripten::EM_VAL, initDecoderJS, (void* decoder, emscripten::EM_VAL decCfgHandle), { + function onInteropMessage(msg) { + let data = msg['data']; + if (data['customCmd'] == 'sendFrame') { +@@ -197,6 +205,18 @@ EM_JS(emscripten::EM_VAL, initDecoderJS, (void* decoder), { + await p; + } + ++ function onDecoderWorkerMessage(msg) { ++ const data = msg['data']; ++ if (data['customCmd'] == 'decode') { ++ Module.decoder.decode( data['chunk'] ); ++ _releaseBlock( data['block'] ); ++ } else if ( data['customCmd'] == 'flush' ) { ++ Module.decoder.flush(); ++ } else if ( data['customCmd'] == 'close' ) { ++ Module.decoder.close(); ++ } ++ } ++ + async function getPictureAsync(dec) { + function getPicture(dec, resolve) { + let pic = _tryGetPictureFromPool(dec); +@@ -214,6 +234,25 @@ EM_JS(emscripten::EM_VAL, initDecoderJS, (void* decoder), { + return await p; + } + ++ self.addEventListener('message', (e) => { ++ let msg = e['data']; ++ ++ console.log('Received message in decoder worker'); ++ console.dir(msg); ++ ++ if (msg['customCmd'] == 'getDecoderWorkerMessagePort') { ++ Module.msgChannel = new MessageChannel(); ++ Module.msgChannel.port1.onmessage = function(msg) { ++ onDecoderWorkerMessage(msg); ++ }; ++ self.postMessage({ ++ customCmd: 'transferMessagePort', ++ targetThread: msg['replyTo'], ++ transferList: [Module.msgChannel.port2], ++ }, [Module.msgChannel.port2]); ++ } ++ }); ++ + /* Store a handle to our decoder_t for later invocations */ + Module.webCodecCtx = decoder; + +@@ -250,46 +289,21 @@ EM_JS(emscripten::EM_VAL, initDecoderJS, (void* decoder), { + console.log(err); + } + }; +- return Emval.toHandle( new VideoDecoder( initCfg ) ); ++ Module.decoder = new VideoDecoder( initCfg ); ++ let decCfg = Emval.toValue( decCfgHandle ); ++ Module.decoder.configure( decCfg ); + }); + + static bool initDecoder( decoder_t* dec ) + { +- auto sys = static_cast<decoder_sys_t*>( dec->p_sys ); +- +- sys->decoder = emval::take_ownership( initDecoderJS( dec ) ); +- if ( sys->decoder.isUndefined() ) +- { +- msg_Err( dec, "Failed to instantiate VideoDecoder" ); +- return false; +- } +- +- sys->decoder.call<void>( "configure", getDecoderConfig( dec, true ) ); ++ auto decCfg = getDecoderConfig( dec, true ); ++ initDecoderJS( dec, decCfg.as_handle() ); + + return true; + } + +-static void WebcodecDecodeWorkerTick( void* arg ) ++static void mainloop_tick() + { +- auto dec = static_cast<decoder_t*>( arg ); +- auto sys = static_cast<decoder_sys_t*>( dec->p_sys ); +- block_t* block; +- +- vlc::threads::mutex_locker lock{ sys->mutex }; +- while ( sys->blocks.empty() == false ) +- { +- block = sys->blocks.front(); +- sys->blocks.pop(); +- +- auto chunk = blockToEncodedVideoChunk( dec, block ); +- block_Release(block); +- sys->decoder.call<void>( "decode", chunk ); +- +-// auto queueSize = sys->decoder["decodeQueueSize"]; +-// auto state = sys->decoder["state"]; +-// msg_Err( dec, "Decoder state: %s ; queue size: %ld", +-// state.as<std::string>().c_str(), queueSize.as<long int>()); +- } + } + + static void* WebcodecDecodeWorker( void* arg ) +@@ -314,29 +328,58 @@ static void* WebcodecDecodeWorker( void* arg ) + auto vctxPrivate = static_cast<webcodec_context*>( + vlc_video_context_GetPrivate(sys->vctx, VLC_VIDEO_CONTEXT_WEBCODEC)); + vctxPrivate->decoder_worker = pthread_self(); +- emscripten_set_main_loop_arg( &WebcodecDecodeWorkerTick, dec, 0, false ); +- /* +- * We want our tick function to be invoked ASAP even if we'd only be blocking +- * waiting for a new block to be queued +- */ +- emscripten_set_main_loop_timing( EM_TIMING_SETTIMEOUT, 1 ); ++ emscripten_set_main_loop(mainloop_tick, 1, true); + return NULL; + } + ++EM_ASYNC_JS(void, initDecoderWorkerMessagePort, (decoder_t* dec), { ++ if (Module.decoderWorkerPort !== undefined) { ++ return; ++ } ++ let workerMessagePortPromise = new Promise((resolve, reject) => { ++ self.addEventListener('message', function(e) { ++ let msg = e['data']; ++ if (msg.customCmd == 'transferMessagePort') { ++ let port = msg['transferList'][0]; ++ if (!port) { ++ console.log('No port provided, rejecting'); ++ reject(); ++ } ++ Module.decoderWorkerPort = port; ++ resolve(); ++ } ++ }); ++ self.postMessage({ ++ customCmd: 'getDecoderWorkerMessagePort', ++ targetThread: _getVlcDecoderWorkerThread(dec), ++ replyTo: _pthread_self() ++ }); ++ }); ++ await workerMessagePortPromise; ++}) ++ + static int Decode( decoder_t* dec, block_t* block ) + { +- auto sys = static_cast<decoder_sys_t*>( dec->p_sys ); +- vlc::threads::mutex_locker lock{ sys->mutex }; +- sys->blocks.push( block ); +- sys->cond.signal(); ++ initDecoderWorkerMessagePort(dec); ++ auto chunk = blockToEncodedVideoChunk( dec, block ); ++ EM_ASM({ ++ Module.decoderWorkerPort.postMessage({ ++ customCmd: 'decode', ++ chunk: Emval.toValue($0), ++ block: $1 ++ }); ++ }, chunk.as_handle(), block); + return VLCDEC_SUCCESS; + } + + static void Flush( decoder_t* dec ) + { +- //FIXME: Needs to be called from the decoder thread +- auto sys = static_cast<decoder_sys_t*>( dec->p_sys ); +- sys->decoder.call<emval>("flush").await(); ++ initDecoderWorkerMessagePort(dec); ++ EM_ASM({ ++ Module.decoderWorkerPort.postMessage({ ++ customCmd: 'flush' ++ }); ++ }); + } + + static int Open( vlc_object_t* obj ) +@@ -397,10 +440,14 @@ static int Open( vlc_object_t* obj ) + static void Close( decoder_t* dec ) + { + auto sys = static_cast<decoder_sys_t*>( dec->p_sys ); +- sys->decoder.call<void>("close"); + auto vctx = static_cast<webcodec_context*>( + vlc_video_context_GetPrivate(sys->vctx, VLC_VIDEO_CONTEXT_WEBCODEC ) ); + picture_pool_Release(vctx->pool); ++ EM_ASM({ ++ Module.decoderWorkerPort.postMessage({ ++ customCmd: 'close' ++ }); ++ }); + delete sys; + } + +-- +2.35.1 + diff --git a/vlc_patches/aug/0054-webcodec-remove-debug.patch b/vlc_patches/aug/0054-webcodec-remove-debug.patch new file mode 100644 index 0000000000000000000000000000000000000000..b855dbb9e0c72dc38ae2b47f726b29d721ee8b1e --- /dev/null +++ b/vlc_patches/aug/0054-webcodec-remove-debug.patch @@ -0,0 +1,24 @@ +From 1c9fdc8f0e1df8ea34f6f6cae882b2559d802a88 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr> +Date: Tue, 10 May 2022 10:23:28 +0200 +Subject: [PATCH 54/77] webcodec: remove debug + +--- + modules/codec/webcodec.cpp | 1 - + 1 file changed, 1 deletion(-) + +diff --git a/modules/codec/webcodec.cpp b/modules/codec/webcodec.cpp +index 48383575f0..983084103e 100644 +--- a/modules/codec/webcodec.cpp ++++ b/modules/codec/webcodec.cpp +@@ -77,7 +77,6 @@ EMSCRIPTEN_KEEPALIVE int32_t queuePicture(void* ctx, picture_t* pic, int64_t tim + { + auto dec = static_cast<decoder_t*>(ctx); + pic->date = VLC_TICK_FROM_US(timestamp); +- msg_Dbg(dec, "Picture timestamp: %" PRId64 "\n", timestamp); + pic->b_progressive = true; + decoder_QueueVideo(dec, pic); + return PictureContextPrivate(pic->context)->pictureIdx; +-- +2.35.1 + diff --git a/vlc_patches/aug/0055-webcodec-Only-transfer-a-block-pointer-to-the-decode.patch b/vlc_patches/aug/0055-webcodec-Only-transfer-a-block-pointer-to-the-decode.patch new file mode 100644 index 0000000000000000000000000000000000000000..7b168604ea35b70a6f57f396f57d3e9069c63e8e --- /dev/null +++ b/vlc_patches/aug/0055-webcodec-Only-transfer-a-block-pointer-to-the-decode.patch @@ -0,0 +1,100 @@ +From 95375e731c42928ae886c7c18f852f73916ace45 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr> +Date: Tue, 10 May 2022 10:25:00 +0200 +Subject: [PATCH 55/77] webcodec: Only transfer a block pointer to the decoder + worker + +It's unclear how performant it is to transfer an entire object and its +embedded memory view +--- + modules/codec/webcodec.cpp | 47 +++++++++++++++++++++++--------------- + 1 file changed, 28 insertions(+), 19 deletions(-) + +diff --git a/modules/codec/webcodec.cpp b/modules/codec/webcodec.cpp +index 983084103e..51f63a2089 100644 +--- a/modules/codec/webcodec.cpp ++++ b/modules/codec/webcodec.cpp +@@ -93,18 +93,7 @@ EMSCRIPTEN_KEEPALIVE void releaseBlock( block_t* block ) + block_Release( block ); + } + +-} +- +-EM_ASYNC_JS(bool, probeConfig, (emscripten::EM_VAL cfg), { +- var decoderCfg = Emval.toValue(cfg); +- var res = await VideoDecoder.isConfigSupported(decoderCfg).catch((err) => { +- console.log(err); +- return {'supported': false}; +- }); +- return res['supported']; +-}); +- +-emval blockToEncodedVideoChunk( decoder_t* dec, block_t* block ) ++EMSCRIPTEN_KEEPALIVE void decodeBlock( block_t* block ) + { + auto chunkType = emval::global("EncodedVideoChunk"); + auto chunkCfg = emval::object(); +@@ -123,9 +112,30 @@ emval blockToEncodedVideoChunk( decoder_t* dec, block_t* block ) + //if ( block->i_length > 0 ) + // chunkCfg.set( "duration", (long int)US_FROM_VLC_TICK( block->i_length ) ); + chunkCfg.set( "data", emscripten::typed_memory_view( block->i_buffer, block->p_buffer ) ); +- return chunkType.new_( std::move( chunkCfg ) ); ++ auto chunk = chunkType.new_( std::move( chunkCfg ) ); ++ ++ /* We can't return a javascript object from the stack back to the JS caller ++ * as the object would be destroyed when falling out of scope. ++ * We can't also pass an object by reference as the only way to get a ++ * emscripten::val instance is to take ownership from the javascript object ++ * so we couldn't use it back in the JS ++ * As a work around, we access the JS decoder instance straight from the C++ code ++ */ ++ EM_ASM({Module.decoder.decode( Emval.toValue( $0 ) ); }, chunk.as_handle()); + } + ++ ++} ++ ++EM_ASYNC_JS(bool, probeConfig, (emscripten::EM_VAL cfg), { ++ var decoderCfg = Emval.toValue(cfg); ++ var res = await VideoDecoder.isConfigSupported(decoderCfg).catch((err) => { ++ console.log(err); ++ return {'supported': false}; ++ }); ++ return res['supported']; ++}); ++ + static emval getDecoderConfig( decoder_t* dec, bool includeExtraData ) + { + auto decoderConfig = emval::object(); +@@ -207,8 +217,9 @@ EM_JS(emscripten::EM_VAL, initDecoderJS, (void* decoder, emscripten::EM_VAL decC + function onDecoderWorkerMessage(msg) { + const data = msg['data']; + if (data['customCmd'] == 'decode') { +- Module.decoder.decode( data['chunk'] ); +- _releaseBlock( data['block'] ); ++ let block = data['block']; ++ _decodeBlock( block ); ++ _releaseBlock( block ); + } else if ( data['customCmd'] == 'flush' ) { + Module.decoder.flush(); + } else if ( data['customCmd'] == 'close' ) { +@@ -360,14 +371,12 @@ EM_ASYNC_JS(void, initDecoderWorkerMessagePort, (decoder_t* dec), { + static int Decode( decoder_t* dec, block_t* block ) + { + initDecoderWorkerMessagePort(dec); +- auto chunk = blockToEncodedVideoChunk( dec, block ); + EM_ASM({ + Module.decoderWorkerPort.postMessage({ + customCmd: 'decode', +- chunk: Emval.toValue($0), +- block: $1 ++ block: $0 + }); +- }, chunk.as_handle(), block); ++ }, block); + return VLCDEC_SUCCESS; + } + +-- +2.35.1 + diff --git a/vlc_patches/aug/0056-webcodec-minor-simplification.patch b/vlc_patches/aug/0056-webcodec-minor-simplification.patch new file mode 100644 index 0000000000000000000000000000000000000000..4a571879473bb58c8671bd9a04d643e55b8849e1 --- /dev/null +++ b/vlc_patches/aug/0056-webcodec-minor-simplification.patch @@ -0,0 +1,27 @@ +From 19e4b631ba7faf7e11bf2c07008346e6d5d3657a Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr> +Date: Tue, 10 May 2022 10:31:16 +0200 +Subject: [PATCH 56/77] webcodec: minor simplification + +--- + modules/codec/webcodec.cpp | 4 +--- + 1 file changed, 1 insertion(+), 3 deletions(-) + +diff --git a/modules/codec/webcodec.cpp b/modules/codec/webcodec.cpp +index 51f63a2089..f82b1a7bf0 100644 +--- a/modules/codec/webcodec.cpp ++++ b/modules/codec/webcodec.cpp +@@ -252,9 +252,7 @@ EM_JS(emscripten::EM_VAL, initDecoderJS, (void* decoder, emscripten::EM_VAL decC + + if (msg['customCmd'] == 'getDecoderWorkerMessagePort') { + Module.msgChannel = new MessageChannel(); +- Module.msgChannel.port1.onmessage = function(msg) { +- onDecoderWorkerMessage(msg); +- }; ++ Module.msgChannel.port1.onmessage = onDecoderWorkerMessage; + self.postMessage({ + customCmd: 'transferMessagePort', + targetThread: msg['replyTo'], +-- +2.35.1 + diff --git a/vlc_patches/aug/0057-webcodec-Acquire-vout-message-port-before-queuing-pi.patch b/vlc_patches/aug/0057-webcodec-Acquire-vout-message-port-before-queuing-pi.patch new file mode 100644 index 0000000000000000000000000000000000000000..3313a9a098201cb12b1022241e68718896ab9e12 --- /dev/null +++ b/vlc_patches/aug/0057-webcodec-Acquire-vout-message-port-before-queuing-pi.patch @@ -0,0 +1,30 @@ +From 452cad2efcd15a8f30e9f5ef58bb70c82e23ad00 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr> +Date: Tue, 10 May 2022 10:31:33 +0200 +Subject: [PATCH 57/77] webcodec: Acquire vout message port before queuing + pictures + +--- + modules/codec/webcodec.cpp | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/modules/codec/webcodec.cpp b/modules/codec/webcodec.cpp +index f82b1a7bf0..4c6250294f 100644 +--- a/modules/codec/webcodec.cpp ++++ b/modules/codec/webcodec.cpp +@@ -277,10 +277,10 @@ EM_JS(emscripten::EM_VAL, initDecoderJS, (void* decoder, emscripten::EM_VAL decC + frame.close(); + return; + } +- let p = await getPictureAsync(Module.webCodecCtx); +- let picIdx = _queuePicture(Module.webCodecCtx, p, frame.timestamp); + if (Module.voutPort === undefined) + await getVoutMessagePort(); ++ let p = await getPictureAsync(Module.webCodecCtx); ++ let picIdx = _queuePicture(Module.webCodecCtx, p, frame.timestamp); + if ( Module.framesReady[picIdx] ) { + /* + * If we end up overriding a frame, it means it was dropped +-- +2.35.1 + diff --git a/vlc_patches/aug/0058-webcodec-Fix-invalid-error-return.patch b/vlc_patches/aug/0058-webcodec-Fix-invalid-error-return.patch new file mode 100644 index 0000000000000000000000000000000000000000..5983bee8780ba9276cb92cb7fb00707de614e0f8 --- /dev/null +++ b/vlc_patches/aug/0058-webcodec-Fix-invalid-error-return.patch @@ -0,0 +1,25 @@ +From 85987521d4a5995038b155277268bbd07ab4c34d Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr> +Date: Wed, 11 May 2022 12:03:39 +0200 +Subject: [PATCH 58/77] webcodec: Fix invalid error return + +--- + modules/codec/webcodec.cpp | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/modules/codec/webcodec.cpp b/modules/codec/webcodec.cpp +index 4c6250294f..b3546edaea 100644 +--- a/modules/codec/webcodec.cpp ++++ b/modules/codec/webcodec.cpp +@@ -400,7 +400,7 @@ static int Open( vlc_object_t* obj ) + { + msg_Err( obj, "Can't get VideoDecoder type, webcodec is probably not " + "supported on this browser" ); +- return false; ++ return VLC_EGENERIC; + } + auto sys = std::make_unique<decoder_sys_t>(); + dec->p_sys = sys.get(); +-- +2.35.1 + diff --git a/vlc_patches/aug/0059-WIP-webcodec-Reintroduce-a-block-queue-hoping-to-sol.patch b/vlc_patches/aug/0059-WIP-webcodec-Reintroduce-a-block-queue-hoping-to-sol.patch new file mode 100644 index 0000000000000000000000000000000000000000..27dd1830bd97daf8d0fa52400d2f86535a308548 --- /dev/null +++ b/vlc_patches/aug/0059-WIP-webcodec-Reintroduce-a-block-queue-hoping-to-sol.patch @@ -0,0 +1,117 @@ +From 55339e6437de6ccc6813b0551ac737d6f7a43d3d Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr> +Date: Wed, 11 May 2022 13:17:03 +0200 +Subject: [PATCH 59/77] WIP: webcodec: Reintroduce a block queue hoping to + solve the late frames + +which doesn't work for now +--- + modules/codec/webcodec.cpp | 44 +++++++++++++++++++++++++++++++------- + 1 file changed, 36 insertions(+), 8 deletions(-) + +diff --git a/modules/codec/webcodec.cpp b/modules/codec/webcodec.cpp +index b3546edaea..1967231999 100644 +--- a/modules/codec/webcodec.cpp ++++ b/modules/codec/webcodec.cpp +@@ -48,6 +48,8 @@ using emval = emscripten::val; + struct decoder_sys_t + { + vlc_thread_t th; ++ std::queue<block_t*> blocks; ++ vlc::threads::mutex mutex; + + vlc_video_context* vctx; + }; +@@ -93,7 +95,8 @@ EMSCRIPTEN_KEEPALIVE void releaseBlock( block_t* block ) + block_Release( block ); + } + +-EMSCRIPTEN_KEEPALIVE void decodeBlock( block_t* block ) ++// doesn't need to be extern anymore ++void decodeBlock( block_t* block ) + { + auto chunkType = emval::global("EncodedVideoChunk"); + auto chunkCfg = emval::object(); +@@ -124,6 +127,23 @@ EMSCRIPTEN_KEEPALIVE void decodeBlock( block_t* block ) + EM_ASM({Module.decoder.decode( Emval.toValue( $0 ) ); }, chunk.as_handle()); + } + ++EMSCRIPTEN_KEEPALIVE void decodeBlocks( decoder_t* dec ) ++{ ++ auto sys = static_cast<decoder_sys_t*>( dec->p_sys ); ++ while ( true ) ++ { ++ block_t* block; ++ { ++ vlc::threads::mutex_locker lock{ sys->mutex }; ++ if ( sys->blocks.empty() ) ++ return; ++ block = sys->blocks.front(); ++ sys->blocks.pop(); ++ } ++ decodeBlock( block ); ++ block_Release( block ); ++ } ++} + + } + +@@ -217,9 +237,7 @@ EM_JS(emscripten::EM_VAL, initDecoderJS, (void* decoder, emscripten::EM_VAL decC + function onDecoderWorkerMessage(msg) { + const data = msg['data']; + if (data['customCmd'] == 'decode') { +- let block = data['block']; +- _decodeBlock( block ); +- _releaseBlock( block ); ++ _decodeBlocks( Module.webCodecCtx ); + } else if ( data['customCmd'] == 'flush' ) { + Module.decoder.flush(); + } else if ( data['customCmd'] == 'close' ) { +@@ -310,8 +328,10 @@ static bool initDecoder( decoder_t* dec ) + return true; + } + +-static void mainloop_tick() ++static void mainloop_tick( void* arg ) + { ++// auto dec = static_cast<decoder_t*>( arg ); ++// decodeBlocks( dec ); + } + + static void* WebcodecDecodeWorker( void* arg ) +@@ -336,7 +356,7 @@ static void* WebcodecDecodeWorker( void* arg ) + auto vctxPrivate = static_cast<webcodec_context*>( + vlc_video_context_GetPrivate(sys->vctx, VLC_VIDEO_CONTEXT_WEBCODEC)); + vctxPrivate->decoder_worker = pthread_self(); +- emscripten_set_main_loop(mainloop_tick, 1, true); ++ emscripten_set_main_loop_arg(mainloop_tick, dec, 1, true); + return NULL; + } + +@@ -368,13 +388,21 @@ EM_ASYNC_JS(void, initDecoderWorkerMessagePort, (decoder_t* dec), { + + static int Decode( decoder_t* dec, block_t* block ) + { ++ if ( block == nullptr ) ++ fprintf(stderr, "NULL BLOCK\n"); ++ auto sys = static_cast<decoder_sys_t*>( dec->p_sys ); + initDecoderWorkerMessagePort(dec); ++ { ++ vlc::threads::mutex_locker lock{ sys->mutex }; ++ sys->blocks.push( block ); ++ if ( sys->blocks.size() > 1 ) ++ return VLCDEC_SUCCESS; ++ } + EM_ASM({ + Module.decoderWorkerPort.postMessage({ + customCmd: 'decode', +- block: $0 + }); +- }, block); ++ }); + return VLCDEC_SUCCESS; + } + +-- +2.35.1 + diff --git a/vlc_patches/aug/0060-wip-setjmp-lgjmp-requires-atomics.patch b/vlc_patches/aug/0060-wip-setjmp-lgjmp-requires-atomics.patch new file mode 100644 index 0000000000000000000000000000000000000000..9cc5f2179b6017c88e7796f091a9505da92448f8 --- /dev/null +++ b/vlc_patches/aug/0060-wip-setjmp-lgjmp-requires-atomics.patch @@ -0,0 +1,60 @@ +From 68e2e53d6acf832d57322911dd06e48d85485357 Mon Sep 17 00:00:00 2001 +From: Mehdi Sabwat <mehdi@videolabs.io> +Date: Tue, 26 Apr 2022 13:45:07 +0200 +Subject: [PATCH 60/77] wip: setjmp/lgjmp requires atomics + +I suppose this is because we can use it for coroutines. Non atomic operations could create race conditions. +--- + contrib/src/aribb24/rules.mak | 4 ++++ + contrib/src/png/rules.mak | 4 ++++ + contrib/src/zlib/rules.mak | 4 ++++ + 3 files changed, 12 insertions(+) + +diff --git a/contrib/src/aribb24/rules.mak b/contrib/src/aribb24/rules.mak +index 0c0ba9ff91..ba8a70b6d5 100644 +--- a/contrib/src/aribb24/rules.mak ++++ b/contrib/src/aribb24/rules.mak +@@ -25,6 +25,10 @@ aribb24: aribb24-$(ARIBB24_VERSION).tar.gz .sum-aribb24 + + DEPS_aribb24 = png + ++ifdef HAVE_EMSCRIPTEN ++CFLAGS+="-pthread" ++endif ++ + .aribb24: aribb24 + $(REQUIRE_GPL) + $(REQUIRE_GNUV3) +diff --git a/contrib/src/png/rules.mak b/contrib/src/png/rules.mak +index 7a8db11d5a..f1c93b835b 100644 +--- a/contrib/src/png/rules.mak ++++ b/contrib/src/png/rules.mak +@@ -7,6 +7,10 @@ ifeq ($(call need_pkg,"libpng >= 1.5.4"),) + PKGS_FOUND += png + endif + ++ifdef HAVE_EMSCRIPTEN ++HOSTVARS += CFLAGS="$(CFLAGS) -pthread" ++endif ++ + $(TARBALLS)/libpng-$(PNG_VERSION).tar.xz: + $(call download_pkg,$(PNG_URL),png) + +diff --git a/contrib/src/zlib/rules.mak b/contrib/src/zlib/rules.mak +index 4f7fc7397a..166d6b528a 100644 +--- a/contrib/src/zlib/rules.mak ++++ b/contrib/src/zlib/rules.mak +@@ -7,6 +7,10 @@ ifeq ($(call need_pkg,"zlib"),) + PKGS_FOUND += zlib + endif + ++ifdef HAVE_EMSCRIPTEN ++CFLAGS+="-pthread" ++endif ++ + ifeq ($(shell uname),Darwin) # zlib tries to use libtool on Darwin + ifdef HAVE_CROSS_COMPILE + ZLIB_CONFIG_VARS=CHOST=$(HOST) +-- +2.35.1 + diff --git a/vlc_patches/aug/0061-webcodec-revert-manual-block-queue.patch b/vlc_patches/aug/0061-webcodec-revert-manual-block-queue.patch new file mode 100644 index 0000000000000000000000000000000000000000..9b416a4c8f85dfff4e1af39cf6ccf72dc7f48326 --- /dev/null +++ b/vlc_patches/aug/0061-webcodec-revert-manual-block-queue.patch @@ -0,0 +1,107 @@ +From 409c444f88c5a3e9f05ecdcd6de2c16098b3ca2e Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr> +Date: Thu, 12 May 2022 09:47:32 +0200 +Subject: [PATCH 61/77] webcodec: revert manual block queue + +--- + modules/codec/webcodec.cpp | 43 +++++++------------------------------- + 1 file changed, 8 insertions(+), 35 deletions(-) + +diff --git a/modules/codec/webcodec.cpp b/modules/codec/webcodec.cpp +index 1967231999..9c39a5c2b4 100644 +--- a/modules/codec/webcodec.cpp ++++ b/modules/codec/webcodec.cpp +@@ -95,8 +95,7 @@ EMSCRIPTEN_KEEPALIVE void releaseBlock( block_t* block ) + block_Release( block ); + } + +-// doesn't need to be extern anymore +-void decodeBlock( block_t* block ) ++EMSCRIPTEN_KEEPALIVE void decodeBlock( block_t* block ) + { + auto chunkType = emval::global("EncodedVideoChunk"); + auto chunkCfg = emval::object(); +@@ -127,24 +126,6 @@ void decodeBlock( block_t* block ) + EM_ASM({Module.decoder.decode( Emval.toValue( $0 ) ); }, chunk.as_handle()); + } + +-EMSCRIPTEN_KEEPALIVE void decodeBlocks( decoder_t* dec ) +-{ +- auto sys = static_cast<decoder_sys_t*>( dec->p_sys ); +- while ( true ) +- { +- block_t* block; +- { +- vlc::threads::mutex_locker lock{ sys->mutex }; +- if ( sys->blocks.empty() ) +- return; +- block = sys->blocks.front(); +- sys->blocks.pop(); +- } +- decodeBlock( block ); +- block_Release( block ); +- } +-} +- + } + + EM_ASYNC_JS(bool, probeConfig, (emscripten::EM_VAL cfg), { +@@ -237,7 +218,9 @@ EM_JS(emscripten::EM_VAL, initDecoderJS, (void* decoder, emscripten::EM_VAL decC + function onDecoderWorkerMessage(msg) { + const data = msg['data']; + if (data['customCmd'] == 'decode') { +- _decodeBlocks( Module.webCodecCtx ); ++ let block = data['block']; ++ _decodeBlock( block ); ++ _releaseBlock(block); + } else if ( data['customCmd'] == 'flush' ) { + Module.decoder.flush(); + } else if ( data['customCmd'] == 'close' ) { +@@ -328,10 +311,8 @@ static bool initDecoder( decoder_t* dec ) + return true; + } + +-static void mainloop_tick( void* arg ) ++static void mainloop_tick() + { +-// auto dec = static_cast<decoder_t*>( arg ); +-// decodeBlocks( dec ); + } + + static void* WebcodecDecodeWorker( void* arg ) +@@ -356,7 +337,7 @@ static void* WebcodecDecodeWorker( void* arg ) + auto vctxPrivate = static_cast<webcodec_context*>( + vlc_video_context_GetPrivate(sys->vctx, VLC_VIDEO_CONTEXT_WEBCODEC)); + vctxPrivate->decoder_worker = pthread_self(); +- emscripten_set_main_loop_arg(mainloop_tick, dec, 1, true); ++ emscripten_set_main_loop(mainloop_tick, 1, true); + return NULL; + } + +@@ -388,21 +369,13 @@ EM_ASYNC_JS(void, initDecoderWorkerMessagePort, (decoder_t* dec), { + + static int Decode( decoder_t* dec, block_t* block ) + { +- if ( block == nullptr ) +- fprintf(stderr, "NULL BLOCK\n"); +- auto sys = static_cast<decoder_sys_t*>( dec->p_sys ); + initDecoderWorkerMessagePort(dec); +- { +- vlc::threads::mutex_locker lock{ sys->mutex }; +- sys->blocks.push( block ); +- if ( sys->blocks.size() > 1 ) +- return VLCDEC_SUCCESS; +- } + EM_ASM({ + Module.decoderWorkerPort.postMessage({ + customCmd: 'decode', ++ block: $0 + }); +- }); ++ }, block); + return VLCDEC_SUCCESS; + } + +-- +2.35.1 + diff --git a/vlc_patches/aug/0062-webcodec-Don-t-optimize-for-latency.patch b/vlc_patches/aug/0062-webcodec-Don-t-optimize-for-latency.patch new file mode 100644 index 0000000000000000000000000000000000000000..9d70992522e4595e36f2ee9ae22bb3989f42c415 --- /dev/null +++ b/vlc_patches/aug/0062-webcodec-Don-t-optimize-for-latency.patch @@ -0,0 +1,27 @@ +From 7de88352bd623463cd6d4c5df50ef951e3b4d76a Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr> +Date: Thu, 12 May 2022 09:48:20 +0200 +Subject: [PATCH 62/77] webcodec: Don't optimize for latency + +This seems to pace the decoding better but is definitely not a stable +enough solution, more of a temporary work around +--- + modules/codec/webcodec.cpp | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/modules/codec/webcodec.cpp b/modules/codec/webcodec.cpp +index 9c39a5c2b4..fbdbd3af5f 100644 +--- a/modules/codec/webcodec.cpp ++++ b/modules/codec/webcodec.cpp +@@ -168,7 +168,7 @@ static emval getDecoderConfig( decoder_t* dec, bool includeExtraData ) + decoderConfig.set( "codedHeight", dec->fmt_in.video.i_height ); + decoderConfig.set( "displayAspectWidth", dec->fmt_in.video.i_visible_width ); + decoderConfig.set( "displayAspectHeight", dec->fmt_in.video.i_visible_height ); +- decoderConfig.set( "optimizeForLatency", true ); ++ decoderConfig.set( "optimizeForLatency", false ); + if ( includeExtraData ) + { + if ( dec->fmt_in.i_extra > 0 ) +-- +2.35.1 + diff --git a/vlc_patches/aug/0063-webcodec-Properly-implement-flushing.patch b/vlc_patches/aug/0063-webcodec-Properly-implement-flushing.patch new file mode 100644 index 0000000000000000000000000000000000000000..cacd8244f1e4e2f2e370ec7bc23d59420476a119 --- /dev/null +++ b/vlc_patches/aug/0063-webcodec-Properly-implement-flushing.patch @@ -0,0 +1,81 @@ +From dc500714aeea0c8d2d630245244df2a7b8c24177 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr> +Date: Thu, 12 May 2022 09:50:23 +0200 +Subject: [PATCH 63/77] webcodec: Properly implement flushing + +--- + modules/codec/webcodec.cpp | 34 ++++++++++++++++++++++++++++------ + 1 file changed, 28 insertions(+), 6 deletions(-) + +diff --git a/modules/codec/webcodec.cpp b/modules/codec/webcodec.cpp +index fbdbd3af5f..a7b67d4ef6 100644 +--- a/modules/codec/webcodec.cpp ++++ b/modules/codec/webcodec.cpp +@@ -215,14 +215,23 @@ EM_JS(emscripten::EM_VAL, initDecoderJS, (void* decoder, emscripten::EM_VAL decC + await p; + } + +- function onDecoderWorkerMessage(msg) { ++ async function onDecoderWorkerMessage(msg) { + const data = msg['data']; + if (data['customCmd'] == 'decode') { + let block = data['block']; + _decodeBlock( block ); + _releaseBlock(block); + } else if ( data['customCmd'] == 'flush' ) { +- Module.decoder.flush(); ++ await Module.decoder.flush(); ++ for ( let i = 0; i < Module.framesReady.length; ++i ) { ++ if (Module.framesReady[i]) { ++ Module.framesReady[i].close(); ++ Module.framesReady[i] = undefined; ++ } ++ } ++ Module.msgChannel.port1.postMessage({ ++ customCmd: 'onFlushCompleted' ++ }); + } else if ( data['customCmd'] == 'close' ) { + Module.decoder.close(); + } +@@ -365,6 +374,12 @@ EM_ASYNC_JS(void, initDecoderWorkerMessagePort, (decoder_t* dec), { + }); + }); + await workerMessagePortPromise; ++ Module.decoderWorkerPort.onmessage = (e) => { ++ let data = e['data']; ++ if (data['customCmd'] == 'onFlushCompleted') { ++ Module.flushPromiseResolver(); ++ } ++ }; + }) + + static int Decode( decoder_t* dec, block_t* block ) +@@ -379,14 +394,21 @@ static int Decode( decoder_t* dec, block_t* block ) + return VLCDEC_SUCCESS; + } + +-static void Flush( decoder_t* dec ) +-{ +- initDecoderWorkerMessagePort(dec); +- EM_ASM({ ++EM_ASYNC_JS(void, flushAsync, (), { ++ let p = new Promise((r) => { ++ Module.flushPromiseResolver = r; + Module.decoderWorkerPort.postMessage({ + customCmd: 'flush' + }); + }); ++ await p; ++ Module.flushPromiseResolver = undefined; ++}); ++ ++static void Flush( decoder_t* dec ) ++{ ++ initDecoderWorkerMessagePort(dec); ++ flushAsync(); + } + + static int Open( vlc_object_t* obj ) +-- +2.35.1 + diff --git a/vlc_patches/aug/0064-emscripten-Handle-late-displayFrame-messages-after-a.patch b/vlc_patches/aug/0064-emscripten-Handle-late-displayFrame-messages-after-a.patch new file mode 100644 index 0000000000000000000000000000000000000000..2bc6f2c08c3e712bf253d1b59a53d270872cfdb2 --- /dev/null +++ b/vlc_patches/aug/0064-emscripten-Handle-late-displayFrame-messages-after-a.patch @@ -0,0 +1,50 @@ +From 2a5fac1b3e80e4647a535f9162e59d7f130db558 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr> +Date: Thu, 12 May 2022 09:58:01 +0200 +Subject: [PATCH 64/77] emscripten: Handle late displayFrame messages after a + flush + +--- + modules/codec/webcodec.cpp | 12 ++++++++++++ + modules/video_output/opengl/interop_emscripten.cpp | 2 ++ + 2 files changed, 14 insertions(+) + +diff --git a/modules/codec/webcodec.cpp b/modules/codec/webcodec.cpp +index a7b67d4ef6..e619cdde00 100644 +--- a/modules/codec/webcodec.cpp ++++ b/modules/codec/webcodec.cpp +@@ -190,6 +190,18 @@ EM_JS(emscripten::EM_VAL, initDecoderJS, (void* decoder, emscripten::EM_VAL decC + let picIdx = data['pictureIdx']; + let frame = Module.framesReady[picIdx]; + Module.framesReady[picIdx] = undefined; ++ if (!frame) { ++ /* We may receive a late message after a flush, in which case ++ * we still need to reply to the interop, but we don't have ++ * anything to provide to it ++ */ ++ Module.voutPort.postMessage({ ++ customCmd: 'displayFrame', ++ frame: undefined, ++ pictureId: picIdx ++ }); ++ return; ++ } + Module.voutPort.postMessage({ + customCmd: 'displayFrame', + frame: frame, +diff --git a/modules/video_output/opengl/interop_emscripten.cpp b/modules/video_output/opengl/interop_emscripten.cpp +index e9905ac1a3..f745fcfef4 100644 +--- a/modules/video_output/opengl/interop_emscripten.cpp ++++ b/modules/video_output/opengl/interop_emscripten.cpp +@@ -60,6 +60,8 @@ tc_emscripten_op_allocate_textures(const struct vlc_gl_interop *interop, GLuint + + EM_ASYNC_JS(void, bindVideoFrame, (int pictureIdx), { + let frame = await Module.awaitFrame(pictureIdx); ++ if (!frame) ++ return; + + let glCtx = Module.glCtx; + glCtx.texImage2D(glCtx.TEXTURE_2D, 0, glCtx.RGBA, frame.codedWidth, frame.codedHeight, 0, +-- +2.35.1 + diff --git a/vlc_patches/aug/0065-webcodec-Minor-simplification.patch b/vlc_patches/aug/0065-webcodec-Minor-simplification.patch new file mode 100644 index 0000000000000000000000000000000000000000..fe95a1398313f39ef243282b4a0740d2e8d9ff15 --- /dev/null +++ b/vlc_patches/aug/0065-webcodec-Minor-simplification.patch @@ -0,0 +1,29 @@ +From cd80be3a8c37d4c294c957dac7d7515d71a53843 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr> +Date: Thu, 12 May 2022 09:59:06 +0200 +Subject: [PATCH 65/77] webcodec: Minor simplification + +--- + modules/codec/webcodec.cpp | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +diff --git a/modules/codec/webcodec.cpp b/modules/codec/webcodec.cpp +index e619cdde00..07b3690dab 100644 +--- a/modules/codec/webcodec.cpp ++++ b/modules/codec/webcodec.cpp +@@ -253,9 +253,9 @@ EM_JS(emscripten::EM_VAL, initDecoderJS, (void* decoder, emscripten::EM_VAL decC + function getPicture(dec, resolve) { + let pic = _tryGetPictureFromPool(dec); + if (!pic) { +- setTimeout( function(d, r) { +- getPicture(d, r); +- }, 1, dec, resolve ); ++ setTimeout( function() { ++ getPicture(dec, resolve); ++ }, 1); + return; + } + resolve(pic); +-- +2.35.1 + diff --git a/vlc_patches/aug/0066-emscripten-add-js-File-access-plugin.patch b/vlc_patches/aug/0066-emscripten-add-js-File-access-plugin.patch new file mode 100644 index 0000000000000000000000000000000000000000..30e2d184b5acda2f148dc2456e3d4d39b2355a2f --- /dev/null +++ b/vlc_patches/aug/0066-emscripten-add-js-File-access-plugin.patch @@ -0,0 +1,368 @@ +From 49f585a2a5fbbc04d85fbccbf63881d2a659cc54 Mon Sep 17 00:00:00 2001 +From: Mehdi Sabwat <mehdi@videolabs.io> +Date: Thu, 9 Jun 2022 21:05:04 +0200 +Subject: [PATCH 66/77] emscripten: add js File access plugin + +Emscripten currently does not support picking a file from the DOM with the <input> element, and reading it from a webassembly application. + +This access plugin will be able to receive a File object handle in the input thread, and read into buffers in linear memory with the file content while keeping track of the offset. + +A basic setup could look like : + +``` +<input type="file" id="fpicker_btn">Select File</input> +<script> +let instance = _libvlc_new(); +let media_player_ptr = _libvlc_media_player_new(instance); + +/* + Module is the emscripten runtime object, + it has the vlc_access_file array as a property +*/ + +let inputElement = document.getElementById("fpicker_btn"); + +function handleFile() { + var name = this.files.item(0).name; + console.log("opened file: ", name); + let path_ptr = Module.allocateUTF8("emjsfile:///mediafile_" + name); + let media_ptr = _libvlc_media_new_location(path_ptr); + Module._free(path_ptr); + _libvlc_media_player_set_media(media_player_ptr, media_ptr); + Module['vlc_access_file'].push(this.files.item(0)); +} +inputElement.addEventListener("change", handleFile, false); + +_libvlc_media_player_play(media_player_ptr); +</script> +``` +--- + modules/access/Makefile.am | 5 + + modules/access/emjsfile.c | 299 +++++++++++++++++++++++++++++++++++++ + 2 files changed, 304 insertions(+) + create mode 100644 modules/access/emjsfile.c + +diff --git a/modules/access/Makefile.am b/modules/access/Makefile.am +index d4f6b5f10a..cc827027df 100644 +--- a/modules/access/Makefile.am ++++ b/modules/access/Makefile.am +@@ -26,6 +26,11 @@ libfilesystem_plugin_la_LIBADD = -lshlwapi + endif + access_LTLIBRARIES += libfilesystem_plugin.la + ++if HAVE_EMSCRIPTEN ++libemjsfile_plugin_la_SOURCES = access/emjsfile.c ++access_LTLIBRARIES += libemjsfile_plugin.la ++endif ++ + libidummy_plugin_la_SOURCES = access/idummy.c + access_LTLIBRARIES += libidummy_plugin.la + +diff --git a/modules/access/emjsfile.c b/modules/access/emjsfile.c +new file mode 100644 +index 0000000000..920a408f95 +--- /dev/null ++++ b/modules/access/emjsfile.c +@@ -0,0 +1,299 @@ ++/***************************************************************************** ++ * emjsfile.c: emscripten js file access plugin ++ ***************************************************************************** ++ * Copyright (C) 2022 VLC authors Videolabs, and VideoLAN ++ * ++ * ++ * This program is free software; you can redistribute it and/or modify it ++ * under the terms of the GNU Lesser General Public License as published by ++ * the Free Software Foundation; either version 2.1 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU Lesser General Public License for more details. ++ * ++ * You should have received a copy of the GNU Lesser General Public License ++ * along with this program; if not, write to the Free Software Foundation, ++ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. ++ *****************************************************************************/ ++ ++#ifdef HAVE_CONFIG_H ++# include "config.h" ++#endif ++ ++#include <vlc_common.h> ++#include <vlc_plugin.h> ++#include <vlc_access.h> ++#include <vlc_threads.h> ++ ++#include <emscripten.h> ++ ++typedef struct ++{ ++ uint64_t offset; ++ uint64_t js_file_size; ++} access_sys_t; ++ ++static ssize_t Read (stream_t *p_access, void *buffer, size_t size) { ++ access_sys_t *p_sys = p_access->p_sys; ++ ++ size_t offset = p_sys->offset; ++ size_t js_file_size = p_sys->js_file_size; ++ ++ if (offset >= js_file_size) ++ return 0; ++ if (size > offset + js_file_size) { ++ size = js_file_size - offset; ++ } ++ EM_ASM({ ++ const offset = $0; ++ const buffer = $1; ++ const size = $2; ++ const blob = Module.vlcAccess[$3].worker_js_file.slice(offset, offset + size); ++ HEAPU8.set(new Uint8Array(Module.vlcAccess[$3].reader.readAsArrayBuffer(blob)), buffer); ++ }, offset, buffer, size, p_access); ++ p_sys->offset += size; ++ return size; ++} ++ ++static int Seek (stream_t *p_access, uint64_t offset) { ++ access_sys_t *p_sys = p_access->p_sys; ++ ++ p_sys->offset = offset; ++ return VLC_SUCCESS; ++} ++ ++static int get_js_file_size(stream_t *p_access, uint64_t *value) { ++ /* ++ to avoid RangeError on BigUint64 view creation, ++ the start offset must be a multiple of 8. ++ */ ++ if ((uintptr_t)value % 8 != 0) { ++ msg_Err(p_access, "error: value is not aligned in get_js_file_size!"); ++ return 1; ++ } ++ return EM_ASM_INT({ ++ try { ++ var v = new BigUint64Array(wasmMemory.buffer, $0, 1); ++ v[0] = BigInt(Module.vlcAccess[$1].worker_js_file.size); ++ return 0; ++ } ++ catch (error) { ++ if (error instanceof RangeError) { ++ // TODO: ++ // RangeError: start offset of BigUint64Array should be a multiple of 8 ++ // unaligned accesses are not supported on all platforms? ++ return 0; ++ } ++ console.error("get_js_file_size error: " + error); ++ return 1; ++ } ++ }, value, p_access); ++} ++ ++static int Control( stream_t *p_access, int i_query, va_list args ) ++{ ++ bool *pb_bool; ++ vlc_tick_t *pi_64; ++ access_sys_t *p_sys = p_access->p_sys; ++ ++ switch( i_query ) ++ { ++ case STREAM_CAN_SEEK: ++ case STREAM_CAN_FASTSEEK: ++ pb_bool = va_arg( args, bool * ); ++ *pb_bool = true; ++ break; ++ ++ case STREAM_CAN_PAUSE: ++ case STREAM_CAN_CONTROL_PACE: ++ pb_bool = va_arg( args, bool * ); ++ *pb_bool = true; ++ break; ++ ++ case STREAM_GET_SIZE: ++ { ++ *va_arg( args, uint64_t * ) = p_sys->js_file_size; ++ break; ++ } ++ ++ case STREAM_GET_PTS_DELAY: ++ pi_64 = va_arg( args, vlc_tick_t * ); ++ *pi_64 = VLC_TICK_FROM_MS( ++ var_InheritInteger (p_access, "file-caching") ); ++ break; ++ ++ case STREAM_SET_PAUSE_STATE: ++ break; ++ ++ default: ++ return VLC_EGENERIC; ++ ++ } ++ return VLC_SUCCESS; ++} ++ ++EM_ASYNC_JS(int, init_js_file, (stream_t *p_access, int id), { ++ let p = new Promise((resolve, reject) => { ++ function handleFileResult(e) { ++ const msg = e['data']; ++ if (msg.type === 'FileResult') { ++ self.removeEventListener('message', handleFileResult); ++ if (msg.file !== undefined) { ++ Module.vlcAccess[p_access].worker_js_file = msg.file; ++ Module.vlcAccess[p_access].reader = new FileReaderSync(); ++ resolve(); ++ } ++ else { ++ reject("error: sent an undefined File object from the main thread"); ++ } ++ } ++ else if (msg.type === 'ErrorVLCAccessFileUndefined') { ++ reject("error: vlc_access_file object is not defined"); ++ } ++ else if (msg.type === 'ErrorRequestFileMessageWithoutId') { ++ reject("error: request file message send without an id"); ++ } ++ else if (msg.type === 'ErrorMissingFile') { ++ reject("error: missing file, bad id or vlc_access_file[id] is not defined"); ++ } ++ } ++ self.addEventListener('message', handleFileResult); ++ }); ++ let timer = undefined; ++ let timeout = new Promise(function (resolve, reject) { ++ timer = setTimeout(resolve, 1000, 'timeout') ++ }); ++ let promises = [p, timeout]; ++ /* id must be unique */ ++ self.postMessage({ cmd: "customCmd", type: "requestFile", id: id}); ++ try { ++ let return_value = await Promise.race(promises); ++ if (return_value === 'timeout') { ++ console.error("vlc_access timeout: could not get file!"); ++ } ++ } ++ catch(error) { ++ console.error("vlc_access error in init_js_file(): ", error); ++ return 1; ++ } ++ clearTimeout(timer); ++ return 0; ++}); ++ ++static int EmFileOpen( vlc_object_t *p_this ) { ++ stream_t *p_access = (stream_t*)p_this; ++ ++ /* init per worker module.vlcAccess object */ ++ EM_ASM({ ++ if (Module.vlcAccess === undefined) { ++ Module.vlcAccess = {}; ++ } ++ (Module.vlcAccess[$0] = {worker_js_file: undefined, reader: undefined}); ++ }, p_access); ++ ++ /* ++ This block will run in the main thread, to access the DOM. ++ When the user selects a file, it is assigned to the Module.vlc_access_file ++ array. ++ ++ We listen to 'message' events so that when the file is requested by the ++ input thread, we can answer and send the File object from the main thread. ++ */ ++ MAIN_THREAD_EM_ASM({ ++ const thread_id = $0; ++ let w = Module.PThread.pthreads[thread_id].worker; ++ function handleFileRequest(e) { ++ const msg = e.data; ++ if (msg.type === "requestFile") { ++ w.removeEventListener('message', handleFileRequest); ++ if (Module.vlc_access_file === undefined) { ++ console.error("vlc_access_file property missing!"); ++ w.postMessage({ cmd: "customCmd", ++ type: "ErrorVLCAccessFileUndefined" ++ }); ++ return ; ++ } ++ if (msg.id === undefined) { ++ console.error("id property missing in requestFile message!"); ++ w.postMessage({ cmd: "customCmd", ++ type: "ErrorRequestFileMessageWithoutId" ++ }); ++ return ; ++ } ++ if (Module.vlc_access_file[msg.id] === undefined) { ++ console.error("error file missing!"); ++ w.postMessage({ cmd: "customCmd", ++ type: "ErrorMissingFile" ++ }); ++ return ; ++ } ++ /* ++ keeping the File object in the main thread too, ++ in case we want to reopen the media, without re-selecting ++ the file. ++ */ ++ w.postMessage({ cmd: "customCmd", ++ type: "FileResult", ++ file: Module.vlc_access_file[msg.id] ++ }); ++ } ++ } ++ w.addEventListener('message', handleFileRequest); ++ }, pthread_self()); ++ ++ int id = atoi(p_access->psz_location); ++ if (id == 0) { ++ msg_Err(p_access, "error: failed init uri has invalid id!"); ++ return VLC_EGENERIC; ++ } ++ ++ /* ++ Request the file from the main thread. ++ If it was not selected, it will return an error. ++ ++ to open a file, we need to call libvlc_media_new_location with ++ the following uri : emjsfile://<id> ++ */ ++ if (init_js_file(p_access, id)) { ++ msg_Err(p_access, "EMJsFile error: failed init!"); ++ return VLC_EGENERIC; ++ } ++ ++ access_sys_t *p_sys = vlc_obj_malloc(p_this, sizeof (*p_sys)); ++ if (unlikely(p_sys == NULL)) ++ return VLC_ENOMEM; ++ ++ p_access->pf_read = Read; ++ p_access->pf_block = NULL; ++ p_access->pf_control = Control; ++ p_access->pf_seek = Seek; ++ p_access->p_sys = p_sys; ++ p_sys->js_file_size = 0; ++ p_sys->offset = 0; ++ if (get_js_file_size(p_access, &p_sys->js_file_size)) { ++ msg_Err(p_access, "EMJsFile error: could not get file size!"); ++ return VLC_EGENERIC; ++ } ++ ++ return VLC_SUCCESS; ++} ++ ++static void EmFileClose (vlc_object_t * p_this) { ++ stream_t *p_access = (stream_t*)p_this; ++ EM_ASM({ ++ Module.vlcAccess[$0].worker_js_file = undefined; ++ Module.vlcAccess[$0].reader = undefined; ++ }, p_access); ++} ++ ++vlc_module_begin () ++ set_description( N_("Emscripten module to allow reading local files from the DOM's <input>") ) ++ set_shortname( N_("Emscripten Local File Input") ) ++ set_subcategory( SUBCAT_INPUT_ACCESS ) ++ set_capability( "access", 50 ) ++ add_shortcut( "emjsfile" ) ++ set_callbacks( EmFileOpen, EmFileClose ) ++vlc_module_end() +-- +2.35.1 + diff --git a/vlc_patches/aug/0067-gl-utils-Add-a-vlc_gl_IsWebGL-helper.patch b/vlc_patches/aug/0067-gl-utils-Add-a-vlc_gl_IsWebGL-helper.patch new file mode 100644 index 0000000000000000000000000000000000000000..49cf77039e27fd53932a06a5c275c5c94f3b3048 --- /dev/null +++ b/vlc_patches/aug/0067-gl-utils-Add-a-vlc_gl_IsWebGL-helper.patch @@ -0,0 +1,29 @@ +From 92253242486f94556cb52ebaa603e7779545d897 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr> +Date: Tue, 19 Jul 2022 11:24:01 +0200 +Subject: [PATCH 67/77] gl: utils: Add a vlc_gl_IsWebGL helper + +--- + modules/video_output/opengl/gl_util.h | 8 ++++++++ + 1 file changed, 8 insertions(+) + +diff --git a/modules/video_output/opengl/gl_util.h b/modules/video_output/opengl/gl_util.h +index 551e9ac85e..f72131a9fd 100644 +--- a/modules/video_output/opengl/gl_util.h ++++ b/modules/video_output/opengl/gl_util.h +@@ -155,4 +155,12 @@ vlc_gl_HasExtension( + return false; + } + ++static inline bool vlc_gl_IsWebGL(struct vlc_gl_extension_vt *vt) ++{ ++ const GLubyte *version = vt->GetString(GL_VERSION); ++ if (!version) ++ return false; ++ return strcasestr((const char*)version, "WebGL") != NULL; ++} ++ + #endif +-- +2.35.1 + diff --git a/vlc_patches/aug/0068-opengl-interop_sw-Force-use-of-GL_LUMINANCE-for-webg.patch b/vlc_patches/aug/0068-opengl-interop_sw-Force-use-of-GL_LUMINANCE-for-webg.patch new file mode 100644 index 0000000000000000000000000000000000000000..e2ced6ed301f98e459929ccef2f7d277d27feffd --- /dev/null +++ b/vlc_patches/aug/0068-opengl-interop_sw-Force-use-of-GL_LUMINANCE-for-webg.patch @@ -0,0 +1,34 @@ +From c763ea6946e168d1c6c04f83f789e64ea6035bdc Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr> +Date: Tue, 19 Jul 2022 11:24:17 +0200 +Subject: [PATCH 68/77] opengl: interop_sw: Force use of GL_LUMINANCE for webgl + +WebGL doesn't handle our combination of internal format & formats the +same way opengl(es) does, which leads to GL_INVALID_OPERATION being +returned when the interop initializes +--- + modules/video_output/opengl/interop_sw.c | 5 +++-- + 1 file changed, 3 insertions(+), 2 deletions(-) + +diff --git a/modules/video_output/opengl/interop_sw.c b/modules/video_output/opengl/interop_sw.c +index 6669559b21..d87e20c2aa 100644 +--- a/modules/video_output/opengl/interop_sw.c ++++ b/modules/video_output/opengl/interop_sw.c +@@ -587,11 +587,12 @@ opengl_interop_generic_init(struct vlc_gl_interop *interop, bool allow_dr) + || vlc_gl_HasExtension(&extension_vt, "GL_EXT_unpack_subimage"); + + /* RG textures are available natively since OpenGL 3.0 and OpenGL ES 3.0 */ +- priv->has_texture_rg = vlc_gl_GetVersionMajor(&extension_vt) >= 3 ++ priv->has_texture_rg = vlc_gl_IsWebGL(&extension_vt) == false && ++ (vlc_gl_GetVersionMajor(&extension_vt) >= 3 + || (interop->gl->api_type == VLC_OPENGL + && vlc_gl_HasExtension(&extension_vt, "GL_ARB_texture_rg")) + || (interop->gl->api_type == VLC_OPENGL_ES2 +- && vlc_gl_HasExtension(&extension_vt, "GL_EXT_texture_rg")); ++ && vlc_gl_HasExtension(&extension_vt, "GL_EXT_texture_rg"))); + + video_color_space_t space; + const vlc_fourcc_t *list; +-- +2.35.1 + diff --git a/vlc_patches/aug/0069-wip-cast-to-avoid-compilation-error.patch b/vlc_patches/aug/0069-wip-cast-to-avoid-compilation-error.patch new file mode 100644 index 0000000000000000000000000000000000000000..ae7c75bdbb911d46c37b1eaf2613889d94113bb4 --- /dev/null +++ b/vlc_patches/aug/0069-wip-cast-to-avoid-compilation-error.patch @@ -0,0 +1,23 @@ +From 9814aba20e525c5dfce0f531a7481aef9dcfde90 Mon Sep 17 00:00:00 2001 +From: Mehdi Sabwat <mehdi@videolabs.io> +Date: Wed, 10 Aug 2022 13:43:34 +0200 +Subject: [PATCH 1/1] wip: cast to avoid compilation error + +--- + src/emscripten/thread.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/emscripten/thread.c b/src/emscripten/thread.c +index 8496174278..8fb8c5bb57 100644 +--- a/src/emscripten/thread.c ++++ b/src/emscripten/thread.c +@@ -28,5 +28,5 @@ + unsigned long vlc_thread_id(void) + { + static_assert(sizeof(pthread_t) <= sizeof(unsigned long),"invalid pthread_t size"); +- return pthread_self(); ++ return (unsigned long) pthread_self(); + } +-- +2.35.1 + diff --git a/vlc_patches/aug/0069-wip.patch b/vlc_patches/aug/0069-wip.patch new file mode 100644 index 0000000000000000000000000000000000000000..24f72761490a4f4064714f8cca1b340df034ee55 --- /dev/null +++ b/vlc_patches/aug/0069-wip.patch @@ -0,0 +1,67 @@ +From 08c0fe66b51b2a54b143b079e72674c8f0d5e792 Mon Sep 17 00:00:00 2001 +From: Mehdi Sabwat <mehdi@videolabs.io> +Date: Wed, 3 Aug 2022 10:43:19 +0200 +Subject: [PATCH 69/77] wip + +--- + src/posix/thread.c | 35 ++++++++++++++++++++++++++++++++++- + 1 file changed, 34 insertions(+), 1 deletion(-) + +diff --git a/src/posix/thread.c b/src/posix/thread.c +index 78045d9746..9c7afdfbb1 100644 +--- a/src/posix/thread.c ++++ b/src/posix/thread.c +@@ -128,7 +128,29 @@ void vlc_threads_setup (libvlc_int_t *p_libvlc) + { + (void) p_libvlc; + } ++#ifdef __EMSCRIPTEN__ ++#include <emscripten.h> ++typedef struct s_vlc_clone_data { ++ void *(*entry) (void *); ++ void *data; ++} vlc_clone_data_t; ++ ++void vlc_js_runner(void *args) { VLC_UNUSED(args); } ++ ++void *vlc_js_entry(void *args) { ++ vlc_clone_data_t *data = (vlc_clone_data_t *)args; ++ /* ++ don't simulate infinite loop to allow running ++ the pthread entry point. ++ */ ++ emscripten_set_main_loop_arg(vlc_js_runner, args, -1, 0); ++ void *ret = data->entry(data->data); ++ emscripten_cancel_main_loop(); ++ free(data); ++ return ret; ++} + ++#endif + static int vlc_clone_attr (vlc_thread_t *th, pthread_attr_t *attr, + void *(*entry) (void *), void *data, int priority) + { +@@ -175,8 +197,19 @@ static int vlc_clone_attr (vlc_thread_t *th, pthread_attr_t *attr, + ret = pthread_attr_setstacksize (attr, VLC_STACKSIZE); + assert (ret == 0); /* fails iff VLC_STACKSIZE is invalid */ + #endif +- ++#ifdef __EMSCRIPTEN__ ++ vlc_clone_data_t *vlc_js_data = (vlc_clone_data_t *) malloc(sizeof(vlc_clone_data_t)); ++ if (unlikely(vlc_js_data == NULL)) { ++ ret = EAGAIN; ++ goto error; ++ } ++ vlc_js_data->entry = entry; ++ vlc_js_data->data = data; ++ ret = pthread_create(&th->handle, attr, vlc_js_entry, vlc_js_data); ++#else + ret = pthread_create(&th->handle, attr, entry, data); ++#endif ++error: + pthread_sigmask (SIG_SETMASK, &oldset, NULL); + pthread_attr_destroy (attr); + (void) priority; +-- +2.35.1 + diff --git a/vlc_patches/aug/0070-adapt-to-new-emscripten-version.patch b/vlc_patches/aug/0070-adapt-to-new-emscripten-version.patch new file mode 100644 index 0000000000000000000000000000000000000000..54b71cb2980e26cc9fbe3b9ba44d8cc73fc60769 --- /dev/null +++ b/vlc_patches/aug/0070-adapt-to-new-emscripten-version.patch @@ -0,0 +1,39 @@ +From 54b9e53c6709ed2f15720f3e66396adeef8359ce Mon Sep 17 00:00:00 2001 +From: Mehdi Sabwat <mehdi@videolabs.io> +Date: Wed, 3 Aug 2022 13:17:46 +0200 +Subject: [PATCH 70/77] adapt to new emscripten version + +--- + modules/access/emjsfile.c | 2 +- + modules/video_output/emscripten.cpp | 2 +- + 2 files changed, 2 insertions(+), 2 deletions(-) + +diff --git a/modules/access/emjsfile.c b/modules/access/emjsfile.c +index 920a408f95..0fea11afc4 100644 +--- a/modules/access/emjsfile.c ++++ b/modules/access/emjsfile.c +@@ -204,7 +204,7 @@ static int EmFileOpen( vlc_object_t *p_this ) { + */ + MAIN_THREAD_EM_ASM({ + const thread_id = $0; +- let w = Module.PThread.pthreads[thread_id].worker; ++ let w = Module.PThread.pthreads[thread_id]; + function handleFileRequest(e) { + const msg = e.data; + if (msg.type === "requestFile") { +diff --git a/modules/video_output/emscripten.cpp b/modules/video_output/emscripten.cpp +index 9fd34350b5..e22342e894 100644 +--- a/modules/video_output/emscripten.cpp ++++ b/modules/video_output/emscripten.cpp +@@ -188,7 +188,7 @@ static int Open (vlc_gl_t *gl, unsigned width, unsigned height) + bmp.close(); + } + } +- let w = Module.PThread.pthreads[$0].worker; ++ let w = Module.PThread.pthreads[$0]; + w.addEventListener('message', function (e) { + let msg = e['data']; + if (msg.customCmd == 'getVoutMessagePort') { +-- +2.35.1 + diff --git a/vlc_patches/aug/0071-don-t-call-emscripten_set_main_loop-twice.patch b/vlc_patches/aug/0071-don-t-call-emscripten_set_main_loop-twice.patch new file mode 100644 index 0000000000000000000000000000000000000000..b6126ed5b1c00e8941e1d3c618d482a05be6faec --- /dev/null +++ b/vlc_patches/aug/0071-don-t-call-emscripten_set_main_loop-twice.patch @@ -0,0 +1,25 @@ +From 394a123418b39a827467598bf19aef7d9e0a3c3c Mon Sep 17 00:00:00 2001 +From: Mehdi Sabwat <mehdi@videolabs.io> +Date: Wed, 3 Aug 2022 17:17:27 +0200 +Subject: [PATCH 71/77] don't call emscripten_set_main_loop twice + +--- + modules/codec/webcodec.cpp | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/modules/codec/webcodec.cpp b/modules/codec/webcodec.cpp +index 07b3690dab..fd93713480 100644 +--- a/modules/codec/webcodec.cpp ++++ b/modules/codec/webcodec.cpp +@@ -358,7 +358,7 @@ static void* WebcodecDecodeWorker( void* arg ) + auto vctxPrivate = static_cast<webcodec_context*>( + vlc_video_context_GetPrivate(sys->vctx, VLC_VIDEO_CONTEXT_WEBCODEC)); + vctxPrivate->decoder_worker = pthread_self(); +- emscripten_set_main_loop(mainloop_tick, 1, true); ++ + return NULL; + } + +-- +2.35.1 + diff --git a/vlc_patches/aug/0072-Revert-emscripten-add-js-File-access-plugin.patch b/vlc_patches/aug/0072-Revert-emscripten-add-js-File-access-plugin.patch new file mode 100644 index 0000000000000000000000000000000000000000..20a093f103fa2649f310f60b77ff5eec81c14d16 --- /dev/null +++ b/vlc_patches/aug/0072-Revert-emscripten-add-js-File-access-plugin.patch @@ -0,0 +1,336 @@ +From 000a50334fe09da173a986455c796d97c348108c Mon Sep 17 00:00:00 2001 +From: Mehdi Sabwat <mehdi@videolabs.io> +Date: Mon, 8 Aug 2022 13:55:50 +0200 +Subject: [PATCH 72/77] Revert "emscripten: add js File access plugin" + +This reverts commit 49f585a2a5fbbc04d85fbccbf63881d2a659cc54. +--- + modules/access/Makefile.am | 5 - + modules/access/emjsfile.c | 299 ------------------------------------- + 2 files changed, 304 deletions(-) + delete mode 100644 modules/access/emjsfile.c + +diff --git a/modules/access/Makefile.am b/modules/access/Makefile.am +index cc827027df..d4f6b5f10a 100644 +--- a/modules/access/Makefile.am ++++ b/modules/access/Makefile.am +@@ -26,11 +26,6 @@ libfilesystem_plugin_la_LIBADD = -lshlwapi + endif + access_LTLIBRARIES += libfilesystem_plugin.la + +-if HAVE_EMSCRIPTEN +-libemjsfile_plugin_la_SOURCES = access/emjsfile.c +-access_LTLIBRARIES += libemjsfile_plugin.la +-endif +- + libidummy_plugin_la_SOURCES = access/idummy.c + access_LTLIBRARIES += libidummy_plugin.la + +diff --git a/modules/access/emjsfile.c b/modules/access/emjsfile.c +deleted file mode 100644 +index 0fea11afc4..0000000000 +--- a/modules/access/emjsfile.c ++++ /dev/null +@@ -1,299 +0,0 @@ +-/***************************************************************************** +- * emjsfile.c: emscripten js file access plugin +- ***************************************************************************** +- * Copyright (C) 2022 VLC authors Videolabs, and VideoLAN +- * +- * +- * This program is free software; you can redistribute it and/or modify it +- * under the terms of the GNU Lesser General Public License as published by +- * the Free Software Foundation; either version 2.1 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +- * GNU Lesser General Public License for more details. +- * +- * You should have received a copy of the GNU Lesser General Public License +- * along with this program; if not, write to the Free Software Foundation, +- * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. +- *****************************************************************************/ +- +-#ifdef HAVE_CONFIG_H +-# include "config.h" +-#endif +- +-#include <vlc_common.h> +-#include <vlc_plugin.h> +-#include <vlc_access.h> +-#include <vlc_threads.h> +- +-#include <emscripten.h> +- +-typedef struct +-{ +- uint64_t offset; +- uint64_t js_file_size; +-} access_sys_t; +- +-static ssize_t Read (stream_t *p_access, void *buffer, size_t size) { +- access_sys_t *p_sys = p_access->p_sys; +- +- size_t offset = p_sys->offset; +- size_t js_file_size = p_sys->js_file_size; +- +- if (offset >= js_file_size) +- return 0; +- if (size > offset + js_file_size) { +- size = js_file_size - offset; +- } +- EM_ASM({ +- const offset = $0; +- const buffer = $1; +- const size = $2; +- const blob = Module.vlcAccess[$3].worker_js_file.slice(offset, offset + size); +- HEAPU8.set(new Uint8Array(Module.vlcAccess[$3].reader.readAsArrayBuffer(blob)), buffer); +- }, offset, buffer, size, p_access); +- p_sys->offset += size; +- return size; +-} +- +-static int Seek (stream_t *p_access, uint64_t offset) { +- access_sys_t *p_sys = p_access->p_sys; +- +- p_sys->offset = offset; +- return VLC_SUCCESS; +-} +- +-static int get_js_file_size(stream_t *p_access, uint64_t *value) { +- /* +- to avoid RangeError on BigUint64 view creation, +- the start offset must be a multiple of 8. +- */ +- if ((uintptr_t)value % 8 != 0) { +- msg_Err(p_access, "error: value is not aligned in get_js_file_size!"); +- return 1; +- } +- return EM_ASM_INT({ +- try { +- var v = new BigUint64Array(wasmMemory.buffer, $0, 1); +- v[0] = BigInt(Module.vlcAccess[$1].worker_js_file.size); +- return 0; +- } +- catch (error) { +- if (error instanceof RangeError) { +- // TODO: +- // RangeError: start offset of BigUint64Array should be a multiple of 8 +- // unaligned accesses are not supported on all platforms? +- return 0; +- } +- console.error("get_js_file_size error: " + error); +- return 1; +- } +- }, value, p_access); +-} +- +-static int Control( stream_t *p_access, int i_query, va_list args ) +-{ +- bool *pb_bool; +- vlc_tick_t *pi_64; +- access_sys_t *p_sys = p_access->p_sys; +- +- switch( i_query ) +- { +- case STREAM_CAN_SEEK: +- case STREAM_CAN_FASTSEEK: +- pb_bool = va_arg( args, bool * ); +- *pb_bool = true; +- break; +- +- case STREAM_CAN_PAUSE: +- case STREAM_CAN_CONTROL_PACE: +- pb_bool = va_arg( args, bool * ); +- *pb_bool = true; +- break; +- +- case STREAM_GET_SIZE: +- { +- *va_arg( args, uint64_t * ) = p_sys->js_file_size; +- break; +- } +- +- case STREAM_GET_PTS_DELAY: +- pi_64 = va_arg( args, vlc_tick_t * ); +- *pi_64 = VLC_TICK_FROM_MS( +- var_InheritInteger (p_access, "file-caching") ); +- break; +- +- case STREAM_SET_PAUSE_STATE: +- break; +- +- default: +- return VLC_EGENERIC; +- +- } +- return VLC_SUCCESS; +-} +- +-EM_ASYNC_JS(int, init_js_file, (stream_t *p_access, int id), { +- let p = new Promise((resolve, reject) => { +- function handleFileResult(e) { +- const msg = e['data']; +- if (msg.type === 'FileResult') { +- self.removeEventListener('message', handleFileResult); +- if (msg.file !== undefined) { +- Module.vlcAccess[p_access].worker_js_file = msg.file; +- Module.vlcAccess[p_access].reader = new FileReaderSync(); +- resolve(); +- } +- else { +- reject("error: sent an undefined File object from the main thread"); +- } +- } +- else if (msg.type === 'ErrorVLCAccessFileUndefined') { +- reject("error: vlc_access_file object is not defined"); +- } +- else if (msg.type === 'ErrorRequestFileMessageWithoutId') { +- reject("error: request file message send without an id"); +- } +- else if (msg.type === 'ErrorMissingFile') { +- reject("error: missing file, bad id or vlc_access_file[id] is not defined"); +- } +- } +- self.addEventListener('message', handleFileResult); +- }); +- let timer = undefined; +- let timeout = new Promise(function (resolve, reject) { +- timer = setTimeout(resolve, 1000, 'timeout') +- }); +- let promises = [p, timeout]; +- /* id must be unique */ +- self.postMessage({ cmd: "customCmd", type: "requestFile", id: id}); +- try { +- let return_value = await Promise.race(promises); +- if (return_value === 'timeout') { +- console.error("vlc_access timeout: could not get file!"); +- } +- } +- catch(error) { +- console.error("vlc_access error in init_js_file(): ", error); +- return 1; +- } +- clearTimeout(timer); +- return 0; +-}); +- +-static int EmFileOpen( vlc_object_t *p_this ) { +- stream_t *p_access = (stream_t*)p_this; +- +- /* init per worker module.vlcAccess object */ +- EM_ASM({ +- if (Module.vlcAccess === undefined) { +- Module.vlcAccess = {}; +- } +- (Module.vlcAccess[$0] = {worker_js_file: undefined, reader: undefined}); +- }, p_access); +- +- /* +- This block will run in the main thread, to access the DOM. +- When the user selects a file, it is assigned to the Module.vlc_access_file +- array. +- +- We listen to 'message' events so that when the file is requested by the +- input thread, we can answer and send the File object from the main thread. +- */ +- MAIN_THREAD_EM_ASM({ +- const thread_id = $0; +- let w = Module.PThread.pthreads[thread_id]; +- function handleFileRequest(e) { +- const msg = e.data; +- if (msg.type === "requestFile") { +- w.removeEventListener('message', handleFileRequest); +- if (Module.vlc_access_file === undefined) { +- console.error("vlc_access_file property missing!"); +- w.postMessage({ cmd: "customCmd", +- type: "ErrorVLCAccessFileUndefined" +- }); +- return ; +- } +- if (msg.id === undefined) { +- console.error("id property missing in requestFile message!"); +- w.postMessage({ cmd: "customCmd", +- type: "ErrorRequestFileMessageWithoutId" +- }); +- return ; +- } +- if (Module.vlc_access_file[msg.id] === undefined) { +- console.error("error file missing!"); +- w.postMessage({ cmd: "customCmd", +- type: "ErrorMissingFile" +- }); +- return ; +- } +- /* +- keeping the File object in the main thread too, +- in case we want to reopen the media, without re-selecting +- the file. +- */ +- w.postMessage({ cmd: "customCmd", +- type: "FileResult", +- file: Module.vlc_access_file[msg.id] +- }); +- } +- } +- w.addEventListener('message', handleFileRequest); +- }, pthread_self()); +- +- int id = atoi(p_access->psz_location); +- if (id == 0) { +- msg_Err(p_access, "error: failed init uri has invalid id!"); +- return VLC_EGENERIC; +- } +- +- /* +- Request the file from the main thread. +- If it was not selected, it will return an error. +- +- to open a file, we need to call libvlc_media_new_location with +- the following uri : emjsfile://<id> +- */ +- if (init_js_file(p_access, id)) { +- msg_Err(p_access, "EMJsFile error: failed init!"); +- return VLC_EGENERIC; +- } +- +- access_sys_t *p_sys = vlc_obj_malloc(p_this, sizeof (*p_sys)); +- if (unlikely(p_sys == NULL)) +- return VLC_ENOMEM; +- +- p_access->pf_read = Read; +- p_access->pf_block = NULL; +- p_access->pf_control = Control; +- p_access->pf_seek = Seek; +- p_access->p_sys = p_sys; +- p_sys->js_file_size = 0; +- p_sys->offset = 0; +- if (get_js_file_size(p_access, &p_sys->js_file_size)) { +- msg_Err(p_access, "EMJsFile error: could not get file size!"); +- return VLC_EGENERIC; +- } +- +- return VLC_SUCCESS; +-} +- +-static void EmFileClose (vlc_object_t * p_this) { +- stream_t *p_access = (stream_t*)p_this; +- EM_ASM({ +- Module.vlcAccess[$0].worker_js_file = undefined; +- Module.vlcAccess[$0].reader = undefined; +- }, p_access); +-} +- +-vlc_module_begin () +- set_description( N_("Emscripten module to allow reading local files from the DOM's <input>") ) +- set_shortname( N_("Emscripten Local File Input") ) +- set_subcategory( SUBCAT_INPUT_ACCESS ) +- set_capability( "access", 50 ) +- add_shortcut( "emjsfile" ) +- set_callbacks( EmFileOpen, EmFileClose ) +-vlc_module_end() +-- +2.35.1 + diff --git a/vlc_patches/aug/0073-emscripten-add-js-File-access-plugin.patch b/vlc_patches/aug/0073-emscripten-add-js-File-access-plugin.patch new file mode 100644 index 0000000000000000000000000000000000000000..d8f31f0b2096e8771af342c01d058b78df290208 --- /dev/null +++ b/vlc_patches/aug/0073-emscripten-add-js-File-access-plugin.patch @@ -0,0 +1,368 @@ +From 77373ae9ca4cb51cadc14a12e2d5d928c5328727 Mon Sep 17 00:00:00 2001 +From: Mehdi Sabwat <mehdi@videolabs.io> +Date: Thu, 9 Jun 2022 21:05:04 +0200 +Subject: [PATCH 73/77] emscripten: add js File access plugin + +Emscripten currently does not support picking a file from the DOM with the element, and reading it from a webassembly application. + +This access plugin will be able to receive a File object handle in the input thread, and read into buffers in linear memory with the file content while keeping track of the offset. + +A basic setup could look like : + +``` +<input type="file" id="fpicker_btn">Select File</input> +<script> +let instance = _libvlc_new(); +let media_player_ptr = _libvlc_media_player_new(instance); + +/* + Module is the emscripten runtime object, + it has the vlc_access_file array as a property +*/ + +let inputElement = document.getElementById("fpicker_btn"); + +function handleFile() { + var name = this.files.item(0).name; + console.log("opened file: ", name); + // id starts at 1 + let id = 1; + let path_ptr = Module.allocateUTF8("emjsfile://" + id); + let media_ptr = _libvlc_media_new_location(path_ptr); + Module._free(path_ptr); + _libvlc_media_player_set_media(media_player_ptr, media_ptr); + Module['vlc_access_file'][id] = this.files.item(0); +} +inputElement.addEventListener("change", handleFile, false); + +_libvlc_media_player_play(media_player_ptr); +</script> +``` +--- + modules/access/Makefile.am | 5 + + modules/access/emjsfile.c | 297 +++++++++++++++++++++++++++++++++++++ + 2 files changed, 302 insertions(+) + create mode 100644 modules/access/emjsfile.c + +diff --git a/modules/access/Makefile.am b/modules/access/Makefile.am +index d4f6b5f10a..cc827027df 100644 +--- a/modules/access/Makefile.am ++++ b/modules/access/Makefile.am +@@ -26,6 +26,11 @@ libfilesystem_plugin_la_LIBADD = -lshlwapi + endif + access_LTLIBRARIES += libfilesystem_plugin.la + ++if HAVE_EMSCRIPTEN ++libemjsfile_plugin_la_SOURCES = access/emjsfile.c ++access_LTLIBRARIES += libemjsfile_plugin.la ++endif ++ + libidummy_plugin_la_SOURCES = access/idummy.c + access_LTLIBRARIES += libidummy_plugin.la + +diff --git a/modules/access/emjsfile.c b/modules/access/emjsfile.c +new file mode 100644 +index 0000000000..139b12b68a +--- /dev/null ++++ b/modules/access/emjsfile.c +@@ -0,0 +1,297 @@ ++/***************************************************************************** ++ * emjsfile.c: emscripten js file access plugin ++ ***************************************************************************** ++ * Copyright (C) 2022 VLC authors Videolabs, and VideoLAN ++ * ++ * ++ * This program is free software; you can redistribute it and/or modify it ++ * under the terms of the GNU Lesser General Public License as published by ++ * the Free Software Foundation; either version 2.1 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU Lesser General Public License for more details. ++ * ++ * You should have received a copy of the GNU Lesser General Public License ++ * along with this program; if not, write to the Free Software Foundation, ++ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. ++ *****************************************************************************/ ++ ++#ifdef HAVE_CONFIG_H ++# include "config.h" ++#endif ++ ++#include <vlc_common.h> ++#include <vlc_plugin.h> ++#include <vlc_access.h> ++#include <vlc_threads.h> ++ ++#include <emscripten.h> ++ ++typedef struct ++{ ++ uint64_t offset; ++ uint64_t js_file_size; ++} access_sys_t; ++ ++static ssize_t Read (stream_t *p_access, void *buffer, size_t size) { ++ access_sys_t *p_sys = p_access->p_sys; ++ ++ size_t offset = p_sys->offset; ++ size_t js_file_size = p_sys->js_file_size; ++ ++ if (offset >= js_file_size) ++ return 0; ++ if (size > offset + js_file_size) { ++ size = js_file_size - offset; ++ } ++ EM_ASM({ ++ const offset = $0; ++ const buffer = $1; ++ const size = $2; ++ const blob = Module.vlcAccess[$3].worker_js_file.slice(offset, offset + size); ++ HEAPU8.set(new Uint8Array(Module.vlcAccess[$3].reader.readAsArrayBuffer(blob)), buffer); ++ }, offset, buffer, size, p_access); ++ p_sys->offset += size; ++ return size; ++} ++ ++static int Seek (stream_t *p_access, uint64_t offset) { ++ access_sys_t *p_sys = p_access->p_sys; ++ ++ p_sys->offset = offset; ++ return VLC_SUCCESS; ++} ++ ++static int get_js_file_size(stream_t *p_access, uint64_t *value) { ++ /* ++ to avoid RangeError on BigUint64 view creation, ++ the start offset must be a multiple of 8. ++ */ ++ if ((uintptr_t)value % 8 != 0) { ++ msg_Err(p_access, "error: value is not aligned in get_js_file_size!"); ++ return VLC_EGENERIC; ++ } ++ return (EM_ASM_INT({ ++ try { ++ var v = new BigUint64Array(wasmMemory.buffer, $0, 1); ++ v[0] = BigInt(Module.vlcAccess[$1].worker_js_file.size); ++ return 0; ++ } ++ catch (error) { ++ console.error("get_js_file_size error: " + error); ++ return 1; ++ } ++ }, value, p_access) == 0) ? VLC_SUCCESS: VLC_EGENERIC; ++} ++ ++static int Control( stream_t *p_access, int i_query, va_list args ) ++{ ++ bool *pb_bool; ++ vlc_tick_t *pi_64; ++ access_sys_t *p_sys = p_access->p_sys; ++ ++ switch( i_query ) ++ { ++ case STREAM_CAN_SEEK: ++ case STREAM_CAN_FASTSEEK: ++ pb_bool = va_arg( args, bool * ); ++ *pb_bool = true; ++ break; ++ ++ case STREAM_CAN_PAUSE: ++ case STREAM_CAN_CONTROL_PACE: ++ pb_bool = va_arg( args, bool * ); ++ *pb_bool = true; ++ break; ++ ++ case STREAM_GET_SIZE: ++ { ++ *va_arg( args, uint64_t * ) = p_sys->js_file_size; ++ break; ++ } ++ ++ case STREAM_GET_PTS_DELAY: ++ pi_64 = va_arg( args, vlc_tick_t * ); ++ *pi_64 = VLC_TICK_FROM_MS( ++ var_InheritInteger (p_access, "file-caching") ); ++ break; ++ ++ case STREAM_SET_PAUSE_STATE: ++ break; ++ ++ default: ++ return VLC_EGENERIC; ++ ++ } ++ return VLC_SUCCESS; ++} ++ ++EM_ASYNC_JS(int, init_js_file, (stream_t *p_access, long id), { ++ let p = new Promise((resolve, reject) => { ++ function handleFileResult(e) { ++ const msg = e['data']; ++ if (msg.type === 'FileResult') { ++ self.removeEventListener('message', handleFileResult); ++ if (msg.file !== undefined) { ++ Module.vlcAccess[p_access].worker_js_file = msg.file; ++ Module.vlcAccess[p_access].reader = new FileReaderSync(); ++ resolve(); ++ } ++ else { ++ reject("error: sent an undefined File object from the main thread"); ++ } ++ } ++ else if (msg.type === 'ErrorVLCAccessFileUndefined') { ++ reject("error: vlc_access_file object is not defined"); ++ } ++ else if (msg.type === 'ErrorRequestFileMessageWithoutId') { ++ reject("error: request file message send without an id"); ++ } ++ else if (msg.type === 'ErrorMissingFile') { ++ reject("error: missing file, bad id or vlc_access_file[id] is not defined"); ++ } ++ } ++ self.addEventListener('message', handleFileResult); ++ }); ++ let timer = undefined; ++ let timeout = new Promise(function (resolve, reject) { ++ timer = setTimeout(resolve, 1000, 'timeout') ++ }); ++ let promises = [p, timeout]; ++ /* id must be unique */ ++ self.postMessage({ cmd: "customCmd", type: "requestFile", id: id}); ++ let return_value = 0; ++ try { ++ let value = await Promise.race(promises); ++ if (value === 'timeout') { ++ console.error("vlc_access timeout: could not get file!"); ++ return_value = 1; ++ } ++ } ++ catch(error) { ++ console.error("vlc_access error in init_js_file(): ", error); ++ return_value = 1; ++ } ++ clearTimeout(timer); ++ return return_value; ++}); ++ ++static int EmFileOpen( vlc_object_t *p_this ) { ++ stream_t *p_access = (stream_t*)p_this; ++ ++ /* init per worker module.vlcAccess object */ ++ EM_ASM({ ++ if (Module.vlcAccess === undefined) { ++ Module.vlcAccess = {}; ++ } ++ (Module.vlcAccess[$0] = {worker_js_file: undefined, reader: undefined}); ++ }, p_access); ++ ++ /* ++ This block will run in the main thread, to access the DOM. ++ When the user selects a file, it is assigned to the Module.vlc_access_file ++ array. ++ ++ We listen to 'message' events so that when the file is requested by the ++ input thread, we can answer and send the File object from the main thread. ++ */ ++ MAIN_THREAD_EM_ASM({ ++ const thread_id = $0; ++ let w = Module.PThread.pthreads[thread_id].worker; ++ function handleFileRequest(e) { ++ const msg = e.data; ++ if (msg.type === "requestFile") { ++ w.removeEventListener('message', handleFileRequest); ++ if (Module.vlc_access_file === undefined) { ++ console.error("vlc_access_file property missing!"); ++ w.postMessage({ cmd: "customCmd", ++ type: "ErrorVLCAccessFileUndefined" ++ }); ++ return ; ++ } ++ if (msg.id === undefined) { ++ console.error("id property missing in requestFile message!"); ++ w.postMessage({ cmd: "customCmd", ++ type: "ErrorRequestFileMessageWithoutId" ++ }); ++ return ; ++ } ++ if (Module.vlc_access_file[msg.id] === undefined) { ++ console.error("error file missing!"); ++ w.postMessage({ cmd: "customCmd", ++ type: "ErrorMissingFile" ++ }); ++ return ; ++ } ++ /* ++ keeping the File object in the main thread too, ++ in case we want to reopen the media, without re-selecting ++ the file. ++ */ ++ w.postMessage({ cmd: "customCmd", ++ type: "FileResult", ++ file: Module.vlc_access_file[msg.id] ++ }); ++ } ++ } ++ w.addEventListener('message', handleFileRequest); ++ }, pthread_self()); ++ ++ char *endPtr; ++ long id = strtol(p_access->psz_location, &endPtr, 10); ++ if ((endPtr == p_access->psz_location) || (*endPtr != '\0')) { ++ msg_Err(p_access, "error: failed init uri has invalid id!"); ++ return VLC_EGENERIC; ++ } ++ ++ /* ++ Request the file from the main thread. ++ If it was not selected, it will return an error. ++ ++ To open a file, we need to call libvlc_media_new_location with ++ the following uri : emjsfile://<id> ++ To avoid confusion with atoi() return error, id starts at 1. ++ */ ++ if (init_js_file(p_access, id)) { ++ msg_Err(p_access, "EMJsFile error: failed init!"); ++ return VLC_EGENERIC; ++ } ++ ++ access_sys_t *p_sys = vlc_obj_malloc(p_this, sizeof (*p_sys)); ++ if (unlikely(p_sys == NULL)) ++ return VLC_ENOMEM; ++ ++ p_access->pf_read = Read; ++ p_access->pf_block = NULL; ++ p_access->pf_control = Control; ++ p_access->pf_seek = Seek; ++ p_access->p_sys = p_sys; ++ p_sys->js_file_size = 0; ++ p_sys->offset = 0; ++ if (get_js_file_size(p_access, &p_sys->js_file_size)) { ++ msg_Err(p_access, "EMJsFile error: could not get file size!"); ++ return VLC_EGENERIC; ++ } ++ ++ return VLC_SUCCESS; ++} ++ ++static void EmFileClose (vlc_object_t * p_this) { ++ stream_t *p_access = (stream_t*)p_this; ++ EM_ASM({ ++ Module.vlcAccess[$0].worker_js_file = undefined; ++ Module.vlcAccess[$0].reader = undefined; ++ }, p_access); ++} ++ ++vlc_module_begin () ++ set_description( N_("Emscripten module to allow reading local files from the DOM's <input>") ) ++ set_shortname( N_("Emscripten Local File Input") ) ++ set_subcategory( SUBCAT_INPUT_ACCESS ) ++ set_capability( "access", 0 ) ++ add_shortcut( "emjsfile" ) ++ set_callbacks( EmFileOpen, EmFileClose ) ++vlc_module_end() +-- +2.35.1 + diff --git a/vlc_patches/aug/0074-emjsfile-Close-file-access-if-open-failed.patch b/vlc_patches/aug/0074-emjsfile-Close-file-access-if-open-failed.patch new file mode 100644 index 0000000000000000000000000000000000000000..648eb7e03423c594ae8c953a8a14315b608f5e2e --- /dev/null +++ b/vlc_patches/aug/0074-emjsfile-Close-file-access-if-open-failed.patch @@ -0,0 +1,75 @@ +From 935282fa99a0356589692c3493e143eccb77cd2d Mon Sep 17 00:00:00 2001 +From: Mehdi Sabwat <mehdi@videolabs.io> +Date: Mon, 8 Aug 2022 13:46:48 +0200 +Subject: [PATCH 74/77] emjsfile: Close file access if open() failed + +--- + modules/access/emjsfile.c | 25 +++++++++++++------------ + 1 file changed, 13 insertions(+), 12 deletions(-) + +diff --git a/modules/access/emjsfile.c b/modules/access/emjsfile.c +index 139b12b68a..488b6fb449 100644 +--- a/modules/access/emjsfile.c ++++ b/modules/access/emjsfile.c +@@ -179,6 +179,14 @@ EM_ASYNC_JS(int, init_js_file, (stream_t *p_access, long id), { + return return_value; + }); + ++static void EmFileClose (vlc_object_t * p_this) { ++ stream_t *p_access = (stream_t*)p_this; ++ EM_ASM({ ++ Module.vlcAccess[$0].worker_js_file = undefined; ++ Module.vlcAccess[$0].reader = undefined; ++ }, p_access); ++} ++ + static int EmFileOpen( vlc_object_t *p_this ) { + stream_t *p_access = (stream_t*)p_this; + +@@ -247,6 +255,10 @@ static int EmFileOpen( vlc_object_t *p_this ) { + return VLC_EGENERIC; + } + ++ access_sys_t *p_sys = vlc_obj_malloc(p_this, sizeof (*p_sys)); ++ if (unlikely(p_sys == NULL)) ++ return VLC_ENOMEM; ++ + /* + Request the file from the main thread. + If it was not selected, it will return an error. +@@ -260,10 +272,6 @@ static int EmFileOpen( vlc_object_t *p_this ) { + return VLC_EGENERIC; + } + +- access_sys_t *p_sys = vlc_obj_malloc(p_this, sizeof (*p_sys)); +- if (unlikely(p_sys == NULL)) +- return VLC_ENOMEM; +- + p_access->pf_read = Read; + p_access->pf_block = NULL; + p_access->pf_control = Control; +@@ -273,20 +281,13 @@ static int EmFileOpen( vlc_object_t *p_this ) { + p_sys->offset = 0; + if (get_js_file_size(p_access, &p_sys->js_file_size)) { + msg_Err(p_access, "EMJsFile error: could not get file size!"); ++ EmFileClose(p_this); + return VLC_EGENERIC; + } + + return VLC_SUCCESS; + } + +-static void EmFileClose (vlc_object_t * p_this) { +- stream_t *p_access = (stream_t*)p_this; +- EM_ASM({ +- Module.vlcAccess[$0].worker_js_file = undefined; +- Module.vlcAccess[$0].reader = undefined; +- }, p_access); +-} +- + vlc_module_begin () + set_description( N_("Emscripten module to allow reading local files from the DOM's <input>") ) + set_shortname( N_("Emscripten Local File Input") ) +-- +2.35.1 + diff --git a/vlc_patches/aug/0075-emjsfile-document-js_file_size-struct-field.patch b/vlc_patches/aug/0075-emjsfile-document-js_file_size-struct-field.patch new file mode 100644 index 0000000000000000000000000000000000000000..c231ebeb954b0ecdb260b4ad0420647b09a9ec1e --- /dev/null +++ b/vlc_patches/aug/0075-emjsfile-document-js_file_size-struct-field.patch @@ -0,0 +1,32 @@ +From e913df9e055f1fee9d17d9b23ed46d59600c89b2 Mon Sep 17 00:00:00 2001 +From: Mehdi Sabwat <mehdi@videolabs.io> +Date: Mon, 8 Aug 2022 13:49:54 +0200 +Subject: [PATCH 75/77] emjsfile: document js_file_size struct field + +--- + modules/access/emjsfile.c | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/modules/access/emjsfile.c b/modules/access/emjsfile.c +index 488b6fb449..245a6a7201 100644 +--- a/modules/access/emjsfile.c ++++ b/modules/access/emjsfile.c +@@ -27,13 +27,14 @@ + #include <vlc_plugin.h> + #include <vlc_access.h> + #include <vlc_threads.h> ++#include <stdalign.h> + + #include <emscripten.h> + + typedef struct + { + uint64_t offset; +- uint64_t js_file_size; ++ uint64_t alignas(8) js_file_size; + } access_sys_t; + + static ssize_t Read (stream_t *p_access, void *buffer, size_t size) { +-- +2.35.1 + diff --git a/vlc_patches/aug/0076-emjsfile-adapt-to-emscripten-change.patch b/vlc_patches/aug/0076-emjsfile-adapt-to-emscripten-change.patch new file mode 100644 index 0000000000000000000000000000000000000000..7e7d0a21a364089be1f1fa200a4f6018abff5aa2 --- /dev/null +++ b/vlc_patches/aug/0076-emjsfile-adapt-to-emscripten-change.patch @@ -0,0 +1,25 @@ +From 153eecd6159eaae8ab98dd98accc6eb7f033064d Mon Sep 17 00:00:00 2001 +From: Mehdi Sabwat <mehdi@videolabs.io> +Date: Mon, 8 Aug 2022 16:55:53 +0200 +Subject: [PATCH 76/77] emjsfile: adapt to emscripten change + +--- + modules/access/emjsfile.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/modules/access/emjsfile.c b/modules/access/emjsfile.c +index 245a6a7201..9123a4f0fc 100644 +--- a/modules/access/emjsfile.c ++++ b/modules/access/emjsfile.c +@@ -209,7 +209,7 @@ static int EmFileOpen( vlc_object_t *p_this ) { + */ + MAIN_THREAD_EM_ASM({ + const thread_id = $0; +- let w = Module.PThread.pthreads[thread_id].worker; ++ let w = Module.PThread.pthreads[thread_id]; + function handleFileRequest(e) { + const msg = e.data; + if (msg.type === "requestFile") { +-- +2.35.1 + diff --git a/vlc_patches/aug/0077-wip-allow-decoding-NULL-blocks-drain.patch b/vlc_patches/aug/0077-wip-allow-decoding-NULL-blocks-drain.patch new file mode 100644 index 0000000000000000000000000000000000000000..18abe207b45d787f161fa27a628b7a6d4ac34609 --- /dev/null +++ b/vlc_patches/aug/0077-wip-allow-decoding-NULL-blocks-drain.patch @@ -0,0 +1,24 @@ +From c3e109460e95fba32914a1b525a8b3f85b705ff9 Mon Sep 17 00:00:00 2001 +From: Mehdi Sabwat <mehdi@videolabs.io> +Date: Mon, 8 Aug 2022 16:57:45 +0200 +Subject: [PATCH 77/77] wip: allow decoding NULL blocks (drain ?) + +--- + modules/codec/webcodec.cpp | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/modules/codec/webcodec.cpp b/modules/codec/webcodec.cpp +index fd93713480..aefe6a6153 100644 +--- a/modules/codec/webcodec.cpp ++++ b/modules/codec/webcodec.cpp +@@ -396,6 +396,7 @@ EM_ASYNC_JS(void, initDecoderWorkerMessagePort, (decoder_t* dec), { + + static int Decode( decoder_t* dec, block_t* block ) + { ++ if ( !block ) return VLCDEC_SUCCESS; + initDecoderWorkerMessagePort(dec); + EM_ASM({ + Module.decoderWorkerPort.postMessage({ +-- +2.35.1 + diff --git a/vlc_patches/demo_alpha/0001-wip-vlc.js-patchset.patch b/vlc_patches/demo_alpha/0001-wip-vlc.js-patchset.patch new file mode 100644 index 0000000000000000000000000000000000000000..95c65ba823e072c61943b5978a08f704cfa64b12 --- /dev/null +++ b/vlc_patches/demo_alpha/0001-wip-vlc.js-patchset.patch @@ -0,0 +1,2990 @@ +From 88e59dbaa68156d0a00c88217a9e1a4c6502b731 Mon Sep 17 00:00:00 2001 +From: Mehdi Sabwat <mehdi@videolabs.io> +Date: Tue, 27 Apr 2021 15:34:23 +0200 +Subject: [PATCH 1/1] wip: vlc.js patchset + +--- + configure.ac | 10 +- + contrib/src/aribb24/rules.mak | 4 + + contrib/src/ass/rules.mak | 5 + + ...nitial-support-for-wasm32-emscripten.patch | 105 ++++ + contrib/src/fontconfig/rules.mak | 4 + + contrib/src/gcrypt/rules.mak | 6 + + contrib/src/gmp/rules.mak | 3 + + contrib/src/gnutls/rules.mak | 4 + + contrib/src/gpg-error/emscripten.patch | 43 ++ + contrib/src/gpg-error/rules.mak | 1 + + contrib/src/png/rules.mak | 4 + + contrib/src/zlib/rules.mak | 4 + + extras/package/wasm-emscripten/build.sh | 5 + + include/vlc_codec.h | 1 + + include/vlc_fourcc.h | 3 + + include/vlc_opengl_filter.h | 157 ++++++ + include/vlc_picture.h | 1 + + include/vlc_vout_display.h | 2 + + include/vlc_window.h | 3 + + modules/Makefile.am | 1 + + modules/audio_output/Makefile.am | 5 + + modules/audio_output/emscripten.cpp | 421 ++++++++++++++ + modules/codec/Makefile.am | 6 + + modules/codec/webcodec.cpp | 518 ++++++++++++++++++ + modules/demux/Makefile.am | 2 + + modules/hw/emscripten/Makefile.am | 10 + + modules/hw/emscripten/converter.cpp | 137 +++++ + modules/video_output/Makefile.am | 14 + + modules/video_output/emscripten.cpp | 257 +++++++++ + modules/video_output/emscripten/common.cpp | 82 +++ + modules/video_output/emscripten/common.h | 57 ++ + modules/video_output/opengl/Makefile.am | 6 + + .../video_output/opengl/egl_display_generic.c | 4 +- + modules/video_output/opengl/filter.c | 1 + + modules/video_output/opengl/filter.h | 137 +---- + modules/video_output/opengl/filter_mock.c | 5 + + modules/video_output/opengl/filter_priv.h | 1 + + modules/video_output/opengl/filters.c | 1 + + .../opengl/interop_emscripten.cpp | 287 ++++++++++ + modules/video_output/opengl/renderer.c | 1 + + modules/video_output/opengl/sampler.c | 27 +- + modules/video_output/opengl/sampler.h | 9 + + src/misc/fourcc.c | 2 + + src/video_output/display.c | 2 +- + src/video_output/video_output.c | 31 +- + 45 files changed, 2234 insertions(+), 155 deletions(-) + create mode 100644 contrib/src/fontconfig/add-initial-support-for-wasm32-emscripten.patch + create mode 100644 contrib/src/gpg-error/emscripten.patch + create mode 100644 include/vlc_opengl_filter.h + create mode 100644 modules/audio_output/emscripten.cpp + create mode 100644 modules/codec/webcodec.cpp + create mode 100644 modules/hw/emscripten/Makefile.am + create mode 100644 modules/hw/emscripten/converter.cpp + create mode 100644 modules/video_output/emscripten.cpp + create mode 100644 modules/video_output/emscripten/common.cpp + create mode 100644 modules/video_output/emscripten/common.h + create mode 100644 modules/video_output/opengl/interop_emscripten.cpp + +diff --git a/configure.ac b/configure.ac +index d0e5f8e216..9ef4138d9f 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -3122,13 +3122,17 @@ have_gl="no" + PKG_CHECK_MODULES([GL], [gl], [ + have_gl="yes" + ], [ +- AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ ++ AC_MSG_CHECKING([for OpenGL]) ++ AC_LINK_IFELSE([AC_LANG_PROGRAM([[ + #ifdef _WIN32 + # include <GL/glew.h> + #endif + #include <GL/gl.h> +-]], [ +- [int t0 = GL_TEXTURE0;]]) ++]], [[ ++ int t0 = GL_TEXTURE0; ++ // glColorMaterial is unavailable in webgl, and emscripten ++ glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE); ++ ]]) + ], [ + GL_CFLAGS="" + AS_IF([test "${SYS}" != "mingw32"], [ +diff --git a/contrib/src/aribb24/rules.mak b/contrib/src/aribb24/rules.mak +index 0c0ba9ff91..ba8a70b6d5 100644 +--- a/contrib/src/aribb24/rules.mak ++++ b/contrib/src/aribb24/rules.mak +@@ -25,6 +25,10 @@ aribb24: aribb24-$(ARIBB24_VERSION).tar.gz .sum-aribb24 + + DEPS_aribb24 = png + ++ifdef HAVE_EMSCRIPTEN ++CFLAGS+="-pthread" ++endif ++ + .aribb24: aribb24 + $(REQUIRE_GPL) + $(REQUIRE_GNUV3) +diff --git a/contrib/src/ass/rules.mak b/contrib/src/ass/rules.mak +index d5fd21ff29..f70216f028 100644 +--- a/contrib/src/ass/rules.mak ++++ b/contrib/src/ass/rules.mak +@@ -25,6 +25,11 @@ WITH_DWRITE = 1 + else + WITH_FONTCONFIG = 1 + WITH_HARFBUZZ = 1 ++ifdef HAVE_EMSCRIPTEN ++WITH_FONTCONFIG = 1 ++WITH_HARFBUZZ = 1 ++WITH_ASS_ASM = 0 ++endif + endif + endif + endif +diff --git a/contrib/src/fontconfig/add-initial-support-for-wasm32-emscripten.patch b/contrib/src/fontconfig/add-initial-support-for-wasm32-emscripten.patch +new file mode 100644 +index 0000000000..b1308fb19e +--- /dev/null ++++ b/contrib/src/fontconfig/add-initial-support-for-wasm32-emscripten.patch +@@ -0,0 +1,105 @@ ++From b7f21ca85efd78c8034223c63786a0c01b8378fe Mon Sep 17 00:00:00 2001 ++From: Mehdi Sabwat <mehdi@videolabs.io> ++Date: Wed, 9 Jun 2021 03:42:51 +0200 ++Subject: [PATCH 1/1] add initial support for wasm32-emscripten ++ ++This commit adds a check for uuid_generate_random which is not supported for now, and fixes a failing test. ++It also handles a case where F_FSTYPENAME field is not present in statfs struct. ++--- ++ configure.ac | 1 + ++ src/Makefile.am | 1 + ++ src/fcint.h | 5 +++++ ++ src/fcstat.c | 2 +- ++ src/uuid_generate_random.c | 9 +++++++++ ++ test/test-hash.c | 5 ++++- ++ 6 files changed, 21 insertions(+), 2 deletions(-) ++ create mode 100644 src/uuid_generate_random.c ++ ++diff --git a/configure.ac b/configure.ac ++index fb8af46..018cfc1 100644 ++--- a/configure.ac +++++ b/configure.ac ++@@ -171,2 +171,3 @@ AC_FUNC_VPRINTF ++ AC_FUNC_MMAP ++ AC_CHECK_FUNCS([link mkstemp mkostemp _mktemp_s mkdtemp getopt getopt_long getprogname getexecname rand random lrand48 random_r rand_r readlink fstatvfs fstatfs lstat strerror strerror_r]) +++AC_REPLACE_FUNCS([uuid_generate_random]) ++ ++ dnl AC_CHECK_FUNCS doesn't check for header files. ++ dnl posix_fadvise() may be not available in older libc. ++ AC_CHECK_SYMBOL([posix_fadvise], [fcntl.h], [fc_func_posix_fadvise=1], [fc_func_posix_fadvise=0]) ++diff --git a/src/Makefile.am b/src/Makefile.am ++index 7b414df..de1d785 100644 ++--- a/src/Makefile.am +++++ b/src/Makefile.am ++@@ -127,6 +127,7 @@ EXTRA_DIST += \ ++ fcobjshash.gperf.h ++ ++ libfontconfig_la_SOURCES = \ +++ uuid_generate_random.c \ ++ fcarch.h \ ++ fcatomic.c \ ++ fcatomic.h \ ++diff --git a/src/fcint.h b/src/fcint.h ++index a9d075a..d8fdbfd 100644 ++--- a/src/fcint.h +++++ b/src/fcint.h ++@@ -598,6 +598,11 @@ struct _FcValuePromotionBuffer { ++ FcPrivate FcCache * ++ FcDirCacheScan (const FcChar8 *dir, FcConfig *config); ++ +++#ifndef HAVE_UUID_GENERATE_RANDOM +++#include <uuid/uuid.h> +++void uuid_generate_random(uuid_t out); +++#endif +++ ++ FcPrivate FcCache * ++ FcDirCacheBuild (FcFontSet *set, const FcChar8 *dir, struct stat *dir_stat, FcStrSet *dirs); ++ ++diff --git a/src/fcstat.c b/src/fcstat.c ++index 5aa1643..d1240c5 100644 ++--- a/src/fcstat.c +++++ b/src/fcstat.c ++@@ -384,7 +384,7 @@ FcFStatFs (int fd, FcStatFS *statb) ++ # endif ++ # if defined(HAVE_STRUCT_STATFS_F_FSTYPENAME) ++ p = buf.f_fstypename; ++-# elif defined(__linux__) +++# elif defined(__linux__) || defined(__EMSCRIPTEN__) ++ switch (buf.f_type) ++ { ++ case 0x6969: /* nfs */ ++diff --git a/src/uuid_generate_random.c b/src/uuid_generate_random.c ++new file mode 100644 ++index 0000000..c17a58d ++--- /dev/null +++++ b/src/uuid_generate_random.c ++@@ -0,0 +1,9 @@ +++// compat function for uuid_generate_random +++#include "fcint.h" +++ +++#ifndef HAVE_UUID_GENERATE_RANDOM +++void uuid_generate_random(uuid_t out) +++{ +++ uuid_generate(out); +++} +++#endif ++diff --git a/test/test-hash.c b/test/test-hash.c ++index 7530e82..221029d 100644 ++--- a/test/test-hash.c +++++ b/test/test-hash.c ++@@ -51,8 +51,11 @@ test_add (Test *test, FcChar8 *key, FcBool replace) ++ void *u; ++ FcBool (*hash_add) (FcHashTable *, void *, void *); ++ FcBool ret = FcFalse; ++- +++#ifdef HAVE_UUID_GENERATE_RANDOM ++ uuid_generate_random (uuid); +++#else +++ uuid_generate(uuid); +++#endif ++ if (replace) ++ hash_add = FcHashTableReplace; ++ else ++-- ++2.32.0 ++ +diff --git a/contrib/src/fontconfig/rules.mak b/contrib/src/fontconfig/rules.mak +index 7f60dcad6c..8a72d05288 100644 +--- a/contrib/src/fontconfig/rules.mak ++++ b/contrib/src/fontconfig/rules.mak +@@ -23,6 +23,10 @@ ifdef HAVE_WIN32 + endif + $(RM) $(UNPACK_DIR)/src/fcobjshash.gperf + $(call pkg_static, "fontconfig.pc.in") ++ifdef HAVE_EMSCRIPTEN ++ $(APPLY) $(SRC)/fontconfig/add-initial-support-for-wasm32-emscripten.patch ++ $(UPDATE_AUTOCONFIG) ++endif + $(MOVE) + + FONTCONFIG_CONF := $(HOSTCONF) \ +diff --git a/contrib/src/gcrypt/rules.mak b/contrib/src/gcrypt/rules.mak +index c9c9be3efa..0275e21fe8 100644 +--- a/contrib/src/gcrypt/rules.mak ++++ b/contrib/src/gcrypt/rules.mak +@@ -72,6 +72,12 @@ ifeq ($(ARCH),aarch64) + GCRYPT_CONF += --disable-arm-crypto-support + endif + endif ++ifdef HAVE_EMSCRIPTEN ++GCRYPT_CONF += --disable-asm --disable-aesni-support ac_cv_func_syslog=no --disable-sse41-support ++GCRYPT_CONF += --disable-avx-support --disable-avx2-support --disable-padlock-support ++GCRYPT_CONF += --disable-amd64-as-feature-detection --disable-drng-support ++GCRYPT_CONF += --disable-pclmul-support ++endif + + .gcrypt: gcrypt + # Reconfiguring this requires a git repo to be available, to +diff --git a/contrib/src/gmp/rules.mak b/contrib/src/gmp/rules.mak +index 09604e2a89..42bb60f3fe 100644 +--- a/contrib/src/gmp/rules.mak ++++ b/contrib/src/gmp/rules.mak +@@ -13,6 +13,9 @@ ifeq ($(ARCH),mips64el) + GMP_CONF += --disable-assembly + endif + endif ++ifdef HAVE_EMSCRIPTEN ++GMP_CONF += --disable-assembly ++endif + + ifdef HAVE_WIN32 + ifeq ($(ARCH),arm) +diff --git a/contrib/src/gnutls/rules.mak b/contrib/src/gnutls/rules.mak +index edec0ad9ee..fb372f9da1 100644 +--- a/contrib/src/gnutls/rules.mak ++++ b/contrib/src/gnutls/rules.mak +@@ -82,6 +82,10 @@ ifeq ($(ARCH),aarch64) + endif + endif + ++ifdef HAVE_EMSCRIPTEN ++ GNUTLS_CONF += --disable-hardware-acceleration ++endif ++ + .gnutls: gnutls + cd $< && $(GNUTLS_ENV) ./configure $(GNUTLS_CONF) + $(call pkg_static,"lib/gnutls.pc") +diff --git a/contrib/src/gpg-error/emscripten.patch b/contrib/src/gpg-error/emscripten.patch +new file mode 100644 +index 0000000000..f60695c513 +--- /dev/null ++++ b/contrib/src/gpg-error/emscripten.patch +@@ -0,0 +1,43 @@ ++From 63aa1523659914acd6c84229fb31ff9b712fbf8b Mon Sep 17 00:00:00 2001 ++From: Mehdi Sabwat <mehdi@videolabs.io> ++Date: Wed, 2 Jun 2021 11:42:46 +0200 ++Subject: [PATCH 1/1] emscripten ++ ++--- ++ .../lock-obj-pub.wasm32-unknown-emscripten.h | 24 +++++++++++++++++++ ++ 1 file changed, 24 insertions(+) ++ create mode 100644 src/syscfg/lock-obj-pub.wasm32-unknown-emscripten.h ++ ++diff --git a/src/syscfg/lock-obj-pub.wasm32-unknown-emscripten.h b/src/syscfg/lock-obj-pub.wasm32-unknown-emscripten.h ++new file mode 100644 ++index 0000000..1651518 ++--- /dev/null +++++ b/src/syscfg/lock-obj-pub.wasm32-unknown-emscripten.h ++@@ -0,0 +1,24 @@ +++## lock-obj-pub.wasm32-unknown-emscripten.h +++## File created by gen-posix-lock-obj - DO NOT EDIT +++## To be included by mkheader into gpg-error.h +++ +++typedef struct +++{ +++ long _vers; +++ union { +++ volatile char _priv[28]; +++ long _x_align; +++ long *_xp_align; +++ } u; +++} gpgrt_lock_t; +++ +++#define GPGRT_LOCK_INITIALIZER {1,{{0,0,0,0,0,0,0,0, \ +++ 0,0,0,0,0,0,0,0, \ +++ 0,0,0,0,0,0,0,0, \ +++ 0,0,0,0}}} +++## +++## Local Variables: +++## mode: c +++## buffer-read-only: t +++## End: +++## ++-- ++2.31.1 ++ +diff --git a/contrib/src/gpg-error/rules.mak b/contrib/src/gpg-error/rules.mak +index 0ceb75d7a3..5d4236b77f 100644 +--- a/contrib/src/gpg-error/rules.mak ++++ b/contrib/src/gpg-error/rules.mak +@@ -25,6 +25,7 @@ endif + $(APPLY) $(SRC)/gpg-error/version-bump-gawk-5.patch + $(APPLY) $(SRC)/gpg-error/win32-extern-struct.patch + $(APPLY) $(SRC)/gpg-error/darwin-triplet.patch ++ $(APPLY) $(SRC)/gpg-error/emscripten.patch + ifndef HAVE_WIN32 + cp -f -- "$(SRC)/gpg-error/lock-obj-pub.posix.h" \ + "$(UNPACK_DIR)/src/lock-obj-pub.native.h" +diff --git a/contrib/src/png/rules.mak b/contrib/src/png/rules.mak +index 7a8db11d5a..f1c93b835b 100644 +--- a/contrib/src/png/rules.mak ++++ b/contrib/src/png/rules.mak +@@ -7,6 +7,10 @@ ifeq ($(call need_pkg,"libpng >= 1.5.4"),) + PKGS_FOUND += png + endif + ++ifdef HAVE_EMSCRIPTEN ++HOSTVARS += CFLAGS="$(CFLAGS) -pthread" ++endif ++ + $(TARBALLS)/libpng-$(PNG_VERSION).tar.xz: + $(call download_pkg,$(PNG_URL),png) + +diff --git a/contrib/src/zlib/rules.mak b/contrib/src/zlib/rules.mak +index 17688df984..e3ae815dfc 100644 +--- a/contrib/src/zlib/rules.mak ++++ b/contrib/src/zlib/rules.mak +@@ -7,6 +7,10 @@ ifeq ($(call need_pkg,"zlib"),) + PKGS_FOUND += zlib + endif + ++ifdef HAVE_EMSCRIPTEN ++CFLAGS+="-pthread" ++endif ++ + ifeq ($(shell uname),Darwin) # zlib tries to use libtool on Darwin + ifdef HAVE_CROSS_COMPILE + ZLIB_CONFIG_VARS=CHOST=$(HOST) +diff --git a/extras/package/wasm-emscripten/build.sh b/extras/package/wasm-emscripten/build.sh +index 69d6b98c47..acd74591fc 100755 +--- a/extras/package/wasm-emscripten/build.sh ++++ b/extras/package/wasm-emscripten/build.sh +@@ -164,6 +164,11 @@ if [ $BUILD_MODE -eq 1 ]; then + --disable-sout --disable-vlm --disable-a52 --disable-xcb --disable-lua \ + --disable-addonmanagermodules --disable-ssp --disable-nls \ + --enable-gles2 \ ++ ac_cv_func_if_nameindex=yes ac_cv_header_sys_shm_h=no \ ++ ac_cv_func_accept4=no \ ++ ac_cv_func_pipe2=no \ ++ --enable-gles2 \ ++ --disable-nvdec \ + --with-contrib="$VLC_SRCPATH"/contrib/wasm32-unknown-emscripten + fi + +diff --git a/include/vlc_codec.h b/include/vlc_codec.h +index f68da3bde4..baa6977bbd 100644 +--- a/include/vlc_codec.h ++++ b/include/vlc_codec.h +@@ -583,6 +583,7 @@ enum vlc_decoder_device_type + VLC_DECODER_DEVICE_AWINDOW, + VLC_DECODER_DEVICE_NVDEC, + VLC_DECODER_DEVICE_MMAL, ++ VLC_DECODER_DEVICE_WEBCODEC, + }; + + struct vlc_decoder_device_operations +diff --git a/include/vlc_fourcc.h b/include/vlc_fourcc.h +index 6ed227d2c6..10f82ff535 100644 +--- a/include/vlc_fourcc.h ++++ b/include/vlc_fourcc.h +@@ -439,6 +439,9 @@ + #define VLC_CODEC_CVPX_BGRA VLC_FOURCC('C','V','P','B') + #define VLC_CODEC_CVPX_P010 VLC_FOURCC('C','V','P','P') + ++/* Webcodec opaque VideoFrame types */ ++#define VLC_CODEC_WEBCODEC_OPAQUE VLC_FOURCC('W','C','O','P') ++ + /* Image codec (video) */ + #define VLC_CODEC_PNG VLC_FOURCC('p','n','g',' ') + #define VLC_CODEC_PPM VLC_FOURCC('p','p','m',' ') +diff --git a/include/vlc_opengl_filter.h b/include/vlc_opengl_filter.h +new file mode 100644 +index 0000000000..3a1f18197d +--- /dev/null ++++ b/include/vlc_opengl_filter.h +@@ -0,0 +1,157 @@ ++/***************************************************************************** ++ * vlc_opengl_filter.h ++ ***************************************************************************** ++ * Copyright (C) 2020 VLC authors and VideoLAN ++ * Copyright (C) 2020 Videolabs ++ * ++ * This program is free software; you can redistribute it and/or modify it ++ * under the terms of the GNU Lesser General Public License as published by ++ * the Free Software Foundation; either version 2.1 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU Lesser General Public License for more details. ++ * ++ * You should have received a copy of the GNU Lesser General Public License ++ * along with this program; if not, write to the Free Software Foundation, ++ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. ++ *****************************************************************************/ ++ ++#ifndef VLC_OPENGL_FILTER_H ++#define VLC_OPENGL_FILTER_H ++ ++#include <vlc_tick.h> ++ ++struct vlc_gl_filter; ++struct vlc_gl_picture; ++struct vlc_gl_format; ++ ++#ifdef __cplusplus ++extern "C" ++{ ++#endif ++ ++struct vlc_gl_tex_size { ++ unsigned width; ++ unsigned height; ++}; ++ ++struct vlc_gl_input_meta { ++ vlc_tick_t pts; ++ unsigned plane; ++}; ++ ++typedef int ++vlc_gl_filter_open_fn(struct vlc_gl_filter *filter, ++ const config_chain_t *config, ++ const struct vlc_gl_format *glfmt, ++ struct vlc_gl_tex_size *size_out); ++ ++#define set_callback_opengl_filter(open) \ ++ { \ ++ vlc_gl_filter_open_fn *fn = open; \ ++ (void) fn; \ ++ set_callback(fn); \ ++ } ++ ++struct vlc_gl_filter_ops { ++ /** ++ * Draw the result of the filter to the current framebuffer ++ */ ++ int (*draw)(struct vlc_gl_filter *filter, const struct vlc_gl_picture *pic, ++ const struct vlc_gl_input_meta *meta); ++ ++ /** ++ * Free filter resources ++ */ ++ void (*close)(struct vlc_gl_filter *filter); ++ ++ /** ++ * Request a (responsive) filter to adapt its output size (optional) ++ * ++ * A responsive filter is a filter for which the size of the produced ++ * pictures depends on the output (e.g. display) size rather than the ++ * input. This is for example the case for a renderer. ++ * ++ * A new output size is requested (size_out). The filter is authorized to ++ * change the size_out to enforce its own constraints. ++ * ++ * In addition, it may request to the previous filter (if any) an optimal ++ * size it wants to receive. If set to non-zero value, this previous filter ++ * will receive this size as its requested size (and so on). ++ * ++ * \retval true if the resize is accepted (possibly with a modified ++ * size_out) ++ * \retval false if the resize is rejected (included on error) ++ */ ++ int (*request_output_size)(struct vlc_gl_filter *filter, ++ struct vlc_gl_tex_size *size_out, ++ struct vlc_gl_tex_size *optimal_in); ++ ++ /** ++ * Callback to notify input size changes ++ * ++ * When a filter changes its output size as a result of ++ * request_output_size(), the next filter is notified by this callback. ++ */ ++ void (*on_input_size_change)(struct vlc_gl_filter *filter, ++ const struct vlc_gl_tex_size *size); ++}; ++ ++/** ++ * OpenGL filter, in charge of a rendering pass. ++ */ ++struct vlc_gl_filter { ++ vlc_object_t obj; ++ module_t *module; ++ ++ struct vlc_gl_t *gl; ++ const struct vlc_gl_api *api; ++ const struct vlc_gl_format *glfmt_in; ++ ++ struct { ++ /** ++ * An OpenGL filter may either operate on the input RGBA picture, or on ++ * individual input planes (without chroma conversion) separately. ++ * ++ * In practice, this is useful for deinterlace filters. ++ * ++ * This flag must be set by the filter module (default is false). ++ */ ++ bool filter_planes; ++ ++ /** ++ * A blend filter draws over the input picture (without reading it). ++ * ++ * Meaningless if filter_planes is true. ++ * ++ * This flag must be set by the filter module (default is false). ++ */ ++ bool blend; ++ ++ /** ++ * Request MSAA level. ++ * ++ * This value must be set by the filter module (default is 0, which ++ * means disabled). ++ * ++ * Meaningless if filter_planes is true. ++ * ++ * The actual MSAA level may be overwritten to 0 if multisampling is ++ * not supported, or to a higher value if another filter rendering on ++ * the same framebuffer requested a higher MSAA level. ++ */ ++ unsigned msaa_level; ++ } config; ++ ++ const struct vlc_gl_filter_ops *ops; ++ void *sys; ++}; ++ ++#ifdef __cplusplus ++} ++#endif ++ ++#endif +diff --git a/include/vlc_picture.h b/include/vlc_picture.h +index c37abed150..fc3b757d14 100644 +--- a/include/vlc_picture.h ++++ b/include/vlc_picture.h +@@ -98,6 +98,7 @@ enum vlc_video_context_type + VLC_VIDEO_CONTEXT_NVDEC, //!< empty + VLC_VIDEO_CONTEXT_CVPX, //!< private: cvpx_video_context* + VLC_VIDEO_CONTEXT_MMAL, //!< empty ++ VLC_VIDEO_CONTEXT_WEBCODEC, + }; + + VLC_API vlc_video_context * vlc_video_context_Create(vlc_decoder_device *, +diff --git a/include/vlc_vout_display.h b/include/vlc_vout_display.h +index 4220793ae0..1b2f79b3b4 100644 +--- a/include/vlc_vout_display.h ++++ b/include/vlc_vout_display.h +@@ -383,6 +383,8 @@ vout_display_t *vout_display_New(vlc_object_t *, + const video_format_t *, vlc_video_context *, + const vout_display_cfg_t *, const char *module, + const vout_display_owner_t *); ++#define vout_display_New(O, ...) \ ++ (vout_display_New)(VLC_OBJECT(O), __VA_ARGS__) + + /** + * Destroys a video output display. +diff --git a/include/vlc_window.h b/include/vlc_window.h +index 9c8596ddaf..2124cdd028 100644 +--- a/include/vlc_window.h ++++ b/include/vlc_window.h +@@ -65,6 +65,7 @@ enum vlc_window_type { + VLC_WINDOW_TYPE_WAYLAND /**< Wayland surface */, + VLC_WINDOW_TYPE_DCOMP /**< Win32 DirectComposition */, + VLC_WINDOW_TYPE_KMS /**< DRM KMS CRTC */, ++ VLC_WINDOW_TYPE_EMSCRIPTEN_WEBGL /**< Emscripten surface */, + }; + + /** +@@ -385,6 +386,8 @@ typedef struct vlc_window { + struct wl_surface *wl; /**< Wayland surface (client pointer) */ + void *dcomp_visual; /**< Win32 direct composition visual */ + uint32_t crtc; /**< KMS CRTC identifier */ ++ uint32_t em_context; /* Emscripten webgl context */ ++ const char *canvas; + } handle; + + /** Display server (mandatory) +diff --git a/modules/Makefile.am b/modules/Makefile.am +index 333acb0586..0c931d51ca 100644 +--- a/modules/Makefile.am ++++ b/modules/Makefile.am +@@ -31,6 +31,7 @@ include hw/d3d11/Makefile.am + include hw/vaapi/Makefile.am + include hw/vdpau/Makefile.am + include hw/mmal/Makefile.am ++include hw/emscripten/Makefile.am + include isa/aarch64/Makefile.am + include isa/arm/Makefile.am + include isa/riscv/Makefile.am +diff --git a/modules/audio_output/Makefile.am b/modules/audio_output/Makefile.am +index 38e47bc838..290b724983 100644 +--- a/modules/audio_output/Makefile.am ++++ b/modules/audio_output/Makefile.am +@@ -116,3 +116,8 @@ endif + if HAVE_TVOS + aout_LTLIBRARIES += libaudiounit_ios_plugin.la + endif ++ ++libemworklet_audio_plugin_la_SOURCES = audio_output/emscripten.cpp ++if HAVE_EMSCRIPTEN ++aout_LTLIBRARIES += libemworklet_audio_plugin.la ++endif +diff --git a/modules/audio_output/emscripten.cpp b/modules/audio_output/emscripten.cpp +new file mode 100644 +index 0000000000..a7e9fb0d4d +--- /dev/null ++++ b/modules/audio_output/emscripten.cpp +@@ -0,0 +1,421 @@ ++/***************************************************************************** ++ * emscripten.c: audio output module using audio worklets ++ ***************************************************************************** ++ * Copyright © 2020 VLC authors and VideoLAN ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, write to the Free Software Foundation, Inc., ++ * 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. ++ *****************************************************************************/ ++ ++#ifdef HAVE_CONFIG_H ++# include "config.h" ++#endif ++ ++#include <atomic> ++ ++#include <assert.h> ++#include <vlc_common.h> ++#include <vlc_plugin.h> ++#include <vlc_aout.h> ++ ++#include <emscripten.h> ++#include <emscripten/val.h> ++#include <emscripten/bind.h> ++#include <emscripten/html5.h> ++ ++#include <cstdint> ++#include <stdlib.h> ++ ++#define STORAGE_SIZE 1024 * 1024 ++// Sample rate might change, and it would be good to be able to change it during playback. ++#define AUDIO_WORKLET_SAMPLE_RATE 44100 ++// Don't know any way to get the browser's supported number of channels. ++#define AUDIO_WORKLET_NB_CHANNELS 2 ++ ++using namespace emscripten; ++namespace { ++ EM_BOOL requestAnimationFrame_cb( double time, void *userData ); ++ ++ typedef struct sound_buffer_t ++ { ++ // TODO - should be bool? ++ std::atomic<uint32_t> is_paused; ++ std::atomic<uint32_t> head; ++ std::atomic<uint32_t> tail; ++ std::atomic<uint32_t> can_write; ++ std::atomic<uint32_t> volume; ++ std::atomic<uint32_t> is_muted; ++ int8_t storage[STORAGE_SIZE]; ++ ++ } sound_buffer_t; ++ ++ class AWNodeWrapper { ++ public: ++ val context = val::undefined(); ++ val getCtx() const { return context; }; ++ void setCtx(val v_context) { context = v_context; }; ++ ++ uintptr_t sab_ptr; ++ uintptr_t getSabPtr() const { return sab_ptr; }; ++ void setSabPtr(uintptr_t p_sab) { sab_ptr = p_sab; }; ++ ++ int8_t channels; ++ int8_t getChannels() const { return channels; }; ++ void setChannels(int8_t chan) { channels = chan; }; ++ ++ AWNodeWrapper(int sample_rate) { ++ // Prepare audio context options ++ val audio_ctx_options = val::object(); ++ audio_ctx_options.set("sampleRate", sample_rate); ++ ++ context = val::global("AudioContext").new_(audio_ctx_options); ++ context.call<void>("suspend"); ++ } ++ ++ val operator()( val undefined_promise_argument ) { ++ (val)undefined_promise_argument; ++ ++ // Prepare AWN Options ++ val awn_options = val::object(); ++ val awn_opt_outputChannelCount = val::array(); ++ awn_opt_outputChannelCount.call<val>("push", channels); ++ awn_options.set("outputChannelCount", awn_opt_outputChannelCount); ++ awn_options.set("numberOfInputs", 0); ++ awn_options.set("numberOfOutputs", 1); ++ ++ val AudioNode = val::global("AudioWorkletNode").new_(context, std::string("worklet-processor"), awn_options); ++ AudioNode.set("channelCount", channels); ++ ++ val Uint32Array = val::global("Uint32Array"); ++ val Int32Array = val::global("Int32Array"); ++ val Float32Array = val::global("Float32Array"); ++ ++ auto wasm_mem = val::module_property("wasmMemory")["buffer"]; ++ ++ //Prepare postMessage message ++ val msg = val::object(); ++ msg.set("type", std::string("recv-audio-queue")); ++ ++ msg.set("is_paused", ++ Uint32Array.new_(wasm_mem, sab_ptr + offsetof(sound_buffer_t, is_paused), 1)); ++ msg.set("head", ++ Uint32Array.new_(wasm_mem, sab_ptr + offsetof(sound_buffer_t, head), 1)); ++ msg.set("tail", ++ Uint32Array.new_(wasm_mem, sab_ptr + offsetof(sound_buffer_t, tail), 1)); ++ msg.set("can_write", ++ Int32Array.new_(wasm_mem, sab_ptr + offsetof(sound_buffer_t, can_write), 1)); ++ msg.set("volume", ++ Int32Array.new_(wasm_mem, sab_ptr + offsetof(sound_buffer_t, volume), 1)); ++ msg.set("is_muted", ++ Uint32Array.new_(wasm_mem, sab_ptr + offsetof(sound_buffer_t, is_muted), 1)); ++ ++ uint32_t storage_capacity = STORAGE_SIZE / 4; ++ msg.set("storage", ++ Float32Array.new_(wasm_mem, sab_ptr + offsetof(sound_buffer_t, storage), storage_capacity)); ++ ++ AudioNode["port"].call<val>("postMessage", msg); ++ AudioNode.call<val>("connect", context["destination"]); ++ ++ emscripten_request_animation_frame_loop(requestAnimationFrame_cb, this); ++ ++ return val::undefined(); ++ } ++ }; ++ ++ EMSCRIPTEN_BINDINGS(AWWSCOPE) { ++ class_<AWNodeWrapper>("awn_cb_wrapper") ++ .constructor<int>() ++ .property("context", &AWNodeWrapper::getCtx, &AWNodeWrapper::setCtx) ++ .property("sab_ptr", &AWNodeWrapper::getSabPtr, &AWNodeWrapper::setSabPtr) ++ .property("channels", &AWNodeWrapper::getChannels, &AWNodeWrapper::setChannels) ++ .function("awn_call", &AWNodeWrapper::operator()); ++ }; ++ ++ typedef struct aout_sys_t ++ { ++ sound_buffer_t *sab; // TODO - rename to sound_buff ++ AWNodeWrapper *awn_inst; ++ ++ } aout_sys_t; ++ ++ EM_BOOL requestAnimationFrame_cb( double time, void *userData ) { ++ VLC_UNUSED(time); ++ // FIXME - this function seems to mix two different views on the ++ // same memory, not sure why ++ AWNodeWrapper *inst = reinterpret_cast<AWNodeWrapper *>(userData); ++ uint32_t *sab = reinterpret_cast<uint32_t *>(inst->getSabPtr()); ++ val view = val(typed_memory_view(sizeof(sound_buffer_t), sab)); ++ val context = inst->getCtx(); ++ if ( view[0].as<int>() == 1 ) { ++ context.call<val>("resume"); ++ sab[0] = 0; ++ return EM_FALSE; ++ } ++ return EM_TRUE; ++ } ++ ++ // careful when calling this, you cannot wait on any index ++ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics/wait ++ void js_index_wait(sound_buffer_t *sab_ptr, int8_t index) { ++ int32_t *buffer_view = reinterpret_cast<int32_t *>(sab_ptr); ++ val buffer = val(typed_memory_view(STORAGE_SIZE, buffer_view)); ++ ++ val::global("Atomics").call<val>("wait", buffer, index, 0); ++ } ++ ++ void Flush( audio_output_t *aout ) ++ { ++ aout_sys_t * sys = reinterpret_cast<aout_sys_t *>(aout->sys); ++ bzero(&sys->sab->storage, sizeof(sys->sab->storage)); ++ } ++ ++ int Start( audio_output_t *aout, audio_sample_format_t *restrict fmt ) ++ { ++ aout_sys_t *sys = reinterpret_cast<aout_sys_t *>(aout->sys); ++ unsigned nbChannels = aout_FormatNbChannels(fmt); ++ ++ if (( nbChannels == 0 ) || !AOUT_FMT_LINEAR(fmt)) ++ return VLC_EGENERIC; ++ fmt->i_format = VLC_CODEC_FL32; ++ fmt->i_channels = AUDIO_WORKLET_NB_CHANNELS; ++ fmt->i_rate = AUDIO_WORKLET_SAMPLE_RATE; ++ ++ // resume audio context (first start, it is paused when initialized) ++ sys->sab->is_paused.store(1); ++ ++ return VLC_SUCCESS; ++ } ++ ++ void Stop (audio_output_t *aout) ++ { ++ Flush(aout); ++ } ++ ++ int audio_worklet_push (audio_output_t *aout, const int8_t *data, uint32_t data_size) { ++ aout_sys_t *sys = reinterpret_cast<aout_sys_t *>(aout->sys); ++ int8_t *sab_view = sys->sab->storage; ++ uint32_t head = sys->sab->head.load(); ++ ++ // TODO: check that we do not write on unconsumed data. ++ if (head + data_size > STORAGE_SIZE) { ++ // Copy the part of the data at the buffer end ++ unsigned data_size_copy_end = STORAGE_SIZE - head; ++ memcpy(sab_view + head, data, data_size_copy_end); ++ head = 0; ++ ++ // Copy the part of the data at the buffer start ++ unsigned data_size_copy_start = data_size - data_size_copy_end; ++ memcpy(sab_view + head, data, data_size_copy_start); ++ head = data_size_copy_start; ++ } ++ else { ++ memcpy(sab_view + head, data, data_size); ++ head += data_size; ++ } ++ sys->sab->head.store(head); ++ return 0; // return success to indicate successful push. ++ } ++ ++ void Play( audio_output_t *aout, block_t *block, vlc_tick_t date) ++ { ++ VLC_UNUSED(date); ++ aout_sys_t *sys = reinterpret_cast<aout_sys_t *>(aout->sys); ++ const int8_t* data = (int8_t *)block->p_buffer; ++ size_t data_size = block->i_buffer; ++ ++ uint32_t head = sys->sab->head.load(); ++ uint32_t tail = sys->sab->tail.load(); ++ uint32_t new_head = (head + data_size) % STORAGE_SIZE; ++ if (new_head > tail) ++ { ++ // the worklet processor keeps rendering until tail matches head ++ // it will be notified by an Atomics.notify() from the process() callback ++ // FIXME - This is layout-dependent, which isn't ideal ++ js_index_wait(sys->sab, 3); ++ } ++ ++ audio_worklet_push(aout, data, data_size); ++ block_Release(block); ++ } ++ ++ void Pause( audio_output_t *aout, bool paused, vlc_tick_t date ) ++ { ++ VLC_UNUSED(date); ++ aout_sys_t * sys = reinterpret_cast<aout_sys_t *>(aout->sys); ++ if (paused == false) { ++ sys->sab->is_paused.store(0); ++ } ++ else { ++ sys->sab->is_paused.store(1); ++ } ++ Flush(aout); ++ } ++ ++ int Time_Get( audio_output_t *aout, vlc_tick_t *delay) ++ { ++ return aout_TimeGetDefault(aout, delay); ++ } ++ ++ void Close( vlc_object_t *obj ) ++ { ++ audio_output_t *aout = (audio_output_t *)obj; ++ struct aout_sys_t *sys = reinterpret_cast<struct aout_sys_t *>(aout->sys); ++ ++ // FIXME ++ delete sys->awn_inst; ++ free(sys->sab); ++ free(sys); ++ } ++ ++ int Volume_Set( audio_output_t *aout, float volume) ++ { ++ struct aout_sys_t *sys = reinterpret_cast<struct aout_sys_t *>(aout->sys); ++ ++ if (volume > 1.0f) ++ volume = 1.0f; ++ else if (volume < 0.0f) ++ volume = 0.0f; ++ // TODO: implement gain ++ // Note: We store volume as an integer between 0..100 because ++ // for some reason Float32Array doesn't allow atomic operations ++ sys->sab->volume.store((int)(volume * 100)); ++ aout_VolumeReport(aout, volume); ++ ++ return 0; ++ } ++ ++ int Mute_Set( audio_output_t *aout, bool mute) ++ { ++ struct aout_sys_t *sys = reinterpret_cast<struct aout_sys_t *>(aout->sys); ++ ++ sys->sab->is_muted.store(mute); ++ aout_MuteReport(aout, mute); ++ ++ return 0; ++ } ++ ++ ++ int Open( vlc_object_t *obj ) ++ { ++ audio_output_t * aout = (audio_output_t *) obj; ++ ++ /* Allocate structures */ ++ aout_sys_t *sys = reinterpret_cast<aout_sys_t *>(malloc( sizeof( *sys ) )); ++ if( unlikely(sys == NULL) ) ++ return VLC_ENOMEM; ++ ++ aout->sys = sys; ++ aout->start = Start; ++ aout->stop = Stop; ++ aout->play = Play; ++ aout->pause = Pause; ++ aout->flush = Flush; ++ aout->time_get = Time_Get; ++ aout->volume_set = Volume_Set; ++ aout->mute_set = Mute_Set; ++ ++ sys->awn_inst = new AWNodeWrapper(AUDIO_WORKLET_SAMPLE_RATE); ++ sys->sab = (sound_buffer_t*)malloc(sizeof(sound_buffer_t)); ++ ++ if ( unlikely(sys->sab == NULL) ) ++ return VLC_ENOMEM; ++ bzero(sys->sab, sizeof(sound_buffer_t)); ++ sys->sab->volume = 100; ++ ++ val webaudio_context = sys->awn_inst->getCtx(); ++ ++ // Prepare audioWorkletProcessor blob ++ val document = val::global("document"); ++ val script = document.call<val>("createElement", std::string("SCRIPT")); ++ script.set("type", std::string("worklet")); ++ std::string processorStr = "class Processor extends AudioWorkletProcessor { \ ++ constructor() { \ ++ super(); \ ++ this.port.onmessage = e => { \ ++ if (e.data.type === 'recv-audio-queue') { \ ++ this.is_paused = e.data.is_paused; \ ++ this.head = e.data.head; \ ++ this.tail = e.data.tail; \ ++ this.can_write = e.data.can_write; \ ++ this.volume = e.data.volume; \ ++ this.is_muted = e.data.is_muted; \ ++ this.storage = e.data.storage; \ ++ } else { \ ++ throw 'unexpected.'; \ ++ } \ ++ }; \ ++ } \ ++ process(inputs, outputs, parameters) { \ ++ const output = outputs[0]; \ ++ const nbChannels = output.length; \ ++ const nbSamples = output[0].length; \ ++ if (this.head.buffer.byteLength == 0) { \ ++ throw new Error('wasmMemory grew'); \ ++ } \ ++ var head = Atomics.load(this.head, 0) / 4; \ ++ var tail = Atomics.load(this.tail, 0) / 4; \ ++ var i = 0; \ ++ var volume = Atomics.load(this.volume, 0) / 100; \ ++ if (Atomics.load(this.is_paused, 0) != 0 || Atomics.load(this.is_muted, 0) != 0) { \ ++ volume = 0; \ ++ } \ ++ while (tail != head && i < nbSamples) \ ++ { \ ++ for (let c = 0; c < nbChannels; ++c) { \ ++ output[c][i] = this.storage[tail] * volume; \ ++ tail++; \ ++ if (tail == this.storage.length) { \ ++ tail = 0; \ ++ } \ ++ } \ ++ i++; \ ++ } \ ++ Atomics.store(this.tail, 0, tail * 4); \ ++ Atomics.store(this.can_write, 0, 1); \ ++ Atomics.notify(this.can_write, 0); \ ++ return true; \ ++ } \ ++} \ ++registerProcessor('worklet-processor', Processor);"; ++ script.set("innerText", processorStr); ++ val ProcessorTextArray = val::array(); ++ ProcessorTextArray.call<val>("push", script["innerText"]); ++ val BlobObject = val::object(); ++ BlobObject.set("type", std::string("application/javascript")); ++ val WorkletModuleUrl = val::global("URL").call<val>("createObjectURL", val::global("Blob").new_(ProcessorTextArray, BlobObject)); ++ ++ // Prepare audioWorkletProcessor callback ++ val cb_caller = val::module_property("awn_cb_wrapper").new_(AUDIO_WORKLET_SAMPLE_RATE); ++ cb_caller.set("context", val(webaudio_context)); ++ cb_caller.set("sab_ptr", val(reinterpret_cast<uintptr_t>(sys->sab))); ++ cb_caller.set("channels", val(AUDIO_WORKLET_NB_CHANNELS)); ++ val awn_caller = cb_caller["awn_call"]; ++ val awn_cb = awn_caller.call<val>("bind", cb_caller); ++ ++ // start audio worklet (since the context is suspended, sound won't start now ++ // Since the WebAudio Context cannot be created in a worker, we create ++ // it in the main_thread and use the SAB to signal it when we want it to start ++ webaudio_context["audioWorklet"].call<val>("addModule", WorkletModuleUrl).call<val>("then", awn_cb); ++ ++ return VLC_SUCCESS; ++ } ++} ++ ++vlc_module_begin () ++ set_description( N_("Emscripten Worklet audio output") ) ++ set_shortname( "emworklet" ) ++ set_capability( "audio output", 100 ) ++ set_subcategory( SUBCAT_AUDIO_AOUT ) ++ set_callbacks( Open, Close ) ++vlc_module_end () +diff --git a/modules/codec/Makefile.am b/modules/codec/Makefile.am +index 20a9606fd8..13ec8fa952 100644 +--- a/modules/codec/Makefile.am ++++ b/modules/codec/Makefile.am +@@ -644,3 +644,9 @@ noinst_LTLIBRARIES += libhxxxhelper_testdec_plugin.la + libvlc_vtutils_la_SOURCES = codec/vt_utils.c codec/vt_utils.h + libvlc_vtutils_la_LDFLAGS = -static -no-undefined + EXTRA_LTLIBRARIES += libvlc_vtutils.la ++ ++libwebcodec_plugin_la_SOURCES = codec/webcodec.cpp video_output/emscripten/common.cpp ++libwebcodec_plugin_la_LDFLAGS = $(AM_LDFLAGS) ++if HAVE_EMSCRIPTEN ++codec_LTLIBRARIES += libwebcodec_plugin.la ++endif +diff --git a/modules/codec/webcodec.cpp b/modules/codec/webcodec.cpp +new file mode 100644 +index 0000000000..4d1fa5d932 +--- /dev/null ++++ b/modules/codec/webcodec.cpp +@@ -0,0 +1,518 @@ ++/***************************************************************************** ++ * webcodec.cpp: Decoder module using browser provided codec implementations ++ ***************************************************************************** ++ * Copyright © 2021 VLC authors and VideoLAN ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, write to the Free Software Foundation, Inc., ++ * 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. ++ *****************************************************************************/ ++ ++#ifdef HAVE_CONFIG_H ++# include "config.h" ++#endif ++ ++#include <vlc_common.h> ++#include <vlc_plugin.h> ++#include <vlc_codec.h> ++#include <vlc_threads.h> ++#include <vlc_cxx_helpers.hpp> ++#include <vlc_block.h> ++#include <vlc_tick.h> ++#include <vlc_picture.h> ++ ++#include "../video_output/emscripten/common.h" ++ ++#include <emscripten/emscripten.h> ++#include <emscripten/val.h> ++#include <emscripten/bind.h> ++#include <emscripten/em_js.h> ++#include <emscripten/wire.h> ++#include <memory> ++#include <functional> ++#include <cstdint> ++#include <queue> ++ ++using emval = emscripten::val; ++ ++struct decoder_sys_t ++{ ++ vlc_thread_t th; ++ std::queue<block_t*> blocks; ++ vlc::threads::mutex mutex; ++ ++ vlc_video_context* vctx; ++}; ++ ++extern "C" ++{ ++EMSCRIPTEN_KEEPALIVE picture_t* tryGetPictureFromPool(decoder_t* dec) ++{ ++ auto sys = static_cast<decoder_sys_t*>( dec->p_sys ); ++ auto vctx = static_cast<webcodec_context*>( ++ vlc_video_context_GetPrivate(sys->vctx, VLC_VIDEO_CONTEXT_WEBCODEC) ); ++ return picture_pool_Get(vctx->pool); ++} ++ ++EMSCRIPTEN_KEEPALIVE bool updateVideoOutput(decoder_t* dec) ++{ ++ auto sys = static_cast<decoder_sys_t*>( dec->p_sys ); ++ if ( decoder_UpdateVideoOutput( dec, sys->vctx ) ) ++ { ++ msg_Err( dec, "Failure during UpdateVideoOutput! FIXME" ); ++ return false; ++ } ++ return true; ++} ++ ++EMSCRIPTEN_KEEPALIVE int32_t queuePicture(void* ctx, picture_t* pic, int64_t timestamp) ++{ ++ auto dec = static_cast<decoder_t*>(ctx); ++ pic->date = VLC_TICK_FROM_US(timestamp); ++ pic->b_progressive = true; ++ decoder_QueueVideo(dec, pic); ++ return PictureContextPrivate(pic->context)->pictureIdx; ++} ++ ++EMSCRIPTEN_KEEPALIVE pthread_t getVlcDecoderWorkerThread(decoder_t* dec) ++{ ++ auto sys = static_cast<decoder_sys_t*>( dec->p_sys ); ++ return sys->th.handle; ++} ++ ++EMSCRIPTEN_KEEPALIVE void releaseBlock( block_t* block ) ++{ ++ block_Release( block ); ++} ++ ++EMSCRIPTEN_KEEPALIVE void decodeBlock( block_t* block ) ++{ ++ auto chunkType = emval::global("EncodedVideoChunk"); ++ auto chunkCfg = emval::object(); ++ ++ static bool first = true; ++ ++ if ( first == true ) ++ { ++ chunkCfg.set( "type", "key" ); ++ first = false; ++ } ++ else ++ chunkCfg.set( "type", (block->i_flags & BLOCK_FLAG_TYPE_I) ? "key" : "delta" ); ++ auto timestamp = block->i_pts ? block->i_pts : block->i_dts; ++ chunkCfg.set( "timestamp", (long int)US_FROM_VLC_TICK( timestamp ) ); ++ //if ( block->i_length > 0 ) ++ // chunkCfg.set( "duration", (long int)US_FROM_VLC_TICK( block->i_length ) ); ++ chunkCfg.set( "data", emscripten::typed_memory_view( block->i_buffer, block->p_buffer ) ); ++ auto chunk = chunkType.new_( std::move( chunkCfg ) ); ++ ++ /* We can't return a javascript object from the stack back to the JS caller ++ * as the object would be destroyed when falling out of scope. ++ * We can't also pass an object by reference as the only way to get a ++ * emscripten::val instance is to take ownership from the javascript object ++ * so we couldn't use it back in the JS ++ * As a work around, we access the JS decoder instance straight from the C++ code ++ */ ++ EM_ASM({Module.decoder.decode( Emval.toValue( $0 ) ); }, chunk.as_handle()); ++} ++ ++} ++ ++EM_ASYNC_JS(bool, probeConfig, (emscripten::EM_VAL cfg), { ++ var decoderCfg = Emval.toValue(cfg); ++ var res = await VideoDecoder.isConfigSupported(decoderCfg).catch((err) => { ++ console.log(err); ++ return {'supported': false}; ++ }); ++ return res['supported']; ++}); ++ ++static emval getDecoderConfig( decoder_t* dec, bool includeExtraData ) ++{ ++ auto decoderConfig = emval::object(); ++ switch( dec->fmt_in.i_codec ) ++ { ++ case VLC_CODEC_VP8: ++ decoderConfig.set( "codec", "vp8" ); ++ break; ++ case VLC_CODEC_VP9: ++ decoderConfig.set( "codec", "vp09.*" ); ++ break; ++ case VLC_CODEC_H264: ++ { ++ char codec[12]; ++ snprintf(codec, sizeof(codec), "avc1.%.2X%.2X%.2X", dec->fmt_in.i_profile, ++ 0, dec->fmt_in.i_level); ++ decoderConfig.set( "codec", codec ); ++ ++ //decoderConfig.set("codec", "avc1.64000b" ); ++ break; ++ } ++ case VLC_CODEC_AV1: ++ decoderConfig.set( "codec", "av01" ); ++ break; ++ default: ++ return emval::undefined(); ++ } ++ decoderConfig.set( "codedWidth", dec->fmt_in.video.i_width ); ++ decoderConfig.set( "codedHeight", dec->fmt_in.video.i_height ); ++ decoderConfig.set( "displayAspectWidth", dec->fmt_in.video.i_visible_width ); ++ decoderConfig.set( "displayAspectHeight", dec->fmt_in.video.i_visible_height ); ++ decoderConfig.set( "optimizeForLatency", false ); ++ if ( includeExtraData ) ++ { ++ if ( dec->fmt_in.i_extra > 0 ) ++ { ++ decoderConfig.set( "description", ++ emscripten::typed_memory_view( ++ dec->fmt_in.i_extra, ++ static_cast<uint8_t*>( dec->fmt_in.p_extra ) ) ++ ); ++ } ++ } ++ return decoderConfig; ++} ++ ++EM_JS(emscripten::EM_VAL, initDecoderJS, (void* decoder, emscripten::EM_VAL decCfgHandle), { ++ function onInteropMessage(msg) { ++ let data = msg['data']; ++ if (data['customCmd'] == 'sendFrame') { ++ let picIdx = data['pictureIdx']; ++ let frame = Module.framesReady[picIdx]; ++ Module.framesReady[picIdx] = undefined; ++ if (!frame) { ++ /* We may receive a late message after a flush, in which case ++ * we still need to reply to the interop, but we don't have ++ * anything to provide to it ++ */ ++ Module.voutPort.postMessage({ ++ customCmd: 'displayFrame', ++ frame: undefined, ++ pictureId: picIdx ++ }); ++ return; ++ } ++ Module.voutPort.postMessage({ ++ customCmd: 'displayFrame', ++ frame: frame, ++ pictureId: picIdx, ++ }, [frame]); ++ } ++ } ++ ++ async function getVoutMessagePort() { ++ let p = new Promise((resolve, reject) => { ++ self.addEventListener('message', function(e) { ++ let msg = e['data']; ++ if (msg.customCmd == 'transferMessagePort') { ++ let port = msg['transferList'][0]; ++ if (!port) ++ reject(); ++ Module.voutPort = port; ++ Module.voutPort.onmessage = onInteropMessage; ++ resolve(); ++ } ++ }); ++ }); ++ await p; ++ } ++ ++ async function onDecoderWorkerMessage(msg) { ++ const data = msg['data']; ++ if (data['customCmd'] == 'decode') { ++ let block = data['block']; ++ _decodeBlock( block ); ++ _releaseBlock(block); ++ } else if ( data['customCmd'] == 'flush' ) { ++ await Module.decoder.flush(); ++ for ( let i = 0; i < Module.framesReady.length; ++i ) { ++ if (Module.framesReady[i]) { ++ Module.framesReady[i].close(); ++ Module.framesReady[i] = undefined; ++ } ++ } ++ Module.msgChannel.port1.postMessage({ ++ customCmd: 'onFlushCompleted' ++ }); ++ } else if ( data['customCmd'] == 'close' ) { ++ Module.decoder.close(); ++ } ++ } ++ ++ async function getPictureAsync(dec) { ++ function getPicture(dec, resolve) { ++ let pic = _tryGetPictureFromPool(dec); ++ if (!pic) { ++ setTimeout( function() { ++ getPicture(dec, resolve); ++ }, 1); ++ return; ++ } ++ resolve(pic); ++ } ++ let p = new Promise((resolve) => { ++ getPicture(dec, resolve); ++ }); ++ return await p; ++ } ++ ++ self.addEventListener('message', (e) => { ++ let msg = e['data']; ++ ++ console.log('Received message in decoder worker'); ++ console.dir(msg); ++ ++ if (msg['customCmd'] == 'getDecoderWorkerMessagePort') { ++ Module.msgChannel = new MessageChannel(); ++ Module.msgChannel.port1.onmessage = onDecoderWorkerMessage; ++ self.postMessage({ ++ customCmd: 'transferMessagePort', ++ targetThread: msg['replyTo'], ++ transferList: [Module.msgChannel.port2], ++ }, [Module.msgChannel.port2]); ++ } ++ }); ++ ++ /* Store a handle to our decoder_t for later invocations */ ++ Module.webCodecCtx = decoder; ++ ++ /* Prepare an array containing the decoded frames, so the interop can query ++ * then later */ ++ Module.framesReady = []; ++ ++ let initCfg = { ++ 'output': async function (frame) { ++ /* Always update the video output format so we can ensure the vout ++ * is created when we try to acquire its message port */ ++ if ( !_updateVideoOutput(Module.webCodecCtx) ) ++ { ++ frame.close(); ++ return; ++ } ++ if (Module.voutPort === undefined) ++ await getVoutMessagePort(); ++ let p = await getPictureAsync(Module.webCodecCtx); ++ let picIdx = _queuePicture(Module.webCodecCtx, p, frame.timestamp); ++ if ( Module.framesReady[picIdx] ) { ++ /* ++ * If we end up overriding a frame, it means it was dropped ++ * by the vout before the interop asked for it, so we can close ++ * it safely. ++ */ ++ console.log('Discarding dropped frame ', picIdx); ++ Module.framesReady[picIdx].close(); ++ } ++ Module.framesReady[picIdx] = frame; ++ }, ++ 'error': function(err) { ++ console.log('Error while decoding: '); ++ console.log(err); ++ } ++ }; ++ Module.decoder = new VideoDecoder( initCfg ); ++ let decCfg = Emval.toValue( decCfgHandle ); ++ Module.decoder.configure( decCfg ); ++}); ++ ++static bool initDecoder( decoder_t* dec ) ++{ ++ auto decCfg = getDecoderConfig( dec, true ); ++ initDecoderJS( dec, decCfg.as_handle() ); ++ ++ return true; ++} ++ ++static void mainloop_tick() ++{ ++} ++ ++static void* WebcodecDecodeWorker( void* arg ) ++{ ++ /* ++ * We need to be able to yield back to the browser main loop after we ++ * queue some blocks for decoding, otherwise the callback will never be ++ * invoked. ++ * In order to do so, we spawn a dedidated thread that will run a "main loop" ++ * which consist of dequeuing a single block and passing it to webcodec. ++ * After doing so, it yields back to the message loop and so on. If no ++ * blocks are queued for decoding, we can block as there nothing else being ++ * done from this worker. ++ */ ++ auto dec = static_cast<decoder_t*>( arg ); ++ if ( !initDecoder( dec ) ) ++ { ++ msg_Err( dec, "Failed to initialize decoder: FIXME" ); ++ return NULL; ++ } ++ auto sys = static_cast<decoder_sys_t*>( dec->p_sys ); ++ auto vctxPrivate = static_cast<webcodec_context*>( ++ vlc_video_context_GetPrivate(sys->vctx, VLC_VIDEO_CONTEXT_WEBCODEC)); ++ vctxPrivate->decoder_worker = pthread_self(); ++ emscripten_set_main_loop(mainloop_tick, 1, true); ++ return NULL; ++} ++ ++EM_ASYNC_JS(void, initDecoderWorkerMessagePort, (decoder_t* dec), { ++ if (Module.decoderWorkerPort !== undefined) { ++ return; ++ } ++ let workerMessagePortPromise = new Promise((resolve, reject) => { ++ self.addEventListener('message', function(e) { ++ let msg = e['data']; ++ if (msg.customCmd == 'transferMessagePort') { ++ let port = msg['transferList'][0]; ++ if (!port) { ++ console.log('No port provided, rejecting'); ++ reject(); ++ } ++ Module.decoderWorkerPort = port; ++ resolve(); ++ } ++ }); ++ self.postMessage({ ++ customCmd: 'getDecoderWorkerMessagePort', ++ targetThread: _getVlcDecoderWorkerThread(dec), ++ replyTo: _pthread_self() ++ }); ++ }); ++ await workerMessagePortPromise; ++ Module.decoderWorkerPort.onmessage = (e) => { ++ let data = e['data']; ++ if (data['customCmd'] == 'onFlushCompleted') { ++ Module.flushPromiseResolver(); ++ } ++ }; ++}) ++ ++static int Decode( decoder_t* dec, block_t* block ) ++{ ++ initDecoderWorkerMessagePort(dec); ++ EM_ASM({ ++ Module.decoderWorkerPort.postMessage({ ++ customCmd: 'decode', ++ block: $0 ++ }); ++ }, block); ++ return VLCDEC_SUCCESS; ++} ++ ++EM_ASYNC_JS(void, flushAsync, (), { ++ let p = new Promise((r) => { ++ Module.flushPromiseResolver = r; ++ Module.decoderWorkerPort.postMessage({ ++ customCmd: 'flush' ++ }); ++ }); ++ await p; ++ Module.flushPromiseResolver = undefined; ++}); ++ ++static void Flush( decoder_t* dec ) ++{ ++ initDecoderWorkerMessagePort(dec); ++ flushAsync(); ++} ++ ++static int Open( vlc_object_t* obj ) ++{ ++ auto dec = reinterpret_cast<decoder_t*>(obj); ++ ++ if ( dec->fmt_in.i_cat != VIDEO_ES ) ++ return VLC_EGENERIC; ++ ++ auto decoderType = emval::global("VideoDecoder"); ++ if ( !decoderType.as<bool>() ) ++ { ++ msg_Err( obj, "Can't get VideoDecoder type, webcodec is probably not " ++ "supported on this browser" ); ++ return VLC_EGENERIC; ++ } ++ auto sys = std::make_unique<decoder_sys_t>(); ++ dec->p_sys = sys.get(); ++ ++ auto decoderConfig = getDecoderConfig( dec, false ); ++ if ( decoderConfig.isUndefined() ) ++ return VLC_EGENERIC; ++ auto isSupported = probeConfig(decoderConfig.as_handle()); ++ ++ if ( isSupported == false ) ++ { ++ msg_Err( dec, "VideoDecoder doesn't support this configuration" ); ++ return VLC_EGENERIC; ++ } ++ ++ if ( es_format_Copy( &dec->fmt_out, &dec->fmt_in ) != VLC_SUCCESS ) ++ return VLC_ENOMEM; ++ dec->fmt_out.i_codec = dec->fmt_out.video.i_chroma = VLC_CODEC_WEBCODEC_OPAQUE; ++ ++ auto dec_dev = decoder_GetDecoderDevice(dec); ++ sys->vctx = vlc_video_context_Create(dec_dev, VLC_VIDEO_CONTEXT_WEBCODEC, ++ sizeof(webcodec_context), nullptr); ++ auto vctxPrivate = static_cast<webcodec_context*>( ++ vlc_video_context_GetPrivate(sys->vctx, VLC_VIDEO_CONTEXT_WEBCODEC)); ++ new (vctxPrivate) webcodec_context(); ++ ++ if ( webcodec_CreatePool(sys->vctx, &dec->fmt_out.video) != VLC_SUCCESS ) ++ return VLC_EGENERIC; ++ ++ if ( vlc_clone( &sys->th, &WebcodecDecodeWorker, dec ) != VLC_SUCCESS ) ++ { ++ msg_Err( obj, "Failed to create webcodec thread" ); ++ return VLC_EGENERIC; ++ } ++ ++ dec->pf_decode = &Decode; ++ dec->pf_flush = &Flush; ++ ++ sys.release(); ++ return VLCDEC_SUCCESS; ++} ++ ++static void Close( decoder_t* dec ) ++{ ++ auto sys = static_cast<decoder_sys_t*>( dec->p_sys ); ++ auto vctx = static_cast<webcodec_context*>( ++ vlc_video_context_GetPrivate(sys->vctx, VLC_VIDEO_CONTEXT_WEBCODEC ) ); ++ picture_pool_Release(vctx->pool); ++ EM_ASM({ ++ Module.decoderWorkerPort.postMessage({ ++ customCmd: 'close' ++ }); ++ }); ++ delete sys; ++} ++ ++static int ++OpenDecDevice(vlc_decoder_device *device, vlc_window_t *) ++{ ++ static const struct vlc_decoder_device_operations ops = ++ { ++ nullptr, ++ }; ++ device->ops = &ops; ++ device->type = VLC_DECODER_DEVICE_WEBCODEC; ++ ++ return VLC_SUCCESS; ++} ++ ++vlc_module_begin () ++ set_description("Video decoder using browser provided implementation") ++ set_subcategory(SUBCAT_INPUT_VCODEC) ++ set_section(N_("Decoding"), NULL) ++ set_capability("video decoder", 100) ++ set_callbacks(Open, Close) ++ add_shortcut("webcodec") ++ ++ add_submodule() ++ set_callback_dec_device(OpenDecDevice, 1) ++vlc_module_end () +diff --git a/modules/demux/Makefile.am b/modules/demux/Makefile.am +index 802ce9038b..171a9ce8e3 100644 +--- a/modules/demux/Makefile.am ++++ b/modules/demux/Makefile.am +@@ -543,4 +543,6 @@ libvlc_json_la_SOURCES = \ + libvlc_json_la_CPPFLAGS = $(AM_CPPFLAGS) -I$(srcdir)/demux/json + libvlc_json_la_LIBADD = $(LTLIBVLCCORE) ../compat/libcompat.la $(LIBM) + libvlc_json_la_LDFLAGS = -static ++if !HAVE_EMSCRIPTEN + noinst_LTLIBRARIES += libvlc_json.la ++endif +diff --git a/modules/hw/emscripten/Makefile.am b/modules/hw/emscripten/Makefile.am +new file mode 100644 +index 0000000000..3f2a118bc1 +--- /dev/null ++++ b/modules/hw/emscripten/Makefile.am +@@ -0,0 +1,10 @@ ++emscriptendir = $(pluginsdir)/emscripten ++ ++libglconverter_emscripten_plugin_la_SOURCES = hw/emscripten/converter.cpp video_output/emscripten/common.cpp ++libglconverter_emscripten_plugin_la_CPPFLAGS = $(AM_CPPFLAGS) ++libglconverter_emscripten_plugin_la_CXXFLAGS = $(AM_CXXFLAGS) ++libglconverter_emscripten_plugin_la_LIBADD = $(AM_LIBADD) ++ ++if HAVE_EMSCRIPTEN ++emscripten_LTLIBRARIES = libglconverter_emscripten_plugin.la ++endif +diff --git a/modules/hw/emscripten/converter.cpp b/modules/hw/emscripten/converter.cpp +new file mode 100644 +index 0000000000..edc8d93bc0 +--- /dev/null ++++ b/modules/hw/emscripten/converter.cpp +@@ -0,0 +1,137 @@ ++/***************************************************************************** ++ * webcodec.cpp: VLC picture to WebCodec VideoFrame ++ ***************************************************************************** ++ * Copyright (C) 2022 VLC authors, VideoLAN and VideoLabs ++ * ++ * This program is free software; you can redistribute it and/or modify it ++ * under the terms of the GNU Lesser General Public License as published by ++ * the Free Software Foundation; either version 2.1 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU Lesser General Public License for more details. ++ * ++ * You should have received a copy of the GNU Lesser General Public License ++ * along with this program; if not, write to the Free Software Foundation, ++ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. ++ *****************************************************************************/ ++ ++#ifdef HAVE_CONFIG_H ++# include "config.h" ++#endif ++ ++#include <vlc_common.h> ++#include <vlc_filter.h> ++#include <vlc_plugin.h> ++ ++#include "../../video_output/emscripten/common.h" ++ ++#include <emscripten/emscripten.h> ++#include <emscripten/em_js.h> ++#include <emscripten/val.h> ++ ++struct chroma_sys_t ++{ ++}; ++ ++static picture_t* UploadSurface(filter_t* filter, picture_t* src) ++{ ++ auto vctx = static_cast<webcodec_context*>( ++ vlc_video_context_GetPrivate( filter->vctx_out, VLC_VIDEO_CONTEXT_WEBCODEC) ); ++ auto dst = picture_pool_Wait( vctx->pool ); ++ if (dst == nullptr) ++ { ++ picture_Release(src); ++ return nullptr; ++ } ++ ++ auto size = 0u; ++ for ( auto i = 0; i < src->i_planes; ++i ) ++ size += src->p[i].i_lines * src->p[i].i_pitch; ++ auto buffer = emscripten::val::global("Uint8Array").new_( size ); ++ auto planesLayout = emscripten::val::array(); ++ auto offset = 0u; ++ for ( auto i = 0; i < src->i_planes; ++i ) ++ { ++ auto planeSize = src->p[i].i_lines * src->p[i].i_pitch; ++ auto p = emscripten::typed_memory_view( planeSize, src->p[i].p_pixels ); ++ buffer.call<void>("set", p, offset); ++ auto layout = emscripten::val::object(); ++ layout.set("offset", offset); ++ layout.set("stride", src->p[i].i_pitch); ++ planesLayout.call<void>( "push", layout ); ++ offset += planeSize; ++ } ++ ++ auto init = emscripten::val::object(); ++ init.set( "format", "I420" ); ++ init.set( "codedWidth", src->format.i_width ); ++ init.set( "codedHeight", src->format.i_height ); ++ init.set( "timestamp", (long)US_FROM_VLC_TICK( src->date ) ); ++ init.set( "layout", planesLayout ); ++ init.set( "displayWidth", src->format.i_visible_width); ++ init.set( "displayHeight", src->format.i_visible_height ); ++ ++ auto visibleRect = emscripten::val::object(); ++ visibleRect.set( "x", src->format.i_x_offset ); ++ visibleRect.set( "y", src->format.i_y_offset ); ++ visibleRect.set( "width", src->format.i_visible_width); ++ visibleRect.set( "height", src->format.i_visible_height ); ++ init.set( "visibleRect", visibleRect ); ++ ++ auto frame = emscripten::val::global("VideoFrame").new_( buffer, init ); ++ auto pictureIdx = PictureContextPrivate(dst->context)->pictureIdx; ++ EM_ASM({ ++ let frame = Emval.toValue($0); ++ Module.msgChannel.port2.postMessage({ ++ customCmd: 'displayFrame', ++ pictureId: $1, ++ frame: frame ++ }); ++ }, frame.as_handle(), pictureIdx); ++ ++ ++ picture_CopyProperties( dst, src ); ++ picture_Release(src); ++ return dst; ++} ++ ++static void vlc_webcodec_CloseChroma(filter_t* filter) ++{ ++ auto vctx = static_cast<webcodec_context*>( ++ vlc_video_context_GetPrivate(filter->vctx_out, VLC_VIDEO_CONTEXT_WEBCODEC ) ); ++ picture_pool_Release(vctx->pool); ++} ++ ++static const struct vlc_filter_operations filter_ops = { ++ .filter_video = UploadSurface, .close = vlc_webcodec_CloseChroma, ++}; ++ ++static int vlc_webcodec_OpenChroma(filter_t* filter) ++{ ++ if (filter->fmt_in.video.i_height != filter->fmt_out.video.i_height ++ || filter->fmt_in.video.i_width != filter->fmt_out.video.i_width ++ || filter->fmt_in.video.orientation != filter->fmt_out.video.orientation) ++ { ++ return VLC_EGENERIC; ++ } ++ ++ if (filter->fmt_out.video.i_chroma != VLC_CODEC_WEBCODEC_OPAQUE) ++ return VLC_EGENERIC; ++ if (filter->fmt_in.video.i_chroma != VLC_CODEC_I420) ++ return VLC_EGENERIC; ++ ++ filter->ops = &filter_ops; ++ return webcodec_CreatePool(filter->vctx_out, &filter->fmt_out.video); ++} ++ ++vlc_module_begin() ++ set_shortname(N_("WebCodec filters")) ++ set_description(N_("WebCodec filters")) ++ set_subcategory(SUBCAT_VIDEO_VFILTER) ++ ++ add_submodule() ++ set_callback_video_converter(vlc_webcodec_OpenChroma, 10) ++vlc_module_end() +diff --git a/modules/video_output/Makefile.am b/modules/video_output/Makefile.am +index 381c9cd0eb..a3104c5e63 100644 +--- a/modules/video_output/Makefile.am ++++ b/modules/video_output/Makefile.am +@@ -303,6 +303,13 @@ vout_LTLIBRARIES += libegl_android_plugin.la libglinterop_android_plugin.la + endif + endif + ++### Emscripten ++libglinterop_emscripten_plugin_la_SOURCES = video_output/opengl/interop_emscripten.cpp \ ++ video_output/opengl/interop.h ++libglinterop_emscripten_plugin_la_CXXFLAGS = $(AM_CXXFLAGS) -DUSE_OPENGL_ES2 ++if HAVE_EMSCRIPTEN ++vout_LTLIBRARIES += libglinterop_emscripten_plugin.la ++endif + + ### Direct Rendering Manager (DRM) ### + +@@ -339,6 +346,13 @@ libcaca_plugin_la_LDFLAGS = $(AM_LDFLAGS) -rpath '$(voutdir)' + EXTRA_LTLIBRARIES += libcaca_plugin.la + vout_LTLIBRARIES += $(LTLIBcaca) + ++### Emscripten ### ++libemscripten_window_plugin_la_SOURCES = video_output/emscripten.cpp ++ ++if HAVE_EMSCRIPTEN ++vout_LTLIBRARIES += libemscripten_window_plugin.la ++endif ++ + ### Common ### + + libflaschen_plugin_la_SOURCES = video_output/flaschen.c +diff --git a/modules/video_output/emscripten.cpp b/modules/video_output/emscripten.cpp +new file mode 100644 +index 0000000000..e054826263 +--- /dev/null ++++ b/modules/video_output/emscripten.cpp +@@ -0,0 +1,257 @@ ++/** ++ * @file emscripten.c ++ * @brief Emscripten webgl video output for VLC media player ++ */ ++/***************************************************************************** ++ * Copyright © 2020 VLC authors and VideoLAN ++ * ++ * ++ * This program is free software; you can redistribute it and/or modify it ++ * under the terms of the GNU Lesser General Public License as published by ++ * the Free Software Foundation; either version 2.1 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU Lesser General Public License for more details. ++ * ++ * You should have received a copy of the GNU Lesser General Public License ++ * along with this program; if not, write to the Free Software Foundation, ++ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. ++ *****************************************************************************/ ++ ++#ifdef HAVE_CONFIG_H ++# include <config.h> ++#endif ++ ++#include <cstdarg> ++#include <new> ++#include <string> ++ ++#include <vlc_common.h> ++#include <vlc_plugin.h> ++#include <vlc_window.h> ++#include <vlc_vout_display.h> ++#include <vlc_opengl.h> ++ ++#include "./opengl/vout_helper.h" ++#include "opengl/renderer.h" ++ ++#include "emscripten/common.h" ++ ++#include <emscripten.h> ++#include <emscripten/html5.h> ++#include <emscripten/val.h> ++#include <webgl/webgl2.h> ++// eglGetProcAddress ++#include <EGL/egl.h> ++ ++static const struct vlc_window_operations ops = { ++ //TODO: Implement canvas operations ++ //vlc_window_ReportSize() should be called from here ++}; ++ ++//MAIN_THREAD_ ++//EM_JS(emscripten::EM_VAL, GetCanvasSize, (), { ++// let canvas = document.getElementById('canvas'); ++// if (!canvas) ++// return Emval.bindValue([]); ++// return Emval.bindValue([canvas.width, canvas.height]); ++//}); ++ ++typedef struct emscripten_gl_sys_t ++{ ++ EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context; ++} emscripten_gl_sys_t; ++ ++static int OpenWindow(vlc_window_t *wnd) ++{ ++ wnd->type = VLC_WINDOW_TYPE_EMSCRIPTEN_WEBGL; ++ wnd->ops = &ops; ++ wnd->handle.canvas = "canvas"; ++ ++ //emscripten::val size = GetCanvasSize(); ++ //std::vector<int> vec_size = emscripten::vecFromJSArray(size); ++ //if (vec_size.size() != 2) ++ // return VLC_EGENERIC; ++ ++ //vlc_window_ReportSize(wnd, vec_size[0], vec_size[1]); ++ vlc_window_ReportSize(wnd, 1280, 720); ++ return VLC_SUCCESS; ++} ++ ++static void *GetProcAddress(vlc_gl_t *gl, const char *name) ++{ ++ VLC_UNUSED(gl); ++ ++ return reinterpret_cast<void*>(eglGetProcAddress(name)); ++} ++ ++static int MakeCurrent(vlc_gl_t *gl) ++{ ++ auto sys = static_cast<emscripten_gl_sys_t*>( gl->sys ); ++ ++ if (emscripten_webgl_make_context_current(sys->context) != EMSCRIPTEN_RESULT_SUCCESS) ++ return VLC_EGENERIC; ++ return VLC_SUCCESS; ++} ++ ++static void ReleaseCurrent(vlc_gl_t *gl) ++{ ++ VLC_UNUSED(gl); ++ emscripten_webgl_make_context_current(0); ++} ++ ++static void Swap(vlc_gl_t *gl) ++{ ++ /** ++ * There is no way to commit/swapbuffer from an offscreen canvas, so instead ++ * we render the offscreen canvas onto a bitmap and let the actual canvas ++ * display that bitmap from the main thread. ++ */ ++ VLC_UNUSED(gl); ++ EM_ASM({ ++ let bitmap = Module.offscreenCanvas.transferToImageBitmap(); ++ Module.voutMsgPort.postMessage({ ++ customCmd: 'commitFrame', ++ bitmap: bitmap, ++ }, [bitmap]); ++ }); ++} ++ ++static void Resize(vlc_gl_t *gl, unsigned w, unsigned h) ++{ ++ VLC_UNUSED(gl); ++ VLC_UNUSED(w); ++ VLC_UNUSED(h); ++} ++ ++EM_ASYNC_JS(void, getVoutMessagePort, (), { ++ let p = new Promise((resolve, reject) => { ++ let listener = function(e) { ++ let msg = e.data; ++ if (msg.customCmd == 'getVoutMessagePortResult') { ++ Module.voutMsgPort = msg['msgPort']; ++ resolve(); ++ self.removeEventListener('message', listener); ++ }; ++ }; ++ self.addEventListener('message', listener); ++ self.postMessage({ ++ customCmd: 'getVoutMessagePort' ++ }); ++ }); ++ await p; ++}); ++ ++EM_JS(int, createGlContext, (int width, int height), { ++ Module.offscreenCanvas = new OffscreenCanvas(width, height); ++ Module.glCtx = Module.offscreenCanvas.getContext('webgl2'); ++ ++ return GL.registerContext(Module.glCtx, { ++ antialias: false ++ }); ++}); ++ ++static void Close (vlc_gl_t *gl) ++{ ++ auto sys = static_cast<emscripten_gl_sys_t*>( gl->sys ); ++ delete sys; ++} ++ ++static int Open (vlc_gl_t *gl, unsigned width, unsigned height) ++{ ++ VLC_UNUSED(width), VLC_UNUSED(height); ++ ++ vlc_window_t *wnd = gl->surface; ++ ++ if (wnd->type != VLC_WINDOW_TYPE_EMSCRIPTEN_WEBGL) ++ goto error; ++ ++ emscripten_gl_sys_t *sys; ++ ++ gl->sys = sys = new (std::nothrow)emscripten_gl_sys_t; ++ if (!sys) ++ return VLC_ENOMEM; ++ ++ MAIN_THREAD_EM_ASM({ ++ if (Module.canvasCtx === undefined) { ++ let canvasName = UTF8ToString($1); ++ Module.canvasCtx = document.getElementById(canvasName).getContext('2d'); ++ } ++ function onVoutMessage(msg) { ++ let data = msg['data']; ++ if (data.customCmd == 'commitFrame') { ++ let bmp = data['bitmap']; ++ Module.canvasCtx.drawImage(bmp, 0, 0); ++ bmp.close(); ++ } ++ } ++ let w = Module.PThread.pthreads[$0].worker; ++ w.addEventListener('message', function (e) { ++ let msg = e['data']; ++ if (msg.customCmd == 'getVoutMessagePort') { ++ let msgChannel = new MessageChannel(); ++ msgChannel.port1.onmessage = onVoutMessage; ++ w.postMessage({ ++ customCmd: 'getVoutMessagePortResult', ++ msgPort: msgChannel.port2, ++ }, [msgChannel.port2]); ++ } ++ }); ++ }, pthread_self(), wnd->handle.canvas ? wnd->handle.canvas : "canvas"); ++ ++ getVoutMessagePort(); ++ sys->context = createGlContext(width, height); ++ ++ if (!sys->context) { ++ goto error; ++ } ++ // Check that the WebGL context is valid ++ if (emscripten_webgl_make_context_current(sys->context) != EMSCRIPTEN_RESULT_SUCCESS) { ++ emscripten_log(EM_LOG_CONSOLE, "failed to make context current"); ++ goto error; ++ } ++ ++ // Release the context ++ emscripten_webgl_make_context_current(0); ++ wnd->handle.em_context = sys->context; ++ ++ static const struct vlc_gl_operations gl_ops = ++ { ++ .make_current = MakeCurrent, ++ .release_current = ReleaseCurrent, ++ .resize = Resize, ++ .swap = Swap, ++ .get_proc_address = GetProcAddress, ++ .close = Close, ++ }; ++ gl->ops = &gl_ops; ++ ++ return VLC_SUCCESS; ++ ++error: ++ Close(gl); ++ return VLC_EGENERIC; ++} ++ ++/* ++ * Module descriptor ++ */ ++vlc_module_begin() ++ set_shortname(N_("Emscripten Window")) ++ set_description(N_("Emscripten drawing area")) ++ set_subcategory(SUBCAT_VIDEO_VOUT) ++ set_capability("vout window", 10) ++ set_callbacks(OpenWindow, nullptr) ++ ++ add_submodule () ++ set_shortname("Emscripten GL") ++ set_description(N_("Emscripten extension for OpenGL")) ++ set_subcategory(SUBCAT_VIDEO_VOUT) ++ set_capability("opengl es2", 50) ++ set_callback(Open) ++ add_shortcut("em_webgl") ++vlc_module_end() ++ +diff --git a/modules/video_output/emscripten/common.cpp b/modules/video_output/emscripten/common.cpp +new file mode 100644 +index 0000000000..b2065be3b4 +--- /dev/null ++++ b/modules/video_output/emscripten/common.cpp +@@ -0,0 +1,82 @@ ++/***************************************************************************** ++ * common.h: Emscripten decoder/vout common code ++ ***************************************************************************** ++ * Copyright (C) 2021 VLC authors and VideoLAN ++ * ++ * This program is free software; you can redistribute it and/or modify it ++ * under the terms of the GNU Lesser General Public License as published by ++ * the Free Software Foundation; either version 2.1 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU Lesser General Public License for more details. ++ * ++ * You should have received a copy of the GNU Lesser General Public License ++ * along with this program; if not, write to the Free Software Foundation, ++ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. ++ *****************************************************************************/ ++ ++#ifdef HAVE_CONFIG_H ++# include "config.h" ++#endif ++ ++#include <vlc_common.h> ++#include <vlc_decoder.h> ++#include <vlc_picture_pool.h> ++ ++#include "common.h" ++ ++static picture_context_t* PictureContextCopy(picture_context_t* picCtx) ++{ ++ vlc_video_context_Hold( picCtx->vctx ); ++ return picCtx; ++} ++ ++static void PictureContextDestroy( picture_context_t* ) ++{ ++ /* Context is already released by picture_Release() */ ++} ++ ++static picture_context_t* webcodec_CreatePictureContext( vlc_video_context* vctx, uint32_t picIdx ) ++{ ++ auto picCtx = static_cast<webcodec_picture_ctx*>( ++ malloc( sizeof( webcodec_picture_ctx ) ) ); ++ if ( picCtx == nullptr ) ++ return nullptr; ++ picCtx->context.copy = PictureContextCopy; ++ picCtx->context.destroy = PictureContextDestroy; ++ picCtx->context.vctx = vctx; ++ picCtx->pictureIdx = picIdx; ++ return &picCtx->context; ++} ++ ++ ++int webcodec_CreatePool( vlc_video_context* vctx, const video_format_t* fmt ) ++{ ++ auto ctx = static_cast<webcodec_context*>( ++ vlc_video_context_GetPrivate( vctx, VLC_VIDEO_CONTEXT_WEBCODEC ) ); ++ ++ for ( auto i = 0u; i < ARRAY_SIZE(ctx->pictures); ++i ) ++ { ++ auto pic = picture_NewFromFormat(fmt); ++ if ( pic != nullptr ) ++ pic->context = webcodec_CreatePictureContext( vctx, i ); ++ if ( pic == nullptr || pic->context == nullptr ) ++ { ++ while ( i-- ) ++ picture_Release( ctx->pictures[i] ); ++ return VLC_EGENERIC; ++ } ++ ctx->pictures[i] = pic; ++ } ++ ctx->pool = picture_pool_New(ARRAY_SIZE(ctx->pictures), ctx->pictures); ++ if ( ctx->pool == nullptr ) ++ { ++ for ( auto i = 0u; i < ARRAY_SIZE(ctx->pictures); ++i ) ++ picture_Release( ctx->pictures[i] ); ++ return VLC_EGENERIC; ++ } ++ return VLC_SUCCESS; ++} +diff --git a/modules/video_output/emscripten/common.h b/modules/video_output/emscripten/common.h +new file mode 100644 +index 0000000000..08967d0703 +--- /dev/null ++++ b/modules/video_output/emscripten/common.h +@@ -0,0 +1,57 @@ ++/***************************************************************************** ++ * common.h: Emscripten decoder/vout common code ++ ***************************************************************************** ++ * Copyright (C) 2021 VLC authors and VideoLAN ++ * ++ * This program is free software; you can redistribute it and/or modify it ++ * under the terms of the GNU Lesser General Public License as published by ++ * the Free Software Foundation; either version 2.1 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU Lesser General Public License for more details. ++ * ++ * You should have received a copy of the GNU Lesser General Public License ++ * along with this program; if not, write to the Free Software Foundation, ++ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. ++ *****************************************************************************/ ++ ++#ifndef EMSCRIPTEN_COMMON_H ++#define EMSCRIPTEN_COMMON_H ++ ++#ifndef __cplusplus ++# error This only supports C++ ++#endif ++ ++#include <vlc_threads.h> ++#include <vlc_picture.h> ++#include <vlc_picture_pool.h> ++ ++#include <emscripten/html5_webgl.h> ++//#include <emscripten/val.h> ++ ++#define WEBCODEC_MAX_PICTURES 32 ++ ++struct webcodec_context ++{ ++ pthread_t decoder_worker; ++ picture_pool_t* pool; ++ picture_t* pictures[WEBCODEC_MAX_PICTURES]; ++}; ++ ++struct webcodec_picture_ctx ++{ ++ picture_context_t context; ++ uint32_t pictureIdx; ++}; ++ ++int webcodec_CreatePool( vlc_video_context* vctx, const video_format_t* fmt ); ++ ++static inline webcodec_picture_ctx* PictureContextPrivate( picture_context_t* picCtx ) ++{ ++ return container_of( picCtx, webcodec_picture_ctx, context ); ++} ++ ++#endif // EMSCRIPTEN_COMMON_H +diff --git a/modules/video_output/opengl/Makefile.am b/modules/video_output/opengl/Makefile.am +index f95a1effaf..e2587f3706 100644 +--- a/modules/video_output/opengl/Makefile.am ++++ b/modules/video_output/opengl/Makefile.am +@@ -112,6 +112,12 @@ libglfilter_mock_plugin_la_LIBADD += libvlc_opengl.la $(GL_LIBS) + noinst_LTLIBRARIES += libglfilter_mock_plugin.la + endif + ++if HAVE_EMSCRIPTEN ++libglfilter_mock_plugin_la_LIBADD += libvlc_opengles.la $(GL_LIBS) ++libglfilter_mock_plugin_la_CFLAGS = -DUSE_OPENGL_ES2=1 ++noinst_LTLIBRARIES += libglfilter_mock_plugin.la ++endif ++ + if HAVE_IOS + libglfilter_mock_plugin_la_LIBADD += libvlc_opengles.la $(GLES2_LIBS) + libglfilter_mock_plugin_la_CFLAGS = -DUSE_OPENGL_ES2=1 +diff --git a/modules/video_output/opengl/egl_display_generic.c b/modules/video_output/opengl/egl_display_generic.c +index dfb841feef..730fb381d2 100644 +--- a/modules/video_output/opengl/egl_display_generic.c ++++ b/modules/video_output/opengl/egl_display_generic.c +@@ -52,8 +52,8 @@ static vlc_egl_display_open_fn Open; + static int + Open(struct vlc_egl_display *display) + { +-#ifdef __ANDROID__ +- /* The default display is refcounted on Android */ ++#if defined(__ANDROID__) || defined(__EMSCRIPTEN__) ++ /* The default display is refcounted on Android and Emscripten */ + display->display = eglGetDisplay(EGL_DEFAULT_DISPLAY); + #elif defined(EGL_KHR_display_reference) + const char *extensions = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); +diff --git a/modules/video_output/opengl/filter.c b/modules/video_output/opengl/filter.c +index 7f0aa73e7c..491ce88dd5 100644 +--- a/modules/video_output/opengl/filter.c ++++ b/modules/video_output/opengl/filter.c +@@ -31,6 +31,7 @@ + #include <vlc_modules.h> + + #include "gl_api.h" ++#include "picture.h" + + struct vlc_gl_filter * + vlc_gl_filter_New(struct vlc_gl_t *gl, const struct vlc_gl_api *api) +diff --git a/modules/video_output/opengl/filter.h b/modules/video_output/opengl/filter.h +index dc81b69d8a..b8272a5115 100644 +--- a/modules/video_output/opengl/filter.h ++++ b/modules/video_output/opengl/filter.h +@@ -19,139 +19,4 @@ + * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ + +-#ifndef VLC_GL_FILTER_H +-#define VLC_GL_FILTER_H +- +-#include <vlc_tick.h> +- +-#include "picture.h" +- +-struct vlc_gl_filter; +- +-#ifdef __cplusplus +-extern "C" +-{ +-#endif +- +-struct vlc_gl_tex_size { +- unsigned width; +- unsigned height; +-}; +- +-struct vlc_gl_input_meta { +- vlc_tick_t pts; +- unsigned plane; +-}; +- +-typedef int +-vlc_gl_filter_open_fn(struct vlc_gl_filter *filter, +- const config_chain_t *config, +- const struct vlc_gl_format *glfmt, +- struct vlc_gl_tex_size *size_out); +- +-#define set_callback_opengl_filter(open) \ +- { \ +- vlc_gl_filter_open_fn *fn = open; \ +- (void) fn; \ +- set_callback(fn); \ +- } +- +-struct vlc_gl_filter_ops { +- /** +- * Draw the result of the filter to the current framebuffer +- */ +- int (*draw)(struct vlc_gl_filter *filter, const struct vlc_gl_picture *pic, +- const struct vlc_gl_input_meta *meta); +- +- /** +- * Free filter resources +- */ +- void (*close)(struct vlc_gl_filter *filter); +- +- /** +- * Request a (responsive) filter to adapt its output size (optional) +- * +- * A responsive filter is a filter for which the size of the produced +- * pictures depends on the output (e.g. display) size rather than the +- * input. This is for example the case for a renderer. +- * +- * A new output size is requested (size_out). The filter is authorized to +- * change the size_out to enforce its own constraints. +- * +- * In addition, it may request to the previous filter (if any) an optimal +- * size it wants to receive. If set to non-zero value, this previous filter +- * will receive this size as its requested size (and so on). +- * +- * \retval true if the resize is accepted (possibly with a modified +- * size_out) +- * \retval false if the resize is rejected (included on error) +- */ +- int (*request_output_size)(struct vlc_gl_filter *filter, +- struct vlc_gl_tex_size *size_out, +- struct vlc_gl_tex_size *optimal_in); +- +- /** +- * Callback to notify input size changes +- * +- * When a filter changes its output size as a result of +- * request_output_size(), the next filter is notified by this callback. +- */ +- void (*on_input_size_change)(struct vlc_gl_filter *filter, +- const struct vlc_gl_tex_size *size); +-}; +- +-/** +- * OpenGL filter, in charge of a rendering pass. +- */ +-struct vlc_gl_filter { +- vlc_object_t obj; +- module_t *module; +- +- struct vlc_gl_t *gl; +- const struct vlc_gl_api *api; +- const struct vlc_gl_format *glfmt_in; +- +- struct { +- /** +- * An OpenGL filter may either operate on the input RGBA picture, or on +- * individual input planes (without chroma conversion) separately. +- * +- * In practice, this is useful for deinterlace filters. +- * +- * This flag must be set by the filter module (default is false). +- */ +- bool filter_planes; +- +- /** +- * A blend filter draws over the input picture (without reading it). +- * +- * Meaningless if filter_planes is true. +- * +- * This flag must be set by the filter module (default is false). +- */ +- bool blend; +- +- /** +- * Request MSAA level. +- * +- * This value must be set by the filter module (default is 0, which +- * means disabled). +- * +- * Meaningless if filter_planes is true. +- * +- * The actual MSAA level may be overwritten to 0 if multisampling is +- * not supported, or to a higher value if another filter rendering on +- * the same framebuffer requested a higher MSAA level. +- */ +- unsigned msaa_level; +- } config; +- +- const struct vlc_gl_filter_ops *ops; +- void *sys; +-}; +- +-#ifdef __cplusplus +-} +-#endif +- +-#endif ++#include <vlc_opengl_filter.h> +diff --git a/modules/video_output/opengl/filter_mock.c b/modules/video_output/opengl/filter_mock.c +index 64a73b303e..3915456d7e 100644 +--- a/modules/video_output/opengl/filter_mock.c ++++ b/modules/video_output/opengl/filter_mock.c +@@ -73,6 +73,7 @@ + #include "gl_common.h" + #include "gl_util.h" + #include "sampler.h" ++#include "picture.h" + + #define MOCK_CFG_PREFIX "mock-" + +@@ -101,6 +102,7 @@ struct sys { + + float rotation_matrix[16]; + float ar; ++ struct vlc_gl_api api; + }; + + static void +@@ -586,6 +588,9 @@ Open(struct vlc_gl_filter *filter, const config_chain_t *config, + + sys->sampler = NULL; + ++ vlc_gl_api_Init(&sys->api, filter->gl); ++ filter->api = &sys->api; ++ + int ret; + if (plane) + ret = InitPlane(filter, glfmt); +diff --git a/modules/video_output/opengl/filter_priv.h b/modules/video_output/opengl/filter_priv.h +index 7b702ddb17..cd0e3084fa 100644 +--- a/modules/video_output/opengl/filter_priv.h ++++ b/modules/video_output/opengl/filter_priv.h +@@ -27,6 +27,7 @@ + #include <vlc_picture.h> + + #include "filter.h" ++#include "picture.h" + + struct vlc_gl_filter_priv { + struct vlc_gl_filter filter; +diff --git a/modules/video_output/opengl/filters.c b/modules/video_output/opengl/filters.c +index b93c1ee2fe..abd00b755b 100644 +--- a/modules/video_output/opengl/filters.c ++++ b/modules/video_output/opengl/filters.c +@@ -183,6 +183,7 @@ vlc_gl_filters_Append(struct vlc_gl_filters *filters, const char *name, + struct vlc_gl_filter *filter = vlc_gl_filter_New(filters->gl, filters->api); + if (!filter) + return NULL; ++ filter->gl = filters->gl; + + struct vlc_gl_filter_priv *priv = vlc_gl_filter_PRIV(filter); + +diff --git a/modules/video_output/opengl/interop_emscripten.cpp b/modules/video_output/opengl/interop_emscripten.cpp +new file mode 100644 +index 0000000000..f745fcfef4 +--- /dev/null ++++ b/modules/video_output/opengl/interop_emscripten.cpp +@@ -0,0 +1,287 @@ ++/***************************************************************************** ++ * interop_emscripten.cpp: OpenGL Emscripten/Webcodec opaque converter ++ ***************************************************************************** ++ * Copyright (C) 2021 VLC authors and VideoLAN ++ * ++ * This program is free software; you can redistribute it and/or modify it ++ * under the terms of the GNU Lesser General Public License as published by ++ * the Free Software Foundation; either version 2.1 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU Lesser General Public License for more details. ++ * ++ * You should have received a copy of the GNU Lesser General Public License ++ * along with this program; if not, write to the Free Software Foundation, ++ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. ++ *****************************************************************************/ ++ ++#ifdef HAVE_CONFIG_H ++# include "config.h" ++#endif ++ ++//#ifndef __EMSCRIPTEN__ ++//# error this file must be built with emscripten ++//#endif ++ ++#include <vlc_common.h> ++#include <vlc_plugin.h> ++#include <vlc_filter.h> ++ ++#include "interop.h" ++#include "gl_common.h" ++#include <cassert> ++#include "../emscripten/common.h" ++ ++#include <emscripten.h> ++#include <emscripten/em_js.h> ++#include <emscripten/val.h> ++#include <emscripten/html5_webgl.h> ++ ++struct EmscriptenInterop ++{ ++ struct ++ { ++ PFNGLBINDTEXTUREPROC BindTexture; ++ } gl; ++}; ++ ++static int ++tc_emscripten_op_allocate_textures(const struct vlc_gl_interop *interop, GLuint *textures, ++ const GLsizei *tex_width, const GLsizei *tex_height) ++{ ++ (void) tex_width; (void) tex_height; ++ assert(textures[0] != 0); ++ ++ return VLC_SUCCESS; ++} ++ ++EM_ASYNC_JS(void, bindVideoFrame, (int pictureIdx), { ++ let frame = await Module.awaitFrame(pictureIdx); ++ if (!frame) ++ return; ++ ++ let glCtx = Module.glCtx; ++ glCtx.texImage2D(glCtx.TEXTURE_2D, 0, glCtx.RGBA, frame.codedWidth, frame.codedHeight, 0, ++ glCtx.RGBA, glCtx.UNSIGNED_BYTE, frame); ++}) ++ ++static int ++tc_emscripten_op_update(const struct vlc_gl_interop *interop, GLuint *textures, ++ const GLsizei *tex_width, const GLsizei *tex_height, ++ picture_t *pic, const size_t *plane_offset) ++{ ++ auto sys = static_cast<EmscriptenInterop *>( interop->priv ); ++ ++ sys->gl.BindTexture(interop->tex_target, textures[0]); ++ ++ auto picCtx = PictureContextPrivate(pic->context); ++ bindVideoFrame(picCtx->pictureIdx); ++ ++ return VLC_SUCCESS; ++} ++ ++extern "C" ++{ ++EMSCRIPTEN_KEEPALIVE int getDecoderWorker(vlc_video_context* vctx) ++{ ++ auto wcCtx = static_cast<webcodec_context*>( ++ vlc_video_context_GetPrivate(vctx, VLC_VIDEO_CONTEXT_WEBCODEC)); ++ if (!vctx) ++ return 0; ++ return wcCtx->decoder_worker; ++} ++} ++ ++EM_JS(void, setupMessagePort, (vlc_video_context* vctx), { ++ function onDecoderMessage(msg) { ++ let data = msg['data']; ++ if (data.customCmd == 'displayFrame') { ++ let pictureIdx = data.pictureId; ++ let frame = data['frame']; ++ Module.glConv.frameResolver( frame ); ++ } ++ }; ++ Module.msgChannel = new MessageChannel(); ++ Module.msgChannel.port1.onmessage = onDecoderMessage; ++ let workerId = _getDecoderWorker(vctx); ++ if (workerId == 0) ++ return; ++ self.postMessage({ ++ customCmd: 'transferMessagePort', ++ targetThread: workerId, ++ transferList: [Module.msgChannel.port2], ++ }, [Module.msgChannel.port2]); ++}); ++ ++EM_JS(void, initGlConvWorker, (int maxPictures), { ++ Module.glConv = {}; ++ Module.glConv.lastFrame = { ++ pictureIdx: -1, ++ frame: undefined ++ }; ++ ++ Module.awaitFrame = async function(pictureIdx) { ++ if (Module.glConv.lastFrame.pictureIdx == pictureIdx) { ++ return Module.glConv.lastFrame.frame; ++ } ++ let p = new Promise((resolve, reject) => { ++ Module.glConv.frameResolver = resolve; ++ Module.msgChannel.port1.postMessage({ ++ 'customCmd': 'sendFrame', ++ 'pictureIdx': pictureIdx, ++ }); ++ ++ }); ++ let frame = await p; ++ Module.glConv.frameResolver = undefined; ++ if (Module.glConv.lastFrame.frame) ++ Module.glConv.lastFrame.frame.close(); ++ Module.glConv.lastFrame.frame = frame; ++ Module.glConv.lastFrame.pictureIdx = pictureIdx; ++ return frame; ++ } ++}) ++ ++EM_JS(void, closeMessagePort, (void), { ++ if (Module.msgChannel) ++ delete Module.msgChannel; ++}); ++ ++static void ++Close(struct vlc_gl_interop *) ++{ ++ closeMessagePort(); ++} ++ ++static int ++Open(vlc_object_t *obj) ++{ ++ auto interop = reinterpret_cast<vlc_gl_interop*>( obj ); ++ ++ if (interop->fmt_in.i_chroma != VLC_CODEC_WEBCODEC_OPAQUE) ++ return VLC_EGENERIC; ++ ++ EmscriptenInterop *sys = (decltype(sys)) ++ vlc_obj_malloc(VLC_OBJECT(interop), sizeof *sys); ++ if (sys == NULL) ++ return VLC_EGENERIC; ++ sys->gl.BindTexture = (decltype(sys->gl.BindTexture)) ++ vlc_gl_GetProcAddress(interop->gl, "glBindTexture"); ++ if (sys->gl.BindTexture == NULL) ++ return VLC_EGENERIC; ++ ++ static const struct vlc_gl_interop_ops ops = { ++ .allocate_textures = tc_emscripten_op_allocate_textures, ++ .update_textures = tc_emscripten_op_update, ++ .close = Close, ++ }; ++ interop->ops = &ops; ++ initGlConvWorker(WEBCODEC_MAX_PICTURES); ++ setupMessagePort(interop->vctx); ++ ++ interop->tex_target = GL_TEXTURE_2D; ++ interop->fmt_out.i_chroma = VLC_CODEC_RGBA; ++ interop->fmt_out.space = COLOR_SPACE_UNDEF; ++ interop->tex_count = 1; ++ interop->texs[0] = vlc_gl_interop::vlc_gl_tex_cfg{ ++ /*.w =*/ { 1, 1 }, ++ /*.h =*/ { 1, 1 }, ++ /*.internal =*/ GL_RGBA, ++ /*.format = */GL_RGBA, ++ /*.type = */GL_UNSIGNED_BYTE, ++ }; ++ interop->fmt_out.i_rmask = 0xFF0000; ++ interop->fmt_out.i_gmask = 0x00FF00; ++ interop->fmt_out.i_bmask = 0x0000FF; ++ interop->priv = sys; ++ ++ interop->fmt_out.orientation = ORIENT_VFLIPPED; ++ ++ return VLC_SUCCESS; ++} ++ ++EM_ASYNC_JS(void, CopyFrameToBuffer, (int pictureIdx, emscripten::EM_VAL infoHandle), { ++ let info = Emval.toValue(infoHandle); ++ let frame = await Module.awaitFrame(pictureIdx); ++ let copyOpts = { ++ rect: frame.codedRect, ++ layout: info.layout ++ }; ++ await frame.copyTo(info.buffer, copyOpts); ++}); ++ ++static picture_t* vlc_webcodec_UploadVideoFrame(filter_t* filter, picture_t* src) ++{ ++ auto pic = filter_NewPicture(filter); ++ if (unlikely(pic == nullptr)) ++ { ++ picture_Release(src); ++ return nullptr; ++ } ++ ++ auto pictureIdx = PictureContextPrivate(src->context)->pictureIdx; ++ ++ size_t picSize = 0; ++ auto info = emscripten::val::object(); ++ auto planesLayout = emscripten::val::array(); ++ for ( auto i = 0; i < pic->i_planes; ++i ) ++ { ++ auto p = pic->p[i]; ++ auto layout = emscripten::val::object(); ++ layout.set( "offset", picSize ); ++ layout.set( "stride", p.i_pitch ); ++ planesLayout.call<void>( "push", std::move( layout ) ); ++ picSize += p.i_lines * p.i_pitch; ++ } ++ ++ info.set( "buffer", emscripten::typed_memory_view( picSize, pic->p[0].p_pixels ) ); ++ info.set( "layout", std::move( planesLayout ) ); ++ ++ CopyFrameToBuffer( pictureIdx, info.as_handle() ); ++ ++ pic->date = src->date; ++ picture_Release(src); ++ return pic; ++} ++ ++static void vlc_webcodec_CloseConverter(filter_t*) ++{ ++ closeMessagePort(); ++} ++ ++static const struct vlc_filter_operations video_frame_to_sw_ops = { ++ .filter_video = vlc_webcodec_UploadVideoFrame, ++ .close = vlc_webcodec_CloseConverter, ++}; ++ ++static int OpenConverter(filter_t* filter) ++{ ++ if (filter->fmt_in.video.i_height != filter->fmt_out.video.i_height ++ || filter->fmt_in.video.i_width != filter->fmt_out.video.i_width ++ || filter->fmt_in.video.orientation != filter->fmt_out.video.orientation ++ || filter->fmt_in.video.i_chroma != VLC_CODEC_WEBCODEC_OPAQUE ++ || filter->fmt_out.video.i_chroma != VLC_CODEC_I420 ) ++ { ++ return VLC_EGENERIC; ++ } ++ initGlConvWorker(WEBCODEC_MAX_PICTURES); ++ setupMessagePort(filter->vctx_in); ++ ++ filter->ops = &video_frame_to_sw_ops; ++ ++ return VLC_SUCCESS; ++} ++ ++vlc_module_begin () ++ set_description("Emscripten OpenGL SurfaceTexture converter") ++ set_capability("glinterop", 1) ++ set_callback(Open) ++ set_subcategory(SUBCAT_VIDEO_VOUT) ++ ++ add_submodule() ++ set_subcategory(SUBCAT_VIDEO_VFILTER) ++ set_callback_video_converter(OpenConverter, 10) ++vlc_module_end () +diff --git a/modules/video_output/opengl/renderer.c b/modules/video_output/opengl/renderer.c +index aebbb84e67..092fc7b7e4 100644 +--- a/modules/video_output/opengl/renderer.c ++++ b/modules/video_output/opengl/renderer.c +@@ -41,6 +41,7 @@ + #include "gl_util.h" + #include "vout_helper.h" + #include "sampler.h" ++#include "picture.h" + + #define SPHERE_RADIUS 1.f + +diff --git a/modules/video_output/opengl/sampler.c b/modules/video_output/opengl/sampler.c +index 846cbc75ab..80f9d43e10 100644 +--- a/modules/video_output/opengl/sampler.c ++++ b/modules/video_output/opengl/sampler.c +@@ -37,6 +37,7 @@ + #include "gl_api.h" + #include "gl_common.h" + #include "gl_util.h" ++#include "picture.h" + + struct vlc_gl_sampler_priv { + struct vlc_gl_sampler sampler; +@@ -292,25 +293,30 @@ sampler_base_load(struct vlc_gl_sampler *sampler) + struct vlc_gl_format *glfmt = &sampler->glfmt; + struct vlc_gl_picture *pic = &priv->pic; + +- if (priv->yuv_color) ++ if (priv->yuv_color && priv->uloc.ConvMatrix != -1) + vt->UniformMatrix4fv(priv->uloc.ConvMatrix, 1, GL_FALSE, + priv->conv_matrix); + + for (unsigned i = 0; i < glfmt->tex_count; ++i) + { ++ if (priv->uloc.Textures[i] == -1) ++ continue; + vt->Uniform1i(priv->uloc.Textures[i], i); + + assert(pic->textures[i] != 0); + vt->ActiveTexture(GL_TEXTURE0 + i); + vt->BindTexture(glfmt->tex_target, pic->textures[i]); +- + } + + if (glfmt->tex_target == GL_TEXTURE_RECTANGLE) + { + for (unsigned i = 0; i < glfmt->tex_count; ++i) ++ { ++ if (priv->uloc.TexSizes[i] == -1) ++ continue; + vt->Uniform2f(priv->uloc.TexSizes[i], glfmt->tex_widths[i], + glfmt->tex_heights[i]); ++ } + } + + #ifdef HAVE_LIBPLACEBO +@@ -365,6 +371,9 @@ sampler_xyz12_load(struct vlc_gl_sampler *sampler) + struct vlc_gl_format *glfmt = &sampler->glfmt; + struct vlc_gl_picture *pic = &priv->pic; + ++ if (priv->uloc.Textures[0] == -1) ++ return; ++ + vt->Uniform1i(priv->uloc.Textures[0], 0); + + assert(pic->textures[0] != 0); +@@ -537,13 +546,17 @@ sampler_planes_load(struct vlc_gl_sampler *sampler) + struct vlc_gl_format *glfmt = &sampler->glfmt; + struct vlc_gl_picture *pic = &priv->pic; + +- vt->Uniform1i(priv->uloc.Textures[0], 0); ++ if (priv->uloc.Textures[0] != -1) ++ { ++ vt->Uniform1i(priv->uloc.Textures[0], 0); + +- assert(pic->textures[plane] != 0); +- vt->ActiveTexture(GL_TEXTURE0); +- vt->BindTexture(glfmt->tex_target, pic->textures[plane]); ++ assert(pic->textures[plane] != 0); ++ vt->ActiveTexture(GL_TEXTURE0); ++ vt->BindTexture(glfmt->tex_target, pic->textures[plane]); ++ } + +- if (glfmt->tex_target == GL_TEXTURE_RECTANGLE) ++ if (glfmt->tex_target == GL_TEXTURE_RECTANGLE && ++ priv->uloc.TexSizes[0] != -1) + { + vt->Uniform2f(priv->uloc.TexSizes[0], glfmt->tex_widths[plane], + glfmt->tex_heights[plane]); +diff --git a/modules/video_output/opengl/sampler.h b/modules/video_output/opengl/sampler.h +index c779f2a9ba..2a5f0ef063 100644 +--- a/modules/video_output/opengl/sampler.h ++++ b/modules/video_output/opengl/sampler.h +@@ -38,6 +38,11 @@ extern "C" + { + #endif + ++#ifdef __cplusplus ++extern "C" ++{ ++#endif ++ + /** + * The purpose of a sampler is to provide pixel values of a VLC input picture, + * stored in any format. +@@ -199,4 +204,8 @@ vlc_gl_sampler_SelectPlane(struct vlc_gl_sampler *sampler, unsigned plane); + } + #endif + ++#ifdef __cplusplus ++} ++#endif ++ + #endif +diff --git a/src/misc/fourcc.c b/src/misc/fourcc.c +index a0cf8cc3a8..212d07360f 100644 +--- a/src/misc/fourcc.c ++++ b/src/misc/fourcc.c +@@ -838,6 +838,8 @@ static const struct + { { VLC_CODEC_VAAPI_420, VLC_CODEC_VAAPI_420_10BPP }, + FAKE_FMT() }, + ++ { { VLC_CODEC_WEBCODEC_OPAQUE }, FAKE_FMT() }, ++ + { { 0 }, FAKE_FMT() } + }; + +diff --git a/src/video_output/display.c b/src/video_output/display.c +index aae3a26976..43a77c2387 100644 +--- a/src/video_output/display.c ++++ b/src/video_output/display.c +@@ -629,7 +629,7 @@ void vout_SetDisplayViewpoint(vout_display_t *vd, + } + } + +-vout_display_t *vout_display_New(vlc_object_t *parent, ++vout_display_t *(vout_display_New)(vlc_object_t *parent, + const video_format_t *source, + vlc_video_context *vctx, + const vout_display_cfg_t *cfg, +diff --git a/src/video_output/video_output.c b/src/video_output/video_output.c +index 2a18e01ba7..3b35b2bf69 100644 +--- a/src/video_output/video_output.c ++++ b/src/video_output/video_output.c +@@ -102,6 +102,13 @@ typedef struct vout_thread_sys_t + vout_control_t control; + atomic_bool control_is_terminated; // shutdown the vout thread + vlc_thread_t thread; ++ vlc_sem_t thread_ready_sem; ++ bool thread_success; ++ ++ // Begin lazy ass chouquette test: ++ vlc_video_context* vctx_vout_start; ++ const vout_configuration_t *cfg_vout_start; ++ //end of lazy ass chouquette test. don't use this for anything else than vout_start + + struct { + vlc_tick_t date; +@@ -1725,6 +1732,8 @@ error: + return VLC_EGENERIC; + } + ++ ++static void vout_DisableWindow(vout_thread_sys_t *sys); + /***************************************************************************** + * Thread: video output thread + ***************************************************************************** +@@ -1739,6 +1748,17 @@ static void *Thread(void *object) + + vlc_thread_set_name("vlc-vout"); + ++ if (vout_Start(vout, sys->vctx_vout_start, sys->cfg_vout_start)) ++ { ++ sys->thread_success = false; ++ msg_Err(&vout->obj, "video output display creation failed"); ++ vout_DisableWindow(vout); ++ vlc_sem_post(&sys->thread_ready_sem); ++ return NULL; ++ } ++ sys->thread_success = true; ++ vlc_sem_post(&sys->thread_ready_sem); ++ + vlc_tick_t deadline = VLC_TICK_INVALID; + + for (;;) { +@@ -2129,19 +2149,18 @@ int vout_Request(const vout_configuration_t *cfg, vlc_video_context *vctx, input + sys->str_id = cfg->str_id; + sys->clock = cfg->clock; + sys->delay = 0; ++ vlc_sem_init(&sys->thread_ready_sem, 0); ++ ++ sys->cfg_vout_start = cfg; ++ sys->vctx_vout_start = vctx; + +- if (vout_Start(vout, vctx, cfg)) +- { +- msg_Err(cfg->vout, "video output display creation failed"); +- vout_DisableWindow(vout); +- return -1; +- } + atomic_store(&sys->control_is_terminated, false); + if (vlc_clone(&sys->thread, Thread, vout)) { + vout_ReleaseDisplay(vout); + vout_DisableWindow(vout); + return -1; + } ++ vlc_sem_wait(&sys->thread_ready_sem); + + if (input != NULL && sys->spu) + spu_Attach(sys->spu, input); +-- +2.35.1 + diff --git a/vlc_patches/demo_alpha/0002-wip-prepare-access-module.patch b/vlc_patches/demo_alpha/0002-wip-prepare-access-module.patch new file mode 100644 index 0000000000000000000000000000000000000000..16a47e6c398bccda3096f9f148b17b1c56174ed4 --- /dev/null +++ b/vlc_patches/demo_alpha/0002-wip-prepare-access-module.patch @@ -0,0 +1,238 @@ +From 635cea37412f500c29fad197fa5a1ba99b7b7e0a Mon Sep 17 00:00:00 2001 +From: Mehdi Sabwat <mehdi@videolabs.io> +Date: Thu, 9 Jun 2022 21:05:04 +0200 +Subject: [PATCH 1/2] wip: prepare access module + +--- + modules/access/Makefile.am | 5 + + modules/access/emjsfile.c | 202 +++++++++++++++++++++++++++++++++++++ + 2 files changed, 207 insertions(+) + create mode 100644 modules/access/emjsfile.c + +diff --git a/modules/access/Makefile.am b/modules/access/Makefile.am +index d4f6b5f10a..cc827027df 100644 +--- a/modules/access/Makefile.am ++++ b/modules/access/Makefile.am +@@ -26,6 +26,11 @@ libfilesystem_plugin_la_LIBADD = -lshlwapi + endif + access_LTLIBRARIES += libfilesystem_plugin.la + ++if HAVE_EMSCRIPTEN ++libemjsfile_plugin_la_SOURCES = access/emjsfile.c ++access_LTLIBRARIES += libemjsfile_plugin.la ++endif ++ + libidummy_plugin_la_SOURCES = access/idummy.c + access_LTLIBRARIES += libidummy_plugin.la + +diff --git a/modules/access/emjsfile.c b/modules/access/emjsfile.c +new file mode 100644 +index 0000000000..f0736bc34c +--- /dev/null ++++ b/modules/access/emjsfile.c +@@ -0,0 +1,202 @@ ++/***************************************************************************** ++ * emjsfile.c: emscripten js file access plugin ++ ***************************************************************************** ++ * Copyright (C) 2022 VLC authors Videolabs, and VideoLAN ++ * ++ * ++ * This program is free software; you can redistribute it and/or modify it ++ * under the terms of the GNU Lesser General Public License as published by ++ * the Free Software Foundation; either version 2.1 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU Lesser General Public License for more details. ++ * ++ * You should have received a copy of the GNU Lesser General Public License ++ * along with this program; if not, write to the Free Software Foundation, ++ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. ++ *****************************************************************************/ ++ ++#ifdef HAVE_CONFIG_H ++# include "config.h" ++#endif ++ ++#include <vlc_common.h> ++#include <vlc_plugin.h> ++#include <vlc_access.h> ++#include <vlc_threads.h> ++ ++#include <emscripten.h> ++ ++typedef struct ++{ ++ size_t offset; ++ size_t js_file_size; ++} access_sys_t; ++ ++static ssize_t Read (stream_t *p_access, void *buffer, size_t size) { ++ access_sys_t *p_sys = p_access->p_sys; ++ ++ size_t offset = p_sys->offset; ++ size_t js_file_size = p_sys->js_file_size; ++ ++ if (offset == js_file_size) ++ return 0; ++ if (size > offset + js_file_size) { ++ size = js_file_size - offset; ++ } ++ EM_ASM({ ++ const offset = $0; ++ const buffer = $1; ++ const size = $2; ++ var blob = Module.worker_js_file.slice(offset, offset + size); ++ HEAPU8.set(new Uint8Array(Module.reader.readAsArrayBuffer(blob)), buffer); ++ }, offset, buffer, size); ++ p_sys->offset += size; ++ return size; ++} ++ ++static int FileSeek (stream_t *p_access, uint64_t offset) { ++ access_sys_t *p_sys = p_access->p_sys; ++ size_t js_file_size = p_sys->js_file_size; ++ ++ if (offset > js_file_size) { ++ msg_Err(p_access, "emjsfile: could not seek!"); ++ return VLC_EGENERIC; ++ } ++ p_sys->offset = offset; ++ return VLC_SUCCESS; ++} ++ ++static int FileControl( stream_t *p_access, int i_query, va_list args ) ++{ ++ bool *pb_bool; ++ vlc_tick_t *pi_64; ++ ++ switch( i_query ) ++ { ++ case STREAM_CAN_SEEK: ++ case STREAM_CAN_FASTSEEK: ++ pb_bool = va_arg( args, bool * ); ++ *pb_bool = (p_access->pf_seek != NULL); ++ break; ++ ++ case STREAM_CAN_PAUSE: ++ case STREAM_CAN_CONTROL_PACE: ++ pb_bool = va_arg( args, bool * ); ++ *pb_bool = 1; ++ break; ++ ++ case STREAM_GET_SIZE: ++ { ++ *va_arg( args, uint64_t * ) = EM_ASM_DOUBLE({ return Module.worker_js_file.size }); ++ break; ++ } ++ ++ case STREAM_GET_PTS_DELAY: ++ pi_64 = va_arg( args, vlc_tick_t * ); ++ *pi_64 = VLC_TICK_FROM_MS( ++ var_InheritInteger (p_access, "file-caching") ); ++ break; ++ ++ case STREAM_SET_PAUSE_STATE: ++ break; ++ ++ default: ++ return VLC_EGENERIC; ++ ++ } ++ return VLC_SUCCESS; ++} ++ ++EM_ASYNC_JS(int, init_js_file, (), { ++ let p = new Promise((resolve, reject) => { ++ self.addEventListener('message', function(e) { ++ let msg = e['data']; ++ if (msg.type === 'FileResult') { ++ if (msg.file !== undefined) { ++ Module.worker_js_file = msg.file; ++ Module.reader = new FileReaderSync(); ++ resolve(); ++ } ++ else { ++ reject(); ++ } ++ } ++ }); ++ }); ++ self.postMessage({ cmd: "customCmd", type: "requestFile"}); ++ try { ++ await p; ++ } ++ catch (error) { ++ console.error("vlc_access error in init_js_file(): ", error); ++ return 1; ++ } ++ return 0; ++ }); ++ ++int EmFileOpen( vlc_object_t *p_this ) { ++ stream_t *p_access = (stream_t*)p_this; ++ /* ++ This block will run in the main thread, to access the DOM. ++ When the user selects a file, it is assigned to the Module.vlc_access_file ++ array. ++ ++ We listen to 'message' events so that when the file is requested by the ++ input thread, we can answer and send the File object from the main thread. ++ */ ++ MAIN_THREAD_EM_ASM({ ++ const thread_id = $0; ++ let w = Module.PThread.pthreads[thread_id].worker; ++ w.addEventListener('message', function (e) { ++ var msg = e.data; ++ if (msg.type === "requestFile") { ++ w.postMessage({ cmd: "customCmd", ++ type: "FileResult", ++ file: Module.vlc_access_file.pop() ++ }); ++ } ++ }); ++ }, pthread_self()); ++ ++ /* ++ Request the file from the main thread. ++ */ ++ if (init_js_file()) { ++ return VLC_EGENERIC; ++ } ++ ++ access_sys_t *p_sys = vlc_obj_malloc(p_this, sizeof (*p_sys)); ++ if (unlikely(p_sys == NULL)) ++ return VLC_ENOMEM; ++ ++ p_access->pf_read = Read; ++ p_access->pf_block = NULL; ++ p_access->pf_control = FileControl; ++ p_access->pf_seek = FileSeek; ++ p_access->p_sys = p_sys; ++ p_sys->js_file_size = EM_ASM_DOUBLE({ return Module.worker_js_file.size }); ++ p_sys->offset = 0; ++ ++ return VLC_SUCCESS; ++} ++ ++void EmFileClose (vlc_object_t * p_this) { ++ VLC_UNUSED(p_this); ++ EM_ASM({ ++ Module.worker_js_file = undefined; ++ Module.reader = undefined; ++ }); ++} ++ ++vlc_module_begin () ++ set_description( N_("Emscripten module to allow reading local files from the DOM's <input>") ) ++ set_shortname( N_("Emscripten Local File Input") ) ++ set_subcategory( SUBCAT_INPUT_ACCESS ) ++ set_capability( "access", 50 ) ++ add_shortcut( "emjsfile" ) ++ set_callbacks( EmFileOpen, EmFileClose ) ++vlc_module_end() +-- +2.35.1 + diff --git a/vlccore_opengl.html b/vlccore_opengl.html new file mode 100644 index 0000000000000000000000000000000000000000..80ab11c03cad9de8b5ebe76849bffa8a33bbc85b --- /dev/null +++ b/vlccore_opengl.html @@ -0,0 +1,222 @@ +<!doctype html> +<html lang="en-us"> +<head> + <meta charset="utf-8"> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> + <title>VLC.js: OpenGL test suite</title> + <style> + .emscripten { + text-align: center; + } + body { + background-color: #343a40; + } + #canvas { + border:0 !important; + display: flex; + margin: 0 auto; + background-color: #000; + } + #overlay { + border:0 !important; + display: flex; + margin: 0 auto; + } + #stack { + display: grid; + } + #stack > canvas { + grid-column: 1; + grid-row: 1; + } + #spinner { + display: none; + } + #status { + display: none; + } + #progress { + margin-left: auto; + margin-right: auto; + } + #logo { + width: 64px; + height: 64px; + } + #em_logo { + position: absolute; + width: 200px; + height: 80px; + } + #point { + color: #fff; + font-size: 60px; + } + #js { + color: #fff; + font-size: 60px; + font-weight: bold; + } + .banner { + display: flex; + align-items: center; + justify-content: space-between; + } + .vlc_head { + flex: 1 1 0px; + } + </style> +</head> +<body> + <div class="emscripten_border"> + <div class="emscripten"> + <div class="banner"> + <img src="./assets/emscripten.svg" id ="em_logo"> + <div class="vlc_head"> + <img src="./assets/VLC_Icon.svg" id="logo"> + <span id="point">.</span> + <span id="js">JS</span> + </div> + </div> + <progress id="progress" value="0" max="100"></progress> + <div class="spinner" id='spinner'></div> + <div class="emscripten" id="status">Downloading...</div> + </div> + </div> + + + <h3> 1/ Checking that we can setup OpenGL correctly </h3> + <p> + As a first step, we'll need to setup a vlc_gl_t instance on a canvas. + The test succeeds if we can set the color to red. + </p> + <div> + <canvas + class="emscripten" id="canvas_step1" + oncontextmenu="event.preventDefault()" tabindex=-1 + width=400 height=300 + ></canvas> + </div> + + <h3> 2/ Checking that we can setup different canvas</h3> + <p> + As a second step, we check whether we can setup multiple OpenGL + contexts. The canvas should appear green. + </p> + + <div> + <canvas + class="emscripten" id="canvas_step2" + oncontextmenu="event.preventDefault()" tabindex=-1 + width=400 height=300 + ></canvas> + </div> + + <h3> 3/ Checking that canvas update is smooth</h3> + <p> + As a third step, we check how much the canvas update can be smooth. + </p> + + <div> + <canvas + class="emscripten" id="canvas_step3" + oncontextmenu="event.preventDefault()" tabindex=-1 + width=400 height=300 + ></canvas> + </div> + + <h3> 4/ Checking that coordinates are correct with glmock</h3> + <p> + </p> + + <div> + <canvas + class="emscripten" id="canvas_step4" + oncontextmenu="event.preventDefault()" tabindex=-1 + width=400 height=300 + ></canvas> + </div> + + <h3> 5/ Check interop behaviour </h3> + <p> + </p> + + <div> + <canvas + class="emscripten" id="canvas_step5" + oncontextmenu="event.preventDefault()" tabindex=-1 + width=400 height=300 + ></canvas> + </div> + + <h3> 7/ Check display behaviour </h3> + <p> + </p> + + <div> + <canvas + class="emscripten" id="canvas_step7" + oncontextmenu="event.preventDefault()" tabindex=-1 + width=400 height=300 + ></canvas> + </div> + + + <script src="./opengl.js"></script> + + <script type="module"> + // VlcModule is a function generated by emscripten in experimental.js, + // that loads the wasm file and generates a module object from it. + // VlcModuleExt is an object defined in 'lib/module-loader.js' with a + // bunch of options; also, all the fields of VlcModuleExt are added to + // the returned Module. + const defaultModule = { + 'onCustomMessage': function(msg) { + console.log('Received custom message'); + console.log(msg); + if (msg.customCmd == 'getCanvas') { + let name = 'canvas'; + if (msg.canvasId != undefined) + name = msg.canvasId + console.log('Replying to getCanvas("'+name+'")'); + let w = Module.PThread.pthreads[msg.threadId].worker; + let canvas = document.getElementById(name); + let offscreen = canvas.transferControlToOffscreen(); + // Ensure emscripten won't try to re-transfer canvas control + // to offscreen + console.log(offscreen); + offscreen.controlTransferredOffscreen = true; + w.postMessage({ + cmd: 'custom', + customCmd: 'getCanvasResult', + canvas: offscreen + }, [offscreen]); + } + } + }; + globalThis.Module = await initModule(defaultModule); +// globalThis.Module.postRun += [ +// function() { +// // This should run after the wasm module is instantiated +// // before, the Pthread object won't be available +// PThread.receiveObjectTransfer = function (data) { +// // Transfer messages from worker threads to the main window. +// let event = new CustomEvent('worker_message', { +// detail: data.msg +// }); +// window.dispatchEvent(event); +// }; +// }]; + //window.onerror = function(event) { + // // TODO: do not warn on ok events like simulating an infinite loop or exitStatus + // Module.setStatus('Exception thrown, see JavaScript console'); + // spinnerElement.style.display = 'none'; + // Module.setStatus = function(text) { + // if (text) Module.printErr('[post-exception status] ' + text); + // }; + //}; + </script> + + +</body> +</html>